完成主要交互、高性能组件、国际化和A型传感器数据包接收

This commit is contained in:
2026-01-13 16:34:28 +08:00
parent 47e6dc7244
commit 1960e6a5b9
84 changed files with 7752 additions and 332 deletions

View File

@@ -0,0 +1,241 @@
#include "piezoresistive_a_protocol.h"
#include <QDateTime>
#include <qcontainerfwd.h>
#include <qtypes.h>
namespace {
constexpr quint8 kReplyStart0 = 0x55; // 0x55AA little-endian
constexpr quint8 kReplyStart1 = 0xAA;
constexpr quint8 kReplyStartAlt0 = 0xAA;
constexpr quint8 kReplyStartAlt1 = 0x55;
constexpr quint8 kRequestStart0 = 0x55; // 0x55AA little-endian
constexpr quint8 kRequestStart1 = 0xAA;
quint16 readLe16(const QByteArray& data, int offset) {
const quint8 b0 = static_cast<quint8>(data[offset]);
const quint8 b1 = static_cast<quint8>(data[offset + 1]);
return static_cast<quint16>(b0 | (b1 << 8));
}
quint32 readLe32(const QByteArray& data, int offset) {
const quint8 b0 = static_cast<quint8>(data[offset]);
const quint8 b1 = static_cast<quint8>(data[offset + 1]);
const quint8 b2 = static_cast<quint8>(data[offset + 2]);
const quint8 b3 = static_cast<quint8>(data[offset + 3]);
return static_cast<quint32>(b0 | (b1 << 8) | (b2 << 16) | (b3 << 24));
}
void appendLe16(QByteArray& data, quint16 v) {
data.append(static_cast<char>(v & 0xFF));
data.append(static_cast<char>((v >> 8) & 0xFF));
}
void appendLe32(QByteArray& data, quint32 v) {
data.append(static_cast<char>(v & 0xFF));
data.append(static_cast<char>((v >> 8) & 0xFF));
data.append(static_cast<char>((v >> 16) & 0xFF));
data.append(static_cast<char>((v >> 24) & 0xFF));
}
}
quint8 PiezoresistiveAFormat::crc8ITU(const QByteArray& data, int length) {
quint8 crc = 0x00;
const int limit = qMin(length, data.size());
for (int i = 0; i < limit; ++i) {
crc ^= static_cast<quint8>(data[i]);
for (int bit = 0; bit < 8; ++bit) {
if (crc & 0x80)
crc = static_cast<quint8>((crc << 1) ^ 0x07);
else
crc = static_cast<quint8>(crc << 1);
}
}
return static_cast<quint8>(crc ^ 0x55);
}
ISerialFormat::ParseResult PiezoresistiveAFormat::tryParse(QByteArray* buffer, QByteArray* packet, QString* error) {
if (!buffer || buffer->isEmpty())
return ParseResult::NeedMore;
int startIndex = -1;
for (int i = 0; i + 1 < buffer->size(); ++i) {
if (static_cast<quint8>((*buffer)[i]) == kReplyStart0 &&
static_cast<quint8>((*buffer)[i + 1]) == kReplyStart1) {
startIndex = i;
break;
}
if (static_cast<quint8>((*buffer)[i]) == kReplyStartAlt0 &&
static_cast<quint8>((*buffer)[i + 1]) == kReplyStartAlt1) {
startIndex = i;
break;
}
}
if (startIndex < 0) {
const quint8 tail = static_cast<quint8>(buffer->back());
buffer->clear();
if (tail == kReplyStart0 || tail == kReplyStart1)
buffer->append(static_cast<char>(tail));
return ParseResult::NeedMore;
}
if (startIndex > 0)
buffer->remove(0, startIndex);
if (buffer->size() < 5)
return ParseResult::NeedMore;
const quint16 payloadLen = readLe16(*buffer, 2);
const int totalLen = 4 + payloadLen + 1;
if (buffer->size() < totalLen)
return ParseResult::NeedMore;
QByteArray candidate = buffer->left(totalLen);
buffer->remove(0, totalLen);
const quint8 crc = static_cast<quint8>(candidate.at(candidate.size() - 1));
const quint8 calc = crc8ITU(candidate, candidate.size() - 1);
if (crc != calc) {
if (error)
*error = QStringLiteral("CRC mismatch");
return ParseResult::Invalid;
}
if (packet)
*packet = candidate;
if (error)
error->clear();
return ParseResult::Ok;
}
QByteArray PiezoresistiveACodec::buildRequest(const SerialConfig& config, const SensorRequest& request) {
QByteArray packet;
packet.reserve(15);
packet.append(static_cast<char>(kRequestStart0));
packet.append(static_cast<char>(kRequestStart1));
const quint16 payloadLen = 9;
appendLe16(packet, payloadLen);
packet.append(static_cast<char>(config.deviceAddress));
packet.append(static_cast<char>(0x00));
packet.append(static_cast<char>(0x80 | request.functionCode));
appendLe32(packet, request.startAddress);
appendLe16(packet, request.dataLength);
const quint8 crc = PiezoresistiveAFormat::crc8ITU(packet, packet.size());
packet.append(static_cast<char>(crc));
return packet;
}
QByteArray PiezoresistiveACodec::buildGetVersionRequest(const SerialConfig& config) {
// TODO:待实现内容(压阻 A 型版本号查询请求帧)
Q_UNUSED(config)
return QByteArray();
}
QByteArray PiezoresistiveACodec::buildGetSpecRequest(const SerialConfig& config) {
// TODO:待实现内容(压阻 A 型规格查询请求帧)
Q_UNUSED(config)
return QByteArray();
}
bool PiezoresistiveADecoder::decodeFrame(const QByteArray& packet, DataFrame* frame, QString* error) {
if (!frame) {
if (error)
*error = QStringLiteral("Null frame output");
return false;
}
if (packet.size() < 15) {
if (error)
*error = QStringLiteral("Packet too short");
return false;
}
const quint8 start0 = static_cast<quint8>(packet[0]);
const quint8 start1 = static_cast<quint8>(packet[1]);
const bool startOk = (start0 == kReplyStart0 && start1 == kReplyStart1) ||
(start0 == kReplyStartAlt0 && start1 == kReplyStartAlt1);
if (!startOk) {
if (error)
*error = QStringLiteral("Bad start bytes");
return false;
}
const quint16 payloadLen = readLe16(packet, 2);
const int totalLen = 4 + payloadLen + 1;
if (packet.size() != totalLen) {
if (error)
*error = QStringLiteral("Length mismatch");
return false;
}
const quint8 crc = static_cast<quint8>(packet.at(packet.size() - 1));
const quint8 calc = PiezoresistiveAFormat::crc8ITU(packet, packet.size() - 1);
if (crc != calc) {
if (error)
*error = QStringLiteral("CRC mismatch");
return false;
}
const quint8 funcRaw = static_cast<quint8>(packet[6]);
const quint16 dataLen = readLe16(packet, 11);
const quint8 status = static_cast<quint8>(packet[13]);
if (payloadLen != static_cast<quint16>(10 + dataLen)) {
if (error)
*error = QStringLiteral("Payload length mismatch");
return false;
}
if (status != 0) {
if (error)
*error = QStringLiteral("Device status error");
return false;
}
if (packet.size() < 15 + dataLen) {
if (error)
*error = QStringLiteral("Data length mismatch");
return false;
}
if ((dataLen % 2) != 0) {
if (error)
*error = QStringLiteral("Odd data length");
return false;
}
const int sampleCount = dataLen / 2;
QVector<float> values;
values.reserve(sampleCount);
const int dataOffset = 14;
for (int i = 0; i < sampleCount; ++i) {
const int offset = dataOffset + i * 2;
const quint16 raw = readLe16(packet, offset);
values.push_back(static_cast<float>(raw));
}
frame->pts = DataFrame::makePts(QDateTime::currentDateTime());
frame->functionCode = (funcRaw >= 0x80) ? static_cast<quint8>(funcRaw - 0x80) : funcRaw;
frame->data = values;
if (error)
error->clear();
return true;
}
bool PiezoresistiveADecoder::decodeSpec(const QByteArray& packet, SensorSpec* spec, QString* error) {
// TODO:待实现内容(解析压阻 A 型规格回复并写入 SensorSpec)
Q_UNUSED(packet)
Q_UNUSED(spec)
if (error)
*error = QStringLiteral("Not implemented");
return false;
}

