feat:export data while running or close
This commit is contained in:
@@ -6,13 +6,22 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <QMetaObject>
|
||||
#include <QStringList>
|
||||
#include <QtCore/Qt>
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <qsize.h>
|
||||
@@ -42,6 +51,7 @@
|
||||
#include <modern-qt/widget/text-fields.hh>
|
||||
#include <modern-qt/widget/text.hh>
|
||||
#include <modern-qt/widget/select.hh>
|
||||
#include "components/ffmsep/presist/presist.hh"
|
||||
#include "components/ffmsep/tactile/tacdec.hh"
|
||||
|
||||
#define DEBUG 0
|
||||
@@ -146,7 +156,7 @@ public:
|
||||
cfg.packet_queue_capacity = 128;
|
||||
cfg.frame_queue_capacity = 32;
|
||||
cfg.slave_request_command.assign(kSlaveRequestCommand.begin(), kSlaveRequestCommand.end());
|
||||
cfg.slave_request_interval = std::chrono::milliseconds{200};
|
||||
cfg.slave_request_interval = 3ms;
|
||||
|
||||
reset_core();
|
||||
core_ = std::make_unique<ffmsep::CPStreamCore>();
|
||||
@@ -175,14 +185,29 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
reset_core();
|
||||
active_port_.clear();
|
||||
if (heatmap_data_ && matrix_context_) {
|
||||
heatmap_data_->set(make_flat_points(matrix_context_->get()));
|
||||
}
|
||||
connected_ = false;
|
||||
}
|
||||
void stop() {
|
||||
if (!core_) {
|
||||
active_port_.clear();
|
||||
if (heatmap_data_ && matrix_context_) {
|
||||
heatmap_data_->set(make_flat_points(matrix_context_->get()));
|
||||
}
|
||||
connected_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
core_->set_frame_callback({});
|
||||
if (core_->is_running()) {
|
||||
core_->stop();
|
||||
}
|
||||
|
||||
core_->clear_frames();
|
||||
|
||||
connected_ = false;
|
||||
active_port_.clear();
|
||||
if (heatmap_data_ && matrix_context_) {
|
||||
heatmap_data_->set(make_flat_points(matrix_context_->get()));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_running() const noexcept {
|
||||
return core_ && core_->is_running();
|
||||
@@ -200,6 +225,21 @@ public:
|
||||
return last_error_;
|
||||
}
|
||||
|
||||
std::future<ffmsep::persist::WriteResult> export_frames(const QString& path,
|
||||
bool clear_after_export) {
|
||||
if (path.isEmpty()) {
|
||||
return make_failed_future(path, "export path is empty");
|
||||
}
|
||||
if (!core_) {
|
||||
return make_failed_future(path, "stream is not active");
|
||||
}
|
||||
if (core_->recorded_frame_count() == 0U) {
|
||||
return make_failed_future(path, "no tactile frames recorded");
|
||||
}
|
||||
const auto normalized = QDir::toNativeSeparators(path);
|
||||
return core_->export_recorded_frames(normalized.toStdString(), clear_after_export);
|
||||
}
|
||||
|
||||
private:
|
||||
void reset_core() {
|
||||
connected_ = false;
|
||||
@@ -380,6 +420,20 @@ private:
|
||||
QString active_port_;
|
||||
QString last_error_;
|
||||
bool connected_ = false;
|
||||
|
||||
static std::future<ffmsep::persist::WriteResult> make_failed_future(
|
||||
const QString& path,
|
||||
std::string message) {
|
||||
std::promise<ffmsep::persist::WriteResult> promise;
|
||||
auto future = promise.get_future();
|
||||
ffmsep::persist::WriteResult result{
|
||||
false,
|
||||
std::move(message),
|
||||
path.toStdString()
|
||||
};
|
||||
promise.set_value(std::move(result));
|
||||
return future;
|
||||
}
|
||||
};
|
||||
|
||||
struct SensorUiState {
|
||||
@@ -482,7 +536,7 @@ static auto ComConfigComponent(ThemeManager& manager) {
|
||||
ibpro::ThemeManager {manager},
|
||||
ibpro::FixedSize {40, 40},
|
||||
ibpro::Color { IconButton::Color::TONAL },
|
||||
ibpro::Font { material::kRoundSmallFont },
|
||||
ibpro::Font { material::kRegularExtraSmallFont },
|
||||
MutableForward {
|
||||
icon_button::pro::FontIcon {},
|
||||
link_icon_context,
|
||||
@@ -513,18 +567,15 @@ static auto ComConfigComponent(ThemeManager& manager) {
|
||||
ibpro::ThemeManager { manager },
|
||||
ibpro::FixedSize { 40, 40 },
|
||||
ibpro::Color { IconButton::Color::TONAL },
|
||||
ibpro::Font { material::kRoundSmallFont },
|
||||
ibpro::Font { material::kRegularExtraSmallFont },
|
||||
ibpro::FontIcon { material::icon::kRefresh },
|
||||
ibpro::Clickable {[&sensor] {
|
||||
// 刷新串口列表
|
||||
QStringList ports_list;
|
||||
const auto ports = ffmsep::CPStreamCore::list_available_ports();
|
||||
ports_list.reserve(static_cast<qsizetype>(ports.size()));
|
||||
for (const auto& info : ports) {
|
||||
ports_list.emplace_back(QString::fromStdString(info.port));
|
||||
}
|
||||
|
||||
// 保持原选择(若仍然存在)
|
||||
if (!sensor.selected_port.isEmpty()) {
|
||||
const bool exists = ports_list.contains(sensor.selected_port);
|
||||
if (!exists) {
|
||||
@@ -539,15 +590,74 @@ static auto ComConfigComponent(ThemeManager& manager) {
|
||||
},
|
||||
lnpro::Item<FilledButton> {
|
||||
fbpro::ThemeManager {manager},
|
||||
fbpro::FixedSize {40, 40},
|
||||
fbpro::Color {FilledButton::Color::TONAL},
|
||||
fbpro::Font {material::kRoundSmallFont},
|
||||
fbpro::FontIcon {material::icon::kRefresh},
|
||||
fbpro::Clickable {
|
||||
[] {
|
||||
qDebug() << "export";
|
||||
fbpro::FixedSize {40, 40},
|
||||
fbpro::Radius {8.0},
|
||||
// fbpro::Color { IconButton::Color::TONAL },
|
||||
fbpro::Font { material::kRegularExtraSmallFont },
|
||||
fbpro::Text { "drive_file_move" },
|
||||
fbpro::Clickable {[&sensor] {
|
||||
auto* controller = sensor.controller.get();
|
||||
if (!controller) {
|
||||
QMessageBox::warning(nullptr,
|
||||
QStringLiteral("导出失败"),
|
||||
QStringLiteral("当前串流尚未初始化。"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||
const auto timestamp = QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_HHmmss"));
|
||||
QString suggested_name = QStringLiteral("touchsensor_%1.json").arg(timestamp);
|
||||
QString initial_path = documents.isEmpty()
|
||||
? suggested_name
|
||||
: QDir(documents).filePath(suggested_name);
|
||||
|
||||
const QString chosen_path = QFileDialog::getSaveFileName(
|
||||
nullptr,
|
||||
QStringLiteral("导出触觉帧"),
|
||||
initial_path,
|
||||
QStringLiteral("JSON 文件 (*.json)"));
|
||||
if (chosen_path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto future = controller->export_frames(chosen_path, false);
|
||||
std::thread([future = std::move(future)]() mutable {
|
||||
ffmsep::persist::WriteResult result{};
|
||||
try {
|
||||
result = future.get();
|
||||
} catch (const std::exception& ex) {
|
||||
result.ok = false;
|
||||
result.error = ex.what();
|
||||
} catch (...) {
|
||||
result.ok = false;
|
||||
result.error = "unknown export failure";
|
||||
}
|
||||
|
||||
if (auto* app = QCoreApplication::instance()) {
|
||||
QMetaObject::invokeMethod(app,
|
||||
[res = std::move(result)]() {
|
||||
if (res.ok) {
|
||||
QMessageBox::information(
|
||||
nullptr,
|
||||
QStringLiteral("导出成功"),
|
||||
QStringLiteral("触觉帧已导出至:\n%1")
|
||||
.arg(QString::fromStdString(res.path)));
|
||||
} else {
|
||||
const auto error = QString::fromStdString(
|
||||
res.error.empty() ? std::string{"unknown error"} : res.error);
|
||||
const auto target = QString::fromStdString(res.path);
|
||||
const auto message = target.isEmpty()
|
||||
? QStringLiteral("原因:%1").arg(error)
|
||||
: QStringLiteral("原因:%1\n目标:%2").arg(error, target);
|
||||
QMessageBox::warning(nullptr,
|
||||
QStringLiteral("导出失败"),
|
||||
message);
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}).detach();
|
||||
}}
|
||||
}
|
||||
};
|
||||
return new Widget {
|
||||
|
||||
Reference in New Issue
Block a user