import unittest from modbus_rtu import build_read_holding, build_write_multiple, build_write_single, valid_crc from plsr_protocol import ( PULSE_POINTS, SECONDARY_POINTS, Command, DirectionLogic, Register, SendMode, STATUS_REGISTER_COUNT, WaitCondition, calculate_execution_total, join_i32, join_u32, parse_status, segment_base, signed_to_u32, split_u32, register_read_count, ) class ParameterTable2026Tests(unittest.TestCase): def test_low_word_is_at_low_address(self): self.assertEqual(split_u32(0x12345678), [0x5678, 0x1234]) self.assertEqual(join_u32(0x5678, 0x1234), 0x12345678) self.assertEqual(join_i32(0xFF9C, 0xFFFF), -100) def test_system_and_status_addresses(self): self.assertEqual(int(Register.PULSE_POINT), 0x1000) self.assertEqual(int(Register.MAX_FREQ_HI), 0x1015) self.assertEqual(register_read_count(), 0x16) self.assertEqual(int(Register.CUMULATIVE_POSITION_LO), 0x2000) self.assertEqual(int(Register.CONTROL), 0x3000) def test_segment_stride(self): self.assertEqual(segment_base(0), 0x1100) self.assertEqual(segment_base(9), 0x1190) def test_start_control_frame(self): frame = build_write_single(1, int(Register.CONTROL), int(Command.START)) self.assertTrue(valid_crc(frame)) self.assertEqual(frame[:6], bytes.fromhex("01 06 30 00 00 01")) def test_segment_frame_layout(self): values = split_u32(1000) + split_u32((-200) & 0xFFFFFFFF) values += [int(WaitCondition.WAIT_TIME), 500, 0, 2] frame = build_write_multiple(1, segment_base(0), values) self.assertTrue(valid_crc(frame)) self.assertEqual(frame[2:7], bytes.fromhex("11 00 00 08 10")) def test_status_read_frame(self): frame = build_read_holding( 1, int(Register.CUMULATIVE_POSITION_LO), STATUS_REGISTER_COUNT ) self.assertTrue(valid_crc(frame)) self.assertEqual(frame[:6], bytes.fromhex("01 03 20 00 00 1B")) def test_status_pulse_counts(self): status = parse_status([30, 0, 0, 0, 2, 0, 0, 10, 0, 30, 0]) self.assertEqual(status["current_pulses"], 10) self.assertEqual(status["total_pulses"], 30) def test_diagnostic_status_values(self): registers = [0] * STATUS_REGISTER_COUNT registers[11] = 0x001E registers[12] = 2 registers[13] = 3 registers[14:16] = split_u32(1000) registers[16:18] = split_u32(1000) registers[18:20] = split_u32(5000) registers[20:22] = split_u32(4998) registers[22:24] = split_u32(2) status = parse_status(registers) self.assertEqual(status["diagnostic_expected_pulses"], 1000) self.assertEqual(status["diagnostic_observed_pulses"], 1000) self.assertEqual(status["diagnostic_actual_frequency"], 4998) self.assertEqual(status["diagnostic_max_frequency_error"], 2) def test_ab_reverse_segment_uses_signed_pulse_count(self): self.assertEqual(split_u32(signed_to_u32(-100)), [0xFF9C, 0xFFFF]) def test_b_phase_point_encoding(self): self.assertEqual(SECONDARY_POINTS[0].split()[0], "Y12") self.assertEqual(SECONDARY_POINTS[3].split()[0], "Y15") def test_output_mode_register(self): self.assertEqual(int(Register.OUTPUT_MODE), 0x100F) def test_send_mode_register_values(self): self.assertEqual(int(Register.SEND_MODE), 0x1004) self.assertEqual(int(SendMode.COMPLETE), 0) self.assertEqual(int(SendMode.FOLLOW), 1) def test_direction_logic_register_values(self): self.assertEqual(int(Register.DIRECTION_LOGIC), 0x1006) self.assertEqual(int(DirectionLogic.POSITIVE), 0) self.assertEqual(int(DirectionLogic.NEGATIVE), 1) def test_y3_uses_pf9_tim14(self): self.assertEqual(PULSE_POINTS[3], "Y3 / PF9 / TIM14_CH1") def test_execution_total_starts_from_selected_segment(self): segments = [ (100, 1000, 0), (200, 1000, 0), (-300, 1000, 0), ] self.assertEqual(calculate_execution_total(segments, 2), (500, False)) def test_execution_total_follows_jump_and_detects_cycle(self): segments = [ (100, 1000, 3), (200, 1000, 0), (300, 1000, 1), ] self.assertEqual(calculate_execution_total(segments, 1), (400, True)) def test_clear_total_control_frame(self): frame = build_write_single( 1, int(Register.CONTROL), int(Command.CLEAR_TOTAL) ) self.assertTrue(valid_crc(frame)) self.assertEqual(frame[:6], bytes.fromhex("01 06 30 00 00 08")) def test_save_outputs_control_frame(self): frame = build_write_single( 1, int(Register.CONTROL), int(Command.SAVE_OUTPUTS) ) self.assertTrue(valid_crc(frame)) self.assertEqual(frame[:6], bytes.fromhex("01 06 30 00 00 10")) def test_clear_diagnostic_control_frame(self): frame = build_write_single( 1, int(Register.CONTROL), int(Command.CLEAR_DIAGNOSTIC) ) self.assertTrue(valid_crc(frame)) self.assertEqual(frame[:6], bytes.fromhex("01 06 30 00 00 20")) def test_runtime_frequency_is_separate_from_default_speed(self): self.assertEqual(int(Register.TARGET_FREQ_LO), 0x100B) self.assertEqual(int(Register.RUNTIME_FREQ_LO), 0x1016) frame = build_write_single( 1, int(Register.CONTROL), int(Command.UPDATE_FREQUENCY) ) self.assertTrue(valid_crc(frame)) self.assertEqual(frame[:6], bytes.fromhex("01 06 30 00 00 40")) if __name__ == "__main__": unittest.main()