Files
tactileipc3d/docs/ARCHITECTURE.md

21 KiB
Raw Blame History

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)

  • 作用:统一后端入口,串联 SerialBackendDataBackend
  • 属性/接口:
    • lightMode / language / connectedUI 基础状态。
    • 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)

  • 字段:
    • ptsyyyyMMddhhmmsszzz 时间戳。
    • 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
  • CRCCRC-8/ITU多项式 0x07初始值 0x00

QML 层级与组件职责

总体布局(qml/content/App.qml

  • 顶部 NavBar:标题、连接状态、明暗切换、语言选择。
  • 中部三栏:
    • 左侧 LeftPanel:串口连接、采样参数、规格与显示控制。
    • 中间 OpenGL 视图:由 C++ GLWidget 挂载显示 3D 传感器数据,并提供点点击信号。
    • 右侧 RightPanel:折线趋势、指标卡片、会话信息。

NavBarqml/content/NavBar.qml

  • Title软件名称显示。
  • 连接指示:绿/红状态灯 + 文本。
  • SwitchLight/Dark 切换。
  • ComboBox:语言选择。

LeftPanelqml/content/LeftPanel.qml

  • 连接设置:
    • COM 端口、波特率。
    • 模式选择(主站/从站)。
    • 设备地址输入(十六进制 0x01 形式)。
    • 采样周期(从站模式可用)。
  • 采样参数:功能码、起始地址、读取长度。
  • 传感器规格:协议名、型号、网格规格占位。
  • 显示控制:显示网络/坐标轴、回放与导出入口。

RightPanelqml/content/RightPanel.qml

  • Live Trend折线趋势图示例。
  • Metrics峰值、RMS、均值、Delta 等指标卡片。
  • Session帧数、回放状态。
  • LiveTrendCard封装 SparklinePlotC++ QQuickItem用于趋势绘制数据接入留有 TODO。

预留扩展(实现建议)

  • 串口线程化已落地:
    • SerialReadThread 读取字节流 -> PacketQueue
    • SerialDecodeThread 解包/解码 -> FrameQueue
    • SerialSendWorker 从站模式下定时发送请求。
  • 队列溢出策略:SerialQueue 中已预留 TODO 注释,可扩展为阻塞等待或丢弃最新。
  • 回调扩展:
    • DataBackend 的渲染回调用于自定义 OpenGL/曲线刷新逻辑。
  • 可视化接入:
    • main.cppqml/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 内被裁剪。
  • 待开发
    • saveToDataBackend 导出接口打通,并补充失败提示。
    • 覆盖/追加/压缩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:371 buildGetVersionRequest TODO。
    • docs/ARCHITECTURE.md:372 buildGetSpecRequest TODO。
    • docs/ARCHITECTURE.md:375 decodeSpec TODO。
    • docs/ARCHITECTURE.md:449 LiveTrendCard 数据接入 TODO。
    • docs/ARCHITECTURE.md:457 SerialQueue 溢出策略 TODO。
    • docs/ARCHITECTURE.md:461 main.cpp/RightPanel.qml 实时帧推送 TODO。
    • docs/ARCHITECTURE.md:463 buildGetVersion/buildGetSpec/decodeSpec TODO。
    • docs/ARCHITECTURE.md:520 更新记录提及可视化 TODO。
    • docs/ARCHITECTURE.md:522 更新记录提及队列溢出 TODO。
  • 测试/第三方
    • test/onlygl/stb_image.h:1276 move stbi__convert_format to here.
    • test/onlygl/stb_image.h:1302 move stbi__convert_format16 to here.
    • test/onlygl/stb_image.h:1303 special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision.
    • test/onlygl/stb_image.h:5898 tga_x_origin @TODO。
    • test/onlygl/stb_image.h:5899 tga_y_origin @TODO。
  • 其他/说明
    • Prompt.md:60 可视化部分流出 TODO。
    • Prompt.md:72 接口预留位置标注 TODO。
    • Prompt.md:73 文档和 TODO 说明。
    • serial/doc/Doxyfile:603 Doxygen TODO 列表说明。
    • serial/doc/Doxyfile:607 GENERATE_TODOLIST 开关。

更新记录

  • 2026-01-11新增 QtSerialTransportQSerialPort 传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。
  • 2026-01-11补充串口配置流程图与配置接口说明协议/参数/解码器绑定/打开流程)。
  • 2026-01-05新增串口三线程流水线读/解码/发送)与 Packet/Frame 队列,更新协议起始符说明,补充队列溢出 TODO 与线程组件文档。
  • 2026-01-05CollapsiblePanel 组件改为跟随 backend.lightMode 切换暗色主题配色。
  • 2026-01-05QSplitter 句柄配色跟随明暗模式并缩窄,消除 darkmode 下的白色缝隙。
  • 2026-01-05QQuickWidget clearColor 跟随明暗模式,避免面板圆角处漏出白底。
  • 2026-01-05QQuickWidgetsetSource 后重设 backend 上下文属性,修复 QML 访问空指针报错。
  • 2026-01-05QQuickWidget 改为通过 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为每个 QQuickWidgetrootContext() 重复绑定 backend,避免上下文被重建时丢失。
  • 2026-01-06QQuickWidget 改用 QQmlComponent + QQmlContext 创建根对象并 setContent,确保 backend 上下文稳定注入。
  • 2026-01-07GLWidget 增加点阵点击拾取信号 dotClicked,文档补充 OpenGL 交互说明。