#include "tacdec.hh" #include "components/ffmsep/cpdecoder.hh" #include #include #include #include #include #include namespace ffmsep::tactile { namespace { constexpr std::size_t kMinimumFrameSize = 1 + 1 + 1 + 1 + 0 + 2 + 2; constexpr std::uint16_t kCrcInitial = 0xFFFF; constexpr std::uint16_t kCrcPolynomial = 0xA001; struct TactileDecoderContext { std::vector fifo; bool end_of_stream = false; std::int64_t next_pts = 0; }; 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& buf) { return buf.empty() ? nullptr : buf.data(); } 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(data[i]); for (int bit = 0; bit < 8; ++bit) { if ((crc & 0x0001U) != 0U) { crc = static_cast((crc >> 1U) ^ kCrcPolynomial); } else { crc = static_cast(crc >> 1U); } } } return crc; } TactileDecoderContext* get_priv(CPCodecContext* ctx) { return ctx ? ctx->priv_as() : 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(ctx->priv_data); new (storage) TactileDecoderContext(); return CP_SUCCESS; } void tactile_close(CPCodecContext* ctx) { if (!ctx || !ctx->priv_data) { return; } if (auto* priv = get_priv(ctx); priv != nullptr) { priv->~TactileDecoderContext(); } } 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(); 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; } 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; } 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) { 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[1U]; const FunctionCode function = static_cast(data[2U]); const std::uint8_t payload_length = data[3U]; const std::size_t total_frame_length = frame_length_from_payload(payload_length); if (buf.size() < total_frame_length) { if (priv->end_of_stream) { 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; 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(crc_lo) | static_cast(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) { buf.erase(buf.begin()); continue; } const std::size_t crc_region_length = 3U + payload_length; const std::uint16_t computed_crc = crc16_modbus(data + 1U, crc_region_length); if (computed_crc != crc_value) { buf.erase(buf.begin()); continue; } (void)address; (void)function; frame.data.assign(buf.begin(), buf.begin() + static_cast(total_frame_length)); frame.pts = priv->next_pts++; frame.key_frame = true; frame.valid = true; buf.erase(buf.begin(), buf.begin() + static_cast(total_frame_length)); return CP_SUCCESS; } } const CPCodec kTactileCodec { .name = "tactile_serial", .long_name = "Framed tactile sensor serial protocol decoder", .type = CPMediaType::Data, .id = CPCodecID::Tactile, .priv_data_size = sizeof(TactileDecoderContext), .init = &tactile_init, .close = &tactile_close, .send_packet = &tactile_send_packet, .receive_frame = &tactile_receive_frame }; } std::optional 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(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; } std::vector parse_pressure_values(const TactileFrame& frame) { if (frame.payload.empty() || (frame.payload.size() % 2U != 0U)) { return {}; } std::vector 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( static_cast(frame.payload[idx]) | static_cast(frame.payload[idx + 1U] << 8U)); values.push_back(value); } return values; } std::optional 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; } const CPCodec* tactile_codec() { return &kTactileCodec; } void register_tactile_codec() { cpcodec_register(&kTactileCodec); } }