View File

@@ -0,0 +1,32 @@
#ifndef TACTILEIPC3D_PIEZORESISTIVE_A_PROTOCOL_H
#define TACTILEIPC3D_PIEZORESISTIVE_A_PROTOCOL_H
#include "serial_codec.h"
#include "serial_decoder.h"
#include "serial_format.h"
class PiezoresistiveAFormat : public ISerialFormat {
public:
ParseResult tryParse(QByteArray* buffer, QByteArray* packet, QString* error) override;
static quint8 crc8ITU(const QByteArray& data, int length);
};
class PiezoresistiveACodec : public ISerialCodec {
public:
QString name() const override { return QStringLiteral("piezoresistive_a"); }
QByteArray buildRequest(const SerialConfig& config, const SensorRequest& request) override;
QByteArray buildGetVersionRequest(const SerialConfig& config) override;
QByteArray buildGetSpecRequest(const SerialConfig& config) override;
};
class PiezoresistiveADecoder : public ISerialDecoder {
public:
QString name() const override { return QStringLiteral("piezoresistive_a"); }
bool decodeFrame(const QByteArray& packet, DataFrame* frame, QString* error) override;
bool decodeSpec(const QByteArray& packet, SensorSpec* spec, QString* error) override;
};
#endif // TACTILEIPC3D_PIEZORESISTIVE_A_PROTOCOL_H

View File

