// 本文件测试底层 Modbus RTU 协议实现: // CRC16 是否匹配文档示例帧、大端寄存器打包是否正确、TS+XYZABC 位姿转换 // 是否可往返,以及请求/响应解析器是否严格校验。 #include "line_laser_modbus/protocol.hpp" #include "test_support.hpp" #include #include using namespace line_laser_modbus; namespace { void test_documented_read_frames() { test_support::require_bytes_equal( build_read_request(kDefaultSlaveId, kModeCommandAddress, 2U), ByteVector{0x08, 0x03, 0xD0, 0x00, 0x00, 0x02, 0xFC, 0x52}, "read mode/state request must match protocol document"); test_support::require_bytes_equal( build_read_request(kDefaultSlaveId, kCurrentPoseAddress, kPoseRegisterCount), ByteVector{0x08, 0x03, 0xD0, 0x0A, 0x00, 0x0E, 0xDC, 0x55}, "read current pose request must match protocol document"); } void test_documented_mode_write_frames() { const std::array expected_frames{{ {0x08, 0x10, 0xD0, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x1D, 0xCD}, {0x08, 0x10, 0xD0, 0x00, 0x00, 0x01, 0x02, 0x00, 0x01, 0xDC, 0x0D}, {0x08, 0x10, 0xD0, 0x00, 0x00, 0x01, 0x02, 0x00, 0x02, 0x9C, 0x0C}, {0x08, 0x10, 0xD0, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x5D, 0xCC}, {0x08, 0x10, 0xD0, 0x00, 0x00, 0x01, 0x02, 0x00, 0x04, 0x1C, 0x0E}, {0x08, 0x10, 0xD0, 0x00, 0x00, 0x01, 0x02, 0x00, 0x05, 0xDD, 0xCE}, }}; for (std::size_t mode = 0; mode < expected_frames.size(); ++mode) { test_support::require_bytes_equal( build_write_request(kDefaultSlaveId, kModeCommandAddress, RegisterVector{static_cast(mode)}), expected_frames[mode], "mode write request must match protocol document"); } } void test_pose_register_round_trip() { const Pose6D source{1000U, 1.0F, 2.0F, 3.0F, 0.0F, 1.0F, 2.0F}; const RegisterVector registers = pose_to_register_vector(source); test_support::require_equal(registers.size(), static_cast(14U), "pose must occupy 14 registers"); test_support::require_equal(registers[0], static_cast(0x0000), "timestamp high word must be big-endian"); test_support::require_equal(registers[1], static_cast(0x03E8), "timestamp low word must be big-endian"); test_support::require_equal(registers[2], static_cast(0x3F80), "float 1.0 high word must be big-endian"); test_support::require_equal(registers[3], static_cast(0x0000), "float 1.0 low word must be big-endian"); const auto decoded = pose_from_register_vector(registers); test_support::require_true(decoded.has_value(), "pose decode must succeed"); test_support::require_equal(decoded->timestamp_ms, 1000U, "timestamp must round-trip"); test_support::require_float_close(decoded->x, 1.0F, "x must round-trip"); test_support::require_float_close(decoded->y, 2.0F, "y must round-trip"); test_support::require_float_close(decoded->z, 3.0F, "z must round-trip"); test_support::require_float_close(decoded->a, 0.0F, "a must round-trip"); test_support::require_float_close(decoded->b, 1.0F, "b must round-trip"); test_support::require_float_close(decoded->c, 2.0F, "c must round-trip"); } void test_parser_rejects_bad_crc() { ByteVector frame = build_read_request(kDefaultSlaveId, kDeviceStateAddress, 1U); frame.back() ^= 0x01U; const auto parsed = parse_read_request(frame); test_support::require_true(!parsed.ok(), "read request parser must reject bad CRC"); } void test_write_request_parser() { const ByteVector frame = build_write_request(kDefaultSlaveId, kCorrectionAddress, pose_to_register_vector( Pose6D{1000U, 1.0F, 2.0F, 3.0F, 0.0F, 1.0F, 2.0F})); const auto parsed = parse_write_request(frame); test_support::require_true(parsed.ok(), "write request parser must accept valid frame"); test_support::require_equal(parsed.value->slave_id, kDefaultSlaveId, "write request slave id must parse"); test_support::require_equal(parsed.value->start_address, kCorrectionAddress, "write request start address must parse"); test_support::require_equal(parsed.value->registers.size(), static_cast(kPoseRegisterCount), "write request register count must parse"); } } // 匿名命名空间 int main() { test_documented_read_frames(); test_documented_mode_write_frames(); test_pose_register_round_trip(); test_parser_rejects_bad_crc(); test_write_request_parser(); std::cout << "protocol_tests passed\n"; return 0; }