187 lines
4.8 KiB
C++
187 lines
4.8 KiB
C++
//
|
|
// 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
|