feat:heapmap with value;fix:qcustomplot warning
This commit is contained in:
@@ -71,11 +71,13 @@ file(
|
|||||||
set(FFMSEP_SOURCES
|
set(FFMSEP_SOURCES
|
||||||
components/ffmsep/cpdecoder.cc
|
components/ffmsep/cpdecoder.cc
|
||||||
components/ffmsep/cpstream_core.cc
|
components/ffmsep/cpstream_core.cc
|
||||||
|
components/ffmsep/presist/presist.cc
|
||||||
components/ffmsep/tactile/tacdec.cc
|
components/ffmsep/tactile/tacdec.cc
|
||||||
)
|
)
|
||||||
set(FFMSEP_HEADERS
|
set(FFMSEP_HEADERS
|
||||||
components/ffmsep/cpdecoder.hh
|
components/ffmsep/cpdecoder.hh
|
||||||
components/ffmsep/cpstream_core.hh
|
components/ffmsep/cpstream_core.hh
|
||||||
|
components/ffmsep/presist/presist.hh
|
||||||
components/ffmsep/tactile/tacdec.hh
|
components/ffmsep/tactile/tacdec.hh
|
||||||
)
|
)
|
||||||
set(FFMSEP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/components/ffmsep")
|
set(FFMSEP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/components/ffmsep")
|
||||||
|
|||||||
@@ -8,9 +8,13 @@
|
|||||||
#include "heatmap.hh"
|
#include "heatmap.hh"
|
||||||
#include "modern-qt/utility/theme/theme.hh"
|
#include "modern-qt/utility/theme/theme.hh"
|
||||||
#include "modern-qt/widget/sliders.hh"
|
#include "modern-qt/widget/sliders.hh"
|
||||||
|
#include "qcustomplot/qcustomplot.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <qcolor.h>
|
#include <qcolor.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
|
#include <qfont.h>
|
||||||
|
#include <vector>
|
||||||
using namespace creeper::plot_widget::internal;
|
using namespace creeper::plot_widget::internal;
|
||||||
|
|
||||||
struct BasicPlot::Impl {
|
struct BasicPlot::Impl {
|
||||||
@@ -52,9 +56,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto set_color_gradient_range(const double& min, const double& max) -> void {
|
auto set_color_gradient_range(const double& min, const double& max) -> void {
|
||||||
if (initialized && self.plottableCount() > 0) {
|
if (initialized && color_map) {
|
||||||
auto* cpmp = static_cast<QCPColorMap*>(self.plottable(0));
|
color_map->setDataRange(QCPRange(min, max));
|
||||||
cpmp->setDataRange(QCPRange(min, max));
|
|
||||||
self.replot();
|
self.replot();
|
||||||
}
|
}
|
||||||
color_min = min;
|
color_min = min;
|
||||||
@@ -63,7 +66,7 @@ public:
|
|||||||
|
|
||||||
auto set_data(const QVector<PointData>& data) -> void {
|
auto set_data(const QVector<PointData>& data) -> void {
|
||||||
data_points = data;
|
data_points = data;
|
||||||
if (initialized) {
|
if (initialized && color_map) {
|
||||||
update_plot_data();
|
update_plot_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +74,8 @@ public:
|
|||||||
auto initialize_plot() -> void {
|
auto initialize_plot() -> void {
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
|
|
||||||
QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
|
color_map = new QCPColorMap(self.xAxis, self.yAxis);
|
||||||
|
auto* cpmp = color_map;
|
||||||
cpmp->data()->setSize(matrix_size.width(), matrix_size.height());
|
cpmp->data()->setSize(matrix_size.width(), matrix_size.height());
|
||||||
cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5),
|
cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5),
|
||||||
QCPRange(0.5, matrix_size.height() - 0.5));
|
QCPRange(0.5, matrix_size.height() - 0.5));
|
||||||
@@ -103,14 +107,26 @@ QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
|
|||||||
if (!xlabel.isEmpty()) self.xAxis->setLabel(xlabel);
|
if (!xlabel.isEmpty()) self.xAxis->setLabel(xlabel);
|
||||||
if (!ylabel.isEmpty()) self.yAxis->setLabel(ylabel);
|
if (!ylabel.isEmpty()) self.yAxis->setLabel(ylabel);
|
||||||
|
|
||||||
QCPColorScale* color_scale = new QCPColorScale(&self);
|
if (!color_scale) {
|
||||||
|
color_scale = new QCPColorScale(&self);
|
||||||
color_scale->setType(QCPAxis::atBottom);
|
color_scale->setType(QCPAxis::atBottom);
|
||||||
|
}
|
||||||
|
if (self.plotLayout()) {
|
||||||
|
auto existing = self.plotLayout()->element(1, 0);
|
||||||
|
if (existing && existing != color_scale) {
|
||||||
|
self.plotLayout()->take(existing);
|
||||||
|
}
|
||||||
|
if (!existing || existing != color_scale) {
|
||||||
self.plotLayout()->addElement(1, 0, color_scale);
|
self.plotLayout()->addElement(1, 0, color_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
cpmp->setColorScale(color_scale);
|
cpmp->setColorScale(color_scale);
|
||||||
|
|
||||||
QCPColorGradient gradient;
|
QCPColorGradient gradient;
|
||||||
gradient.setColorStopAt(0.0, QColor(246, 239, 166)); // F6EFA6
|
gradient.setColorStopAt(0.0, QColor(240, 246, 255)); // 低值淡色
|
||||||
gradient.setColorStopAt(1.0, QColor(191, 68, 76)); // BF444C
|
gradient.setColorStopAt(0.35, QColor(142, 197, 252));
|
||||||
|
gradient.setColorStopAt(0.7, QColor(56, 128, 199));
|
||||||
|
gradient.setColorStopAt(1.0, QColor(8, 36, 95)); // 高值深色
|
||||||
cpmp->setGradient(gradient);
|
cpmp->setGradient(gradient);
|
||||||
|
|
||||||
cpmp->setDataRange(QCPRange(color_min, color_max));
|
cpmp->setDataRange(QCPRange(color_min, color_max));
|
||||||
@@ -133,23 +149,36 @@ QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
|
|||||||
self.clearGraphs();
|
self.clearGraphs();
|
||||||
self.clearItems();
|
self.clearItems();
|
||||||
self.clearFocus();
|
self.clearFocus();
|
||||||
|
color_map = nullptr;
|
||||||
|
cell_labels.clear();
|
||||||
|
|
||||||
// 重新初始化
|
// 重新初始化
|
||||||
initialized = false;
|
initialized = false;
|
||||||
initialize_plot();
|
initialize_plot();
|
||||||
}
|
}
|
||||||
auto update_plot_data() -> void {
|
auto update_plot_data() -> void {
|
||||||
if (!initialized || self.plottableCount() == 0) return;
|
if (!initialized || !color_map) return;
|
||||||
|
|
||||||
auto* cpmp = static_cast<QCPColorMap*>(self.plottable(0));
|
ensure_labels();
|
||||||
|
|
||||||
|
const int width = matrix_size.width();
|
||||||
|
const int height = matrix_size.height();
|
||||||
|
const int expected = width * height;
|
||||||
|
std::vector<double> values(static_cast<std::size_t>(expected), 0.0);
|
||||||
|
|
||||||
// 设置新数据
|
// 设置新数据
|
||||||
for (const auto& item : data_points) {
|
for (const auto& item : data_points) {
|
||||||
if (item.x >= 0 && item.x < matrix_size.width() &&
|
if (item.x >= 0 && item.x < matrix_size.width() &&
|
||||||
item.y >= 0 && item.y < matrix_size.height()) {
|
item.y >= 0 && item.y < matrix_size.height()) {
|
||||||
cpmp->data()->setCell(item.x, item.y, item.z);
|
color_map->data()->setCell(item.x, item.y, item.z);
|
||||||
|
const int idx = static_cast<int>(item.y) * width + static_cast<int>(item.x);
|
||||||
|
if (idx >= 0 && idx < expected) {
|
||||||
|
values[static_cast<std::size_t>(idx)] = item.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_label_values(values);
|
||||||
|
|
||||||
// 重绘
|
// 重绘
|
||||||
self.replot();
|
self.replot();
|
||||||
@@ -172,6 +201,82 @@ private:
|
|||||||
double color_max = 800.0;
|
double color_max = 800.0;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
BasicPlot& self;
|
BasicPlot& self;
|
||||||
|
QCPColorScale* color_scale = nullptr;
|
||||||
|
QCPColorMap* color_map = nullptr;
|
||||||
|
QVector<QCPItemText*> cell_labels;
|
||||||
|
|
||||||
|
void clear_labels() {
|
||||||
|
for (auto* label : cell_labels) {
|
||||||
|
if (label) {
|
||||||
|
self.removeItem(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cell_labels.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensure_labels() {
|
||||||
|
const int width = matrix_size.width();
|
||||||
|
const int height = matrix_size.height();
|
||||||
|
const int expected = width * height;
|
||||||
|
if (expected <= 0) {
|
||||||
|
clear_labels();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cell_labels.size() == expected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_labels();
|
||||||
|
cell_labels.reserve(expected);
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
for (int x = 0; x < width; ++x) {
|
||||||
|
auto* label = new QCPItemText(&self);
|
||||||
|
label->position->setType(QCPItemPosition::ptPlotCoords);
|
||||||
|
label->setClipToAxisRect(true);
|
||||||
|
label->setClipAxisRect(self.axisRect());
|
||||||
|
label->setPositionAlignment(Qt::AlignCenter);
|
||||||
|
label->position->setCoords(x + 0.5, y + 0.5);
|
||||||
|
label->setBrush(Qt::NoBrush);
|
||||||
|
label->setPen(Qt::NoPen);
|
||||||
|
QFont font = label->font();
|
||||||
|
if (font.pointSize() > 0) {
|
||||||
|
font.setPointSize(std::max(font.pointSize() - 1, 6));
|
||||||
|
} else {
|
||||||
|
font.setPointSize(8);
|
||||||
|
}
|
||||||
|
label->setFont(font);
|
||||||
|
label->setColor(Qt::black);
|
||||||
|
label->setSelectable(false);
|
||||||
|
cell_labels.push_back(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_label_values(const std::vector<double>& values) {
|
||||||
|
const int width = matrix_size.width();
|
||||||
|
const int height = matrix_size.height();
|
||||||
|
const double range = std::max(color_max - color_min, 1.0);
|
||||||
|
const int expected = width * height;
|
||||||
|
for (int idx = 0; idx < expected && idx < cell_labels.size(); ++idx) {
|
||||||
|
auto* label = cell_labels[idx];
|
||||||
|
if (!label) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double value = values.size() > static_cast<std::size_t>(idx)
|
||||||
|
? values[static_cast<std::size_t>(idx)]
|
||||||
|
: 0.0;
|
||||||
|
label->setText(QString::number(value, 'f', 0));
|
||||||
|
const double normalized = std::clamp((value - color_min) / range, 0.0, 1.0);
|
||||||
|
if (normalized > 0.55) {
|
||||||
|
label->setColor(Qt::white);
|
||||||
|
} else {
|
||||||
|
label->setColor(QColor(25, 25, 25));
|
||||||
|
}
|
||||||
|
const int x = idx % width;
|
||||||
|
const int y = idx / width;
|
||||||
|
label->position->setCoords(x + 0.5, y + 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TOUCHSENSOR_HEATMAP_IMPL_HH
|
#endif // TOUCHSENSOR_HEATMAP_IMPL_HH
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "components/ffmsep/cpstream_core.hh"
|
#include "components/ffmsep/cpstream_core.hh"
|
||||||
|
|
||||||
|
#include "components/ffmsep/presist/presist.hh"
|
||||||
|
#include "dlog/dlog.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -7,19 +10,21 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace ffmsep {
|
namespace ffmsep {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kReaderIdleSleep = std::chrono::milliseconds(5);
|
constexpr auto kReaderIdleSleep = 5ms;
|
||||||
constexpr auto kDecoderIdleSleep = std::chrono::milliseconds(1);
|
constexpr auto kDecoderIdleSleep = 1ms;
|
||||||
|
|
||||||
const CPCodec* resolve_requested_codec(const CPStreamConfig& config) {
|
const CPCodec* resolve_requested_codec(const CPStreamConfig& config) {
|
||||||
if (!config.codec_name.empty()) {
|
if (!config.codec_name.empty()) {
|
||||||
@@ -48,6 +53,7 @@ struct CPStreamCore::Impl {
|
|||||||
explicit Impl(CPStreamConfig config)
|
explicit Impl(CPStreamConfig config)
|
||||||
: config_(std::move(config)) {
|
: config_(std::move(config)) {
|
||||||
normalize_config();
|
normalize_config();
|
||||||
|
frame_writer_ = std::make_unique<persist::JsonWritter>();
|
||||||
}
|
}
|
||||||
|
|
||||||
~Impl() = default;
|
~Impl() = default;
|
||||||
@@ -63,7 +69,7 @@ struct CPStreamCore::Impl {
|
|||||||
config_.frame_queue_capacity = 1U;
|
config_.frame_queue_capacity = 1U;
|
||||||
}
|
}
|
||||||
if (config_.slave_request_interval.count() < 0) {
|
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;
|
frame_queue_capacity_ = config_.frame_queue_capacity;
|
||||||
}
|
}
|
||||||
@@ -115,7 +121,7 @@ struct CPStreamCore::Impl {
|
|||||||
serial->flush();
|
serial->flush();
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(serial_mutex_);
|
std::lock_guard<std::mutex> lock(serial_mutex_);
|
||||||
serial_ = std::move(serial);
|
serial_ = std::move(serial);
|
||||||
}
|
}
|
||||||
} catch (const serial::IOException& ex) {
|
} catch (const serial::IOException& ex) {
|
||||||
@@ -139,11 +145,11 @@ struct CPStreamCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(packet_mutex_);
|
std::lock_guard<std::mutex> lock(packet_mutex_);
|
||||||
packet_queue_.clear();
|
packet_queue_.clear();
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard lock(frame_mutex_);
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
||||||
frame_queue_.clear();
|
frame_queue_.clear();
|
||||||
}
|
}
|
||||||
pts_counter_.store(0, std::memory_order_relaxed);
|
pts_counter_.store(0, std::memory_order_relaxed);
|
||||||
@@ -164,7 +170,7 @@ struct CPStreamCore::Impl {
|
|||||||
stop();
|
stop();
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(serial_mutex_);
|
std::lock_guard<std::mutex> lock(serial_mutex_);
|
||||||
if (serial_) {
|
if (serial_) {
|
||||||
try {
|
try {
|
||||||
if (serial_->isOpen()) {
|
if (serial_->isOpen()) {
|
||||||
@@ -184,12 +190,13 @@ struct CPStreamCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(packet_mutex_);
|
std::lock_guard<std::mutex> lock(packet_mutex_);
|
||||||
packet_queue_.clear();
|
packet_queue_.clear();
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard lock(frame_mutex_);
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
||||||
frame_queue_.clear();
|
frame_queue_.clear();
|
||||||
|
frame_record_queue_.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +207,7 @@ struct CPStreamCore::Impl {
|
|||||||
|
|
||||||
std::shared_ptr<serial::Serial> serial_copy;
|
std::shared_ptr<serial::Serial> serial_copy;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(serial_mutex_);
|
std::lock_guard<std::mutex> lock(serial_mutex_);
|
||||||
serial_copy = serial_;
|
serial_copy = serial_;
|
||||||
}
|
}
|
||||||
if (!serial_copy || !serial_copy->isOpen()) {
|
if (!serial_copy || !serial_copy->isOpen()) {
|
||||||
@@ -249,7 +256,7 @@ struct CPStreamCore::Impl {
|
|||||||
stop_requested_.store(false, std::memory_order_release);
|
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();
|
packet_queue_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +266,7 @@ struct CPStreamCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool is_open() const {
|
bool is_open() const {
|
||||||
std::lock_guard lock(serial_mutex_);
|
std::lock_guard<std::mutex> lock(serial_mutex_);
|
||||||
return serial_ && serial_->isOpen();
|
return serial_ && serial_->isOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +285,7 @@ struct CPStreamCore::Impl {
|
|||||||
|
|
||||||
std::shared_ptr<serial::Serial> serial_copy;
|
std::shared_ptr<serial::Serial> serial_copy;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(serial_mutex_);
|
std::lock_guard<std::mutex> lock(serial_mutex_);
|
||||||
serial_copy = serial_;
|
serial_copy = serial_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,17 +307,17 @@ struct CPStreamCore::Impl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<DecodedFrame> try_pop_frame() {
|
std::optional<std::shared_ptr<DecodedFrame>> try_pop_frame() {
|
||||||
std::lock_guard lock(frame_mutex_);
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
||||||
if (frame_queue_.empty()) {
|
if (frame_queue_.empty()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
DecodedFrame frame = std::move(frame_queue_.front());
|
std::shared_ptr<DecodedFrame> frame = std::move(frame_queue_.front());
|
||||||
frame_queue_.pop_front();
|
frame_queue_.pop_front();
|
||||||
return frame;
|
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_);
|
std::unique_lock lock(frame_mutex_);
|
||||||
if (!frame_cv_.wait_for(lock, timeout, [&] {
|
if (!frame_cv_.wait_for(lock, timeout, [&] {
|
||||||
return !frame_queue_.empty();
|
return !frame_queue_.empty();
|
||||||
@@ -323,7 +330,7 @@ struct CPStreamCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void clear_frames() {
|
void clear_frames() {
|
||||||
std::lock_guard lock(frame_mutex_);
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
||||||
frame_queue_.clear();
|
frame_queue_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +339,7 @@ struct CPStreamCore::Impl {
|
|||||||
capacity = 1U;
|
capacity = 1U;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard lock(frame_mutex_);
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
||||||
frame_queue_capacity_ = capacity;
|
frame_queue_capacity_ = capacity;
|
||||||
config_.frame_queue_capacity = capacity;
|
config_.frame_queue_capacity = capacity;
|
||||||
while (frame_queue_.size() > frame_queue_capacity_) {
|
while (frame_queue_.size() > frame_queue_capacity_) {
|
||||||
@@ -341,8 +348,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) {
|
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);
|
frame_callback_ = std::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +396,7 @@ struct CPStreamCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string last_error() const {
|
std::string last_error() const {
|
||||||
std::lock_guard lock(last_error_mutex_);
|
std::lock_guard<std::mutex> lock(last_error_mutex_);
|
||||||
return last_error_;
|
return last_error_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +410,7 @@ struct CPStreamCore::Impl {
|
|||||||
while (!stop_requested_.load(std::memory_order_acquire)) {
|
while (!stop_requested_.load(std::memory_order_acquire)) {
|
||||||
std::shared_ptr<serial::Serial> serial_copy;
|
std::shared_ptr<serial::Serial> serial_copy;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(serial_mutex_);
|
std::lock_guard<std::mutex> lock(serial_mutex_);
|
||||||
serial_copy = serial_;
|
serial_copy = serial_;
|
||||||
}
|
}
|
||||||
if (!serial_copy || !serial_copy->isOpen()) {
|
if (!serial_copy || !serial_copy->isOpen()) {
|
||||||
@@ -400,7 +445,7 @@ struct CPStreamCore::Impl {
|
|||||||
packet.pts = pts_counter_.fetch_add(1, std::memory_order_relaxed);
|
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) {
|
if (packet_queue_.size() >= config_.packet_queue_capacity) {
|
||||||
packet_queue_.pop_front();
|
packet_queue_.pop_front();
|
||||||
}
|
}
|
||||||
@@ -414,7 +459,7 @@ struct CPStreamCore::Impl {
|
|||||||
const auto command = config_.slave_request_command;
|
const auto command = config_.slave_request_command;
|
||||||
auto interval = config_.slave_request_interval;
|
auto interval = config_.slave_request_interval;
|
||||||
if (interval.count() < 0) {
|
if (interval.count() < 0) {
|
||||||
interval = std::chrono::milliseconds{0};
|
interval = 0ms;
|
||||||
}
|
}
|
||||||
const bool repeat = interval.count() > 0;
|
const bool repeat = interval.count() > 0;
|
||||||
|
|
||||||
@@ -484,23 +529,24 @@ struct CPStreamCore::Impl {
|
|||||||
CPFrame frame;
|
CPFrame frame;
|
||||||
rc = cpcodec_receive_frame(codec_ctx_, &frame);
|
rc = cpcodec_receive_frame(codec_ctx_, &frame);
|
||||||
if (rc == CP_SUCCESS) {
|
if (rc == CP_SUCCESS) {
|
||||||
DecodedFrame decoded;
|
auto decoded = std::make_shared<DecodedFrame>();
|
||||||
decoded.pts = frame.pts;
|
decoded->pts = frame.pts;
|
||||||
decoded.received_at = std::chrono::steady_clock::now();
|
decoded->received_at = std::chrono::steady_clock::now();
|
||||||
decoded.frame = std::move(frame);
|
decoded->frame = std::move(frame);
|
||||||
if (codec_descriptor_ && codec_descriptor_->id == CPCodecID::Tactile) {
|
decoded->id = codec_descriptor_ ? codec_descriptor_->id : CPCodecID::Unknow;
|
||||||
if (auto parsed = tactile::parse_frame(decoded.frame)) {
|
if (decoded->id == CPCodecID::Tactile) {
|
||||||
decoded.tactile = parsed;
|
if (auto parsed = tactile::parse_frame(decoded->frame)) {
|
||||||
decoded.tactile_pressures = tactile::parse_pressure_values(*parsed);
|
decoded->tactile = parsed;
|
||||||
|
decoded->tactile_pressures = tactile::parse_pressure_values(*parsed);
|
||||||
if (auto matrix = tactile::parse_matrix_size_payload(*parsed)) {
|
if (auto matrix = tactile::parse_matrix_size_payload(*parsed)) {
|
||||||
decoded.tactile_matrix_size = matrix;
|
decoded->tactile_matrix_size = matrix;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameCallback callback_copy;
|
FrameCallback callback_copy;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(callback_mutex_);
|
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||||
callback_copy = frame_callback_;
|
callback_copy = frame_callback_;
|
||||||
}
|
}
|
||||||
if (callback_copy) {
|
if (callback_copy) {
|
||||||
@@ -508,11 +554,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_) {
|
if (frame_queue_.size() >= frame_queue_capacity_) {
|
||||||
frame_queue_.pop_front();
|
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();
|
frame_cv_.notify_one();
|
||||||
} else if (rc == CP_ERROR_EAGAIN) {
|
} else if (rc == CP_ERROR_EAGAIN) {
|
||||||
@@ -536,7 +585,7 @@ struct CPStreamCore::Impl {
|
|||||||
packet.flush = true;
|
packet.flush = true;
|
||||||
packet.end_of_stream = end_of_stream;
|
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_queue_.push_back(std::move(packet));
|
||||||
}
|
}
|
||||||
packet_cv_.notify_one();
|
packet_cv_.notify_one();
|
||||||
@@ -554,7 +603,7 @@ struct CPStreamCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set_last_error(std::string message) {
|
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);
|
last_error_ = std::move(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,9 +623,12 @@ struct CPStreamCore::Impl {
|
|||||||
std::condition_variable packet_cv_;
|
std::condition_variable packet_cv_;
|
||||||
std::deque<Packet> packet_queue_;
|
std::deque<Packet> packet_queue_;
|
||||||
|
|
||||||
std::mutex frame_mutex_;
|
mutable std::mutex frame_mutex_;
|
||||||
std::condition_variable frame_cv_;
|
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;
|
std::size_t frame_queue_capacity_ = 16;
|
||||||
|
|
||||||
FrameCallback frame_callback_;
|
FrameCallback frame_callback_;
|
||||||
@@ -588,6 +640,8 @@ struct CPStreamCore::Impl {
|
|||||||
|
|
||||||
std::string last_error_;
|
std::string last_error_;
|
||||||
mutable std::mutex last_error_mutex_;
|
mutable std::mutex last_error_mutex_;
|
||||||
|
|
||||||
|
std::unique_ptr<persist::JsonWritter> frame_writer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
CPStreamCore::CPStreamCore(CPStreamConfig config)
|
CPStreamCore::CPStreamCore(CPStreamConfig config)
|
||||||
@@ -640,11 +694,11 @@ bool CPStreamCore::send(const std::uint8_t* data, std::size_t size) {
|
|||||||
return impl_->send(data, 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();
|
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);
|
return impl_->wait_for_frame(frame, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,6 +710,18 @@ void CPStreamCore::set_frame_queue_capacity(std::size_t capacity) {
|
|||||||
impl_->set_frame_queue_capacity(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) {
|
void CPStreamCore::set_frame_callback(FrameCallback callback) {
|
||||||
impl_->set_frame_callback(std::move(callback));
|
impl_->set_frame_callback(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,23 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <serial/serial.h>
|
#include <serial/serial.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace ffmsep {
|
namespace ffmsep {
|
||||||
|
|
||||||
|
namespace persist {
|
||||||
|
struct WriteResult;
|
||||||
|
} // namespace persist
|
||||||
|
|
||||||
struct DecodedFrame {
|
struct DecodedFrame {
|
||||||
CPFrame frame;
|
CPFrame frame;
|
||||||
|
CPCodecID id = CPCodecID::Unknow;
|
||||||
std::chrono::steady_clock::time_point received_at{};
|
std::chrono::steady_clock::time_point received_at{};
|
||||||
std::int64_t pts = 0;
|
std::int64_t pts = 0;
|
||||||
std::optional<tactile::TactileFrame> tactile;
|
std::optional<tactile::TactileFrame> tactile;
|
||||||
@@ -36,12 +43,12 @@ struct CPStreamConfig {
|
|||||||
CPCodecID codec_id = CPCodecID::Unknow;
|
CPCodecID codec_id = CPCodecID::Unknow;
|
||||||
std::string codec_name;
|
std::string codec_name;
|
||||||
std::vector<std::uint8_t> slave_request_command{};
|
std::vector<std::uint8_t> slave_request_command{};
|
||||||
std::chrono::milliseconds slave_request_interval{200};
|
std::chrono::milliseconds slave_request_interval{200ms};
|
||||||
};
|
};
|
||||||
|
|
||||||
class CPStreamCore {
|
class CPStreamCore {
|
||||||
public:
|
public:
|
||||||
using FrameCallback = std::function<void(const DecodedFrame&)>;
|
using FrameCallback = std::function<void(std::shared_ptr<DecodedFrame>)>;
|
||||||
|
|
||||||
explicit CPStreamCore(CPStreamConfig config = {});
|
explicit CPStreamCore(CPStreamConfig config = {});
|
||||||
~CPStreamCore();
|
~CPStreamCore();
|
||||||
@@ -63,10 +70,14 @@ public:
|
|||||||
bool send(const std::vector<std::uint8_t>& data);
|
bool send(const std::vector<std::uint8_t>& data);
|
||||||
bool send(const std::uint8_t* data, std::size_t size);
|
bool send(const std::uint8_t* data, std::size_t size);
|
||||||
|
|
||||||
std::optional<DecodedFrame> try_pop_frame();
|
std::optional<std::shared_ptr<DecodedFrame>> try_pop_frame();
|
||||||
bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout);
|
bool wait_for_frame(std::shared_ptr<DecodedFrame>& frame, std::chrono::milliseconds timeout);
|
||||||
void clear_frames();
|
void clear_frames();
|
||||||
void set_frame_queue_capacity(std::size_t capacity);
|
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);
|
void set_frame_callback(FrameCallback callback);
|
||||||
|
|
||||||
@@ -75,6 +86,8 @@ public:
|
|||||||
|
|
||||||
static std::vector<serial::PortInfo> list_available_ports();
|
static std::vector<serial::PortInfo> list_available_ports();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl_;
|
std::unique_ptr<Impl> impl_;
|
||||||
|
|||||||
186
components/ffmsep/presist/presist.cc
Normal file
186
components/ffmsep/presist/presist.cc
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
//
|
||||||
|
// Created by Lenn on 2025/10/31.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "components/ffmsep/presist/presist.hh"
|
||||||
|
|
||||||
|
#include "components/ffmsep/cpstream_core.hh"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <system_error>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace ffmsep::persist {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << std::setw(2) << root;
|
||||||
|
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
|
||||||
86
components/ffmsep/presist/presist.hh
Normal file
86
components/ffmsep/presist/presist.hh
Normal 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
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
#include <modern-qt/widget/select.hh>
|
#include <modern-qt/widget/select.hh>
|
||||||
#include "components/ffmsep/tactile/tacdec.hh"
|
#include "components/ffmsep/tactile/tacdec.hh"
|
||||||
|
|
||||||
|
#define DEBUG 0
|
||||||
|
|
||||||
using namespace creeper;
|
using namespace creeper;
|
||||||
namespace capro = card::pro;
|
namespace capro = card::pro;
|
||||||
@@ -112,17 +113,23 @@ public:
|
|||||||
[&](const serial::PortInfo& info) { return info.port == port_utf8; });
|
[&](const serial::PortInfo& info) { return info.port == port_utf8; });
|
||||||
if (it == ports.end()) {
|
if (it == ports.end()) {
|
||||||
if (ports.empty()) {
|
if (ports.empty()) {
|
||||||
|
#if DEBUG
|
||||||
std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available and no other ports detected.\n";
|
std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available and no other ports detected.\n";
|
||||||
|
#endif
|
||||||
last_error_ = QString::fromUtf8("未检测到串口");
|
last_error_ = QString::fromUtf8("未检测到串口");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if DEBUG
|
||||||
std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available, falling back to first detected port.\n";
|
std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available, falling back to first detected port.\n";
|
||||||
|
#endif
|
||||||
port_utf8 = ports.front().port;
|
port_utf8 = ports.front().port;
|
||||||
}
|
}
|
||||||
} else if (!ports.empty()) {
|
} else if (!ports.empty()) {
|
||||||
port_utf8 = ports.front().port;
|
port_utf8 = ports.front().port;
|
||||||
} else {
|
} else {
|
||||||
|
#if DEBUG
|
||||||
std::cerr << "SensorStreamController: no serial ports available\n";
|
std::cerr << "SensorStreamController: no serial ports available\n";
|
||||||
|
#endif
|
||||||
last_error_ = QString::fromUtf8("未检测到串口");
|
last_error_ = QString::fromUtf8("未检测到串口");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -149,7 +156,7 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
core_->set_frame_callback([this](const ffmsep::DecodedFrame& frame) {
|
core_->set_frame_callback([this](std::shared_ptr<ffmsep::DecodedFrame> frame) {
|
||||||
handle_frame(frame);
|
handle_frame(frame);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -214,17 +221,17 @@ private:
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_frame(const ffmsep::DecodedFrame& frame) {
|
void handle_frame(std::shared_ptr<ffmsep::DecodedFrame> frame) {
|
||||||
if (!frame.tactile || frame.tactile_pressures.empty()) {
|
if (!frame->tactile || frame->tactile_pressures.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pressures = frame.tactile_pressures;
|
auto pressures = frame->tactile_pressures;
|
||||||
auto size_hint = frame.tactile_matrix_size;
|
auto size_hint = frame->tactile_matrix_size;
|
||||||
auto frame_bytes = frame.frame.data;
|
auto frame_bytes = frame->frame.data;
|
||||||
std::vector<std::uint8_t> raw_payload;
|
std::vector<std::uint8_t> raw_payload;
|
||||||
if (frame.tactile) {
|
if (frame->tactile) {
|
||||||
raw_payload = frame.tactile->payload;
|
raw_payload = frame->tactile->payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -115,40 +116,40 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
// Also demonstrate polling API (in case users don't want callbacks)
|
// Also demonstrate polling API (in case users don't want callbacks)
|
||||||
while (g_running) {
|
while (g_running) {
|
||||||
ffmsep::DecodedFrame df;
|
std::shared_ptr<ffmsep::DecodedFrame> df;
|
||||||
if (core.wait_for_frame(df, 200ms)) {
|
if (core.wait_for_frame(df, 200ms) && df) {
|
||||||
std::cout << "Frame pts=" << df.pts
|
std::cout << "Frame pts=" << df->pts
|
||||||
<< " bytes=" << df.frame.data.size();
|
<< " bytes=" << df->frame.data.size();
|
||||||
if (df.tactile) {
|
if (df->tactile) {
|
||||||
const auto& tf = *df.tactile;
|
const auto& tf = *df->tactile;
|
||||||
std::cout << " addr=" << int(tf.device_address)
|
std::cout << " addr=" << int(tf.device_address)
|
||||||
<< " func=0x" << std::hex << std::uppercase << int(tf.response_function)
|
<< " func=0x" << std::hex << std::uppercase << int(tf.response_function)
|
||||||
<< std::dec;
|
<< std::dec;
|
||||||
if (df.tactile_matrix_size) {
|
if (df->tactile_matrix_size) {
|
||||||
const auto& ms = *df.tactile_matrix_size;
|
const auto& ms = *df->tactile_matrix_size;
|
||||||
std::cout << " matrix=" << int(ms.long_edge)
|
std::cout << " matrix=" << int(ms.long_edge)
|
||||||
<< "x" << int(ms.short_edge);
|
<< "x" << int(ms.short_edge);
|
||||||
}
|
}
|
||||||
if (!df.tactile_pressures.empty()) {
|
if (!df->tactile_pressures.empty()) {
|
||||||
std::cout << " pressures=" << df.tactile_pressures.size()
|
std::cout << " pressures=" << df->tactile_pressures.size()
|
||||||
<< " values=[";
|
<< " values=[";
|
||||||
const std::size_t preview = std::min<std::size_t>(df.tactile_pressures.size(), 8);
|
const std::size_t preview = std::min<std::size_t>(df->tactile_pressures.size(), 8);
|
||||||
for (std::size_t idx = 0; idx < preview; ++idx) {
|
for (std::size_t idx = 0; idx < preview; ++idx) {
|
||||||
if (idx != 0U) {
|
if (idx != 0U) {
|
||||||
std::cout << ", ";
|
std::cout << ", ";
|
||||||
}
|
}
|
||||||
std::cout << df.tactile_pressures[idx];
|
std::cout << df->tactile_pressures[idx];
|
||||||
}
|
}
|
||||||
if (preview < df.tactile_pressures.size()) {
|
if (preview < df->tactile_pressures.size()) {
|
||||||
std::cout << ", ...";
|
std::cout << ", ...";
|
||||||
}
|
}
|
||||||
std::cout << "]";
|
std::cout << "]";
|
||||||
}
|
}
|
||||||
std::cout << "\n raw=";
|
std::cout << "\n raw=";
|
||||||
print_hex(df.frame.data);
|
print_hex(df->frame.data);
|
||||||
} else {
|
} else {
|
||||||
std::cout << " raw=";
|
std::cout << " raw=";
|
||||||
print_hex(df.frame.data);
|
print_hex(df->frame.data);
|
||||||
}
|
}
|
||||||
std::cout << "\n";
|
std::cout << "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,15 +27,12 @@ namespace material {
|
|||||||
private:
|
private:
|
||||||
static QString get_font_family(int fontId, const QString& fallback) {
|
static QString get_font_family(int fontId, const QString& fallback) {
|
||||||
if (fontId == -1) {
|
if (fontId == -1) {
|
||||||
qWarning() << "Failed to load font:" << fallback;
|
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
QStringList families = QFontDatabase::applicationFontFamilies(fontId);
|
QStringList families = QFontDatabase::applicationFontFamilies(fontId);
|
||||||
if (families.isEmpty()) {
|
if (families.isEmpty()) {
|
||||||
qWarning() << "No families found for font:" << fallback;
|
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
qDebug() << "families found for font:" << families;
|
|
||||||
return families.first();
|
return families.first();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user