@@ -0,0 +1,358 @@
#include "serial_backend.h"
#include "piezoresistive_a_protocol.h"
#include "serial_qt_transport.h"
#include <QDebug>
#include <QMetaObject>
#include <QtGlobal>
#include <qcontainerfwd.h>
#include <qserialportinfo.h>
#include <vector>
SerialBackend::SerialBackend(QObject* parent)
: QObject(parent)
, m_packetQueue(2048)
, m_frameQueue(2048)
, m_readThread(&m_packetQueue)
, m_decodeThread(&m_packetQueue, &m_frameQueue) {
m_request.dataLength = 24;
m_spec.model = QStringLiteral("PZR-A");
m_spec.rows = 3;
m_spec.cols = 4;
auto codec = std::make_shared<PiezoresistiveACodec>();
auto decoder = std::make_shared<PiezoresistiveADecoder>();
auto format = std::make_shared<PiezoresistiveAFormat>();
m_manager.registerProtocol(codec->name(), {codec, decoder, format});
m_manager.setActiveProtocol(codec->name());
m_sendWorker = new SerialSendWorker();
m_sendWorker->moveToThread(&m_sendThread);
connect(m_sendWorker, &SerialSendWorker::bytesReceived, this, [this](const QByteArray& data) {
#if 0
if (!data.isEmpty())
qDebug().noquote() << "Serial recv bytes:" << QString::fromLatin1(data.toHex(' '));
#endif
m_readThread.enqueueBytes(data);
});
connect(m_sendWorker, &SerialSendWorker::requestBuilt, this, &SerialBackend::requestBuilt);
connect(m_sendWorker, &SerialSendWorker::writeFailed, this, [](const QString& error) {
if (!error.isEmpty())
qWarning().noquote() << "Serial write failed:" << error;
});
connect(&m_readThread, &SerialReadThread::parseError, this, [](const QString& error) {
if (!error.isEmpty())
qWarning().noquote() << "Serial packet invalid:" << error;
});
connect(&m_decodeThread, &SerialDecodeThread::decodeError, this, [](const QString& error) {
if (!error.isEmpty())
qWarning().noquote() << "Serial decode failed:" << error;
});
connect(&m_decodeThread, &SerialDecodeThread::frameAvailable, this, &SerialBackend::drainFrames_);
m_sendThread.start();
setTransport(std::make_unique<QtSerialTransport>());
updateProtocolBindings_();
syncSendConfig_();
syncSendRequest_();
refreshPorts();
}
SerialBackend::~SerialBackend() {
close();
stopPipeline_();
if (m_sendWorker && m_sendThread.isRunning()) {
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]() {
worker->closeTransport();
}, Qt::BlockingQueuedConnection);
}
if (m_sendWorker && m_sendThread.isRunning()) {
QMetaObject::invokeMethod(m_sendWorker, &QObject::deleteLater, Qt::QueuedConnection);
}
if (m_sendThread.isRunning()) {
m_sendThread.quit();
m_sendThread.wait();
}
m_sendWorker = nullptr;
}
QString SerialBackend::mode() const {
return (m_config.mode == DeviceMode::Slave) ? QStringLiteral("slave") : QStringLiteral("master");
}
QString SerialBackend::sensorGrid() const {
return QStringLiteral("%1x%2").arg(m_spec.rows).arg(m_spec.cols);
}
void SerialBackend::setPortName(const QString& name) {
if (m_config.portName == name)
return;
m_config.portName = name;
syncSendConfig_();
emit portNameChanged();
}
void SerialBackend::setBaudRate(int rate) {
if (m_config.baudRate == rate)
return;
m_config.baudRate = rate;
syncSendConfig_();
emit baudRateChanged();
}
void SerialBackend::setPollIntervalMs(int intervalMs) {
intervalMs = qMax(1, intervalMs);
if (m_config.pollIntervalMs == intervalMs)
return;
m_config.pollIntervalMs = intervalMs;
syncSendConfig_();
emit pollIntervalMsChanged();
}
void SerialBackend::setDeviceAddress(int address) {
const int capped = qBound(0, address, 255);
if (m_config.deviceAddress == static_cast<quint8>(capped))
return;
m_config.deviceAddress = static_cast<quint8>(capped);
syncSendConfig_();
emit deviceAddressChanged();
}
void SerialBackend::setMode(const QString& mode) {
const QString lower = mode.trimmed().toLower();
const DeviceMode next = (lower == QStringLiteral("master")) ? DeviceMode::Master : DeviceMode::Slave;
if (m_config.mode == next)
return;
m_config.mode = next;
syncSendConfig_();
emit modeChanged();
}
void SerialBackend::setRequestFunction(int func) {
const int capped = qBound(0, func, 255);
if (m_request.functionCode == static_cast<quint8>(capped))
return;
m_request.functionCode = static_cast<quint8>(capped);
syncSendRequest_();
emit requestFunctionChanged();
}
void SerialBackend::setRequestStartAddress(int addr) {
const quint32 capped = static_cast<quint32>(qMax(0, addr));
if (m_request.startAddress == capped)
return;
m_request.startAddress = capped;
syncSendRequest_();
emit requestStartAddressChanged();
}
void SerialBackend::setRequestLength(int len) {
const int capped = qBound(0, len, 65535);
if (m_request.dataLength == static_cast<quint16>(capped))
return;
m_request.dataLength = static_cast<quint16>(capped);
syncSendRequest_();
emit requestLengthChanged();
}
void SerialBackend::setProtocol(const QString& name) {
if (!m_manager.setActiveProtocol(name))
return;
updateProtocolBindings_();
emit protocolChanged();
}
void SerialBackend::applySensorSpec(const QString& model, int rows, int cols) {
const QString nextModel = model.trimmed();
const int nextRows = qMax(0, rows);
const int nextCols = qMax(0, cols);
bool changed = false;
if (!nextModel.isEmpty() && m_spec.model != nextModel) {
m_spec.model = nextModel;
emit sensorModelChanged();
changed = true;
}
if (m_spec.rows != nextRows || m_spec.cols != nextCols) {
m_spec.rows = nextRows;
m_spec.cols = nextCols;
emit sensorGridChanged();
changed = true;
}
if (!changed)
return;
}
void SerialBackend::setTransport(std::unique_ptr<ISerialTransport> transport) {
if (!transport || !m_sendWorker)
return;
if (!m_sendThread.isRunning())
m_sendThread.start();
if (transport->thread() != &m_sendThread)
transport->moveToThread(&m_sendThread);
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, transport = std::move(transport)]() mutable {
worker->setTransport(std::move(transport));
}, Qt::BlockingQueuedConnection);
}
void SerialBackend::refreshPorts() {
// Placeholder for real port discovery (QtSerialPort or third-party transport).
m_availablePorts.clear();
auto device_found = QSerialPortInfo::availablePorts();
for (auto item : device_found) {
m_availablePorts.append(item.portName());
}
if (m_config.portName.isEmpty() && !m_availablePorts.isEmpty()) {
m_config.portName = m_availablePorts.first();
emit portNameChanged();
}
emit availablePortsChanged();
}
bool SerialBackend::open() {
if (m_connected)
return true;
startPipeline_();
bool ok = false;
QString error;
if (m_sendWorker) {
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config = m_config, &ok, &error]() {
ok = worker->openTransport(config, &error);
}, Qt::BlockingQueuedConnection);
}
if (!ok) {
qWarning().noquote() << "Serial open failed:" << error;
return false;
}
m_connected = true;
emit connectedChanged();
return true;
}
void SerialBackend::close() {
if (!m_connected)
return;
if (m_sendWorker && m_sendThread.isRunning()) {
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]() {
worker->closeTransport();
}, Qt::BlockingQueuedConnection);
}
stopPipeline_();
m_connected = false;
emit connectedChanged();
}
void SerialBackend::requestOnce() {
if (!m_sendWorker)
return;
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker]() {
worker->requestOnce();
}, Qt::QueuedConnection);
}
void SerialBackend::feedBytes(const QByteArray& data) {
if (!m_readThread.isRunning())
startPipeline_();
m_readThread.enqueueBytes(data);
}
void SerialBackend::drainFrames_() {
if (!m_frameCallback)
return;
DataFrame frame;
while (m_frameQueue.tryPop(&frame))
m_frameCallback(frame);
}
void SerialBackend::startPipeline_() {
if (m_readThread.isRunning() || m_decodeThread.isRunning())
stopPipeline_();
m_packetQueue.reset();
m_frameQueue.reset();
m_readThread.clear();
updateProtocolBindings_();
if (!m_readThread.isRunning())
m_readThread.start();
if (!m_decodeThread.isRunning())
m_decodeThread.start();
}
void SerialBackend::stopPipeline_() {
if (m_readThread.isRunning()) {
m_readThread.stop();
m_readThread.wait();
}
if (m_decodeThread.isRunning()) {
m_decodeThread.stop();
m_decodeThread.wait();
}
m_packetQueue.reset();
m_frameQueue.reset();
m_readThread.clear();
}
void SerialBackend::updateProtocolBindings_() {
const auto bundle = m_manager.activeBundle();
SerialReadThread::ParseFunc parseFunc;
if (bundle.format) {
parseFunc = [format = bundle.format](QByteArray* buffer, QByteArray* packet, QString* error) {
return format->tryParse(buffer, packet, error);
};
}
m_readThread.setParseFunc(std::move(parseFunc));
SerialDecodeThread::DecodeFunc decodeFunc;
if (bundle.decoder) {
decodeFunc = [decoder = bundle.decoder](const QByteArray& packet, DataFrame* frame, QString* error) {
return decoder->decodeFrame(packet, frame, error);
};
}
m_decodeThread.setDecodeFunc(std::move(decodeFunc));
SerialSendWorker::BuildRequestFunc requestFunc;
if (bundle.codec) {
requestFunc = [codec = bundle.codec](const SerialConfig& config, const SensorRequest& request) {
return codec->buildRequest(config, request);
};
}
if (m_sendWorker) {
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, requestFunc]() mutable {
worker->setBuildRequestFunc(std::move(requestFunc));
}, Qt::QueuedConnection);
}
}
void SerialBackend::syncSendConfig_() {
if (!m_sendWorker)
return;
const SerialConfig config = m_config;
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, config]() {
worker->setConfig(config);
}, Qt::QueuedConnection);
}
void SerialBackend::syncSendRequest_() {
if (!m_sendWorker)
return;
const SensorRequest request = m_request;
QMetaObject::invokeMethod(m_sendWorker, [worker = m_sendWorker, request]() {
worker->setRequest(request);
}, Qt::QueuedConnection);
}

