Files
ts-qt/components/comment/tactile/tecdec.cc
2025-10-23 17:22:22 +08:00

298 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 触觉传感器串口协议解码器实现(中文注释版)。
// 使用要点:
// 1. 通过循环缓冲累积串口字节,按起始/结束符重组完整帧;
// 2. 使用 C++17 std::vector/std::optional 管理内存与返回值;
// 3. placement new<new> 头文件)在已有内存上构造上下文对象。
//
#include "tacdec.h"
#include <algorithm> // std::find 等算法,用于搜索起始符
#include <cstddef>
#include <cstdint>
#include <new> // placement new在已分配内存上构造对象
namespace ffmsep::tactile { // 与头文件保持一致的命名空间层次
namespace { // 匿名命名空间,用于封装文件内部的工具常量与函数
constexpr std::size_t kMinimumFrameSize = 1 // start 起始符
+ 1 // address 设备地址
+ 1 // function 功能码
+ 1 // length 数据长度
+ 0 // payload 最短为 0
+ 2 // CRC 校验
+ 2; // end markers 结束符
constexpr std::uint16_t kCrcInitial = 0xFFFF; // CRC 初始值
constexpr std::uint16_t kCrcPolynomial = 0xA001; // CRC-16/MODBUS 多项式 (LSB-first)
// 解码器私有上下文,跟随 CPCodecContext 的 priv_data 生命周期。
struct TactileDecoderContext {
std::vector<std::uint8_t> fifo; // 环形缓冲,存储尚未解析的原始字节
bool end_of_stream = false; // 标记是否收到流结尾
std::int64_t next_pts = 0; // 输出帧的自增 pts
};
std::uint16_t crc16_modbus(const std::uint8_t* data, std::size_t length) {
std::uint16_t crc = kCrcInitial;
for (std::size_t i = 0; i < length; ++i) {
crc ^= static_cast<std::uint16_t>(data[i]);
for (int bit = 0; bit < 8; ++bit) {
if ((crc & 0x0001U) != 0U) {
crc = static_cast<std::uint16_t>((crc >> 1U) ^ kCrcPolynomial); // LSB 为 1 时异或多项式
} else {
crc = static_cast<std::uint16_t>(crc >> 1U); // 否则右移
}
}
}
return crc;
}
TactileDecoderContext* get_priv(CPCodecContext* ctx) {
return ctx ? ctx->priv_as<TactileDecoderContext>() : nullptr; // 使用模板封装的安全转换
}
int tactile_init(CPCodecContext* ctx) {
if (!ctx) {
return CP_ERROR_INVALID_ARGUMENT;
}
if (!ctx->priv_data) {
ctx->ensure_priv_storage(sizeof(TactileDecoderContext));
}
auto* storage = static_cast<TactileDecoderContext*>(ctx->priv_data);
new (storage) TactileDecoderContext(); // placement new在已分配的缓冲区中原地构造对象
return CP_SUCCESS;
}
void tactile_close(CPCodecContext* ctx) {
if (!ctx || !ctx->priv_data) {
return;
}
if (auto* priv = get_priv(ctx); priv != nullptr) {
priv->~TactileDecoderContext(); // 显式调用析构函数,释放内部 std::vector
}
}
int tactile_send_packet(CPCodecContext* ctx, const CPPacket& packet) {
auto* priv = get_priv(ctx);
if (!priv) {
return CP_ERROR_INVALID_STATE;
}
if (packet.flush) {
priv->fifo.clear(); // flush: 清除所有缓存,回到初始状态
priv->end_of_stream = false;
priv->next_pts = 0;
}
if (!packet.payload.empty()) {
priv->fifo.insert(priv->fifo.end(), packet.payload.begin(), packet.payload.end()); // 拼接新字节
}
if (packet.end_of_stream) {
priv->end_of_stream = true; // 标记输入源已结束
}
return CP_SUCCESS;
}
std::size_t frame_length_from_payload(std::uint8_t payload_length) {
return 1U + 1U + 1U + 1U + payload_length + 2U + 2U; // 计算完整帧总长度
}
const std::uint8_t* buffer_data(const std::vector<std::uint8_t>& buf) {
return buf.empty() ? nullptr : buf.data();
}
int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
auto* priv = get_priv(ctx);
if (!priv) {
return CP_ERROR_INVALID_STATE;
}
auto& buf = priv->fifo;
while (true) {
if (buf.empty()) {
if (priv->end_of_stream) {
priv->end_of_stream = false;
return CP_ERROR_EOF; // 没有数据且流结束
}
return CP_ERROR_EAGAIN; // 告诉调用者需要更多数据
}
// Discard bytes until start byte is found.
auto start_it = std::find(buf.begin(), buf.end(), kStartByte);
if (start_it == buf.end()) {
buf.clear();
if (priv->end_of_stream) {
priv->end_of_stream = false;
return CP_ERROR_EOF;
}
return CP_ERROR_EAGAIN;
}
if (start_it != buf.begin()) {
buf.erase(buf.begin(), start_it); // 丢掉起始符前的噪声
}
if (buf.size() < kMinimumFrameSize) {
if (priv->end_of_stream) {
// Incomplete frame at end of stream: drop it and report EOF.
buf.clear();
priv->end_of_stream = false;
return CP_ERROR_EOF;
}
return CP_ERROR_EAGAIN; // 帧尚未完整,继续等待
}
const std::uint8_t* data = buffer_data(buf);
const std::uint8_t address = data[1];
const std::uint8_t function = data[2];
const std::uint8_t payload_length = data[3];
const std::size_t total_frame_length = frame_length_from_payload(payload_length); // 根据长度字段推算完整帧尺寸
if (buf.size() < total_frame_length) {
if (priv->end_of_stream) {
// Not enough data before stream end: treat as EOF and drop buffer.
buf.clear();
priv->end_of_stream = false;
return CP_ERROR_EOF;
}
return CP_ERROR_EAGAIN;
}
const std::size_t payload_offset = 4U;
const std::size_t crc_offset = payload_offset + payload_length;
const std::size_t end_offset = crc_offset + 2U; // CRC 后紧接 0x0D 0x0A
const std::uint8_t crc_lo = data[crc_offset];
const std::uint8_t crc_hi = data[crc_offset + 1U];
const std::uint16_t crc_value = static_cast<std::uint16_t>(crc_lo) |
static_cast<std::uint16_t>(crc_hi << 8U);
const std::uint8_t end_first = data[end_offset];
const std::uint8_t end_second = data[end_offset + 1U];
if (end_first != kEndByteFirst || end_second != kEndByteSecond) {
// Invalid end marker, drop start byte and retry.
buf.erase(buf.begin());
continue;
}
const std::size_t crc_region_length = 3U + payload_length; // address + function + length + payload
const std::uint16_t computed_crc = crc16_modbus(data + 1U, crc_region_length);
if (computed_crc != crc_value) {
buf.erase(buf.begin()); // CRC 校验失败,丢弃该起始符并重新同步
continue;
}
(void)address; // 当前实现仅检查 CRC不直接使用地址/功能码
(void)function;
frame.data.assign(buf.begin(), buf.begin() + static_cast<std::ptrdiff_t>(total_frame_length));
frame.pts = priv->next_pts++;
frame.key_frame = true;
frame.valid = true;
buf.erase(buf.begin(), buf.begin() + static_cast<std::ptrdiff_t>(total_frame_length)); // 移除已消费的内容
return CP_SUCCESS;
}
}
const CPCodec kTactileCodec {
"tactile_serial",
"Framed tactile sensor serial protocol decoder",
CPMediaType::Data,
CPCodecID::Tactile,
sizeof(TactileDecoderContext),
&tactile_init,
&tactile_close,
&tactile_send_packet,
&tactile_receive_frame
};
} // namespace
// 将底层 CPFrame 转换为协议专用的 TactileFrame失败时返回 std::nullopt。
std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
if (!frame.valid || frame.data.size() < kMinimumFrameSize) {
return std::nullopt;
}
const auto* bytes = frame.data.data();
const std::size_t size = frame.data.size();
if (bytes[0] != kStartByte) {
return std::nullopt;
}
if (bytes[size - 2] != kEndByteFirst || bytes[size - 1] != kEndByteSecond) {
return std::nullopt;
}
if (size < 4U) {
return std::nullopt;
}
const std::uint8_t length = bytes[3];
if (frame_length_from_payload(length) != size) {
return std::nullopt;
}
const std::uint8_t address = bytes[1];
const FunctionCode function = static_cast<FunctionCode>(bytes[2]);
const std::size_t payload_offset = 4U;
TactileFrame parsed{};
parsed.device_address = address;
parsed.function = function;
parsed.data_length = length;
parsed.payload.assign(bytes + payload_offset, bytes + payload_offset + length);
return parsed;
}
// 将数据域按照小端排列的 16 位压力值序列解析为整数数组。
std::vector<std::uint16_t> parse_pressure_values(const TactileFrame& frame) {
if (frame.payload.empty() || (frame.payload.size() % 2U != 0U)) {
return {};
}
std::vector<std::uint16_t> values;
values.reserve(frame.payload.size() / 2U);
for (std::size_t idx = 0; idx + 1U < frame.payload.size(); idx += 2U) {
const std::uint16_t value = static_cast<std::uint16_t>(
static_cast<std::uint16_t>(frame.payload[idx]) |
static_cast<std::uint16_t>(frame.payload[idx + 1U] << 8U));
values.push_back(value);
}
return values;
}
// 解析矩阵尺寸载荷:两个字节分别为长边/短边。
std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame) {
if (frame.payload.size() != 2U) {
return std::nullopt;
}
MatrixSize size{};
size.long_edge = frame.payload[0];
size.short_edge = frame.payload[1];
return size;
}
// 解析矩阵坐标:协议格式与尺寸一致,直接复用逻辑。
std::optional<MatrixSize> parse_matrix_coordinate_payload(const TactileFrame& frame) {
return parse_matrix_size_payload(frame);
}
// 提供对外查询:返回触觉协议对应的 CPCodec 描述。
const CPCodec* tactile_codec() {
return &kTactileCodec;
}
// 将触觉协议编解码器注册到全局列表,供 cpcodec_find_decoder 使用。
void register_tactile_codec() {
cpcodec_register(&kTactileCodec);
}
} // namespace ffmsep::tactile