Files
2026-06-09 12:42:15 +08:00

156 lines
6.0 KiB
C++

#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
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<std::uint8_t>;
using RegisterVector = std::vector<std::uint16_t>;
using PoseRegisters = std::array<std::uint16_t, kPoseRegisterCount>;
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 <typename T>
struct ParseResult {
// 这里使用 std::optional 而不是异常,让固件版本的错误处理保持显式且低开销。
// 调用方可以自行决定无效帧是忽略、记录、重试,还是转换为 Modbus 异常响应。
std::optional<T> value;
std::optional<ParseError> 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<ReadRequest> parse_read_request(const ByteVector& frame);
[[nodiscard]] ParseResult<ReadResponse> parse_read_response(const ByteVector& frame);
[[nodiscard]] ParseResult<WriteRequest> parse_write_request(const ByteVector& frame);
[[nodiscard]] ParseResult<WriteResponse> parse_write_response(const ByteVector& frame);
[[nodiscard]] RegisterVector pose_to_register_vector(const Pose6D& pose);
[[nodiscard]] std::optional<Pose6D> pose_from_register_vector(
const RegisterVector& registers);
} // 命名空间 line_laser_modbus