114
src/serial/serial_backend.h Normal file
View File

@@ -0,0 +1,114 @@
#ifndef TACTILEIPC3D_SERIAL_BACKEND_H
#define TACTILEIPC3D_SERIAL_BACKEND_H
#include <QObject>
#include <QStringList>
#include <QString>
#include <functional>
#include <memory>
#include "serial_manager.h"
#include "serial_threads.h"
#include "../data_frame.h"
class SerialBackend : public QObject {
Q_OBJECT
Q_PROPERTY(QString portName READ portName WRITE setPortName NOTIFY portNameChanged)
Q_PROPERTY(int baudRate READ baudRate WRITE setBaudRate NOTIFY baudRateChanged)
Q_PROPERTY(int pollIntervalMs READ pollIntervalMs WRITE setPollIntervalMs NOTIFY pollIntervalMsChanged)
Q_PROPERTY(int deviceAddress READ deviceAddress WRITE setDeviceAddress NOTIFY deviceAddressChanged)
Q_PROPERTY(QString mode READ mode WRITE setMode NOTIFY modeChanged)
Q_PROPERTY(int requestFunction READ requestFunction WRITE setRequestFunction NOTIFY requestFunctionChanged)
Q_PROPERTY(int requestStartAddress READ requestStartAddress WRITE setRequestStartAddress NOTIFY requestStartAddressChanged)
Q_PROPERTY(int requestLength READ requestLength WRITE setRequestLength NOTIFY requestLengthChanged)
Q_PROPERTY(QString protocol READ protocol WRITE setProtocol NOTIFY protocolChanged)
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
Q_PROPERTY(QStringList availablePorts READ availablePorts NOTIFY availablePortsChanged)
Q_PROPERTY(QString sensorModel READ sensorModel NOTIFY sensorModelChanged)
Q_PROPERTY(QString sensorGrid READ sensorGrid NOTIFY sensorGridChanged)
public:
using FrameCallback = std::function<void(const DataFrame&)>;
explicit SerialBackend(QObject* parent = nullptr);
~SerialBackend() override;
QString portName() const { return m_config.portName; }
int baudRate() const { return m_config.baudRate; }
int pollIntervalMs() const { return m_config.pollIntervalMs; }
int deviceAddress() const { return static_cast<int>(m_config.deviceAddress); }
QString mode() const;
int requestFunction() const { return m_request.functionCode; }
int requestStartAddress() const { return static_cast<int>(m_request.startAddress); }
int requestLength() const { return m_request.dataLength; }
QString protocol() const { return m_manager.activeProtocol(); }
bool connected() const { return m_connected; }
QStringList availablePorts() const { return m_availablePorts; }
QString sensorModel() const { return m_spec.model; }
QString sensorGrid() const;
void setPortName(const QString& name);
void setBaudRate(int rate);
void setPollIntervalMs(int intervalMs);
void setDeviceAddress(int address);
void setMode(const QString& mode);
void setRequestFunction(int func);
void setRequestStartAddress(int addr);
void setRequestLength(int len);
void setProtocol(const QString& name);
Q_INVOKABLE void applySensorSpec(const QString& model, int rows, int cols);
void setFrameCallback(FrameCallback cb) { m_frameCallback = std::move(cb); }
void setTransport(std::unique_ptr<ISerialTransport> transport);
Q_INVOKABLE void refreshPorts();
Q_INVOKABLE bool open();
Q_INVOKABLE void close();
Q_INVOKABLE void requestOnce();
Q_INVOKABLE void feedBytes(const QByteArray& data);
signals:
void portNameChanged();
void baudRateChanged();
void pollIntervalMsChanged();
void deviceAddressChanged();
void modeChanged();
void requestFunctionChanged();
void requestStartAddressChanged();
void requestLengthChanged();
void protocolChanged();
void connectedChanged();
void availablePortsChanged();
void sensorModelChanged();
void sensorGridChanged();
void requestBuilt(const QByteArray& data);
private:
void drainFrames_();
void startPipeline_();
void stopPipeline_();
void updateProtocolBindings_();
void syncSendConfig_();
void syncSendRequest_();
SerialConfig m_config;
SensorRequest m_request;
SensorSpec m_spec;
SerialManager m_manager;
PacketQueue m_packetQueue;
FrameQueue m_frameQueue;
SerialReadThread m_readThread;
SerialDecodeThread m_decodeThread;
QThread m_sendThread;
SerialSendWorker* m_sendWorker = nullptr;
FrameCallback m_frameCallback;
bool m_connected = false;
QStringList m_availablePorts;
};
#endif // TACTILEIPC3D_SERIAL_BACKEND_H

