This commit is contained in:
2026-01-20 20:00:49 +08:00
3 changed files with 124 additions and 90 deletions

View File

@@ -104,7 +104,7 @@ Rectangle {
ComboBox { ComboBox {
Layout.fillWidth: true Layout.fillWidth: true
model: ["9600", "57600", "115200", "230400", "912600"] model: ["9600", "57600", "115200", "230400", "921600"]
Component.onCompleted: { Component.onCompleted: {
const idx = model.indexOf(String(Backend.serial.baudRate)) const idx = model.indexOf(String(Backend.serial.baudRate))
if (idx >= 0) if (idx >= 0)

View File

@@ -8,7 +8,6 @@
#include <QtMath> #include <QtMath>
#include <QFile> #include <QFile>
#include <QDebug> #include <QDebug>
#include <GL/gl.h>
#include <qevent.h> #include <qevent.h>
#include <qlogging.h> #include <qlogging.h>
#include <qminmax.h> #include <qminmax.h>

View File

@@ -10,12 +10,9 @@
#include <qserialportinfo.h> #include <qserialportinfo.h>
#include <vector> #include <vector>
SerialBackend::SerialBackend(QObject* parent) SerialBackend::SerialBackend(QObject *parent)
: QObject(parent) : QObject(parent), m_packetQueue(2048), m_frameQueue(2048), m_readThread(&m_packetQueue), m_decodeThread(&m_packetQueue, &m_frameQueue)
, m_packetQueue(2048) {
, m_frameQueue(2048)
, m_readThread(&m_packetQueue)
, m_decodeThread(&m_packetQueue, &m_frameQueue) {
m_request.dataLength = 24; m_request.dataLength = 24;
m_spec.model = QStringLiteral("PZR-A"); m_spec.model = QStringLiteral("PZR-A");
@@ -27,27 +24,27 @@ SerialBackend::SerialBackend(QObject* parent)
m_sendWorker = new SerialSendWorker(); m_sendWorker = new SerialSendWorker();
m_sendWorker->moveToThread(&m_sendThread); m_sendWorker->moveToThread(&m_sendThread);
connect(m_sendWorker, &SerialSendWorker::bytesReceived, this, [this](const QByteArray& data) { connect(m_sendWorker, &SerialSendWorker::bytesReceived, this, [this](const QByteArray &data)
{
#if 0 #if 0
if (!data.isEmpty()) if (!data.isEmpty())
qDebug().noquote() << "Serial recv bytes:" << QString::fromLatin1(data.toHex(' ')); qDebug().noquote() << "Serial recv bytes:" << QString::fromLatin1(data.toHex(' '));
#endif #endif
m_readThread.enqueueBytes(data); m_readThread.enqueueBytes(data); });
});
connect(m_sendWorker, &SerialSendWorker::requestBuilt, this, &SerialBackend::requestBuilt); connect(m_sendWorker, &SerialSendWorker::requestBuilt, this, &SerialBackend::requestBuilt);
connect(m_sendWorker, &SerialSendWorker::writeFailed, this, [](const QString& error) { connect(m_sendWorker, &SerialSendWorker::writeFailed, this, [](const QString &error)
{
if (!error.isEmpty()) if (!error.isEmpty())
qWarning().noquote() << "Serial write failed:" << error; qWarning().noquote() << "Serial write failed:" << error; });
});
connect(&m_readThread, &SerialReadThread::parseError, this, [](const QString& error) { connect(&m_readThread, &SerialReadThread::parseError, this, [](const QString &error)
{
if (!error.isEmpty()) if (!error.isEmpty())
qWarning().noquote() << "Serial packet invalid:" << error; qWarning().noquote() << "Serial packet invalid:" << error; });
}); connect(&m_decodeThread, &SerialDecodeThread::decodeError, this, [](const QString &error)
connect(&m_decodeThread, &SerialDecodeThread::decodeError, this, [](const QString& error) { {
if (!error.isEmpty()) if (!error.isEmpty())
qWarning().noquote() << "Serial decode failed:" << error; qWarning().noquote() << "Serial decode failed:" << error; });
});
connect(&m_decodeThread, &SerialDecodeThread::frameAvailable, this, &SerialBackend::drainFrames_); connect(&m_decodeThread, &SerialDecodeThread::frameAvailable, this, &SerialBackend::drainFrames_);
m_sendThread.start(); m_sendThread.start();
@@ -59,21 +56,24 @@ SerialBackend::SerialBackend(QObject* parent)
refreshPorts(); refreshPorts();
} }
SerialBackend::~SerialBackend() { SerialBackend::~SerialBackend()
{
close(); close();
stopPipeline_(); stopPipeline_();
if (m_sendWorker && m_sendThread.isRunning()) { if (m_sendWorker && m_sendThread.isRunning())
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]() { {
worker->closeTransport(); QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]()
}, Qt::BlockingQueuedConnection); { worker->closeTransport(); }, Qt::BlockingQueuedConnection);
} }
if (m_sendWorker && m_sendThread.isRunning()) { if (m_sendWorker && m_sendThread.isRunning())
{
QMetaObject::invokeMethod(m_sendWorker, &QObject::deleteLater, Qt::QueuedConnection); QMetaObject::invokeMethod(m_sendWorker, &QObject::deleteLater, Qt::QueuedConnection);
} }
if (m_sendThread.isRunning()) { if (m_sendThread.isRunning())
{
m_sendThread.quit(); m_sendThread.quit();
m_sendThread.wait(); m_sendThread.wait();
} }
@@ -81,15 +81,18 @@ SerialBackend::~SerialBackend() {
m_sendWorker = nullptr; m_sendWorker = nullptr;
} }
QString SerialBackend::mode() const { QString SerialBackend::mode() const
{
return (m_config.mode == DeviceMode::Slave) ? QStringLiteral("slave") : QStringLiteral("master"); return (m_config.mode == DeviceMode::Slave) ? QStringLiteral("slave") : QStringLiteral("master");
} }
QString SerialBackend::sensorGrid() const { QString SerialBackend::sensorGrid() const
{
return QStringLiteral("%1x%2").arg(m_spec.rows).arg(m_spec.cols); return QStringLiteral("%1x%2").arg(m_spec.rows).arg(m_spec.cols);
} }
void SerialBackend::setPortName(const QString& name) { void SerialBackend::setPortName(const QString &name)
{
if (m_config.portName == name) if (m_config.portName == name)
return; return;
m_config.portName = name; m_config.portName = name;
@@ -97,7 +100,8 @@ void SerialBackend::setPortName(const QString& name) {
emit portNameChanged(); emit portNameChanged();
} }
void SerialBackend::setBaudRate(int rate) { void SerialBackend::setBaudRate(int rate)
{
if (m_config.baudRate == rate) if (m_config.baudRate == rate)
return; return;
m_config.baudRate = rate; m_config.baudRate = rate;
@@ -105,7 +109,8 @@ void SerialBackend::setBaudRate(int rate) {
emit baudRateChanged(); emit baudRateChanged();
} }
void SerialBackend::setPollIntervalMs(int intervalMs) { void SerialBackend::setPollIntervalMs(int intervalMs)
{
intervalMs = qMax(1, intervalMs); intervalMs = qMax(1, intervalMs);
if (m_config.pollIntervalMs == intervalMs) if (m_config.pollIntervalMs == intervalMs)
return; return;
@@ -114,7 +119,8 @@ void SerialBackend::setPollIntervalMs(int intervalMs) {
emit pollIntervalMsChanged(); emit pollIntervalMsChanged();
} }
void SerialBackend::setDeviceAddress(int address) { void SerialBackend::setDeviceAddress(int address)
{
const int capped = qBound(0, address, 255); const int capped = qBound(0, address, 255);
if (m_config.deviceAddress == static_cast<quint8>(capped)) if (m_config.deviceAddress == static_cast<quint8>(capped))
return; return;
@@ -123,7 +129,8 @@ void SerialBackend::setDeviceAddress(int address) {
emit deviceAddressChanged(); emit deviceAddressChanged();
} }
void SerialBackend::setMode(const QString& mode) { void SerialBackend::setMode(const QString &mode)
{
const QString lower = mode.trimmed().toLower(); const QString lower = mode.trimmed().toLower();
const DeviceMode next = (lower == QStringLiteral("master")) ? DeviceMode::Master : DeviceMode::Slave; const DeviceMode next = (lower == QStringLiteral("master")) ? DeviceMode::Master : DeviceMode::Slave;
if (m_config.mode == next) if (m_config.mode == next)
@@ -133,7 +140,8 @@ void SerialBackend::setMode(const QString& mode) {
emit modeChanged(); emit modeChanged();
} }
void SerialBackend::setRequestFunction(int func) { void SerialBackend::setRequestFunction(int func)
{
const int capped = qBound(0, func, 255); const int capped = qBound(0, func, 255);
if (m_request.functionCode == static_cast<quint8>(capped)) if (m_request.functionCode == static_cast<quint8>(capped))
return; return;
@@ -142,7 +150,8 @@ void SerialBackend::setRequestFunction(int func) {
emit requestFunctionChanged(); emit requestFunctionChanged();
} }
void SerialBackend::setRequestStartAddress(int addr) { void SerialBackend::setRequestStartAddress(int addr)
{
const quint32 capped = static_cast<quint32>(qMax(0, addr)); const quint32 capped = static_cast<quint32>(qMax(0, addr));
if (m_request.startAddress == capped) if (m_request.startAddress == capped)
return; return;
@@ -151,7 +160,8 @@ void SerialBackend::setRequestStartAddress(int addr) {
emit requestStartAddressChanged(); emit requestStartAddressChanged();
} }
void SerialBackend::setRequestLength(int len) { void SerialBackend::setRequestLength(int len)
{
const int capped = qBound(0, len, 65535); const int capped = qBound(0, len, 65535);
if (m_request.dataLength == static_cast<quint16>(capped)) if (m_request.dataLength == static_cast<quint16>(capped))
return; return;
@@ -160,26 +170,30 @@ void SerialBackend::setRequestLength(int len) {
emit requestLengthChanged(); emit requestLengthChanged();
} }
void SerialBackend::setProtocol(const QString& name) { void SerialBackend::setProtocol(const QString &name)
{
if (!m_manager.setActiveProtocol(name)) if (!m_manager.setActiveProtocol(name))
return; return;
updateProtocolBindings_(); updateProtocolBindings_();
emit protocolChanged(); emit protocolChanged();
} }
void SerialBackend::applySensorSpec(const QString& model, int rows, int cols) { void SerialBackend::applySensorSpec(const QString &model, int rows, int cols)
{
const QString nextModel = model.trimmed(); const QString nextModel = model.trimmed();
const int nextRows = qMax(0, rows); const int nextRows = qMax(0, rows);
const int nextCols = qMax(0, cols); const int nextCols = qMax(0, cols);
bool changed = false; bool changed = false;
if (!nextModel.isEmpty() && m_spec.model != nextModel) { if (!nextModel.isEmpty() && m_spec.model != nextModel)
{
m_spec.model = nextModel; m_spec.model = nextModel;
emit sensorModelChanged(); emit sensorModelChanged();
changed = true; changed = true;
} }
if (m_spec.rows != nextRows || m_spec.cols != nextCols) { if (m_spec.rows != nextRows || m_spec.cols != nextCols)
{
m_spec.rows = nextRows; m_spec.rows = nextRows;
m_spec.cols = nextCols; m_spec.cols = nextCols;
emit sensorGridChanged(); emit sensorGridChanged();
@@ -190,34 +204,38 @@ void SerialBackend::applySensorSpec(const QString& model, int rows, int cols) {
return; return;
} }
void SerialBackend::setTransport(std::unique_ptr<ISerialTransport> transport) { void SerialBackend::setTransport(std::unique_ptr<ISerialTransport> transport)
{
if (!transport || !m_sendWorker) if (!transport || !m_sendWorker)
return; return;
if (!m_sendThread.isRunning()) if (!m_sendThread.isRunning())
m_sendThread.start(); m_sendThread.start();
if (transport->thread() != &m_sendThread) if (transport->thread() != &m_sendThread)
transport->moveToThread(&m_sendThread); transport->moveToThread(&m_sendThread);
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, transport = std::move(transport)]() mutable { QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, transport = std::move(transport)]() mutable
worker->setTransport(std::move(transport)); { worker->setTransport(std::move(transport)); }, Qt::BlockingQueuedConnection);
}, Qt::BlockingQueuedConnection);
} }
void SerialBackend::refreshPorts() { void SerialBackend::refreshPorts()
{
// Placeholder for real port discovery (QtSerialPort or third-party transport). // Placeholder for real port discovery (QtSerialPort or third-party transport).
m_availablePorts.clear(); m_availablePorts.clear();
auto device_found = QSerialPortInfo::availablePorts(); auto device_found = QSerialPortInfo::availablePorts();
for (auto item : device_found) { for (auto item : device_found)
{
m_availablePorts.append(item.portName()); m_availablePorts.append(item.portName());
} }
if (m_config.portName.isEmpty() && !m_availablePorts.isEmpty()) { if (m_config.portName.isEmpty() && !m_availablePorts.isEmpty())
{
m_config.portName = m_availablePorts.first(); m_config.portName = m_availablePorts.first();
emit portNameChanged(); emit portNameChanged();
} }
emit availablePortsChanged(); emit availablePortsChanged();
} }
bool SerialBackend::open() { bool SerialBackend::open()
{
if (m_connected) if (m_connected)
return true; return true;
@@ -225,12 +243,13 @@ bool SerialBackend::open() {
bool ok = false; bool ok = false;
QString error; QString error;
if (m_sendWorker) { if (m_sendWorker)
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config = m_config, &ok, &error]() { {
ok = worker->openTransport(config, &error); QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config = m_config, &ok, &error]()
}, Qt::BlockingQueuedConnection); { ok = worker->openTransport(config, &error); }, Qt::BlockingQueuedConnection);
} }
if (!ok) { if (!ok)
{
qWarning().noquote() << "Serial open failed:" << error; qWarning().noquote() << "Serial open failed:" << error;
return false; return false;
} }
@@ -241,44 +260,49 @@ bool SerialBackend::open() {
return true; return true;
} }
void SerialBackend::close() { void SerialBackend::close()
{
if (!m_connected) if (!m_connected)
return; return;
if (m_sendWorker && m_sendThread.isRunning()) { if (m_sendWorker && m_sendThread.isRunning())
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]() { {
worker->closeTransport(); QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]()
}, Qt::BlockingQueuedConnection); { worker->closeTransport(); }, Qt::BlockingQueuedConnection);
} }
stopPipeline_(); stopPipeline_();
m_connected = false; m_connected = false;
emit connectedChanged(); emit connectedChanged();
} }
void SerialBackend::requestOnce() { void SerialBackend::requestOnce()
{
if (!m_sendWorker) if (!m_sendWorker)
return; return;
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]() { QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]()
worker->requestOnce(); { worker->requestOnce(); }, Qt::QueuedConnection);
}, Qt::QueuedConnection);
} }
void SerialBackend::feedBytes(const QByteArray& data) { void SerialBackend::feedBytes(const QByteArray &data)
{
if (!m_readThread.isRunning()) if (!m_readThread.isRunning())
startPipeline_(); startPipeline_();
m_readThread.enqueueBytes(data); m_readThread.enqueueBytes(data);
} }
void SerialBackend::setSensorWidth(int w) { void SerialBackend::setSensorWidth(int w)
{
m_spec.cols = w; m_spec.cols = w;
syncSendConfig_(); syncSendConfig_();
} }
void SerialBackend::setSensorHeight(int h) { void SerialBackend::setSensorHeight(int h)
{
m_spec.rows = h; m_spec.rows = h;
syncSendConfig_(); syncSendConfig_();
} }
void SerialBackend::drainFrames_() { void SerialBackend::drainFrames_()
{
if (!m_frameCallback) if (!m_frameCallback)
return; return;
DataFrame frame; DataFrame frame;
@@ -286,7 +310,8 @@ void SerialBackend::drainFrames_() {
m_frameCallback(frame); m_frameCallback(frame);
} }
void SerialBackend::startPipeline_() { void SerialBackend::startPipeline_()
{
if (m_readThread.isRunning() || m_decodeThread.isRunning()) if (m_readThread.isRunning() || m_decodeThread.isRunning())
stopPipeline_(); stopPipeline_();
m_packetQueue.reset(); m_packetQueue.reset();
@@ -300,12 +325,15 @@ void SerialBackend::startPipeline_() {
m_decodeThread.start(); m_decodeThread.start();
} }
void SerialBackend::stopPipeline_() { void SerialBackend::stopPipeline_()
if (m_readThread.isRunning()) { {
if (m_readThread.isRunning())
{
m_readThread.stop(); m_readThread.stop();
m_readThread.wait(); m_readThread.wait();
} }
if (m_decodeThread.isRunning()) { if (m_decodeThread.isRunning())
{
m_decodeThread.stop(); m_decodeThread.stop();
m_decodeThread.wait(); m_decodeThread.wait();
} }
@@ -314,53 +342,60 @@ void SerialBackend::stopPipeline_() {
m_readThread.clear(); m_readThread.clear();
} }
void SerialBackend::updateProtocolBindings_() { void SerialBackend::updateProtocolBindings_()
{
const auto bundle = m_manager.activeBundle(); const auto bundle = m_manager.activeBundle();
SerialReadThread::ParseFunc parseFunc; SerialReadThread::ParseFunc parseFunc;
if (bundle.format) { if (bundle.format)
parseFunc = [format = bundle.format](QByteArray* buffer, QByteArray* packet, QString* error) { {
parseFunc = [format = bundle.format](QByteArray *buffer, QByteArray *packet, QString *error)
{
return format->tryParse(buffer, packet, error); return format->tryParse(buffer, packet, error);
}; };
} }
m_readThread.setParseFunc(std::move(parseFunc)); m_readThread.setParseFunc(std::move(parseFunc));
SerialDecodeThread::DecodeFunc decodeFunc; SerialDecodeThread::DecodeFunc decodeFunc;
if (bundle.decoder) { if (bundle.decoder)
decodeFunc = [decoder = bundle.decoder](const QByteArray& packet, DataFrame* frame, QString* error) { {
decodeFunc = [decoder = bundle.decoder](const QByteArray &packet, DataFrame *frame, QString *error)
{
return decoder->decodeFrame(packet, frame, error); return decoder->decodeFrame(packet, frame, error);
}; };
} }
m_decodeThread.setDecodeFunc(std::move(decodeFunc)); m_decodeThread.setDecodeFunc(std::move(decodeFunc));
SerialSendWorker::BuildRequestFunc requestFunc; SerialSendWorker::BuildRequestFunc requestFunc;
if (bundle.codec) { if (bundle.codec)
requestFunc = [codec = bundle.codec](const SerialConfig& config, const SensorRequest& request) { {
requestFunc = [codec = bundle.codec](const SerialConfig &config, const SensorRequest &request)
{
return codec->buildRequest(config, request); return codec->buildRequest(config, request);
}; };
} }
if (m_sendWorker) { if (m_sendWorker)
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, requestFunc]() mutable { {
worker->setBuildRequestFunc(std::move(requestFunc)); QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, requestFunc]() mutable
}, Qt::QueuedConnection); { worker->setBuildRequestFunc(std::move(requestFunc)); }, Qt::QueuedConnection);
} }
} }
void SerialBackend::syncSendConfig_() { void SerialBackend::syncSendConfig_()
{
if (!m_sendWorker) if (!m_sendWorker)
return; return;
const SerialConfig config = m_config; const SerialConfig config = m_config;
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config]() { QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config]()
worker->setConfig(config); { worker->setConfig(config); }, Qt::QueuedConnection);
}, Qt::QueuedConnection);
} }
void SerialBackend::syncSendRequest_() { void SerialBackend::syncSendRequest_()
{
if (!m_sendWorker) if (!m_sendWorker)
return; return;
const SensorRequest request = m_request; const SensorRequest request = m_request;
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, request]() { QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, request]()
worker->setRequest(request); { worker->setRequest(request); }, Qt::QueuedConnection);
}, Qt::QueuedConnection);
} }