Merge branch 'dev'

# Conflicts:
#	components/ffmsep/cpstream_core.hh
#	components/view.cc
This commit is contained in:
2025-11-25 16:20:04 +08:00
112 changed files with 10627 additions and 8992 deletions

View File

@@ -1,5 +1,8 @@
#include "components/ffmsep/cpstream_core.hh"
#include "components/ffmsep/presist/presist.hh"
#include "dlog/dlog.hh"
#include <algorithm>
#include <atomic>
#include <chrono>
@@ -7,20 +10,22 @@
#include <cstddef>
#include <cstdint>
#include <deque>
#include <future>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <utility>
#include <vector>
using namespace std::chrono_literals;
namespace ffmsep {
namespace {
constexpr auto kReaderIdleSleep = std::chrono::milliseconds(5);
constexpr auto kDecoderIdleSleep = std::chrono::milliseconds(1);
constexpr auto kReaderIdleSleep = 5ms;
constexpr auto kDecoderIdleSleep = 1ms;
const CPCodec* resolve_requested_codec(const CPStreamConfig& config) {
if (!config.codec_name.empty()) {
@@ -49,6 +54,7 @@ struct CPStreamCore::Impl {
explicit Impl(CPStreamConfig config)
: config_(std::move(config)) {
normalize_config();
frame_writer_ = std::make_unique<persist::JsonWritter>();
}
~Impl() = default;
@@ -64,7 +70,7 @@ struct CPStreamCore::Impl {
config_.frame_queue_capacity = 1U;
}
if (config_.slave_request_interval.count() < 0) {
config_.slave_request_interval = std::chrono::milliseconds{0};
config_.slave_request_interval = 0ms;
}
frame_queue_capacity_ = config_.frame_queue_capacity;
}
@@ -116,7 +122,7 @@ struct CPStreamCore::Impl {
serial->flush();
{
std::lock_guard lock(serial_mutex_);
std::lock_guard<std::mutex> lock(serial_mutex_);
serial_ = std::move(serial);
}
} catch (const serial::IOException& ex) {
@@ -140,11 +146,11 @@ struct CPStreamCore::Impl {
}
{
std::lock_guard lock(packet_mutex_);
std::lock_guard<std::mutex> lock(packet_mutex_);
packet_queue_.clear();
}
{
std::lock_guard lock(frame_mutex_);
std::lock_guard<std::mutex> lock(frame_mutex_);
frame_queue_.clear();
}
pts_counter_.store(0, std::memory_order_relaxed);
@@ -165,7 +171,7 @@ struct CPStreamCore::Impl {
stop();
{
std::lock_guard lock(serial_mutex_);
std::lock_guard<std::mutex> lock(serial_mutex_);
if (serial_) {
try {
if (serial_->isOpen()) {
@@ -185,12 +191,13 @@ struct CPStreamCore::Impl {
}
{
std::lock_guard lock(packet_mutex_);
std::lock_guard<std::mutex> lock(packet_mutex_);
packet_queue_.clear();
}
{
std::lock_guard lock(frame_mutex_);
std::lock_guard<std::mutex> lock(frame_mutex_);
frame_queue_.clear();
frame_record_queue_.clear();
}
}
@@ -201,7 +208,7 @@ struct CPStreamCore::Impl {
std::shared_ptr<serial::Serial> serial_copy;
{
std::lock_guard lock(serial_mutex_);
std::lock_guard<std::mutex> lock(serial_mutex_);
serial_copy = serial_;
}
if (!serial_copy || !serial_copy->isOpen()) {
@@ -250,7 +257,7 @@ struct CPStreamCore::Impl {
stop_requested_.store(false, std::memory_order_release);
{
std::lock_guard lock(packet_mutex_);
std::lock_guard<std::mutex> lock(packet_mutex_);
packet_queue_.clear();
}
@@ -260,7 +267,7 @@ struct CPStreamCore::Impl {
}
bool is_open() const {
std::lock_guard lock(serial_mutex_);
std::lock_guard<std::mutex> lock(serial_mutex_);
return serial_ && serial_->isOpen();
}
@@ -279,7 +286,7 @@ struct CPStreamCore::Impl {
std::shared_ptr<serial::Serial> serial_copy;
{
std::lock_guard lock(serial_mutex_);
std::lock_guard<std::mutex> lock(serial_mutex_);
serial_copy = serial_;
}
@@ -301,17 +308,17 @@ struct CPStreamCore::Impl {
return false;
}
std::optional<DecodedFrame> try_pop_frame() {
std::lock_guard lock(frame_mutex_);
std::optional<std::shared_ptr<DecodedFrame>> try_pop_frame() {
std::lock_guard<std::mutex> lock(frame_mutex_);
if (frame_queue_.empty()) {
return std::nullopt;
}
DecodedFrame frame = std::move(frame_queue_.front());
std::shared_ptr<DecodedFrame> frame = std::move(frame_queue_.front());
frame_queue_.pop_front();
return frame;
}
bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) {
bool wait_for_frame(std::shared_ptr<DecodedFrame>& frame, std::chrono::milliseconds timeout) {
std::unique_lock lock(frame_mutex_);
if (!frame_cv_.wait_for(lock, timeout, [&] {
return !frame_queue_.empty();
@@ -324,7 +331,7 @@ struct CPStreamCore::Impl {
}
void clear_frames() {
std::lock_guard lock(frame_mutex_);
std::lock_guard<std::mutex> lock(frame_mutex_);
frame_queue_.clear();
}
@@ -333,7 +340,7 @@ struct CPStreamCore::Impl {
capacity = 1U;
}
{
std::lock_guard lock(frame_mutex_);
std::lock_guard<std::mutex> lock(frame_mutex_);
frame_queue_capacity_ = capacity;
config_.frame_queue_capacity = capacity;
while (frame_queue_.size() > frame_queue_capacity_) {
@@ -342,8 +349,46 @@ struct CPStreamCore::Impl {
}
}
void clear_recorded_frames() {
std::lock_guard<std::mutex> lock(frame_mutex_);
frame_record_queue_.clear();
}
std::size_t recorded_frame_count() const {
std::lock_guard<std::mutex> lock(frame_mutex_);
return frame_record_queue_.size();
}
std::future<persist::WriteResult> export_recorded_frames(const std::string& path, bool clear_after_export) {
if (!frame_writer_) {
frame_writer_ = std::make_unique<persist::JsonWritter>();
}
std::deque<std::shared_ptr<DecodedFrame>> snapshot;
{
std::lock_guard<std::mutex> lock(frame_mutex_);
snapshot = frame_record_queue_;
if (clear_after_export) {
frame_record_queue_.clear();
}
}
if (snapshot.empty()) {
std::promise<persist::WriteResult> promise;
auto future = promise.get_future();
promise.set_value(persist::WriteResult{
false,
"no recorded frames available",
path
});
return future;
}
return frame_writer_->enqueue(path, std::move(snapshot));
}
void set_frame_callback(FrameCallback callback) {
std::lock_guard lock(callback_mutex_);
std::lock_guard<std::mutex> lock(callback_mutex_);
frame_callback_ = std::move(callback);
}
@@ -352,7 +397,7 @@ struct CPStreamCore::Impl {
}
std::string last_error() const {
std::lock_guard lock(last_error_mutex_);
std::lock_guard<std::mutex> lock(last_error_mutex_);
return last_error_;
}
@@ -366,7 +411,7 @@ struct CPStreamCore::Impl {
while (!stop_requested_.load(std::memory_order_acquire)) {
std::shared_ptr<serial::Serial> serial_copy;
{
std::lock_guard lock(serial_mutex_);
std::lock_guard<std::mutex> lock(serial_mutex_);
serial_copy = serial_;
}
if (!serial_copy || !serial_copy->isOpen()) {
@@ -401,7 +446,7 @@ struct CPStreamCore::Impl {
packet.pts = pts_counter_.fetch_add(1, std::memory_order_relaxed);
{
std::lock_guard lock(packet_mutex_);
std::lock_guard<std::mutex> lock(packet_mutex_);
if (packet_queue_.size() >= config_.packet_queue_capacity) {
packet_queue_.pop_front();
}
@@ -415,7 +460,7 @@ struct CPStreamCore::Impl {
const auto command = config_.slave_request_command;
auto interval = config_.slave_request_interval;
if (interval.count() < 0) {
interval = std::chrono::milliseconds{0};
interval = 0ms;
}
const bool repeat = interval.count() > 0;
@@ -485,23 +530,24 @@ struct CPStreamCore::Impl {
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);
if (codec_descriptor_ && codec_descriptor_->id == CPCodecID::Tactile) {
if (auto parsed = tactile::parse_frame(decoded.frame)) {
decoded.tactile = parsed;
decoded.tactile_pressures = tactile::parse_pressure_values(*parsed);
auto decoded = std::make_shared<DecodedFrame>();
decoded->pts = frame.pts;
decoded->received_at = std::chrono::steady_clock::now();
decoded->frame = std::move(frame);
decoded->id = codec_descriptor_ ? codec_descriptor_->id : CPCodecID::Unknow;
if (decoded->id == CPCodecID::Tactile) {
if (auto parsed = tactile::parse_frame(decoded->frame)) {
decoded->tactile = parsed;
decoded->tactile_pressures = tactile::parse_pressure_values(*parsed);
if (auto matrix = tactile::parse_matrix_size_payload(*parsed)) {
decoded.tactile_matrix_size = matrix;
decoded->tactile_matrix_size = matrix;
}
}
}
FrameCallback callback_copy;
{
std::lock_guard lock(callback_mutex_);
std::lock_guard<std::mutex> lock(callback_mutex_);
callback_copy = frame_callback_;
}
if (callback_copy) {
@@ -509,11 +555,14 @@ struct CPStreamCore::Impl {
}
{
std::lock_guard lock(frame_mutex_);
std::lock_guard<std::mutex> lock(frame_mutex_);
if (frame_queue_.size() >= frame_queue_capacity_) {
frame_queue_.pop_front();
}
frame_queue_.push_back(std::move(decoded));
frame_queue_.push_back(decoded);
if (decoded->id == CPCodecID::Tactile) {
frame_record_queue_.push_back(decoded);
}
}
frame_cv_.notify_one();
} else if (rc == CP_ERROR_EAGAIN) {
@@ -537,7 +586,7 @@ struct CPStreamCore::Impl {
packet.flush = true;
packet.end_of_stream = end_of_stream;
{
std::lock_guard lock(packet_mutex_);
std::lock_guard<std::mutex> lock(packet_mutex_);
packet_queue_.push_back(std::move(packet));
}
packet_cv_.notify_one();
@@ -555,7 +604,7 @@ struct CPStreamCore::Impl {
}
void set_last_error(std::string message) {
std::lock_guard lock(last_error_mutex_);
std::lock_guard<std::mutex> lock(last_error_mutex_);
last_error_ = std::move(message);
}
@@ -575,9 +624,12 @@ struct CPStreamCore::Impl {
std::condition_variable packet_cv_;
std::deque<Packet> packet_queue_;
std::mutex frame_mutex_;
mutable std::mutex frame_mutex_;
std::condition_variable frame_cv_;
std::deque<DecodedFrame> frame_queue_;
// std::deque<DecodedFrame> frame_queue_;
// 更新为智能指针,我们需要更长的生命周期😊
std::deque<std::shared_ptr<DecodedFrame>> frame_queue_;
std::deque<std::shared_ptr<DecodedFrame>> frame_record_queue_;
std::size_t frame_queue_capacity_ = 16;
FrameCallback frame_callback_;
@@ -589,6 +641,8 @@ struct CPStreamCore::Impl {
std::string last_error_;
mutable std::mutex last_error_mutex_;
std::unique_ptr<persist::JsonWritter> frame_writer_;
};
CPStreamCore::CPStreamCore(CPStreamConfig config)
@@ -641,11 +695,11 @@ bool CPStreamCore::send(const std::uint8_t* data, std::size_t size) {
return impl_->send(data, size);
}
std::optional<DecodedFrame> CPStreamCore::try_pop_frame() {
std::optional<std::shared_ptr<DecodedFrame>> CPStreamCore::try_pop_frame() {
return impl_->try_pop_frame();
}
bool CPStreamCore::wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) {
bool CPStreamCore::wait_for_frame(std::shared_ptr<DecodedFrame>& frame, std::chrono::milliseconds timeout) {
return impl_->wait_for_frame(frame, timeout);
}
@@ -657,6 +711,18 @@ void CPStreamCore::set_frame_queue_capacity(std::size_t capacity) {
impl_->set_frame_queue_capacity(capacity);
}
void CPStreamCore::clear_recorded_frames() {
impl_->clear_recorded_frames();
}
std::size_t CPStreamCore::recorded_frame_count() const {
return impl_->recorded_frame_count();
}
std::future<persist::WriteResult> CPStreamCore::export_recorded_frames(const std::string& path, bool clear_after_export) {
return impl_->export_recorded_frames(path, clear_after_export);
}
void CPStreamCore::set_frame_callback(FrameCallback callback) {
impl_->set_frame_callback(std::move(callback));
}

View File

@@ -5,16 +5,23 @@
#include <chrono>
#include <cstdint>
#include <functional>
#include <future>
#include <memory>
#include <optional>
#include <serial/serial.h>
#include <string>
#include <vector>
using namespace std::chrono_literals;
namespace ffmsep {
namespace persist {
struct WriteResult;
} // namespace persist
struct DecodedFrame {
CPFrame frame;
CPCodecID id = CPCodecID::Unknow;
std::chrono::steady_clock::time_point received_at{};
std::int64_t pts = 0;
std::optional<tactile::TactileFrame> tactile;
@@ -22,7 +29,6 @@ struct DecodedFrame {
std::optional<tactile::MatrixSize> tactile_matrix_size;
};
using namespace std::chrono_literals;
struct CPStreamConfig {
std::string port;
std::uint32_t baudrate = 115200;
@@ -37,12 +43,12 @@ struct CPStreamConfig {
CPCodecID codec_id = CPCodecID::Unknow;
std::string codec_name;
std::vector<std::uint8_t> slave_request_command{};
std::chrono::milliseconds slave_request_interval = 200ms;
std::chrono::milliseconds slave_request_interval{200ms};
};
class CPStreamCore {
public:
using FrameCallback = std::function<void(const DecodedFrame&)>;
using FrameCallback = std::function<void(std::shared_ptr<DecodedFrame>)>;
explicit CPStreamCore(CPStreamConfig config = {});
~CPStreamCore();
@@ -64,10 +70,14 @@ public:
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);
std::optional<std::shared_ptr<DecodedFrame>> try_pop_frame();
bool wait_for_frame(std::shared_ptr<DecodedFrame>& frame, std::chrono::milliseconds timeout);
void clear_frames();
void set_frame_queue_capacity(std::size_t capacity);
void clear_recorded_frames();
[[nodiscard]] std::size_t recorded_frame_count() const;
std::future<persist::WriteResult> export_recorded_frames(const std::string& path,
bool clear_after_export = false);
void set_frame_callback(FrameCallback callback);
@@ -76,6 +86,8 @@ public:
static std::vector<serial::PortInfo> list_available_ports();
private:
struct Impl;
std::unique_ptr<Impl> impl_;

View File

@@ -0,0 +1,255 @@
//
// Created by Lenn on 2025/10/31.
//
#include "components/ffmsep/presist/presist.hh"
#include "components/ffmsep/cpstream_core.hh"
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <system_error>
#include <nlohmann/json.hpp>
namespace ffmsep::persist {
namespace {
using nlohmann::json;
bool is_simple_array(const json& value) {
if (!value.is_array()) {
return false;
}
return std::all_of(value.begin(), value.end(), [](const json& item) {
return item.is_primitive();
});
}
void dump_compact_json(std::ostream& out,
const json& value,
int indent = 0,
int indent_step = 2) {
const auto indent_str = std::string(static_cast<std::size_t>(indent), ' ');
const auto child_indent = indent + indent_step;
const auto child_indent_str = std::string(static_cast<std::size_t>(child_indent), ' ');
if (value.is_object()) {
out << "{\n";
bool first = true;
for (auto it = value.begin(); it != value.end(); ++it) {
if (!first) {
out << ",\n";
}
first = false;
out << child_indent_str << json(it.key()).dump() << ": ";
dump_compact_json(out, it.value(), child_indent, indent_step);
}
out << '\n' << indent_str << '}';
return;
}
if (value.is_array()) {
if (value.empty()) {
out << "[]";
return;
}
if (is_simple_array(value)) {
out << '[';
for (std::size_t idx = 0; idx < value.size(); ++idx) {
if (idx != 0U) {
out << ", ";
}
out << value[static_cast<json::size_type>(idx)].dump();
}
out << ']';
return;
}
out << "[\n";
bool first = true;
for (const auto& item : value) {
if (!first) {
out << ",\n";
}
first = false;
out << child_indent_str;
dump_compact_json(out, item, child_indent, indent_step);
}
out << '\n' << indent_str << ']';
return;
}
out << value.dump();
}
json serialize_tactile_frame(const DecodedFrame& frame) {
json result = {
{"pts", frame.pts},
{"raw_frame", frame.frame.data},
{"pressures", frame.tactile_pressures},
};
const auto received = frame.received_at.time_since_epoch();
result["received_at_ns"] =
std::chrono::duration_cast<std::chrono::nanoseconds>(received).count();
if (frame.tactile_matrix_size) {
result["matrix"] = {
{"long_edge", frame.tactile_matrix_size->long_edge},
{"short_edge", frame.tactile_matrix_size->short_edge},
};
}
if (frame.tactile) {
const auto& tactile = *frame.tactile;
result["tactile"] = {
{"device_address", tactile.device_address},
{"response_function", tactile.response_function},
{"function", static_cast<std::uint8_t>(tactile.function)},
{"start_address", tactile.start_address},
{"return_byte_count", tactile.return_byte_count},
{"status", tactile.status},
{"payload", tactile.payload},
};
}
return result;
}
} // namespace
bool WriteQueue::push(WriteRequest&& req) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (stopped_) {
return false;
}
queue_.push(std::move(req));
}
cond_.notify_one();
return true;
}
bool WriteQueue::pop(WriteRequest& out) {
std::unique_lock lock(mutex_);
cond_.wait(lock, [&] {
return stopped_ || !queue_.empty();
});
if (queue_.empty()) {
return false;
}
out = std::move(queue_.front());
queue_.pop();
return true;
}
void WriteQueue::stop() {
{
std::lock_guard<std::mutex> lock(mutex_);
stopped_ = true;
}
cond_.notify_all();
}
JsonWritter::JsonWritter()
: write_thread_([this] { run(); }) {}
JsonWritter::~JsonWritter() {
stop();
}
std::future<WriteResult> JsonWritter::enqueue(std::string path,
std::deque<std::shared_ptr<DecodedFrame>> frames) {
std::promise<WriteResult> promise;
auto future = promise.get_future();
WriteRequest request{std::move(path), std::move(frames), std::move(promise)};
if (!write_queue_.push(std::move(request))) {
WriteResult result{false, "writer has been stopped", request.path};
request.promise.set_value(std::move(result));
}
return future;
}
void JsonWritter::run() {
WriteRequest request;
while (write_queue_.pop(request)) {
try {
auto result = write_once(request.path, std::move(request.frames));
request.promise.set_value(std::move(result));
} catch (const std::exception& ex) {
request.promise.set_value(WriteResult{false, ex.what(), request.path});
} catch (...) {
request.promise.set_value(WriteResult{false, "unknown error", request.path});
}
}
}
WriteResult JsonWritter::write_once(const std::string& path,
std::deque<std::shared_ptr<DecodedFrame>> frames) {
if (path.empty()) {
return {false, "export path is empty", path};
}
json tactile_frames = json::array();
for (const auto& frame : frames) {
if (!frame) {
continue;
}
if (frame->id != CPCodecID::Tactile || !frame->tactile) {
continue;
}
tactile_frames.push_back(serialize_tactile_frame(*frame));
}
if (tactile_frames.empty()) {
return {false, "no tactile frames available for export", path};
}
json root;
root["codec"] = "tactile";
root["frames"] = std::move(tactile_frames);
std::filesystem::path fs_path(path);
if (fs_path.has_parent_path()) {
std::error_code ec;
std::filesystem::create_directories(fs_path.parent_path(), ec);
if (ec) {
return {false, "failed to create export directory: " + ec.message(), path};
}
}
std::ofstream stream(path, std::ios::binary | std::ios::trunc);
if (!stream.is_open()) {
return {false, "failed to open export file", path};
}
dump_compact_json(stream, root);
stream << '\n';
stream.flush();
if (!stream.good()) {
return {false, "failed to write export file", path};
}
return {true, {}, path};
}
void JsonWritter::stop() {
if (!stopped_.exchange(true)) {
write_queue_.stop();
if (write_thread_.joinable()) {
write_thread_.join();
}
}
}
} // namespace ffmsep::persist

View File

@@ -0,0 +1,86 @@
//
// Created by Lenn on 2025/10/31.
//
#ifndef TOUCHSENSOR_PRESIST_HH
#define TOUCHSENSOR_PRESIST_HH
#include <atomic>
#include <condition_variable>
#include <deque>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
namespace ffmsep {
struct DecodedFrame;
namespace persist {
struct WriteResult {
bool ok = false;
std::string error;
std::string path;
};
struct WriteRequest {
WriteRequest() = default;
WriteRequest(std::string p,
std::deque<std::shared_ptr<DecodedFrame>> f,
std::promise<WriteResult>&& pr)
: path(std::move(p))
, frames(std::move(f))
, promise(std::move(pr)) {}
WriteRequest(const WriteRequest&) = delete;
WriteRequest& operator=(const WriteRequest&) = delete;
WriteRequest(WriteRequest&&) noexcept = default;
WriteRequest& operator=(WriteRequest&&) noexcept = default;
std::string path;
std::deque<std::shared_ptr<DecodedFrame>> frames;
std::promise<WriteResult> promise;
};
class WriteQueue {
public:
bool push(WriteRequest&& req);
bool pop(WriteRequest& out);
void stop();
private:
std::mutex mutex_;
std::condition_variable cond_;
std::queue<WriteRequest> queue_;
bool stopped_ = false;
};
class JsonWritter {
public:
JsonWritter();
~JsonWritter();
std::future<WriteResult> enqueue(std::string path,
std::deque<std::shared_ptr<DecodedFrame>> frames);
void stop();
private:
void run();
WriteResult write_once(const std::string& path,
std::deque<std::shared_ptr<DecodedFrame>> frames);
private:
std::thread write_thread_;
WriteQueue write_queue_;
std::atomic_bool stopped_{false};
};
} // namespace persist
} // namespace ffmsep
#endif // TOUCHSENSOR_PRESIST_HH

View File

@@ -1,45 +1,46 @@
#pragma once
#include "cpdecoder.hh"
#include <cstdint>
#include <optional>
#include <vector>
#pragma once
#include "cpdecoder.hh"
#include <cstdint>
#include <optional>
#include <vector>
namespace ffmsep::tactile {
inline constexpr std::uint8_t kStartByteFirst = 0xAA;
inline constexpr std::uint8_t kStartByteSecond = 0x55;
enum class FunctionCode : std::uint8_t {
Unknown = 0x00,
ReadMatrix = 0x01,
ReadSingle = 0x02,
ReadTemperature = 0x03,
SetDeviceId = 0x51,
SetMatrixSize = 0x52,
CalibrationMode = 0x53,
};
struct MatrixSize {
std::uint8_t long_edge = 0;
std::uint8_t short_edge = 0;
};
struct TactileFrame {
std::uint8_t device_address = 0;
std::uint8_t reserved = 0;
std::uint8_t response_function = 0;
FunctionCode function = FunctionCode::Unknown;
std::uint32_t start_address = 0;
std::uint16_t return_byte_count = 0;
std::uint8_t status = 0;
std::vector<std::uint8_t> payload;
Unknown = 0x00,
ReadMatrix = 0x01,
ReadSingle = 0x02,
ReadTemperature = 0x03,
SetDeviceId = 0x51,
SetMatrixSize = 0x52,
CalibrationMode = 0x53,
};
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_patrix_coordinate_payload(const TactileFrame& frame);
const CPCodec* tactile_codec();
void register_tactile_codec();
}
struct MatrixSize {
std::uint8_t long_edge = 0;
std::uint8_t short_edge = 0;
};
struct TactileFrame {
std::uint8_t device_address = 0;
std::uint8_t reserved = 0;
std::uint8_t response_function = 0;
FunctionCode function = FunctionCode::Unknown;
std::uint32_t start_address = 0;
std::uint16_t return_byte_count = 0;
std::uint8_t status = 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_patrix_coordinate_payload(const TactileFrame &frame);
const CPCodec *tactile_codec();
void register_tactile_codec();
} // namespace ffmsep::tactile