23
src/serial/serial_codec.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef TACTILEIPC3D_SERIAL_CODEC_H
#define TACTILEIPC3D_SERIAL_CODEC_H
#include <QByteArray>
#include <QString>
#include "serial_types.h"
class ISerialCodec {
public:
virtual ~ISerialCodec() = default;
virtual QString name() const = 0;
virtual QByteArray buildRequest(const SerialConfig& config, const SensorRequest& request) = 0;
// Reserved hooks for later expansion
// TODO:待实现内容(构建获取版本号的请求帧)
virtual QByteArray buildGetVersionRequest(const SerialConfig& config) = 0;
// TODO:待实现内容(构建获取传感器规格的请求帧)
virtual QByteArray buildGetSpecRequest(const SerialConfig& config) = 0;
};
#endif // TACTILEIPC3D_SERIAL_CODEC_H

View File

@@ -0,0 +1,22 @@
#ifndef TACTILEIPC3D_SERIAL_DECODER_H
#define TACTILEIPC3D_SERIAL_DECODER_H
#include <QByteArray>
#include <QString>
#include "serial_types.h"
#include "../data_frame.h"
class ISerialDecoder {
public:
virtual ~ISerialDecoder() = default;
virtual QString name() const = 0;
virtual bool decodeFrame(const QByteArray& packet, DataFrame* frame, QString* error) = 0;
// Reserved hooks for later expansion
// TODO:待实现内容(解析规格回复帧并填充 SensorSpec)
virtual bool decodeSpec(const QByteArray& packet, SensorSpec* spec, QString* error) = 0;
};
#endif // TACTILEIPC3D_SERIAL_DECODER_H

View File

@@ -0,0 +1,19 @@
#ifndef TACTILEIPC3D_SERIAL_FORMAT_H
#define TACTILEIPC3D_SERIAL_FORMAT_H
#include <QByteArray>
#include <QString>
class ISerialFormat {
public:
enum class ParseResult {
NeedMore = 0,
Ok = 1,
Invalid = 2,
};
virtual ~ISerialFormat() = default;
virtual ParseResult tryParse(QByteArray* buffer, QByteArray* packet, QString* error) = 0;
};
#endif // TACTILEIPC3D_SERIAL_FORMAT_H

View File

@@ -0,0 +1,22 @@
#include "serial_manager.h"
void SerialManager::registerProtocol(const QString& name, const ProtocolBundle& bundle) {
if (name.isEmpty())
return;
m_protocols.insert(name, bundle);
if (m_activeName.isEmpty())
m_activeName = name;
}
bool SerialManager::setActiveProtocol(const QString& name) {
if (!m_protocols.contains(name))
return false;
m_activeName = name;
return true;
}
SerialManager::ProtocolBundle SerialManager::activeBundle() const {
if (!m_protocols.contains(m_activeName))
return {};
return m_protocols.value(m_activeName);
}

View File

@@ -0,0 +1,31 @@
#ifndef TACTILEIPC3D_SERIAL_MANAGER_H
#define TACTILEIPC3D_SERIAL_MANAGER_H
#include <QHash>
#include <QString>
#include <memory>
#include "serial_codec.h"
#include "serial_decoder.h"
#include "serial_format.h"
class SerialManager {
public:
struct ProtocolBundle {
std::shared_ptr<ISerialCodec> codec;
std::shared_ptr<ISerialDecoder> decoder;
std::shared_ptr<ISerialFormat> format;
};
void registerProtocol(const QString& name, const ProtocolBundle& bundle);
bool setActiveProtocol(const QString& name);
QString activeProtocol() const { return m_activeName; }
ProtocolBundle activeBundle() const;
private:
QHash<QString, ProtocolBundle> m_protocols;
QString m_activeName;
};
#endif // TACTILEIPC3D_SERIAL_MANAGER_H

View File

