156 lines
6.0 KiB
C++
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
|