21 KiB
21 KiB
TactileIPC3D 架构说明
UML (Mermaid)
classDiagram
class AppBackend {
+bool lightMode
+string language
+bool connected
+SerialBackend* serial
+DataBackend* data
+setLightMode(bool)
+setLanguage(string)
}
class SerialConfig {
+string portName
+int baudRate
+int dataBits
+int stopBits
+string parity
+uint8 deviceAddress
+DeviceMode mode
+int pollIntervalMs
}
class SensorRequest {
+uint8 functionCode
+uint32 startAddress
+uint16 dataLength
}
class SensorSpec {
+string model
+string version
+int rows
+int cols
+float pitch
+float dotRadius
+float rangeMin
+float rangeMax
}
class SerialBackend {
+string portName
+int baudRate
+int pollIntervalMs
+int deviceAddress
+string mode
+int requestFunction
+int requestStartAddress
+int requestLength
+string protocol
+bool connected
+open()
+close()
+requestOnce()
+feedBytes(bytes)
+setTransport(transport)
}
class GLWidget {
+dotClicked(index, row, col, value)
}
class SerialManager {
+registerProtocol(name, bundle)
+setActiveProtocol(name)
+activeBundle()
}
class ISerialTransport {
<<interface>>
+open(config, error)
+close()
+writeBytes(data, error)
}
class QtSerialTransport {
+open(config, error)
+close()
+writeBytes(data, error)
}
class ISerialFormat {
<<interface>>
+tryParse(buffer, packet, error)
}
class ISerialCodec {
<<interface>>
+buildRequest(config, request)
+buildGetVersionRequest(config)
+buildGetSpecRequest(config)
}
class ISerialDecoder {
<<interface>>
+decodeFrame(packet, frame)
+decodeSpec(packet, spec)
}
class PiezoresistiveAFormat
class PiezoresistiveACodec
class PiezoresistiveADecoder
class DataBackend {
+ingestFrame(frame)
+clear()
+exportJson(path)
+exportCsv(path)
+importJson(path)
+importCsv(path)
+startPlayback(intervalMs)
+stopPlayback()
+setLiveRenderCallback(cb)
+setPlaybackRenderCallback(cb)
}
class DataFrame {
+string pts
+uint8 functionCode
+float[] data
}
class PacketQueue {
+push(packet)
+pop()
+clear()
+stop()
}
class FrameQueue {
+push(frame)
+pop()
+clear()
+stop()
}
class SerialReadThread {
+enqueueBytes(bytes)
+setParseFunc(func)
+start()
+stop()
}
class SerialDecodeThread {
+setDecodeFunc(func)
+start()
+stop()
}
class SerialSendWorker {
+setTransport(transport)
+setBuildRequestFunc(func)
+openTransport(config)
+closeTransport()
+requestOnce()
}
AppBackend --> SerialBackend
AppBackend --> DataBackend
SerialBackend --> SerialConfig
SerialBackend --> SensorRequest
SerialBackend --> SensorSpec
SerialBackend --> SerialManager
SerialManager --> ISerialFormat
SerialManager --> ISerialCodec
SerialManager --> ISerialDecoder
ISerialFormat <|.. PiezoresistiveAFormat
ISerialCodec <|.. PiezoresistiveACodec
ISerialDecoder <|.. PiezoresistiveADecoder
ISerialTransport <|.. QtSerialTransport
SerialBackend --> DataFrame
DataBackend --> DataFrame
SerialBackend --> PacketQueue
SerialBackend --> FrameQueue
SerialBackend --> SerialReadThread
SerialBackend --> SerialDecodeThread
SerialBackend --> SerialSendWorker
SerialSendWorker --> ISerialTransport
SerialReadThread --> PacketQueue
SerialDecodeThread --> PacketQueue
SerialDecodeThread --> FrameQueue
说明:线程与队列已实现,队列溢出策略通过 TODO 注释预留后续扩展。
数据流/线程流程 (Mermaid)
flowchart LR
UI[QML 按钮/配置] -->|open/close| SB[SerialBackend]
SB -->|openTransport| SW[SerialSendWorker]
SW -->|open| TP[QtSerialTransport]
SW -->|slave 模式轮询| REQ[buildRequest]
REQ -->|writeBytes| TP
TP -->|bytesReceived| RT[SerialReadThread]
RT -->|tryParse| PKT[PacketQueue]
PKT --> DT[SerialDecodeThread]
DT -->|decodeFrame| FR[FrameQueue]
FR --> SB
SB -->|drainFrames| DB[DataBackend]
DB -->|liveCallback| GL[GLWidget]
DB -->|TODO| RP[RightPanel 可视化]
时序/函数调用 (Mermaid)
sequenceDiagram
participant UI as QML
participant SB as SerialBackend
participant SW as SerialSendWorker
participant TP as ISerialTransport
participant RT as SerialReadThread
participant DT as SerialDecodeThread
participant DB as DataBackend
participant GL as GLWidget
UI->>SB: open()
SB->>SW: openTransport(config)
SW->>TP: open(config)
Note over SW: slave 模式启动轮询定时器
SW->>SW: buildRequest()
SW->>TP: writeBytes(request)
TP-->>SW: bytesReceived(data)
SW-->>RT: enqueueBytes(data)
RT->>RT: tryParse(buffer)
RT-->>DT: PacketQueue.push(packet)
DT->>DT: decodeFrame(packet)
DT-->>SB: frameAvailable()
SB->>DB: ingestFrame(frame)
DB-->>GL: liveRenderCallback(frame)
配置流程 (Mermaid)
flowchart TD
UI[QML 设置参数] -->|setPortName/ setBaudRate/ setDeviceAddress/ setMode/ setPollIntervalMs| SB[SerialBackend]
UI -->|setRequestFunction/ setRequestStartAddress/ setRequestLength| SB
UI -->|setProtocol| SB
SB -->|syncSendConfig_| SW[SerialSendWorker.setConfig]
SB -->|syncSendRequest_| SW2[SerialSendWorker.setRequest]
SB -->|updateProtocolBindings_| RT[SerialReadThread.setParseFunc]
SB -->|updateProtocolBindings_| DT[SerialDecodeThread.setDecodeFunc]
SB -->|updateProtocolBindings_| SW3[SerialSendWorker.setBuildRequestFunc]
配置接口与步骤说明
- 设置协议:
SerialBackend::setProtocol(name):切换协议后调用updateProtocolBindings_(),更新ParseFunc/DecodeFunc/BuildRequestFunc。
- 设置串口配置(config):
SerialBackend::setPortName(name)SerialBackend::setBaudRate(rate)SerialBackend::setDeviceAddress(addr)(0-255)SerialBackend::setMode("master"/"slave")SerialBackend::setPollIntervalMs(intervalMs)(从站模式轮询周期)- 上述接口内部统一调用
syncSendConfig_(),将SerialConfig下发到SerialSendWorker::setConfig。
- 设置请求参数(request):
SerialBackend::setRequestFunction(func)SerialBackend::setRequestStartAddress(addr)SerialBackend::setRequestLength(len)- 上述接口内部调用
syncSendRequest_(),将SensorRequest下发到SerialSendWorker::setRequest。
- 设置解码/解析器:
SerialManager::registerProtocol(name, {codec, decoder, format})SerialBackend::setProtocol(name)触发updateProtocolBindings_():SerialReadThread::setParseFunc(format->tryParse)SerialDecodeThread::setDecodeFunc(decoder->decodeFrame)SerialSendWorker::setBuildRequestFunc(codec->buildRequest)
- 打开串口:
SerialBackend::open()->SerialSendWorker::openTransport(config),成功后在从站模式启动轮询发送。
分层设计概述
- AppBackend:统一后端入口,驱动串口层与数据层,供 QML 直接绑定。
- 串口采集层:
Transport + Format + Codec + Decoder + Manager分层,独立于业务逻辑。 - 串口线程化:读取/解码/发送三线程 + Packet/Frame 队列,降低 UI 卡顿风险。
- 数据驱动层:负责帧缓存、数据导入导出与回放,提供渲染回调。
- UI 层:
NavBar + LeftPanel + OpenGL View + RightPanel的 1+3 布局。
类接口与成员说明(C++)
AppBackend (src/backend.h)
- 作用:统一后端入口,串联
SerialBackend与DataBackend。 - 属性/接口:
lightMode/language/connected:UI 基础状态。serial()/data():暴露子系统实例给 QML。setLightMode(bool)/setLanguage(string)。
- 成员变量:
m_serial:串口采集层对象。m_data:数据驱动层对象。m_lightMode/m_language:全局 UI 状态。
- 备注:串口连接成功后会清空历史数据缓存,避免旧数据残留。
SerialBackend (src/serial/serial_backend.h)
- 作用:串口采集层的统一控制器,负责协议选择、三线程调度与数据分发。
- 属性/接口:
portName/baudRate/deviceAddress/mode/pollIntervalMs:串口与模式配置。requestFunction/requestStartAddress/requestLength:请求参数。protocol/sensorModel/sensorGrid:协议与传感器规格占位。open()/close()/requestOnce()/feedBytes(bytes)。setTransport(transport):注入真实串口传输层。requestBuilt(bytes):输出原始请求帧,便于调试。
- 成员变量:
m_config:串口参数配置。m_request:请求参数。m_spec:传感器规格占位。m_manager:协议注册与切换。m_packetQueue/m_frameQueue:包/帧队列。m_readThread/m_decodeThread:读取与解码线程。m_sendWorker/m_sendThread:发送线程与 worker。m_frameCallback:解码后数据回调。
- 备注:通过
ParseFunc/DecodeFunc/BuildRequestFunc绑定协议实现,模拟 “find_decodec 后回调赋值”。
PacketQueue / FrameQueue (src/serial/serial_queue.h)
- 作用:线程安全的阻塞队列,用于 Packet/Frame 的跨线程传递。
- 接口:
push(item)/pop(item)/tryPop(item)/clear()/stop()/reset()setMaxSize(size):配置队列容量。
- 备注:队列溢出策略已通过 TODO 注释预留,后续可扩展为丢弃最新/阻塞等待。
SerialReadThread (src/serial/serial_threads.h)
- 作用:读取线程,接收原始字节流并根据
ParseFunc解析为 packet。 - 接口:
enqueueBytes(bytes):注入串口字节流。setParseFunc(func):绑定协议解析函数。start()/stop():线程控制。
SerialDecodeThread (src/serial/serial_threads.h)
- 作用:解码线程,从
PacketQueue解码 packet 并写入FrameQueue。 - 接口:
setDecodeFunc(func):绑定协议解码函数。start()/stop():线程控制。
SerialSendWorker (src/serial/serial_threads.h)
- 作用:发送线程 worker,负责请求编码与发送,并在从站模式下轮询。
- 接口:
setTransport(transport):注入传输层实例。setBuildRequestFunc(func):绑定请求编码函数。openTransport(config)/closeTransport():串口打开/关闭。requestOnce():触发一次请求发送。
SerialManager (src/serial/serial_manager.h)
- 作用:协议包管理器,注册
codec/decoder/format组合。 - 接口:
registerProtocol(name, bundle):注册协议。setActiveProtocol(name):切换协议。activeBundle():获取当前协议绑定。
- 成员变量:
m_protocols:协议字典。m_activeName:当前协议名。
ISerialTransport (src/serial/serial_transport.h)
- 作用:串口传输抽象层,屏蔽不同平台差异。
- 接口:
open(config, error)/close()/writeBytes(data, error)。
- 备注:实际接收数据通过
bytesReceived信号上报。 - 实现:
QtSerialTransport:基于QSerialPort的默认传输实现。- TODO:待实现内容(在传输层支持软/硬件流控配置)
ISerialFormat / ISerialCodec / ISerialDecoder
- 作用:协议拆分层,类比 FFmpeg 的
format/codec/decoder。 - ISerialFormat 接口:
tryParse(buffer, packet, error):从字节流中提取完整包。
- ISerialCodec 接口:
buildRequest(config, request):生成请求帧。buildGetVersionRequest(config):// TODO:待实现内容(构建获取版本号的请求帧)buildGetSpecRequest(config):// TODO:待实现内容(构建获取传感器规格的请求帧)
- ISerialDecoder 接口:
decodeFrame(packet, frame):解码回复帧。decodeSpec(packet, spec):// TODO:待实现内容(解析规格回复帧并填充 SensorSpec)
Piezoresistive A 协议 (src/serial/piezoresistive_a_protocol.*)
PiezoresistiveAFormat:包解析与 CRC-8/ITU 校验。PiezoresistiveACodec:构建请求帧。PiezoresistiveADecoder:解析回复帧为DataFrame。- 备注:数据以小端
uint16解析为 float,若协议变更可在此调整。
DataBackend (src/data_backend.h)
- 作用:数据驱动层,负责帧缓存、导入导出与回放。
- 接口:
ingestFrame(frame):实时采集数据入口。clear():清空缓存。exportJson(path)/exportCsv(path)。importJson(path)/importCsv(path)。startPlayback(intervalMs)/stopPlayback()。setLiveRenderCallback(cb)/setPlaybackRenderCallback(cb):渲染回调占位。
- 成员变量:
m_frames:帧数据容器。m_playbackTimer/m_playbackIndex:回放控制。
GLWidget (src/glwidget.h)
- 作用:OpenGL 渲染窗口,显示传感器点阵与背景。
- 信号:
dotClicked(index, row, col, value):鼠标点击某个点时发出索引与数据值。
- 备注:拾取使用屏幕投影 + 半径阈值,便于 RightPanel 订阅点击事件做曲线展示。
DataFrame (src/data_frame.h)
- 字段:
pts:yyyyMMddhhmmsszzz时间戳。functionCode:功能码。data:传感器数据。
SerialConfig / SensorRequest / SensorSpec (src/serial/serial_types.h)
SerialConfig:串口基础参数配置。SensorRequest:请求参数(功能码、起始地址、读取长度)。SensorSpec:传感器规格占位(型号/网格/量程等)。
协议结构说明(压阻 A 型)
- Request 起始符:
0x55AA(小端 ->AA 55)。 - Reply 起始符:
0x55AA(小端 ->AA 55)。 - 数据长度:从
data[4]到 payload 末尾(不含 CRC)。 - CRC:CRC-8/ITU(多项式 0x07,初始值 0x00)。
QML 层级与组件职责
总体布局(qml/content/App.qml)
- 顶部
NavBar:标题、连接状态、明暗切换、语言选择。 - 中部三栏:
- 左侧
LeftPanel:串口连接、采样参数、规格与显示控制。 - 中间 OpenGL 视图:由 C++
GLWidget挂载显示 3D 传感器数据,并提供点点击信号。 - 右侧
RightPanel:折线趋势、指标卡片、会话信息。
- 左侧
NavBar(qml/content/NavBar.qml)
- Title:软件名称显示。
- 连接指示:绿/红状态灯 + 文本。
Switch:Light/Dark 切换。ComboBox:语言选择。
LeftPanel(qml/content/LeftPanel.qml)
- 连接设置:
- COM 端口、波特率。
- 模式选择(主站/从站)。
- 设备地址输入(十六进制
0x01形式)。 - 采样周期(从站模式可用)。
- 采样参数:功能码、起始地址、读取长度。
- 传感器规格:协议名、型号、网格规格占位。
- 显示控制:显示网络/坐标轴、回放与导出入口。
RightPanel(qml/content/RightPanel.qml)
- Live Trend:折线趋势图示例。
- Metrics:峰值、RMS、均值、Delta 等指标卡片。
- Session:帧数、回放状态。
- LiveTrendCard:封装
SparklinePlot(C++ QQuickItem)用于趋势绘制,数据接入留有 TODO。
预留扩展(实现建议)
- 串口线程化已落地:
SerialReadThread读取字节流 ->PacketQueue。SerialDecodeThread解包/解码 ->FrameQueue。SerialSendWorker从站模式下定时发送请求。
- 队列溢出策略:
SerialQueue中已预留 TODO 注释,可扩展为阻塞等待或丢弃最新。 - 回调扩展:
DataBackend的渲染回调用于自定义 OpenGL/曲线刷新逻辑。
- 可视化接入:
main.cpp与qml/content/RightPanel.qml已标注 // TODO:待实现内容(将实时帧推送到右侧曲线/指标)。
- 协议扩展:
buildGetVersionRequest/buildGetSpecRequest/decodeSpec需实现,已保留 // TODO:待实现内容。
数据导出进度
- 已完成
DataBackend::exportJson(path)/exportCsv(path)已实现导出。DataBackend::importJson(path)/importCsv(path)已实现导入。qml/content/SaveAsExportDialog.qml提供路径、文件名、格式、方式选择,并通过saveTo(...)抛出导出参数。- 导出对话框以独立
Window方式呈现(ApplicationModal),避免在QQuickWidget内被裁剪。
- 待开发
- 将
saveTo与DataBackend导出接口打通,并补充失败提示。 - 覆盖/追加/压缩(zip)策略与文件存在检测流程。
xlsx导出实现与导出图标资源补齐(TODO)。
- 将
TODO 汇总
- 代码/界面
main.cpp:106将 frame 数据分发给右侧曲线/指标的 QML 接口。qml/content/RightPanel.qml:40用 DataBackend 输出的帧更新折线图/指标。src/serial/serial_queue.h:28指定队列溢出策略(丢弃最新/丢弃最旧/阻塞等待)。src/serial/serial_qt_transport.cpp:72根据 SerialConfig 扩展软件/硬件流控配置。src/serial/serial_codec.h:17构建获取版本号的请求帧。src/serial/serial_codec.h:19构建获取传感器规格的请求帧。src/serial/serial_decoder.h:18解析规格回复帧并填充 SensorSpec。src/serial/piezoresistive_a_protocol.cpp:126压阻 A 型版本号查询请求帧。src/serial/piezoresistive_a_protocol.cpp:132压阻 A 型规格查询请求帧。src/serial/piezoresistive_a_protocol.cpp:222解析压阻 A 型规格回复并写入 SensorSpec。
- 文档/流程图标注
docs/ARCHITECTURE.md:187队列溢出策略 TODO 预留说明。docs/ARCHITECTURE.md:205数据流图 RightPanel 可视化 TODO。docs/ARCHITECTURE.md:323队列溢出策略 TODO 备注。docs/ARCHITECTURE.md:363传输层软/硬件流控配置 TODO。docs/ARCHITECTURE.md:371buildGetVersionRequest TODO。docs/ARCHITECTURE.md:372buildGetSpecRequest TODO。docs/ARCHITECTURE.md:375decodeSpec TODO。docs/ARCHITECTURE.md:449LiveTrendCard 数据接入 TODO。docs/ARCHITECTURE.md:457SerialQueue 溢出策略 TODO。docs/ARCHITECTURE.md:461main.cpp/RightPanel.qml 实时帧推送 TODO。docs/ARCHITECTURE.md:463buildGetVersion/buildGetSpec/decodeSpec TODO。docs/ARCHITECTURE.md:520更新记录提及可视化 TODO。docs/ARCHITECTURE.md:522更新记录提及队列溢出 TODO。
- 测试/第三方
test/onlygl/stb_image.h:1276move stbi__convert_format to here.test/onlygl/stb_image.h:1302move stbi__convert_format16 to here.test/onlygl/stb_image.h:1303special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision.test/onlygl/stb_image.h:5898tga_x_origin @TODO。test/onlygl/stb_image.h:5899tga_y_origin @TODO。
- 其他/说明
Prompt.md:60可视化部分流出 TODO。Prompt.md:72接口预留位置标注 TODO。Prompt.md:73文档和 TODO 说明。serial/doc/Doxyfile:603Doxygen TODO 列表说明。serial/doc/Doxyfile:607GENERATE_TODOLIST 开关。
更新记录
- 2026-01-11:新增
QtSerialTransport(QSerialPort传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。 - 2026-01-11:补充串口配置流程图与配置接口说明(协议/参数/解码器绑定/打开流程)。
- 2026-01-05:新增串口三线程流水线(读/解码/发送)与 Packet/Frame 队列,更新协议起始符说明,补充队列溢出 TODO 与线程组件文档。
- 2026-01-05:CollapsiblePanel 组件改为跟随
backend.lightMode切换暗色主题配色。 - 2026-01-05:
QSplitter句柄配色跟随明暗模式并缩窄,消除 darkmode 下的白色缝隙。 - 2026-01-05:
QQuickWidgetclearColor 跟随明暗模式,避免面板圆角处漏出白底。 - 2026-01-05:
QQuickWidget在setSource后重设backend上下文属性,修复 QML 访问空指针报错。 - 2026-01-05:
QQuickWidget改为通过engine()->rootContext()注入backend,避免上下文被重建导致为空。 - 2026-01-05:改为每个
QQuickWidget引擎注入Backend上下文属性,避免多引擎使用单例导致的报错。 - 2026-01-05:调整
SerialSendWorker生命周期清理方式,避免跨线程 moveToThread 警告。 - 2026-01-06:多个
QQuickWidget共享同一个QQmlEngine,统一注入Backend/backend上下文属性以稳定访问。 - 2026-01-06:统一 QML 使用
backend上下文属性名称,避免大写标识被当成类型解析导致取值为空。 - 2026-01-06:为每个
QQuickWidget的rootContext()重复绑定backend,避免上下文被重建时丢失。 - 2026-01-06:
QQuickWidget改用QQmlComponent + QQmlContext创建根对象并setContent,确保backend上下文稳定注入。 - 2026-01-07:
GLWidget增加点阵点击拾取信号dotClicked,文档补充 OpenGL 交互说明。