@@ -0,0 +1,140 @@
#include "serial_qt_transport.h"
#include "serial/serial_types.h"
#include <QSerialPort>
#include <QtGlobal>
#include <qcontainerfwd.h>
namespace {
QSerialPort::DataBits mapDataBits(int bits) {
switch (bits) {
case 5:
return QSerialPort::Data5;
case 6:
return QSerialPort::Data6;
case 7:
return QSerialPort::Data7;
case 8:
default:
return QSerialPort::Data8;
}
}
QSerialPort::StopBits mapStopBits(int bits) {
switch (bits) {
case 2:
return QSerialPort::TwoStop;
case 1:
default:
return QSerialPort::OneStop;
}
}
QSerialPort::Parity mapParity(const QString& parity) {
const QString key = parity.trimmed().toUpper();
if (key == QStringLiteral("E") || key == QStringLiteral("EVEN"))
return QSerialPort::EvenParity;
if (key == QStringLiteral("O") || key == QStringLiteral("ODD"))
return QSerialPort::OddParity;
if (key == QStringLiteral("M") || key == QStringLiteral("MARK"))
return QSerialPort::MarkParity;
if (key == QStringLiteral("S") || key == QStringLiteral("SPACE"))
return QSerialPort::SpaceParity;
return QSerialPort::NoParity;
}
} // namespace
QtSerialTransport::QtSerialTransport(QObject* parent)
: ISerialTransport(parent) {}
QtSerialTransport::~QtSerialTransport() {
close();
}
void QtSerialTransport::ensurePort_() {
if (m_port)
return;
m_port = new QSerialPort(this);
connect(m_port, &QSerialPort::readyRead, this, [this]() {
const QByteArray data = m_port->readAll();
if (!data.isEmpty())
emit bytesReceived(data);
});
}
void applyConfigDebug(const SerialConfig& config) {
#if DEBUG_MODE
QString strd = "";
strd += "baud:" + QString::number(config.baudRate);
#endif
}
void QtSerialTransport::applyConfig_(const SerialConfig& config) {
if (!m_port)
return;
applyConfigDebug(config);
m_port->setBaudRate(config.baudRate);
m_port->setDataBits(mapDataBits(config.dataBits));
m_port->setStopBits(mapStopBits(config.stopBits));
m_port->setParity(mapParity(config.parity));
// TODO:待实现内容(根据SerialConfig扩展软件/硬件流控配置)
m_port->setFlowControl(QSerialPort::NoFlowControl);
}
bool QtSerialTransport::open(const SerialConfig& config, QString* error) {
ensurePort_();
if (config.portName.trimmed().isEmpty()) {
if (error)
*error = QStringLiteral("Port name is empty");
return false;
}
if (m_port->isOpen())
m_port->close();
m_port->setPortName(config.portName);
applyConfig_(config);
const bool ok = m_port->open(QIODevice::ReadWrite);
if (!ok) {
if (error)
*error = m_port->errorString();
return false;
}
if (error)
error->clear();
return true;
}
void QtSerialTransport::close() {
if (m_port && m_port->isOpen())
m_port->close();
}
bool QtSerialTransport::writeBytes(const QByteArray& data, QString* error) {
if (!m_port || !m_port->isOpen()) {
if (error)
*error = QStringLiteral("Serial port not open");
return false;
}
if (data.isEmpty()) {
if (error)
error->clear();
return true;
}
const qint64 written = m_port->write(data);
if (written < 0) {
if (error)
*error = m_port->errorString();
return false;
}
if (error)
error->clear();
return true;
}

View File

@@ -0,0 +1,25 @@
#ifndef TACTILEIPC3D_SERIAL_QT_TRANSPORT_H
#define TACTILEIPC3D_SERIAL_QT_TRANSPORT_H
#include "serial_transport.h"
class QSerialPort;
class QtSerialTransport : public ISerialTransport {
Q_OBJECT
public:
explicit QtSerialTransport(QObject* parent = nullptr);
~QtSerialTransport() override;
bool open(const SerialConfig& config, QString* error) override;
void close() override;
bool writeBytes(const QByteArray& data, QString* error) override;
private:
void ensurePort_();
void applyConfig_(const SerialConfig& config);
QSerialPort* m_port = nullptr;
};
#endif // TACTILEIPC3D_SERIAL_QT_TRANSPORT_H

97
src/serial/serial_queue.h Normal file
View File

@@ -0,0 +1,97 @@
#ifndef TACTILEIPC3D_SERIAL_QUEUE_H
#define TACTILEIPC3D_SERIAL_QUEUE_H
#include <QQueue>
#include <QMutex>
#include <QWaitCondition>
#include <QByteArray>
#include <QtGlobal>
#include "../data_frame.h"
template <typename T>
class SerialQueue {
public:
explicit SerialQueue(int maxSize = 2048)
: m_maxSize(maxSize) {}
void setMaxSize(int maxSize) {
QMutexLocker locker(&m_mutex);
m_maxSize = qMax(1, maxSize);
}
void push(const T& item) {
QMutexLocker locker(&m_mutex);
if (m_stopped)
return;
if (m_maxSize > 0 && m_queue.size() >= m_maxSize) {
// TODO:待实现内容(指定队列溢出策略,例如丢弃最新/丢弃最旧/阻塞等待)
m_queue.dequeue();
}
m_queue.enqueue(item);
m_hasData.wakeOne();
}
bool pop(T* out, int timeoutMs = -1) {
QMutexLocker locker(&m_mutex);
if (timeoutMs < 0) {
while (m_queue.isEmpty() && !m_stopped)
m_hasData.wait(&m_mutex);
} else {
if (m_queue.isEmpty() && !m_stopped)
m_hasData.wait(&m_mutex, timeoutMs);
}
if (m_queue.isEmpty() || m_stopped)
return false;
if (out)
*out = m_queue.dequeue();
else
m_queue.dequeue();
return true;
}
bool tryPop(T* out) {
QMutexLocker locker(&m_mutex);
if (m_queue.isEmpty() || m_stopped)
return false;
if (out)
*out = m_queue.dequeue();
else
m_queue.dequeue();
return true;
}
void clear() {
QMutexLocker locker(&m_mutex);
m_queue.clear();
}
void stop() {
QMutexLocker locker(&m_mutex);
m_stopped = true;
m_hasData.wakeAll();
}
void reset() {
QMutexLocker locker(&m_mutex);
m_stopped = false;
m_queue.clear();
}
int size() const {
QMutexLocker locker(&m_mutex);
return m_queue.size();
}
private:
mutable QMutex m_mutex;
QWaitCondition m_hasData;
QQueue<T> m_queue;
int m_maxSize = 0;
bool m_stopped = false;
};
using PacketQueue = SerialQueue<QByteArray>;
using FrameQueue = SerialQueue<DataFrame>;
#endif // TACTILEIPC3D_SERIAL_QUEUE_H

