feat: integrate tactile stream decoding

This commit is contained in:
2025-10-24 17:15:53 +08:00
parent d819d40fe1
commit 1f65ba0114
32 changed files with 1284 additions and 5400 deletions

View File

@@ -1,14 +1,14 @@
//
// Core FFmpeg-style codec registry and decoding helpers.
//
#include "cpdecoder.hh"
#include "components/ffmsep/cpdecoder.hh"
#include <algorithm>
#include <cstddef>
#include <initializer_list>
#include <mutex>
#include <string_view>
#include <vector>
namespace ffmsep {
namespace {
std::vector<const CPCodec*>& codec_registry() {
@@ -28,7 +28,7 @@ void attach_codec(CPCodecContext* ctx, const CPCodec* codec) {
ctx->codec = codec;
if (!codec) {
ctx->codec_type = CPMediaType::Unknown;
ctx->codec_type = CPMediaType::Data;
ctx->priv_data = nullptr;
ctx->release_priv_storage();
return;
@@ -42,11 +42,12 @@ bool codec_name_equals(const CPCodec* codec, std::string_view name) {
if (!codec || !codec->name) {
return false;
}
return std::string_view(codec->name) == name;
}
} // namespace
using namespace ffmsep;
void* CPCodecContext::ensure_priv_storage(std::size_t size) {
if (size == 0U) {
priv_storage.clear();
@@ -65,7 +66,7 @@ void CPCodecContext::release_priv_storage() noexcept {
priv_data = nullptr;
}
void cpcodec_register(const CPCodec* codec) {
void cpcodec_register(const CPCodec *codec) {
if (!codec || !codec->name) {
return;
}
@@ -76,9 +77,9 @@ void cpcodec_register(const CPCodec* codec) {
if (already != reg.end()) {
return;
}
auto same_id = std::find_if(reg.begin(), reg.end(), [codec](const CPCodec* entry) {
return entry && codec && entry->id == codec->id && codec->id != CPCodecID::Unknown;
return entry && codec && entry->id == codec->id && codec->id != CPCodecID::Unknow;
});
if (same_id != reg.end()) {
*same_id = codec;
@@ -97,7 +98,7 @@ void cpcodec_register_many(std::initializer_list<const CPCodec*> codecs) {
const CPCodec* cpcodec_find_decoder(CPCodecID id) {
std::lock_guard<std::mutex> lock(registry_mutex());
const auto& reg = codec_registry();
auto it = std::find_if(reg.begin(), reg.end(), [id](const CPCodec* codec) {
auto it = std::find_if(reg.begin(), reg.end(), [id](const CPCodec* codec){
return codec && codec->id == id;
});
return it == reg.end() ? nullptr : *it;
@@ -106,10 +107,11 @@ const CPCodec* cpcodec_find_decoder(CPCodecID id) {
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name) {
std::lock_guard<std::mutex> lock(registry_mutex());
const auto& reg = codec_registry();
auto it = std::find_if(reg.begin(), reg.end(), [name](const CPCodec* codec) {
auto it = std::find_if(reg.begin(), reg.end(), [name](const CPCodec* codec){
return codec_name_equals(codec, name);
});
return it == reg.end() ? nullptr : *it;
return it ==reg.end() ? nullptr : *it;
}
std::vector<const CPCodec*> cpcodec_list_codecs() {
@@ -117,15 +119,16 @@ std::vector<const CPCodec*> cpcodec_list_codecs() {
return codec_registry();
}
CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec) {
CPCodecContext* cpcodec_alloc_context(const CPCodec* codec) {
auto* ctx = new CPCodecContext();
if (codec) {
attach_codec(ctx, codec);
}
return ctx;
}
int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec) {
int cpcodec_open(CPCodecContext* ctx, const CPCodec* codec) {
if (!ctx) {
return CP_ERROR_INVALID_ARGUMENT;
}
@@ -153,10 +156,11 @@ int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec) {
return rc;
}
}
return CP_SUCCESS;
}
int cpcodec_close(CPCodecContext* ctx) {
int cpcodec_close(CPCodecContext *ctx) {
if (!ctx) {
return CP_ERROR_INVALID_ARGUMENT;
}
@@ -169,28 +173,33 @@ int cpcodec_close(CPCodecContext* ctx) {
ctx->codec->close(ctx);
}
if (ctx->codec && ctx->codec->close) {
ctx->codec->close(ctx);
}
ctx->is_open = false;
ctx->release_priv_storage();
ctx->codec_type = CPMediaType::Unknown;
ctx->codec_type = CPMediaType::Unknow;
ctx->codec = nullptr;
ctx->priv_data = nullptr;
return CP_SUCCESS;
}
void cpcodec_free_context(CPCodecContext** ctx) {
void cpcodec_free_context(CPCodecContext **ctx) {
if (!ctx || !*ctx) {
return;
}
cpcodec_close(*ctx);
delete *ctx;
*ctx = nullptr;
}
int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet) {
int cpcodec_send_packet(CPCodecContext *ctx, const CPPacket *packet) {
if (!ctx || !packet) {
return CP_ERROR_INVALID_ARGUMENT;
}
if (!ctx->is_open || !ctx->codec) {
if (!ctx || !ctx->codec) {
return CP_ERROR_NOT_OPEN;
}
if (!ctx->codec->send_packet) {
@@ -199,7 +208,7 @@ int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet) {
return ctx->codec->send_packet(ctx, *packet);
}
int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame) {
int cpcodec_receive_frame(CPCodecContext *ctx, CPFrame *frame) {
if (!ctx || !frame) {
return CP_ERROR_INVALID_ARGUMENT;
}
@@ -211,5 +220,4 @@ int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame) {
}
return ctx->codec->receive_frame(ctx, *frame);
}
} // namespace ffmsep
}

View File

@@ -1,9 +1,6 @@
//
// Simple FFmpeg-inspired serial decoding toolkit.
//
#pragma once
#include "components/ffmsep/cpdecoder.hh"
#include <cstdint>
#include <cstddef>
#include <mutex>
@@ -15,7 +12,6 @@
namespace ffmsep {
// Error codes loosely mirroring FFmpeg semantics.
inline constexpr int CP_SUCCESS = 0;
inline constexpr int CP_ERROR_EOF = -1;
inline constexpr int CP_ERROR_EAGAIN = -2;
@@ -24,15 +20,13 @@ inline constexpr int CP_ERROR_INVALID_STATE = -4;
inline constexpr int CP_ERROR_INVALID_ARGUMENT = -5;
enum class CPMediaType : std::uint8_t {
Unknown = 0,
Unknow = 0,
Data,
Audio,
Video
};
enum class CPCodecID : std::uint32_t {
Unknown = 0,
Tactile = 0x54514354u // 'T','Q','C','T' marker for tactile quick codec.
Unknow = 0,
Tactile = 0x54514354u // 'T','Q','C','T':触觉传感器协议标识 Tactile Quick Codec Type
};
struct CPPacket {
@@ -46,7 +40,7 @@ struct CPPacket {
CPPacket(std::vector<std::uint8_t> data, std::int64_t pts_value = 0, std::int64_t dts_value = 0) noexcept
: payload(std::move(data)), pts(pts_value), dts(dts_value) {}
[[nodiscard]] bool empty() const noexcept { return payload.empty(); }
[[nodiscard]] bool empty() const noexcept {return payload.empty();}
};
struct CPFrame {
@@ -57,24 +51,24 @@ struct CPFrame {
void reset() noexcept {
data.clear();
pts = 0;
key_frame = false;
valid = false;
pts = 0;
}
};
struct CPCodecContext;
struct CPCodec {
using InitFn = int (*)(CPCodecContext*);
using CloseFn = void (*)(CPCodecContext*);
using SendPacketFn = int (*)(CPCodecContext*, const CPPacket&);
using ReceiveFrameFn = int (*)(CPCodecContext*, CPFrame&);
using InitFn = int(*)(CPCodecContext*);
using CloseFn = void(*)(CPCodecContext*);
using SendPacketFn = int(*)(CPCodecContext*, const CPPacket&);
using ReceiveFrameFn = int(*)(CPCodecContext*, CPFrame&);
const char* name = nullptr;
const char* long_name = nullptr;
CPMediaType type = CPMediaType::Unknown;
CPCodecID id = CPCodecID::Unknown;
CPMediaType type = CPMediaType::Unknow;
CPCodecID id = CPCodecID::Unknow;
std::size_t priv_data_size = 0;
InitFn init = nullptr;
CloseFn close = nullptr;
@@ -85,13 +79,13 @@ struct CPCodec {
struct CPCodecContext {
const CPCodec* codec = nullptr;
void* priv_data = nullptr;
CPMediaType codec_type = CPMediaType::Unknown;
CPMediaType codec_type = CPMediaType::Unknow;
bool is_open = false;
void clear() noexcept {
codec = nullptr;
priv_data = nullptr;
codec_type = CPMediaType::Unknown;
codec_type = CPMediaType::Unknow;
is_open = false;
priv_storage.clear();
}
@@ -99,21 +93,20 @@ struct CPCodecContext {
void* ensure_priv_storage(std::size_t size);
void release_priv_storage() noexcept;
template <typename T>
template<typename T>
[[nodiscard]] T* priv_as() noexcept {
return static_cast<T*>(priv_data);
}
template <typename T>
template<typename T>
[[nodiscard]] const T* priv_as() const noexcept {
return static_cast<const T*>(priv_data);
}
private:
std::vector<std::uint8_t> priv_storage;
friend CPCodecContext* cpcodec_alloc_context3(const CPCodec*);
friend int cpcodec_open2(CPCodecContext*, const CPCodec*);
friend CPCodecContext* cpcodec_alloc_context(const CPCodec*);
friend int cpcodec_open(CPCodecContext*, const CPCodec*);
friend int cpcodec_close(CPCodecContext*);
};
@@ -123,11 +116,10 @@ const CPCodec* cpcodec_find_decoder(CPCodecID id);
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name);
std::vector<const CPCodec*> cpcodec_list_codecs();
CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec);
int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec = nullptr);
int cpcodec_close(CPCodecContext* ctx);
void cpcodec_free_context(CPCodecContext** ctx);
int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet);
int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame);
} // namespace ffmsep
CPCodecContext* cpcodec_alloc_context(const CPCodec* codec);
int cpcodec_open(CPCodecContext*, const CPCodec*);
int cpcodec_close(CPCodecContext*);
void cpcodec_free_context(CPCodecContext **ctx);
int cpcodec_send_packet(CPCodecContext*, const CPPacket*);
int cpcodec_receive_frame(CPCodecContext*, CPFrame*);
}

View File

@@ -0,0 +1,627 @@
#include "components/ffmsep/cpstream_core.hh"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <utility>
#include <vector>
namespace ffmsep {
namespace {
constexpr auto kReaderIdleSleep = std::chrono::milliseconds(5);
constexpr auto kDecoderIdleSleep = std::chrono::milliseconds(1);
const CPCodec* resolve_requested_codec(const CPStreamConfig& config) {
if (!config.codec_name.empty()) {
if (const CPCodec* codec = cpcodec_find_decoder_by_name(config.codec_name)) {
return codec;
}
}
if (config.codec_id != CPCodecID::Unknow) {
if (const CPCodec* codec = cpcodec_find_decoder(config.codec_id)) {
return codec;
}
}
return nullptr;
}
} // namespace
struct CPStreamCore::Impl {
struct Packet {
std::vector<std::uint8_t> payload;
std::int64_t pts = 0;
bool end_of_stream = false;
bool flush = false;
};
explicit Impl(CPStreamConfig config)
: config_(std::move(config)) {
normalize_config();
}
~Impl() = default;
void normalize_config() {
if (config_.read_chunk_size == 0U) {
config_.read_chunk_size = 256U;
}
if (config_.packet_queue_capacity == 0U) {
config_.packet_queue_capacity = 1U;
}
if (config_.frame_queue_capacity == 0U) {
config_.frame_queue_capacity = 1U;
}
frame_queue_capacity_ = config_.frame_queue_capacity;
}
bool open(const CPStreamConfig& cfg) {
stop();
close();
config_ = cfg;
normalize_config();
if (config_.port.empty()) {
set_last_error("serial port is empty");
return false;
}
codec_descriptor_ = resolve_requested_codec(config_);
if (!codec_descriptor_) {
set_last_error("codec not found for requested identifier");
return false;
}
codec_ctx_ = cpcodec_alloc_context(codec_descriptor_);
if (!codec_ctx_) {
set_last_error("failed to allocate codec context");
return false;
}
int rc = cpcodec_open(codec_ctx_, codec_descriptor_);
if (rc < CP_SUCCESS) {
set_last_error("failed to open codec context: error " + std::to_string(rc));
cpcodec_free_context(&codec_ctx_);
codec_ctx_ = nullptr;
return false;
}
try {
auto serial = std::make_shared<serial::Serial>(
config_.port,
config_.baudrate,
config_.timeout,
config_.bytesize,
config_.parity,
config_.stopbits,
config_.flowcontrol);
serial->open();
serial->flush();
{
std::lock_guard lock(serial_mutex_);
serial_ = std::move(serial);
}
} catch (const serial::IOException& ex) {
set_last_error(ex.what() ? ex.what() : "serial IO exception");
cpcodec_close(codec_ctx_);
cpcodec_free_context(&codec_ctx_);
codec_ctx_ = nullptr;
return false;
} catch (const serial::SerialException& ex) {
set_last_error(ex.what() ? ex.what() : "serial exception");
cpcodec_close(codec_ctx_);
cpcodec_free_context(&codec_ctx_);
codec_ctx_ = nullptr;
return false;
} catch (const std::exception& ex) {
set_last_error(ex.what());
cpcodec_close(codec_ctx_);
cpcodec_free_context(&codec_ctx_);
codec_ctx_ = nullptr;
return false;
}
{
std::lock_guard lock(packet_mutex_);
packet_queue_.clear();
}
{
std::lock_guard lock(frame_mutex_);
frame_queue_.clear();
}
pts_counter_.store(0, std::memory_order_relaxed);
stop_requested_.store(false, std::memory_order_release);
set_last_error({});
return true;
}
bool open() {
return open(config_);
}
bool reopen(const CPStreamConfig& cfg) {
return open(cfg);
}
void close() {
stop();
{
std::lock_guard lock(serial_mutex_);
if (serial_) {
try {
if (serial_->isOpen()) {
serial_->close();
}
} catch (...) {
// Ignore close errors.
}
serial_.reset();
}
}
if (codec_ctx_) {
cpcodec_close(codec_ctx_);
cpcodec_free_context(&codec_ctx_);
codec_ctx_ = nullptr;
}
{
std::lock_guard lock(packet_mutex_);
packet_queue_.clear();
}
{
std::lock_guard lock(frame_mutex_);
frame_queue_.clear();
}
}
bool start() {
if (running_.load(std::memory_order_acquire)) {
return true;
}
std::shared_ptr<serial::Serial> serial_copy;
{
std::lock_guard lock(serial_mutex_);
serial_copy = serial_;
}
if (!serial_copy || !serial_copy->isOpen()) {
set_last_error("serial port is not open");
return false;
}
if (!codec_ctx_ || !codec_ctx_->is_open) {
set_last_error("codec context is not ready");
return false;
}
stop_requested_.store(false, std::memory_order_release);
running_.store(true, std::memory_order_release);
reader_thread_ = std::thread(&Impl::reader_loop, this);
decoder_thread_ = std::thread(&Impl::decoder_loop, this);
return true;
}
void stop() {
if (!running_.exchange(false, std::memory_order_acq_rel)) {
return;
}
stop_requested_.store(true, std::memory_order_release);
packet_cv_.notify_all();
if (reader_thread_.joinable()) {
reader_thread_.join();
}
signal_decoder_flush(true);
packet_cv_.notify_all();
if (decoder_thread_.joinable()) {
decoder_thread_.join();
}
stop_requested_.store(false, std::memory_order_release);
{
std::lock_guard lock(packet_mutex_);
packet_queue_.clear();
}
if (codec_ctx_ && codec_ctx_->is_open) {
reset_decoder();
}
}
bool is_open() const {
std::lock_guard lock(serial_mutex_);
return serial_ && serial_->isOpen();
}
bool is_running() const {
return running_.load(std::memory_order_acquire);
}
bool send(const std::vector<std::uint8_t>& data) {
return send(data.data(), data.size());
}
bool send(const std::uint8_t* data, std::size_t size) {
if (!data || size == 0U) {
return false;
}
std::shared_ptr<serial::Serial> serial_copy;
{
std::lock_guard lock(serial_mutex_);
serial_copy = serial_;
}
if (!serial_copy || !serial_copy->isOpen()) {
set_last_error("serial port is not open");
return false;
}
try {
const auto written = serial_copy->write(data, size);
return written == size;
} catch (const serial::IOException& ex) {
set_last_error(ex.what() ? ex.what() : "serial IO exception");
} catch (const serial::SerialException& ex) {
set_last_error(ex.what() ? ex.what() : "serial exception");
} catch (const std::exception& ex) {
set_last_error(ex.what());
}
return false;
}
std::optional<DecodedFrame> try_pop_frame() {
std::lock_guard lock(frame_mutex_);
if (frame_queue_.empty()) {
return std::nullopt;
}
DecodedFrame frame = std::move(frame_queue_.front());
frame_queue_.pop_front();
return frame;
}
bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) {
std::unique_lock lock(frame_mutex_);
if (!frame_cv_.wait_for(lock, timeout, [&] {
return !frame_queue_.empty();
})) {
return false;
}
frame = std::move(frame_queue_.front());
frame_queue_.pop_front();
return true;
}
void clear_frames() {
std::lock_guard lock(frame_mutex_);
frame_queue_.clear();
}
void set_frame_queue_capacity(std::size_t capacity) {
if (capacity == 0U) {
capacity = 1U;
}
{
std::lock_guard lock(frame_mutex_);
frame_queue_capacity_ = capacity;
config_.frame_queue_capacity = capacity;
while (frame_queue_.size() > frame_queue_capacity_) {
frame_queue_.pop_front();
}
}
}
void set_frame_callback(FrameCallback callback) {
std::lock_guard lock(callback_mutex_);
frame_callback_ = std::move(callback);
}
CPStreamConfig config() const {
return config_;
}
std::string last_error() const {
std::lock_guard lock(last_error_mutex_);
return last_error_;
}
static std::vector<serial::PortInfo> list_ports() {
return serial::list_ports();
}
void reader_loop() {
std::vector<std::uint8_t> buffer(config_.read_chunk_size);
while (!stop_requested_.load(std::memory_order_acquire)) {
std::shared_ptr<serial::Serial> serial_copy;
{
std::lock_guard lock(serial_mutex_);
serial_copy = serial_;
}
if (!serial_copy || !serial_copy->isOpen()) {
std::this_thread::sleep_for(kReaderIdleSleep);
continue;
}
std::size_t bytes_read = 0;
try {
bytes_read = serial_copy->read(buffer.data(), buffer.size());
} catch (const serial::IOException& ex) {
set_last_error(ex.what() ? ex.what() : "serial IO exception");
std::this_thread::sleep_for(kReaderIdleSleep);
continue;
} catch (const serial::SerialException& ex) {
set_last_error(ex.what() ? ex.what() : "serial exception");
std::this_thread::sleep_for(kReaderIdleSleep);
continue;
} catch (const std::exception& ex) {
set_last_error(ex.what());
std::this_thread::sleep_for(kReaderIdleSleep);
continue;
}
if (bytes_read == 0U) {
std::this_thread::sleep_for(kReaderIdleSleep);
continue;
}
Packet packet;
packet.payload.assign(buffer.begin(), buffer.begin() + static_cast<std::ptrdiff_t>(bytes_read));
packet.pts = pts_counter_.fetch_add(1, std::memory_order_relaxed);
{
std::lock_guard lock(packet_mutex_);
if (packet_queue_.size() >= config_.packet_queue_capacity) {
packet_queue_.pop_front();
}
packet_queue_.push_back(std::move(packet));
}
packet_cv_.notify_one();
}
}
void decoder_loop() {
while (true) {
Packet packet;
{
std::unique_lock lock(packet_mutex_);
packet_cv_.wait(lock, [&] {
return stop_requested_.load(std::memory_order_acquire) || !packet_queue_.empty();
});
if (packet_queue_.empty()) {
if (stop_requested_.load(std::memory_order_acquire)) {
break;
}
continue;
}
packet = std::move(packet_queue_.front());
packet_queue_.pop_front();
}
if (!codec_ctx_ || !codec_ctx_->is_open) {
if (packet.end_of_stream) {
break;
}
std::this_thread::sleep_for(kDecoderIdleSleep);
continue;
}
CPPacket cp_packet;
cp_packet.payload = std::move(packet.payload);
cp_packet.pts = packet.pts;
cp_packet.dts = packet.pts;
cp_packet.end_of_stream = packet.end_of_stream;
cp_packet.flush = packet.flush;
int rc = cpcodec_send_packet(codec_ctx_, &cp_packet);
if (rc < CP_SUCCESS) {
if (packet.end_of_stream) {
break;
}
continue;
}
while (true) {
CPFrame frame;
rc = cpcodec_receive_frame(codec_ctx_, &frame);
if (rc == CP_SUCCESS) {
DecodedFrame decoded;
decoded.pts = frame.pts;
decoded.received_at = std::chrono::steady_clock::now();
decoded.frame = std::move(frame);
FrameCallback callback_copy;
{
std::lock_guard lock(callback_mutex_);
callback_copy = frame_callback_;
}
if (callback_copy) {
callback_copy(decoded);
}
{
std::lock_guard lock(frame_mutex_);
if (frame_queue_.size() >= frame_queue_capacity_) {
frame_queue_.pop_front();
}
frame_queue_.push_back(std::move(decoded));
}
frame_cv_.notify_one();
} else if (rc == CP_ERROR_EAGAIN) {
break;
} else {
if (rc == CP_ERROR_EOF && packet.end_of_stream) {
return;
}
break;
}
}
if (packet.end_of_stream) {
break;
}
}
}
void signal_decoder_flush(bool end_of_stream) {
Packet packet;
packet.flush = true;
packet.end_of_stream = end_of_stream;
{
std::lock_guard lock(packet_mutex_);
packet_queue_.push_back(std::move(packet));
}
packet_cv_.notify_one();
}
void reset_decoder() {
if (!codec_ctx_ || !codec_descriptor_) {
return;
}
cpcodec_close(codec_ctx_);
int rc = cpcodec_open(codec_ctx_, codec_descriptor_);
if (rc < CP_SUCCESS) {
set_last_error("failed to reset codec context: error " + std::to_string(rc));
}
}
void set_last_error(std::string message) {
std::lock_guard lock(last_error_mutex_);
last_error_ = std::move(message);
}
CPStreamConfig config_{};
const CPCodec* codec_descriptor_ = nullptr;
std::shared_ptr<serial::Serial> serial_;
mutable std::mutex serial_mutex_;
CPCodecContext* codec_ctx_ = nullptr;
std::thread reader_thread_;
std::thread decoder_thread_;
std::mutex packet_mutex_;
std::condition_variable packet_cv_;
std::deque<Packet> packet_queue_;
std::mutex frame_mutex_;
std::condition_variable frame_cv_;
std::deque<DecodedFrame> frame_queue_;
std::size_t frame_queue_capacity_ = 16;
FrameCallback frame_callback_;
mutable std::mutex callback_mutex_;
std::atomic<bool> running_{false};
std::atomic<bool> stop_requested_{false};
std::atomic<std::int64_t> pts_counter_{0};
std::string last_error_;
mutable std::mutex last_error_mutex_;
};
CPStreamCore::CPStreamCore(CPStreamConfig config)
: impl_(std::make_unique<Impl>(std::move(config))) {}
CPStreamCore::~CPStreamCore() {
if (impl_) {
impl_->stop();
impl_->close();
}
}
bool CPStreamCore::open(const CPStreamConfig& config) {
return impl_->open(config);
}
bool CPStreamCore::open() {
return impl_->open();
}
bool CPStreamCore::reopen(const CPStreamConfig& config) {
return impl_->reopen(config);
}
void CPStreamCore::close() {
impl_->close();
}
bool CPStreamCore::start() {
return impl_->start();
}
void CPStreamCore::stop() {
impl_->stop();
}
bool CPStreamCore::is_open() const noexcept {
return impl_->is_open();
}
bool CPStreamCore::is_running() const noexcept {
return impl_->is_running();
}
bool CPStreamCore::send(const std::vector<std::uint8_t>& data) {
return impl_->send(data);
}
bool CPStreamCore::send(const std::uint8_t* data, std::size_t size) {
return impl_->send(data, size);
}
std::optional<DecodedFrame> CPStreamCore::try_pop_frame() {
return impl_->try_pop_frame();
}
bool CPStreamCore::wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) {
return impl_->wait_for_frame(frame, timeout);
}
void CPStreamCore::clear_frames() {
impl_->clear_frames();
}
void CPStreamCore::set_frame_queue_capacity(std::size_t capacity) {
impl_->set_frame_queue_capacity(capacity);
}
void CPStreamCore::set_frame_callback(FrameCallback callback) {
impl_->set_frame_callback(std::move(callback));
}
CPStreamConfig CPStreamCore::config() const {
return impl_->config();
}
std::string CPStreamCore::last_error() const {
return impl_->last_error();
}
std::vector<serial::PortInfo> CPStreamCore::list_available_ports() {
return Impl::list_ports();
}
} // namespace ffmsep

View File

@@ -0,0 +1,77 @@
#pragma once
#include "components/ffmsep/cpdecoder.hh"
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <serial/serial.h>
#include <string>
#include <vector>
namespace ffmsep {
struct DecodedFrame {
CPFrame frame;
std::chrono::steady_clock::time_point received_at{};
std::int64_t pts = 0;
};
struct CPStreamConfig {
std::string port;
std::uint32_t baudrate = 115200;
serial::Timeout timeout = serial::Timeout::simpleTimeout(50);
serial::bytesize_t bytesize = serial::eightbits;
serial::parity_t parity = serial::parity_none;
serial::stopbits_t stopbits = serial::stopbits_one;
serial::flowcontrol_t flowcontrol = serial::flowcontrol_none;
std::size_t read_chunk_size = 256;
std::size_t packet_queue_capacity = 128;
std::size_t frame_queue_capacity = 16;
CPCodecID codec_id = CPCodecID::Unknow;
std::string codec_name;
};
class CPStreamCore {
public:
using FrameCallback = std::function<void(const DecodedFrame&)>;
explicit CPStreamCore(CPStreamConfig config = {});
~CPStreamCore();
CPStreamCore(const CPStreamCore&) = delete;
CPStreamCore& operator=(const CPStreamCore&) = delete;
bool open(const CPStreamConfig& config);
bool open();
bool reopen(const CPStreamConfig& config);
void close();
bool start();
void stop();
[[nodiscard]] bool is_open() const noexcept;
[[nodiscard]] bool is_running() const noexcept;
bool send(const std::vector<std::uint8_t>& data);
bool send(const std::uint8_t* data, std::size_t size);
std::optional<DecodedFrame> try_pop_frame();
bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout);
void clear_frames();
void set_frame_queue_capacity(std::size_t capacity);
void set_frame_callback(FrameCallback callback);
[[nodiscard]] CPStreamConfig config() const;
[[nodiscard]] std::string last_error() const;
static std::vector<serial::PortInfo> list_available_ports();
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace ffmsep

View File

@@ -1,28 +1,25 @@
//
// Decoder for the tactile sensor framed binary protocol.
//
#include "tacdec.h"
#include "tacdec.hh"
#include "components/ffmsep/cpdecoder.hh"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <new>
#include <optional>
#include <vector>
namespace ffmsep::tactile {
namespace {
constexpr std::size_t kMinimumFrameSize = 1 // start
+ 1 // address
+ 1 // function
+ 1 // length
+ 0 // payload
+ 2 // CRC
+ 2; // end markers
constexpr std::size_t kMinimumFrameSize = 1
+ 1
+ 1
+ 1
+ 0
+ 2
+ 2;
constexpr std::uint16_t kCrcInitial = 0xFFFF;
constexpr std::uint16_t kCrcPolynomial = 0xA001; // CRC-16/MODBUS (LSB first)
constexpr std::uint16_t kCrcPolynomial = 0xA001;
struct TactileDecoderContext {
std::vector<std::uint8_t> fifo;
@@ -30,6 +27,14 @@ struct TactileDecoderContext {
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<std::uint8_t>& 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) {
@@ -37,7 +42,8 @@ std::uint16_t crc16_modbus(const std::uint8_t* data, std::size_t length) {
for (int bit = 0; bit < 8; ++bit) {
if ((crc & 0x0001U) != 0U) {
crc = static_cast<std::uint16_t>((crc >> 1U) ^ kCrcPolynomial);
} else {
}
else {
crc = static_cast<std::uint16_t>(crc >> 1U);
}
}
@@ -71,11 +77,10 @@ void tactile_close(CPCodecContext* ctx) {
}
int tactile_send_packet(CPCodecContext* ctx, const CPPacket& packet) {
auto* priv = get_priv(ctx);
auto priv = get_priv(ctx);
if (!priv) {
return CP_ERROR_INVALID_STATE;
}
if (packet.flush) {
priv->fifo.clear();
priv->end_of_stream = false;
@@ -93,14 +98,6 @@ int tactile_send_packet(CPCodecContext* ctx, const CPPacket& packet) {
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) {
@@ -118,7 +115,6 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
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();
@@ -128,13 +124,13 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
}
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;
@@ -143,14 +139,13 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
}
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::uint8_t address = data[1U];
const FunctionCode function = static_cast<FunctionCode>(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) {
// Not enough data before stream end: treat as EOF and drop buffer.
buf.clear();
priv->end_of_stream = false;
return CP_ERROR_EOF;
@@ -171,12 +166,11 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
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::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());
@@ -192,24 +186,22 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
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
.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
};
} // namespace
}
std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
if (!frame.valid || frame.data.size() < kMinimumFrameSize) {
@@ -221,6 +213,7 @@ std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
if (bytes[0] != kStartByte) {
return std::nullopt;
}
if (bytes[size - 2] != kEndByteFirst || bytes[size - 1] != kEndByteSecond) {
return std::nullopt;
}
@@ -233,7 +226,6 @@ std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
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;
@@ -271,10 +263,6 @@ std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame) {
return size;
}
std::optional<MatrixSize> parse_matrix_coordinate_payload(const TactileFrame& frame) {
return parse_matrix_size_payload(frame);
}
const CPCodec* tactile_codec() {
return &kTactileCodec;
}
@@ -282,5 +270,4 @@ const CPCodec* tactile_codec() {
void register_tactile_codec() {
cpcodec_register(&kTactileCodec);
}
} // namespace ffmsep::tactile
}

View File

@@ -1,29 +1,23 @@
//
// High level helpers for the tactile sensor binary protocol.
//
#pragma once
#include "../cpdecoder.hh"
#include "cpdecoder.hh"
#include <cstdint>
#include <optional>
#include <vector>
namespace ffmsep::tactile {
inline constexpr std::uint8_t kStartByte = 0x3A;
inline constexpr std::uint8_t kEndByteFirst = 0x0D;
inline constexpr std::uint8_t kEndByteSecond = 0x0A;
enum class FunctionCode : std::uint8_t {
Unknown = 0x00,
ReadMatrix = 0x01,
ReadSingle = 0x02,
ReadTemperature = 0x03,
SetDeviceId = 0x51,
SetMatrixSize = 0x52,
CalibrationMode = 0x53
Unknown = 0x00,
ReadMatrix = 0x01,
ReadSingle = 0x02,
ReadTemperature = 0x03,
SetDeviceId = 0x51,
SetMatrixSize = 0x52,
CalibrationMode = 0x53,
};
struct MatrixSize {
@@ -32,18 +26,17 @@ struct MatrixSize {
};
struct TactileFrame {
std::uint8_t device_address = 0;
FunctionCode function = FunctionCode::Unknown;
std::uint8_t data_length = 0;
std::uint8_t device_address = 0;
FunctionCode function = FunctionCode::Unknown;
std::uint8_t data_length = 0;
std::vector<std::uint8_t> payload;
};
std::optional<TactileFrame> parse_frame(const CPFrame& frame);
std::vector<std::uint16_t> parse_pressure_values(const TactileFrame& frame);
std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame);
std::optional<MatrixSize> parse_matrix_coordinate_payload(const TactileFrame& frame);
std::optional<MatrixSize> parse_patrix_coordinate_payload(const TactileFrame& frame);
const CPCodec* tactile_codec();
void register_tactile_codec();
} // namespace ffmsep::tactile
}