#pragma once #include #include #include #include #include #include namespace line_laser_modbus { // 以下协议常量来自 py/docs/proto.md V1.2。 // // 协议有意只保留很小的 Modbus 接口面:功能码 0x03 用于读取保持寄存器, // 功能码 0x10 用于写多个保持寄存器。把公共常量集中在这里,可以避免 // 上位机和下位机在寄存器表变更时出现不一致。 constexpr std::uint8_t kDefaultSlaveId = 0x08; constexpr std::uint8_t kReadHoldingRegisters = 0x03; constexpr std::uint8_t kWriteMultipleRegisters = 0x10; constexpr std::uint16_t kModeCommandAddress = 0xD000; constexpr std::uint16_t kDeviceStateAddress = 0xD001; constexpr std::uint16_t kCurrentPoseAddress = 0xD00A; constexpr std::uint16_t kTargetPoseAddress = 0xD020; constexpr std::uint16_t kCorrectionAddress = 0xD036; constexpr std::uint16_t kCalibrationReserveAddress = 0xD04C; constexpr std::uint16_t kLastMappedAddress = 0xD06B; constexpr std::uint16_t kPoseRegisterCount = 14; constexpr std::size_t kAxisCount = 6; using ByteVector = std::vector; using RegisterVector = std::vector; using PoseRegisters = std::array; enum class WorkMode : std::uint16_t { StandbyReset = 0, Calibration = 1, PreWeldTeaching = 2, OnlineTracking = 3, BatchReplay = 4, EmergencyStop = 5, }; enum class DeviceState : std::uint16_t { StandbyReady = 0, MotionRunning = 1, TeachingComplete = 2, OnlineTrackingNormal = 3, Alarm = 4, CalibrationComplete = 5, EmergencyTriggered = 6, }; struct Pose6D { // 所有位姿类数据块都按下面的顺序存储: // uint32 timestamp_ms, float X, float Y, float Z, float A, float B, float C。 // // 每个 32 位值拆成两个 16 位保持寄存器,并按大端字序保存。最终 RTU // 帧的 CRC 字节顺序仍然遵循 Modbus 标准:低字节在前,高字节在后。 std::uint32_t timestamp_ms = 0; float x = 0.0F; float y = 0.0F; float z = 0.0F; float a = 0.0F; float b = 0.0F; float c = 0.0F; }; struct ReadRequest { std::uint8_t slave_id = 0; std::uint16_t start_address = 0; std::uint16_t quantity = 0; }; struct ReadResponse { std::uint8_t slave_id = 0; RegisterVector registers; }; struct WriteRequest { std::uint8_t slave_id = 0; std::uint16_t start_address = 0; RegisterVector registers; }; struct WriteResponse { std::uint8_t slave_id = 0; std::uint16_t start_address = 0; std::uint16_t quantity = 0; }; struct ParseError { std::string message; }; template struct ParseResult { // 这里使用 std::optional 而不是异常,让固件版本的错误处理保持显式且低开销。 // 调用方可以自行决定无效帧是忽略、记录、重试,还是转换为 Modbus 异常响应。 std::optional value; std::optional error; [[nodiscard]] bool ok() const noexcept { return value.has_value(); } }; [[nodiscard]] bool is_valid_work_mode(std::uint16_t raw) noexcept; [[nodiscard]] bool is_valid_device_state(std::uint16_t raw) noexcept; [[nodiscard]] bool is_mode_transition_allowed(WorkMode current, WorkMode next) noexcept; // CRC16 使用 Modbus RTU 参数:多项式 0xA001,初始值 0xFFFF。 // append_crc() 会按 Modbus RTU 要求先追加低字节,再追加高字节。 [[nodiscard]] std::uint16_t crc16(const std::uint8_t* data, std::size_t size) noexcept; [[nodiscard]] std::uint16_t crc16(const ByteVector& frame) noexcept; [[nodiscard]] bool has_valid_crc(const ByteVector& frame) noexcept; void append_crc(ByteVector& frame); [[nodiscard]] std::uint16_t read_u16_be(const std::uint8_t* data) noexcept; void append_u16_be(ByteVector& frame, std::uint16_t value); // 在业务层位姿结构和协议文档规定的保持寄存器布局之间转换。 // 上位机和下位机共用这组函数,避免两边各自实现 float 序列化导致差异。 [[nodiscard]] PoseRegisters encode_pose_registers(const Pose6D& pose) noexcept; [[nodiscard]] Pose6D decode_pose_registers(const PoseRegisters& registers) noexcept; // 帧构造函数始终返回包含 CRC 的完整 RTU 帧。调用方应通过实际传输层原样发送。 [[nodiscard]] ByteVector build_read_request(std::uint8_t slave_id, std::uint16_t start_address, std::uint16_t quantity); [[nodiscard]] ByteVector build_read_response(std::uint8_t slave_id, const RegisterVector& registers); [[nodiscard]] ByteVector build_write_request(std::uint8_t slave_id, std::uint16_t start_address, const RegisterVector& registers); [[nodiscard]] ByteVector build_write_response(std::uint8_t slave_id, std::uint16_t start_address, std::uint16_t quantity); [[nodiscard]] ByteVector build_exception_response(std::uint8_t slave_id, std::uint8_t function_code, std::uint8_t exception_code); // 解析函数会先校验帧长度、功能码、字节数和 CRC,再返回结构化数据。 // 应用层地址范围不在这里检查,而是由 DeviceServer/RegisterBank 负责。 [[nodiscard]] ParseResult parse_read_request(const ByteVector& frame); [[nodiscard]] ParseResult parse_read_response(const ByteVector& frame); [[nodiscard]] ParseResult parse_write_request(const ByteVector& frame); [[nodiscard]] ParseResult parse_write_response(const ByteVector& frame); [[nodiscard]] RegisterVector pose_to_register_vector(const Pose6D& pose); [[nodiscard]] std::optional pose_from_register_vector( const RegisterVector& registers); } // 命名空间 line_laser_modbus