View File

@@ -0,0 +1,216 @@
#include "serial_threads.h"
#include <QDebug>
#include <QtGlobal>
SerialReadThread::SerialReadThread(PacketQueue* packetQueue, QObject* parent)
: QThread(parent)
, m_packetQueue(packetQueue) {}
void SerialReadThread::setParseFunc(ParseFunc func) {
QMutexLocker locker(&m_funcMutex);
m_parseFunc = std::move(func);
}
void SerialReadThread::enqueueBytes(const QByteArray& data) {
if (data.isEmpty())
return;
QMutexLocker locker(&m_queueMutex);
m_byteQueue.enqueue(data);
m_dataReady.wakeOne();
}
void SerialReadThread::clear() {
QMutexLocker locker(&m_queueMutex);
m_byteQueue.clear();
m_buffer.clear();
}
void SerialReadThread::stop() {
m_running = false;
m_dataReady.wakeAll();
}
void SerialReadThread::run() {
m_running = true;
while (m_running) {
QByteArray chunk;
{
QMutexLocker locker(&m_queueMutex);
while (m_byteQueue.isEmpty() && m_running)
m_dataReady.wait(&m_queueMutex);
if (!m_running)
break;
if (!m_byteQueue.isEmpty())
chunk = m_byteQueue.dequeue();
}
if (chunk.isEmpty())
continue;
m_buffer.append(chunk);
ParseFunc parseFunc;
{
QMutexLocker locker(&m_funcMutex);
parseFunc = m_parseFunc;
}
if (!parseFunc)
continue;
while (m_running) {
QByteArray packet;
QString error;
const ISerialFormat::ParseResult result = parseFunc(&m_buffer, &packet, &error);
if (result == ISerialFormat::ParseResult::NeedMore)
break;
if (result == ISerialFormat::ParseResult::Invalid) {
emit parseError(error);
continue;
}
if (!packet.isEmpty())
qDebug().noquote() << "Serial packet rawdata:" << QString::fromLatin1(packet.toHex(' '));
if (m_packetQueue)
m_packetQueue->push(packet);
}
}
}
SerialDecodeThread::SerialDecodeThread(PacketQueue* packetQueue, FrameQueue* frameQueue, QObject* parent)
: QThread(parent)
, m_packetQueue(packetQueue)
, m_frameQueue(frameQueue) {}
void SerialDecodeThread::setDecodeFunc(DecodeFunc func) {
QMutexLocker locker(&m_funcMutex);
m_decodeFunc = std::move(func);
}
void SerialDecodeThread::stop() {
m_running = false;
if (m_packetQueue)
m_packetQueue->stop();
}
void SerialDecodeThread::run() {
m_running = true;
while (m_running) {
QByteArray packet;
if (!m_packetQueue || !m_packetQueue->pop(&packet))
break;
DecodeFunc decodeFunc;
{
QMutexLocker locker(&m_funcMutex);
decodeFunc = m_decodeFunc;
}
if (!decodeFunc)
continue;
DataFrame frame;
QString error;
if (!decodeFunc(packet, &frame, &error)) {
emit decodeError(error);
continue;
}
if (m_frameQueue)
m_frameQueue->push(frame);
emit frameAvailable();
}
}
SerialSendWorker::SerialSendWorker(QObject* parent)
: QObject(parent) {
m_pollTimer.setParent(this);
m_pollTimer.setTimerType(Qt::PreciseTimer);
connect(&m_pollTimer, &QTimer::timeout, this, [this]() {
sendRequest_();
});
}
void SerialSendWorker::setTransport(std::unique_ptr<ISerialTransport> transport) {
if (m_transport)
m_transport->disconnect(this);
m_transport = std::move(transport);
if (m_transport) {
connect(m_transport.get(), &ISerialTransport::bytesReceived,
this, &SerialSendWorker::bytesReceived);
}
}
void SerialSendWorker::setBuildRequestFunc(BuildRequestFunc func) {
m_buildRequest = std::move(func);
}
void SerialSendWorker::setConfig(const SerialConfig& config) {
m_config = config;
updatePolling_();
}
void SerialSendWorker::setRequest(const SensorRequest& request) {
m_request = request;
}
bool SerialSendWorker::openTransport(const SerialConfig& config, QString* error) {
m_config = config;
if (!m_transport) {
if (error)
*error = QStringLiteral("Transport missing");
return false;
}
if (m_connected) {
if (error)
error->clear();
updatePolling_();
return true;
}
QString localError;
const bool ok = m_transport->open(m_config, &localError);
m_connected = ok;
updatePolling_();
if (error)
*error = localError;
return ok;
}
void SerialSendWorker::closeTransport() {
if (!m_connected)
return;
m_pollTimer.stop();
if (m_transport)
m_transport->close();
m_connected = false;
}
void SerialSendWorker::requestOnce() {
sendRequest_();
}
void SerialSendWorker::updatePolling_() {
if (!m_connected || m_config.mode != DeviceMode::Slave) {
m_pollTimer.stop();
return;
}
const int interval = qMax(1, m_config.pollIntervalMs);
if (m_pollTimer.isActive())
m_pollTimer.setInterval(interval);
else
m_pollTimer.start(interval);
}
void SerialSendWorker::sendRequest_() {
if (!m_connected || !m_transport || !m_buildRequest)
return;
const QByteArray request = m_buildRequest(m_config, m_request);
if (request.isEmpty())
return;
emit requestBuilt(request);
QString error;
if (!m_transport->writeBytes(request, &error))
emit writeFailed(error);
}

