|
- import unittest
-
- import plsr_test
-
-
- class FakeSerial:
- def __init__(self, response):
- self.response = response
- self.rx = bytearray()
- self.writes = []
-
- @property
- def in_waiting(self):
- return len(self.rx)
-
- def reset_input_buffer(self):
- self.rx.clear()
-
- def write(self, data):
- self.writes.append(bytes(data))
- self.rx.extend(bytes.fromhex("40 00") + self.response)
- return len(data)
-
- def flush(self):
- pass
-
- def read(self, size):
- chunk_size = min(size, 3, len(self.rx))
- data = bytes(self.rx[:chunk_size])
- del self.rx[:chunk_size]
- return data
-
-
- class ProtocolTests(unittest.TestCase):
- def test_known_crc(self):
- self.assertEqual(plsr_test.crc16(bytes.fromhex("01 03 00 00 00 0A")), 0xCDC5)
-
- def test_status_request(self):
- frame = plsr_test.read_holding_request(1, 0, 32)
- self.assertEqual(frame.hex(" "), "01 03 00 00 00 20 44 12")
-
- def test_write_register_request(self):
- frame = plsr_test.write_register_request(1, 100, 1)
- self.assertEqual(frame, plsr_test.with_crc(bytes.fromhex("01 06 00 64 00 01")))
-
- def test_parser_recovers_from_stale_bytes(self):
- response = plsr_test.with_crc(bytes.fromhex("01 03 04 00 05 00 06"))
- buffer = bytearray(bytes.fromhex("40 00 00 03 40") + response)
- self.assertEqual(plsr_test.extract_response(buffer, 1, 3), response)
-
- def test_parser_waits_for_partial_frame(self):
- response = plsr_test.with_crc(bytes.fromhex("01 03 04 00 05 00 06"))
- buffer = bytearray(response[:5])
- self.assertIsNone(plsr_test.extract_response(buffer, 1, 3))
- buffer.extend(response[5:])
- self.assertEqual(plsr_test.extract_response(buffer, 1, 3), response)
-
- def test_snapshot_combines_high_and_low_words(self):
- snapshot = plsr_test.Snapshot(5, 2, 10, 0, 0, (1 << 16) | 2, 4, 1)
- self.assertEqual(snapshot.total_pulses, 65538)
- self.assertEqual(snapshot.state_name, "Done")
-
- def test_spec_snapshot_reads_stop_reason_and_signed_total(self):
- class SnapshotClient(plsr_test.ModbusRtuClient):
- def read_holding(self, address, count):
- if address == plsr_test.REG_MONITOR_TOTAL_PULSES:
- return [0xFFFF, 0xFFFE, 0, 2, 6, 1, 0, 1]
- return [2, 1]
-
- snapshot = SnapshotClient("FAKE", protocol=plsr_test.MAP_SPEC).read_snapshot()
- self.assertEqual(snapshot.total_pulses, -2)
- self.assertEqual(snapshot.stop_reason, 1)
- self.assertEqual(snapshot.state_name, "Stopped")
-
- def test_client_handles_fragmented_noisy_response(self):
- response = plsr_test.with_crc(bytes.fromhex("01 03 04 00 05 00 06"))
- fake = FakeSerial(response)
- client = plsr_test.ModbusRtuClient("FAKE", timeout=0.1, retries=0)
- client._serial = fake
- self.assertEqual(client.read_holding(0, 2), [5, 6])
- self.assertEqual(len(fake.writes), 1)
-
- def test_signed_double_word_round_trip(self):
- self.assertEqual(plsr_test.encode_i32(-2147483648), (0x8000, 0x0000))
- self.assertEqual(plsr_test.encode_i32(-1), (0xFFFF, 0xFFFF))
- self.assertEqual(plsr_test.decode_i32(0x7FFF, 0xFFFF), 2147483647)
- self.assertEqual(plsr_test.decode_i32(0x8000, 0), -2147483648)
-
- def test_spec_configuration_validation(self):
- cfg = plsr_test.PlsrConfiguration(
- pulse_output=3, direction_output=3, segment_count=10,
- segments=[plsr_test.SegmentParameters(100000, -1, 4, 10, 20, 0) for _ in range(10)],
- )
- cfg.validate()
- zero_speed_cfg = plsr_test.PlsrConfiguration(
- start_speed_hz=0,
- end_speed_hz=0,
- segments=[plsr_test.SegmentParameters(0, 1)] + [plsr_test.SegmentParameters() for _ in range(9)],
- )
- zero_speed_cfg.validate()
- with self.assertRaisesRegex(ValueError, "frequency must be 1..100000 Hz"):
- plsr_test.PlsrConfiguration(default_speed_hz=0).validate()
- with self.assertRaises(ValueError):
- plsr_test.PlsrConfiguration(segment_count=11).validate()
-
- def test_send_and_run_modes_accept_only_zero_or_one(self):
- config = plsr_test.PlsrConfiguration(send_mode=1, run_mode=1)
- config.validate()
- with self.assertRaises(ValueError):
- plsr_test.PlsrConfiguration(send_mode=2).validate()
- with self.assertRaises(ValueError):
- plsr_test.PlsrConfiguration(run_mode=2).validate()
-
- def test_spec_segment_request_uses_fc16_and_signed_count(self):
- frame = plsr_test.write_registers_request(
- 1, plsr_test.REG_SEGMENT_BASE,
- [100000 >> 16, 100000 & 0xFFFF, 0xFFFF, 0xFFFF, 4, 10, 20, 0],
- )
- self.assertEqual(frame[1], 0x10)
- self.assertEqual(frame[2:6], bytes.fromhex("11 00 00 08"))
- self.assertEqual(frame[6], 16)
-
- def test_spec_commands_have_independent_control_bits(self):
- client = plsr_test.ModbusRtuClient("FAKE", protocol=plsr_test.MAP_SPEC)
- writes = []
- client.write_register = lambda address, value: writes.append((address, value))
-
- client.command(plsr_test.COMMAND_STOP)
- client.command(plsr_test.COMMAND_IMMEDIATE_STOP)
- client.command(plsr_test.COMMAND_CLEAR_COUNT)
- client.command(plsr_test.COMMAND_RESET)
- client.command(plsr_test.COMMAND_RESTART)
-
- self.assertEqual(writes, [
- (plsr_test.REG_CONTROL, plsr_test.CONTROL_STOP),
- (plsr_test.REG_CONTROL, plsr_test.CONTROL_IMMEDIATE_STOP),
- (plsr_test.REG_CONTROL, plsr_test.CONTROL_CLEAR_COUNT),
- (plsr_test.REG_CONTROL, plsr_test.CONTROL_RESET),
- (plsr_test.REG_CONTROL, plsr_test.CONTROL_RESTART),
- ])
-
- def test_running_frequency_updates_current_segment_only(self):
- client = plsr_test.ModbusRtuClient(
- "FAKE", protocol=plsr_test.MAP_SPEC
- )
- snapshots = [
- plsr_test.Snapshot(
- 2, 1, 1000, 0, 0, 25, 0, 0, 3, plsr_test.MAP_SPEC
- ),
- plsr_test.Snapshot(
- 2, 1, 5000, 0, 0, 25, 0, 0, 3, plsr_test.MAP_SPEC
- ),
- ]
- writes = []
- client.read_snapshot = lambda: snapshots.pop(0)
- client.write_u32 = lambda address, value: writes.append((address, value))
-
- result = client.set_running_frequency(5000)
-
- expected_address = (
- plsr_test.REG_SEGMENT_BASE + 2 * plsr_test.REG_SEGMENT_STRIDE
- )
- self.assertEqual(writes, [(expected_address, 5000)])
- self.assertEqual(result.current_frequency, 5000)
-
-
- if __name__ == "__main__":
- unittest.main(verbosity=2)
|