112
src/serial/serial_threads.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef TACTILEIPC3D_SERIAL_THREADS_H
#define TACTILEIPC3D_SERIAL_THREADS_H
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QQueue>
#include <QTimer>
#include <QByteArray>
#include <QString>
#include <atomic>
#include <functional>
#include <memory>
#include "serial_format.h"
#include "serial_transport.h"
#include "serial_queue.h"
#include "serial_types.h"
class SerialReadThread : public QThread {
Q_OBJECT
public:
using ParseFunc = std::function<ISerialFormat::ParseResult(QByteArray*, QByteArray*, QString*)>;
explicit SerialReadThread(PacketQueue* packetQueue, QObject* parent = nullptr);
void setParseFunc(ParseFunc func);
void enqueueBytes(const QByteArray& data);
void clear();
void stop();
signals:
void parseError(const QString& error);
protected:
void run() override;
private:
PacketQueue* m_packetQueue = nullptr;
QMutex m_queueMutex;
QWaitCondition m_dataReady;
QQueue<QByteArray> m_byteQueue;
QMutex m_funcMutex;
ParseFunc m_parseFunc;
QByteArray m_buffer;
std::atomic_bool m_running{false};
};
class SerialDecodeThread : public QThread {
Q_OBJECT
public:
using DecodeFunc = std::function<bool(const QByteArray&, DataFrame*, QString*)>;
explicit SerialDecodeThread(PacketQueue* packetQueue, FrameQueue* frameQueue, QObject* parent = nullptr);
void setDecodeFunc(DecodeFunc func);
void stop();
signals:
void frameAvailable();
void decodeError(const QString& error);
protected:
void run() override;
private:
PacketQueue* m_packetQueue = nullptr;
FrameQueue* m_frameQueue = nullptr;
QMutex m_funcMutex;
DecodeFunc m_decodeFunc;
std::atomic_bool m_running{false};
};
class SerialSendWorker : public QObject {
Q_OBJECT
public:
using BuildRequestFunc = std::function<QByteArray(const SerialConfig&, const SensorRequest&)>;
explicit SerialSendWorker(QObject* parent = nullptr);
void setTransport(std::unique_ptr<ISerialTransport> transport);
void setBuildRequestFunc(BuildRequestFunc func);
void setConfig(const SerialConfig& config);
void setRequest(const SensorRequest& request);
bool openTransport(const SerialConfig& config, QString* error);
void closeTransport();
void requestOnce();
signals:
void bytesReceived(const QByteArray& data);
void requestBuilt(const QByteArray& data);
void writeFailed(const QString& error);
private:
void updatePolling_();
void sendRequest_();
SerialConfig m_config;
SensorRequest m_request;
BuildRequestFunc m_buildRequest;
std::unique_ptr<ISerialTransport> m_transport;
QTimer m_pollTimer;
bool m_connected = false;
};
#endif // TACTILEIPC3D_SERIAL_THREADS_H

View File

@@ -0,0 +1,45 @@
#ifndef TACTILEIPC3D_SERIAL_TRANSPORT_H
#define TACTILEIPC3D_SERIAL_TRANSPORT_H
#include <QObject>
#include <QByteArray>
#include "serial_types.h"
class ISerialTransport : public QObject {
Q_OBJECT
public:
explicit ISerialTransport(QObject* parent = nullptr) : QObject(parent) {}
~ISerialTransport() override = default;
virtual bool open(const SerialConfig& config, QString* error) = 0;
virtual void close() = 0;
virtual bool writeBytes(const QByteArray& data, QString* error) = 0;
signals:
void bytesReceived(const QByteArray& data);
};
class NullSerialTransport : public ISerialTransport {
Q_OBJECT
public:
explicit NullSerialTransport(QObject* parent = nullptr) : ISerialTransport(parent) {}
bool open(const SerialConfig& config, QString* error) override {
Q_UNUSED(config)
if (error)
error->clear();
return true;
}
void close() override {}
bool writeBytes(const QByteArray& data, QString* error) override {
Q_UNUSED(data)
if (error)
error->clear();
return true;
}
};
#endif // TACTILEIPC3D_SERIAL_TRANSPORT_H

43
src/serial/serial_types.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef TACTILEIPC3D_SERIAL_TYPES_H
#define TACTILEIPC3D_SERIAL_TYPES_H
#include <QString>
#include <QtGlobal>
#define DEBUG_MODE 1
enum class DeviceMode {
Master = 0,
Slave = 1,
};
struct SerialConfig {
QString portName;
int baudRate = 115200;
int dataBits = 8;
int stopBits = 1;
QString parity = QStringLiteral("N");
quint8 deviceAddress = 0x01;
DeviceMode mode = DeviceMode::Slave;
int pollIntervalMs = 50;
};
struct SensorRequest {
quint8 functionCode = 0x00;
quint32 startAddress = 0;
quint16 dataLength = 0;
};
struct SensorSpec {
QString model;
QString version;
int rows = 0;
int cols = 0;
float pitch = 0.0f;
float dotRadius = 0.0f;
float rangeMin = 0.0f;
float rangeMax = 0.0f;
};
#endif // TACTILEIPC3D_SERIAL_TYPES_H