From b8dbaa619dddf00a2021dc9dd6dd0eb7bab3b119 Mon Sep 17 00:00:00 2001 From: zhoujie <929834232@qq.com> Date: Sat, 14 Mar 2026 20:54:11 +0800 Subject: [PATCH] 18 --- doc/下位机协议实现完备性分析.md | 120 +++ doc/采集端通信协议规范.md | 1664 +++++++++++++++++++++++++++++++ prj/TCPClient/User/main.c | 111 +++ 3 files changed, 1895 insertions(+) create mode 100644 doc/下位机协议实现完备性分析.md create mode 100644 doc/采集端通信协议规范.md diff --git a/doc/下位机协议实现完备性分析.md b/doc/下位机协议实现完备性分析.md new file mode 100644 index 0000000..b454419 --- /dev/null +++ b/doc/下位机协议实现完备性分析.md @@ -0,0 +1,120 @@ +# 下位机协议实现完备性分析 + +> **分析日期**: 2026-03-14 +> **对照文档**: 采集端通信协议规范 v2.1.3 (2026-02-07) +> **分析对象**: prj/TCPClient 工程全部源码 + +## 前提说明 + +协议规范描述了 ZMQ PUB-SUB + JSON 架构,但实际 MCU 与配置端服务器之间已约定采用**自定义二进制 TLV 帧格式**(`0x55AA` Magic + CRC16-MODBUS)通过双 TCP 通道(5511 控制 / 5512 数据)通信。以下审计基于 TLV 协议层面进行。 + +--- + +## 一、接收端(服务器 → 下位机,控制通道 5511) + +| # | 功能 | 状态 | 说明 | +|---|------|:----:|------| +| 1 | ConfigCommon 全量配置 | ✅ 已实现 | `TYPE_CONFIG_COMMON(0x20)` 完整解析 23 字节 | +| 2 | Config2D 全量配置 | ✅ 已实现 | `TYPE_CONFIG_2D(0x22)` 完整解析 61 字节全部字段 | +| 3 | Config1D 全量配置 | ✅ 已实现 | `TYPE_CONFIG_1D(0x23)` 完整解析 26 字节 | +| 4 | 设备 ID 分配 | ✅ 已实现 | `TYPE_DEVID_ASSIGN(0x05)` 解析后断线重连 | +| 5 | 判定结果接收 | ✅ 已实现 | `TYPE_DETECTION_RESULT(0x40)` → `detect_cb` 回调 NG/OK | +| 6 | 帧请求(截图/采集) | ✅ 已实现 | `TYPE_TEMP_FRAME(0x10)` 请求 → `temp_req_cb` 回调 | +| 7 | ACK 响应接收 | ✅ 已实现 | `TYPE_ACK_PAYLOAD(0x30)` 在 TLV 解析循环内可识别 | +| 8 | 部分参数更新 | ❌ 未实现 | 规范 §4.2.2 定义 `UpdateParameters` 键值点更新,无对应 TLV 类型 | +| 9 | 控制命令组 | ❌ 未实现 | Start/Stop/Pause/Resume/GetStatus/GetConfiguration/SwitchPipelineMode/ResetStats 共 8 个控制命令均无解析 | +| 10 | 文件传输 | ❌ 未实现 | 规范 §3.3.1 `{deviceId}/file` 无对应功能 | +| 11 | 时间同步 | ⚠️ 仅定义 | `TYPE_SYNC_TIME(0x03)` 已定义常量但未在 `parse_and_dispatch_tlv` 中处理 | + +--- + +## 二、发送端(下位机 → 服务器,数据通道 5512) + +| # | 功能 | 状态 | 说明 | +|---|------|:----:|------| +| 1 | 握手 | ✅ 已实现 | `TYPE_HANDSHAKE(0x01)` 连接后立即发送(UUID + 硬件/固件版本) | +| 2 | 心跳 | ✅ 已实现 | `TYPE_HEARTBEAT(0x02)` 每 2s 发送 UpTime + CpuLoad + MemUsage | +| 3 | ACK 应答 | ✅ 已实现 | `TYPE_ACK_PAYLOAD(0x30)` 响应带 `FLAG_ACK_REQ` 的帧 | +| 4 | 温度帧(2D 触发帧) | ✅ 已实现 | `TYPE_TEMP_FRAME(0x10)` 零拷贝发送 + 自动分片 | +| 5 | 原始帧上报 | ❌ 未实现 | `TYPE_RAW_FRAME(0x11)` 仅定义常量,无发送逻辑 | +| 6 | 设备注册 | ❌ 未实现 | 规范 §8.1 `DeviceRegisterEvent` 注册事件未发送 | +| 7 | 状态上报 | ❌ 未实现 | 规范 §5.3.5 定期 `AcquisitionStatusEvent` 未实现 | +| 8 | 命令处理回执 | ❌ 未实现 | 规范 §5.3.3 `ChangedEvent` 回执未实现 | +| 9 | Masked 帧 | ❌ 未实现 | 不区分 Triggered/Masked/Live 帧类型发送 | + +--- + +## 三、核心处理逻辑 + +| # | 功能 | 状态 | 说明 | +|---|------|:----:|------| +| 1 | 2D 滑窗 ROI 提取 | ✅ 已实现 | 积分图 + 滑窗搜索最大平均温度区域,提取 TargetWidth × TargetHeight | +| 2 | 内部温度触发 | ✅ 已实现 | TriggerRoi 区域 Max/Avg vs TriggerTemperatureThreshold | +| 3 | Burst 连拍 | ✅ 已实现 | BurstCount + InternalIntervalMs 状态机 | +| 4 | NG GPIO 输出 | ✅ 已实现 | PA8 脉冲,宽度从 NGioDelay 配置读取 | +| 5 | 帧分片 | ✅ 已实现 | 超 1400B 自动分片,最后一片设置 FLAG_LAST_FRAGMENT | +| 6 | CRC16 校验 | ✅ 已实现 | 收发双向 CRC16-MODBUS 校验 | +| 7 | 自动重连 | ✅ 已实现 | 检测断线后 3s 重连双通道 | +| 8 | 阈值蒙版输出 | ⚠️ 部分 | 滑窗搜索时低温 → 90 替换,但规范要求**输出数据**中低于 MaskThreshold → 替换为 0 | +| 9 | 触发延迟 (DelayMs) | ❌ 未实现 | 字段已解析但触发后无延迟等待逻辑 | +| 10 | 外部 GPIO 触发 | ❌ 未实现 | 无 EXTI 中断配置、消抖(DebounceIntervalMs)、触发逻辑 | +| 11 | Alarm GPIO | ❌ 未实现 | AlarmGpioLine/AlarmHoldMs 字段已解析但无 GPIO 驱动 | +| 12 | 1D 数据采集处理 | ❌ 未实现 | Config1D 可接收但无 1D 环形缓冲区/切片/触发逻辑 | +| 13 | Training 模式 | ❌ 未实现 | 字段可解析但无训练采样功能 | +| 14 | 消抖 (DebounceIntervalMs) | ❌ 未实现 | 依赖外部触发,一并缺失 | + +--- + +## 四、汇总统计 + +| 类别 | ✅ 已实现 | ⚠️ 部分 | ❌ 未实现 | +|------|:---------:|:-------:|:---------:| +| 接收(控制通道) | 7 | 1 | 3 | +| 发送(数据通道) | 4 | 0 | 5 | +| 处理逻辑 | 7 | 1 | 6 | +| **合计** | **18** | **2** | **14** | + +--- + +## 五、优先级建议 + +### P0 — 必须补齐(影响基本功能验证) + +1. **控制命令接收** — 至少实现 `StartAcquisition` / `StopAcquisition` / `GetStatus`,否则服务端无法控制采集启停 +2. **状态上报** — 周期性上报运行状态给服务端,否则服务端无法监控设备健康 +3. **触发延迟 (DelayMs)** — 简单改动,在触发检测到后 delay 再开始采集 +4. **阈值蒙版输出** — 提取后的数据中 `val < MaskThreshold` → 替换为 0 + +### P1 — 高优先级(完善核心流程) + +5. **外部 GPIO 触发** — 实际产线通常用外部触发信号 +6. **Alarm GPIO** — 独立于 NG GPIO 的报警输出 +7. **命令处理回执 (ChangedEvent)** — 服务端需要确认命令被执行 +8. **设备注册事件** — 完整的上线握手流程 + +### P2 — 可延后 + +9. 1D 数据处理(如果当前只做 2D 热成像) +10. Training 模式 +11. 文件传输 +12. 原始帧/Masked 帧分类发送 +13. 时间同步 +14. 部分参数更新 + +--- + +## 六、涉及源文件清单 + +| 文件 | 职责 | +|------|------| +| `Middle/QDXnetworkStack/qdx_protocol.h` | 协议常量、TLV 类型定义、结构体 | +| `Middle/QDXnetworkStack/qdx_protocol.c` | CRC16、帧构建、序列化工具 | +| `Middle/QDXnetworkStack/qdx_tcp_logic.h` | TCP 逻辑层 API 声明 | +| `Middle/QDXnetworkStack/qdx_tcp_logic.c` | 双通道连接管理、TLV 解析/分发、心跳、帧发送 | +| `Middle/QDXnetworkStack/qdx_preprocess.h` | 预处理 API 声明 | +| `Middle/QDXnetworkStack/qdx_preprocess.c` | 滑窗 ROI、温度过滤、内部触发检测 | +| `Middle/QDXnetworkStack/qdx_port.h` | HAL 抽象层声明 | +| `Middle/QDXnetworkStack/qdx_port.c` | FreeRTOS + WCHNET HAL 实现 | +| `User/main.c` | 应用主逻辑:RTOS 任务、Burst 状态机、NG GPIO、测试模式 | +| `Debug/dvp.c` | DVP 图像采集驱动 | +| `Debug/mini212g2.c` | Mini212G2 传感器 UART 驱动 | diff --git a/doc/采集端通信协议规范.md b/doc/采集端通信协议规范.md new file mode 100644 index 0000000..5c9f6ce --- /dev/null +++ b/doc/采集端通信协议规范.md @@ -0,0 +1,1664 @@ +# Page 1 + +采集端通信协议规范 +采集端通信协议规范 +版本: 2.1.3 +日期: 2026-02-07 +基于: Intro.md (PUB-SUB 模式), ZMQ 协议帧设计规范.md (v1.5.0) +1. 协议概述 +1.1 设计背景 +随着系统架构演进,新的下位硬件将承担相机采集和预处理功能,替代原有PipelineV1 中的采 +集和预处理模块。本协议定义了ConfigServer 与采集端之间的通信规范,确保系统平滑迁移。 +版本2.1.0 变更: +• +修改了温度数据上传格式 +• +增加了一维数据的标识方法 +• +将float 格式统一为int 格式 +版本2.1.2 变更: +• +增加了发送配置的定义 +版本2.1.3 变更: +• +增加了描述插图 +• +修改了部分表述 +1.2 设计原则 +简单性: 采用PUB-SUB 单向通信模式,降低实现复杂度 +高效性: 针对温度矩阵传输优化,支持实时数据流 +可扩展性: 支持参数动态配置和功能扩展 +1. +2. +3. + + +# Page 2 + +可靠性: 完善的错误处理和状态监控机制 +兼容性: 完全兼容Intro.md 中的下位机协议 +1.3 协议适用范围 +• +ConfigServer 向采集端下发配置参数 +• +采集端向ConfigServer 返回预处理数据 +• +采集端状态监控与健康检查 +• +实时图像流传输 +• +多设备管理(基于设备ID) +2. 系统架构与数据流 +2.1 系统架构 +4. +5. + + +# Page 3 + +2.2 数据流说明 +2.2.1 命令下发流 (ConfigServer → 采集端) +• +配置参数更新: 通过主题 + 下发 +• +控制命令: 通过主题 + 下发 +• +文件传输: 通过主题 + 下发 +• +设备注册响应: 通过主题 + 下发 +2.2.2 数据返回流 (采集端 → ConfigServer) +• +设备注册: 通过主题 + 上报 +• +命令结果: 通过主题 + 上报 +• +状态信息: 通过主题 + 上报 +• +处理完成回执: 通过主题 + 上报 +• +帧数据: 通过主题 + 上报 +graph TD + A[上位机/UI] --> B[ConfigServer] + B --> C[采集端硬件] + C --> D[相机采集] + D --> E[预处理模块] + E --> F[温度矩阵输出] + F --> B + B --> G[Pipeline预测器] + + subgraph "PUB-SUB通信" + H[ConfigServer PUB] --> I[发布命令/配置] + J[ConfigServer SUB] --> K[接收数据/状态] + L[采集端 SUB] --> M[接收命令/配置] + N[采集端 PUB] --> O[发布数据/状态] + end + + style C fill:#e1f5e1 + style G fill:#fff3e0 + style H fill:#e3f2fd + style J fill:#f3e5f5 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +{deviceId}/setting +{deviceId}/command +{deviceId}/file +{deviceId}/ready +register +result +status +changed +acquisition.* +Plain Text + + +# Page 4 + +2.2.3 数据转发流 (ConfigServer → Pipeline 预测器) +• +预处理后的温度矩阵 +• +触发事件信息 +2.3 端口配置 +端口说明: +• +5511 端口: ConfigServer 作为PUB 发布者,采集端作为SUB 订阅者 +• +5512 端口: ConfigServer 作为SUB 订阅者,采集端作为PUB 发布者 +3. PUB-SUB 协议设计 +3.1 帧结构 +采用标准的PUB-SUB 帧结构,基于ZMTP 3.0 协议: +方向 +ConfigServer +端口 +采集端端口 +用途 +主题示例 +下发命令 +5511 (PUB) +5512 (SUB) +命令下发 +device001/se +tting +接收数据 +5511 (SUB) +5512 (PUB) +数据上报 +, +result +register + + +# Page 5 + +3.2 ZMTP 握手流程 +GREETING 格式(64 字节): +┌────────────────────────────────────────────────────────────────────── +───┐ +│ ZMQ Multipart Message (PUB-SUB) + │ +├──────────────┬──────────────┬──────────────────────────────────────── +───┤ +│ Frame 0 │ Frame 1 │ Frame 2 + │ +│ (Topic) │ (Data) │ (可选扩展) + │ +├──────────────┼──────────────┼──────────────────────────────────────── +───┤ +│ 主题字符串 │ 序列化数据 │ 扩展数据 │ +└──────────────┴──────────────┴──────────────────────────────────────── +───┘ +1 +2 +3 +4 +5 +6 +7 +8 +客户端(采集端) 服务端(ConfigServer) + │ │ + │ 1. GREETING (10 bytes) │ + │─────────────────────────────>│ + │ │ + │ 2. GREETING (10 bytes) + │<─────────────────────────────│ + │ │ + │ 3. READY (Command + Metadata) │ + │─────────────────────────────>│ + │ │ + │ 4. READY (Command + Metadata) + │<─────────────────────────────│ + │ │ + │ 握手完成,开始消息传输 │ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +Plain Text +Plain Text + + +# Page 6 + +• +签名(10 字节): +• +版本(2 字节): +(ZMTP 3.0) +• +机制(20 字节):"NULL" + 16 个0 +• +as_server(1 字节):0x00(客户端)或0x01(服务端) +• +填充(31 字节):0 +3.3 主题命名规范 +3.3.1 设备专属主题 +• + - 配置参数下发 +• + - 控制命令下发 +• + - 文件传输 +• + - 设备就绪信号 +• + - 成功响应 +• + - 错误响应 +3.3.2 通用主题 +• + - 设备注册 +• + - 检测结果 +• + - 状态信息 +• + - 处理完成回执 +• + - 触发帧数据 +• + - 蒙版帧数据 +• + - 采集状态 +3.3.3 主题匹配规则 +• +前缀匹配:订阅 + 将接收所有 + 开头的消息 +• +精确匹配:订阅 + 只接收 + 消息 +4. 参数下发协议 +0xFF 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x7F +{3, 0} +{deviceId}/setting +{deviceId}/command +{deviceId}/file +{deviceId}/ready +{deviceId}/response/success +{deviceId}/response/error +register +result +status +changed +acquisition.frame.triggered +acquisition.frame.masked +acquisition.status +device001/ +device001/ +register +register + + +# Page 7 + +4.1 命令帧结构 +所有命令通过PUB-SUB 模式下发: +4.2 命令定义 +4.2.1 配置更新命令 ( +主题) +用于向采集端下发完整的配置参数。根据一维/ 二维的任务需求, 传递变长参数 +当使用一维任务时,Camera 配置项的DeviceId 一定是-1, 代表不使用相机 +Topic 格式: +Payload 格式 (JSON 二维示例): +Frame 0: Topic (主题字符串,如 "device001/setting") +Frame 1: Payload (参数数据,JSON格式) +1 +2 +setting +{deviceId}/setting +Plain Text + + +# Page 8 + +{ + "RequestId": "req_config_001", + "PipelineId": "line1", + "timestamp": "2026-02-03T08:30:00Z", + "value": 10, + "config": { + "Version": 2, + "Command": "ConfigureAcquisition", + "RequestId": "req_config_001", + "PipelineId": "line1", + "Parameters": { + "Basic": { + "PipelineId": "line1", + "PipelineType": "TemperatureDetection", + "Mode": "Detect", + "ConfigTag": "stand", + "StrictnessLevel": 1, + "IsCustomMode": false + }, + "Camera": { + "DeviceId": 1, + "Width": 384, + "Height": 288, + "VideoMode": "TMP", + "Fps": 30, + "Exposure": 10000, + "AutoExposure": false, + "CaptureTimeoutMs": 100 + }, + "Mask": { + "Enabled": true, + "Threshold": 30.0, + "Width": 185, + "Height": 70, + "Angle": 0.0, + "TargetWidth": 185, + "TargetHeight": 70 + }, + "RegionOfInterest": { +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +JSON + + +# Page 9 + + "X": 0, + "Y": 0, + "Width": 160, + "Height": 40 + }, + "Trigger": { + "TriggerGpioLine": 1, + "Mode": "External", + "DelayMs": 0, + "BurstCount": 1, + "InternalIntervalMs": 100, + "TemperatureThreshold": 45.0, + "DebounceIntervalMs": 0, + "TriggerRoi": {"X":0,"Y":0,"Width":0,"Height":0}, + "TriggerCondition": "Average" + }, + "Output": { + "OutputGpioLine": 2, + "AlarmGpioLine": 3, + "AlarmHoldMs": 1000, + "AlarmDebounceMs": 200 + }, + "Storage": { + "StoreNgImagesOnly": true + }, + "Training": { + "Enabled": false, + "SampleThreshold": 20, + "MaxCachedSamples": 200, + "AutoTrigger": true + }, + "System": { + "ProcessingTimeoutMs": 100, + "MaxProcessingQueueSize": 10, + "EnableDataForwarding": true, + "CommunicationMode": "PUB_SUB" + } + } + } +} +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 + + +# Page 10 + +Payload 格式 (JSON 一维示例): + + +# Page 11 + +{ + "RequestId": "req_config_001", + "PipelineId": "line1", + "timestamp": "2026-02-03T08:30:00Z", + "value": 10, + "config": { + "Version": 2, + "Command": "ConfigureAcquisition", + "RequestId": "req_config_001", + "PipelineId": "line1", + "Parameters": { + "Basic": { + "PipelineId": "line1", + "PipelineType": "TemperatureDetection", + "Mode": "Detect", + "ConfigTag": "stand", + "StrictnessLevel": 1, + "IsCustomMode": false + }, + "Camera": { + "DeviceId": -1 + }, + "Settings": { + "mode": "RUN", + "high_timer_limit":5, + "timer_c_limit":5, + "buffer_size": 5, + "trigger_temp_limit": 5, + "left_window_range": 5, + "start_points_to_remove": 5, + "reference_length": 5, + "ng_count_limit":5, + "trigger":0 + } + } + } +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +JSON + + +# Page 12 + +4.2.2 部分参数更新命令 +通过 + 主题下发,使用简化格式: +Payload 格式: +4.2.3 控制命令 ( +主题) +通过 + 主题下发控制命令: +{deviceId}/setting +{ + "RequestId": "req_update_001", + "PipelineId": "line1", + "timestamp": "2026-02-03T08:31:00Z", + "command": "UpdateParameters", + "parameters": { + "Mask.Threshold": 35.0, + "Trigger.DelayMs": 50, + "Camera.Fps": 25 + } +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +command +{deviceId}/command +JSON + + +# Page 13 + +命令名称 +说明 +参数 +Payload 示例 +StartAcquisition +启动采集 +{"mode": +"Detect"} +{"command": +"StartAcquisition" +, "PipelineId": +"line1", "mode": +"Detect"} +StopAcquisition +停止采集 +无 +{"command": +"StopAcquisition", + "PipelineId": +"line1"} +PauseAcquisition +暂停采集 +无 +{"command": +"PauseAcquisition" +, "PipelineId": +"line1"} +ResumeAcquisitio +n +恢复采集 +无 +{"command": +"ResumeAcquisition +", "PipelineId": +"line1"} +GetStatus +获取状态 +无 +{"command": +"GetStatus", +"PipelineId": +"line1"} +GetConfiguration +获取配置 +无 +{"command": +"GetConfiguration" +, "PipelineId": +"line1"} +SwitchPipelineMod +e +切换配方模式 +{"mode": +"stand"} +{"command": +"SwitchPipelineMod +e", "PipelineId": +"line1", "mode": +"stand"} + + +# Page 14 + +4.3 参数详细说明 +一维专用参数说明 (Settings): +ResetStats +重置统计数据 +无 +{"command": +"ResetStats", +"PipelineId": +"line1"} +GetScreenshot +请求实时截图 +无 +{"command": +"GetScreenshot", +"PipelineId": +"line1"} +参数名 +类型 +说明 +mode +string +指示工作状态: +STOP/RUN/NOP +buffer_size +int +单包采集最长时间(ms) = X/10 +trigger_temp_limit +int +触发温度下限 +start_points_to_remove +int +起始移除点数,过滤触发后前 +N 点 +reference_length +int +预处理对齐长度 +high_timer_limit +int +高位滤波计时参数 +timer_c_limit +int +滤波计时上限参数 +ng_count_limit +int +连续NG 数- 停止采集触发 +lsize_start +int +向右切片起点 +rsize_start +int +向左切片起点 +trigger +int +触发方式(0- 外部,1- 内部) + + +# Page 15 + +一维触发方式说明 +外部触发 +mode 为RUN 时, 始终准备采集, 当mode 为STOP 时, 任何时候不进行采集 +DI 收到电平变化信号(低-> 高) +触发中断/ 定时滤波(high_timer_limit 代表高电平持续时间ms) +满足滤波时间开始采集 +收集温度点, 当采集到连续三个(常量)高于触发温度trigger_temp_limit 时 +认为正在采集, 将数据放入数组 +终止采集有两种方式: +1.buffer_size 被塞满, +2. 连续采集ng_count_limit 个低于trigger_temp_limit 的温度点 +切片数组, 传递[lsize_start,-rsize_start] +数组打包后进入消息发送队列 + + +# Page 16 + +内部触发 +mode 为RUN 时, 始终准备采集, 当mode 为STOP 时, 任何时候不进行采集 +采集流程始终进行, 内部维护数组状态锁, 长度为3 的循环缓冲区 +收集温度点, 当采集到连续三个(常量)高于触发温度trigger_temp_limit 时 +认为正在采集, 将循环缓冲区写入数组前三位, 从第四位起继续输入 +终止采集有两种方式: +1.buffer_size 被塞满, +2. 连续采集ng_count_limit 个低于trigger_temp_limit 的温度点 +切片数组, 传递[lsize_start,-rsize_start] +数组打包后进入消息发送队列 +二维专用参数说明: +基本参数 (Basic) +相机参数 (Camera) +参数名 +类型 +默认值 +说明 +PipelineId +string +"line1" +管线标识 +PipelineType +string +"TemperatureDe +tection" +管线类型 +Mode +string +"Detect" +运行模式: Detect/Train +ConfigTag +string +"stand" +配置标签: +loose/stand/strict/ultra/custom +StrictnessLevel +int +1 +严格等级: 0-3 +IsCustomMode +bool +false +是否为自定义模式 + + +# Page 17 + +蒙版参数 (Mask) +触发参数 (Trigger) +参数名 +类型 +默认值 +说明 +DeviceId +int +1 +相机设备ID +Width +int +384 +分辨率宽度 +Height +int +288 +分辨率高度 +VideoMode +string +"TMP" +视频模式: +TMP/Y16/YUV +Fps +int +30 +帧率 +Exposure +int +10000 +曝光时间(微秒) +AutoExposure +bool +false +自动曝光 +CaptureTimeoutMs +int +100 +采集超时时间 +参数名 +类型 +默认值 +说明 +Enabled +bool +true +是否启用蒙版 +Threshold +int +30 +蒙版阈值(°C) +Width +int +185 +蒙版宽度 +Height +int +70 +蒙版高度 +Angle +int +0 +旋转角度 +TargetWidth +int +185 +目标输出宽度 +TargetHeight +int +70 +目标输出高度 + + +# Page 18 + +ROI 参数 +参数名 +类型 +默认值 +说明 +TriggerGpioLine +int +0 +触发GPIO 线路(0 表示 +禁用) +Mode +string +"External" +触发模式: +Internal/External +DelayMs +int +0 +触发延迟(毫秒) +BurstCount +int +1 +连拍次数 +InternalIntervalMs +int +100 +内部触发间隔(毫秒) +TemperatureThresho +ld +int +45 +内部触发温度阈值(°C) +DebounceIntervalMs +int +0 +外部触发消抖间隔(毫 +秒) +TriggerRoi +Rect +0,0,0,0 +触发专用ROI 区域 +TriggerCondition +string +"Average" +触发条件: +Max/Average +参数名 +类型 +默认值 +说明 +RegionOfInterest +Rect +0,0,160,40 +算法处理边界, 非必传 + + +# Page 19 + + + +# Page 20 + +二维触发方式说明 +外部触发 +TriggerGpioLine 收到电平变化信号(低-> 高) +触发中断/ 定时滤波DebounceIntervalMs +DelayMs 后开始采集, 连续采集BurstCount 张 +对截取图像进行阈值过滤, 低于Threshold 的值, 被替换为0 +使用积分图计算,TargetWidth * TargetHeight 的最佳位置(X,Y) +返回被TargetWidth * TargetHeight 裁剪的原始图像, 写入int[][]中 +数组打包后进入消息发送队列 +内部触发 +采集流程始终进行 +计算图像中TriggerRoi 中的Max/Average, 若大于TemperatureThreshold, 则发生内部触发 +DelayMs 后开始采集, 连续采集BurstCount 张, 每次采集间隔至少InternalIntervalMs 毫秒 +对截取图像进行阈值过滤, 低于Threshold 的值, 被替换为0 +使用积分图计算,TargetWidth * TargetHeight 的最佳位置(X,Y) +返回被TargetWidth * TargetHeight 裁剪的原始图像, 写入int[][]中 +数组打包后进入消息发送队列 +5. 数据返回协议 +5.1 数据帧结构 +采集端返回数据采用PUB-SUB 模式: + + +# Page 21 + +Frame 0: Topic (数据主题) +Frame 1: Data (序列化数据,JSON 格式) +5.2 数据主题定义 +所有上报数据必须携带 + 以支持多设备区分。 +5.3 数据格式定义 +5.3.1 DeviceRegisterEvent(设备注册) +设备启动时发送的注册信息。 +字段定义: +PipelineId +主题 +说明 +数据格式 +发送频率 +register +设备注册信息 +DeviceRegisterEvent +启动时一次 +result +检测结果数据 +AcquisitionResultEve +nt +每3 秒(长帧) +result +检测状态 +AcquisitionStatusEve +nt +每3 +条长帧后(短帧) +changed +处理完成回执 +ChangedEvent +命令处理后 +status +状态信息 +StatusEvent +每5 秒 +acquisition.frame +.triggered +触发帧数据 +AcquisitionFrameEve +nt +触发时 +acquisition.frame +.masked +蒙版帧数据 +AcquisitionFrameEve +nt +每帧 +acquisition.statu +s +采集状态 +AcquisitionStatusEve +nt +每1 秒 + + +# Page 22 + +5.3.3 ChangedEvent(处理完成回执) +命令处理完成后的回执。 +字段定义: +5.3.4 AcquisitionFrameEvent(帧数据) +基于PipelineFrameEvent 的简化版本,用于传输帧数据。 +MessagePack(若采用简化帧)字段定义: +{ + "PipelineId": "逻辑管线ID", + "deviceId": "设备唯一标识符(UUID)", + "sessionRuntimeHours": 0, + "totalRuntimeHours": 0, + "version": "1.0.0", + "status": "Ready", + "capabilities": ["PUB_SUB"], + "preferredMode": "PUB_SUB" +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +{ + "PipelineId": "逻辑管线ID", + "deviceId": "设备ID", + "RequestId": "关联的请求ID", + "changed": "setting/file/command", + "status": "OK/ERROR", + "sessionRuntimeHours": 0, + "totalRuntimeHours": 0, + "errorMessage": "错误信息(如有)" +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +JSON +JSON + + +# Page 23 + +字段索引 +字段名 +类型 +说明 +必选 +0 +Version +int +协议版本(2) +✓ +1 +Timestamp +long +时间戳(毫秒) +✓ +2 +PipelineId +string +管线ID +✓ +3 +FrameNum +ber +long +帧序号 +✓ +4 +Width +int +图像宽度 +✓ +5 +Height +int +图像高度 +✓ +6 +ImageData +byte[] +图像数据(JPEG) +✗ +7 +MinTemper +ature +int +全图最低温度(°C) +✓ +8 +MaxTempe +rature +int +全图最高温度(°C) +✓ +9 +AvgTemper +ature +int +全图平均温度(°C) +✓ +10 +RoiTemper +ature +int +ROI 区域温度(°C), 用于提示当前触发区 +域温度 +✓ +11 +FrameType +string +帧类型 +✓ +12 +EventId +string +事件ID(UUID) +✓ +13 +Format +string +数据格式 +✓ +14 +RoiRegion +int[] +ROI 区域[x,y,w,h] +✗ +15 +Temperatu +reMatrix +int[][] +原始温度矩阵。格式: +。外层代表行,内层 +代表列。数据为整数(实际温度 * +10),例如 345 代表 34.5°C。 +[ [row1], +[row2], ... ] +✗ +16 +Is2D +bool +维度标志位。 +: 二维矩阵数据; +: 一维线性数据(此时高度为 +1,格式为 +)。 +true +false +[[]] +✓ + + +# Page 24 + +数据格式说明: +• +温度数值: 为了提高传输效率, + 中的数值采用整数表示。转换公式: +。 +• +矩阵结构: +◦ +二维模式 ( +): +,外层数组的每个 +元素代表图像的一行。 +◦ +一维模式 ( +): +,外层数组仅包含一个元素(即高度 +为1)。 +JSON 示例 (二维数据): +JSON 示例 (一维数据): +TemperatureMatrix +真实温度 = 整数值 / 10.0 +Is2D=true +[[r1c1, r1c2...], [r2c1, r2c2...]] +Is2D=false +[[v1, v2, v3...]] +{ + "Version": 2, + "Timestamp": 1706941200000, + "PipelineId": "line1", + "FrameNumber": 100, + "Width": 4, + "Height": 3, + "MinTemperature": 250, + "MaxTemperature": 450, + "AvgTemperature": 350, + "RoiTemperature": 360, + "FrameType": "Masked", + "EventId": "uuid-123", + "Format": "JSON", + "Is2D": true, + "TemperatureMatrix": [ + [250, 260, 270, 280], + [300, 310, 320, 330], + [400, 410, 420, 450] + ] +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +JSON + + +# Page 25 + +帧类型 (FrameType): +• +: 触发时的原始帧 +• +: 蒙版处理后的帧 +• +: 热力图图像 +• +: 实时流帧 +5.3.5 AcquisitionStatusEvent(采集状态) +采集端状态信息。 +字段定义: +{ + "Version": 2, + "Timestamp": 1706941200000, + "PipelineId": "line1", + "FrameNumber": 101, + "Width": 4, + "Height": 1, + "MinTemperature": 250, + "MaxTemperature": 300, + "AvgTemperature": 275, + "RoiTemperature": 275, + "FrameType": "Masked", + "EventId": "uuid-456", + "Format": "JSON", + "Is2D": false, + "TemperatureMatrix": [ + [250, 260, 280, 300] + ] +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +Triggered +Masked +Heatmap +Live +JSON + + +# Page 26 + +运行状态 (Status): +• +: 空闲 +• +: 运行中 +• +: 暂停 +• +: 错误 +• +: 初始化中 +• +: 注册中 +6. 命令与响应格式 +6.1 响应传输方式 +由于PUB-SUB 是单向通信,响应通过特定的响应主题发布: +• +成功响应: +• +错误响应: +字段名 +类型 +说明 +Version +int +协议版本 +Timestamp +long +时间戳 +PipelineId +string +管线ID +Status +string +运行状态 +Mode +string +当前模式 +FrameRate +int +当前帧率 +TemperatureRange +int[] +温度范围[min,max] +HardwareStatus +object +硬件状态 +ErrorCount +int +错误计数 +Uptime +long +运行时间(秒) +Idle +Running +Paused +Error +Initializing +Registering +{deviceId}/response/success +{deviceId}/response/error + + +# Page 27 + +6.2 响应格式 +6.2.1 通用响应结构 +响应必须包含 + 和 + 以确保发送端能够正确匹配异步结果。 +6.2.2 命令响应示例 +ConfigureAcquisition 响应: +RequestId +PipelineId +{ + "Version": 2, + "Timestamp": 1706941200000, + "RequestId": "对应请求ID", + "PipelineId": "逻辑管线ID", + "Success": true, + "ErrorCode": 0, + "ErrorMessage": null, + "CommunicationMode": "PUB_SUB", + "Data": { + "Message": "Success" + } +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +{ + "RequestId": "req_config_001", + "Success": true, + "ErrorCode": 0, + "ErrorMessage": null, + "CommunicationMode": "PUB_SUB", + "Data": { + "PipelineId": "line1", + "AppliedParameters": ["Basic", "Camera", "Mask", "Trigger"], + "Timestamp": 1706941200000 + } +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +JSON +JSON + + +# Page 28 + +GetStatus 响应: +6.3 异步响应处理 +由于PUB-SUB 是异步通信,需要以下机制确保可靠性: +请求ID 匹配: 每个命令包含唯一RequestId,响应中携带相同ID +超时机制: 发送命令后等待响应,超时后重试 +重试策略: 失败后按指数退避重试 +状态同步: 定期同步设备状态,确保一致性 +7. 错误处理与状态码 +7.1 错误代码 definition +{ + "RequestId": "req_status_001", + "Success": true, + "ErrorCode": 0, + "ErrorMessage": null, + "CommunicationMode": "PUB_SUB", + "Data": { + "PipelineId": "line1", + "Status": "Running", + "Mode": "Detect", + "FrameRate": 29.5, + "TemperatureRange": [20.5, 45.3], + "Uptime": 3600, + "ErrorCount": 0 + } +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +1. +2. +3. +4. +JSON + + +# Page 29 + +7.2 错误响应示例 +错误代码 +错误类型 +说明 +2001 +参数验证错误 +参数格式或范围错误 +2002 +配置应用失败 +配置无法应用到硬件 +2003 +硬件通信错误 +与相机或GPIO 通信失败 +2004 +预处理错误 +蒙版处理或ROI 裁剪失败 +2005 +资源不足 +内存或存储空间不足 +2006 +状态冲突 +命令与当前状态冲突 +2007 +超时错误 +操作超时 +2008 +数据格式错误 +数据格式不支持 +2101 +通信模式错误 +仅支持PUB_SUB 模式 +2102 +主题解析错误 +PUB-SUB 主题解析失败 +2103 +设备注册失败 +设备注册过程失败 +2104 +订阅失败 +主题订阅失败 +2105 +发布失败 +消息发布失败 +{ + "RequestId": "req_config_001", + "Success": false, + "ErrorCode": 2101, + "ErrorMessage": "仅支持PUB_SUB通信模式", + "CommunicationMode": "PUB_SUB", + "Data": { + "SupportedModes": ["PUB_SUB"], + "RequestedMode": "PUB_SUB" + } +} +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +JSON + + +# Page 30 + +7.3 错误恢复策略 +网络错误: 自动重连,恢复订阅 +数据错误: 丢弃错误数据,记录日志 +配置错误: 回退到上次有效配置 +硬件错误: 进入安全模式,报告错误 +8. 设备注册与管理 +8.1 设备注册流程 +8.2 设备状态管理 +ConfigServer 维护设备状态表: +1. +2. +3. +4. +采集端 ConfigServer + │ │ + │ 1. 连接PUB/SUB Socket │ + │───────────────────────────>│ + │ │ + │ 2. 发送register消息 │ + │───────────────────────────>│ + │ (主题: register) │ + │ │ + │ 3. 注册设备 │ + │<───────────────────────────│ + │ │ + │ 4. 发送ready信号 │ + │<───────────────────────────│ + │ (主题: {deviceId}/ready) │ + │ │ + │ 5. 设备激活,开始通信 │ + │ │ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +Plain Text + + +# Page 31 + +8.3 心跳与健康检查 +设备心跳: 采集端每5 秒发送status 消息 +健康检查: ConfigServer 监控设备活跃状态 +超时处理: 30 秒无消息视为离线 +自动恢复: 离线设备重新注册 +字段 +类型 +说明 +DeviceId +string +设备唯一标识符 +Status +string +设备状态 +LastActiveTime +datetime +最后活跃时间 +SessionRuntimeHours +int +本次会话运行时长 +TotalRuntimeHours +int +累计运行时长 +Version +string +设备版本 +Capabilities +array +设备能力列表 +Subscriptions +array +已订阅主题 +1. +2. +3. +4. + diff --git a/prj/TCPClient/User/main.c b/prj/TCPClient/User/main.c index 4f5673c..c9c558c 100644 --- a/prj/TCPClient/User/main.c +++ b/prj/TCPClient/User/main.c @@ -61,6 +61,17 @@ static void NG_GPIO_Init(void) GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN); } +/* Flag set by TempFrameRequest callback; consumed by business task */ +static volatile uint8_t g_temp_req_pending = 0; +static volatile uint8_t g_temp_req_is2d = 1; + +void OnTempFrameRequest(uint8_t is2dRequest) +{ + g_temp_req_is2d = is2dRequest; + g_temp_req_pending = 1; + DBG_APP("TempFrameReq is2d=%d\r\n", (int)is2dRequest); +} + void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus) { (void)frameNumber; @@ -341,6 +352,58 @@ static void task_test_pattern_entry(void *pvParameters) } #endif /* TEST_PATTERN_MODE */ +/* ============================================================ + * 1D mode: build and send a single 1D temperature frame from + * the current raw image. Scans the center row and packs + * TempPoint1D_t (2B time_offset + 2B temp, little-endian). + * ============================================================ */ +static void send_1d_frame_from_raw(const RawImageBuffer_t *raw, TcpTxBuffer_t *tx_buf) +{ + uint16_t w = raw->Width; + uint16_t h = raw->Height; + uint16_t *src = raw->pData; + uint16_t row = h / 2; /* center row */ + + tx_buf->ValidPayloadLen = 0; + uint8_t *dest = tx_buf->pBuffer + tx_buf->HeadOffset; + uint32_t capacity = tx_buf->TotalCapacity - tx_buf->HeadOffset; + uint16_t points = w; + if ((uint32_t)points * 4 > capacity) + points = (uint16_t)(capacity / 4); + + int16_t min_t = 32767, max_t = -32768; + int32_t sum_t = 0; + + for (uint16_t i = 0; i < points; i++) { + uint16_t temp = src[row * w + i]; + uint16_t time_offset = (uint16_t)((uint32_t)i * 600 / (points > 1 ? points - 1 : 1)); + int16_t t = (int16_t)temp; + if (t < min_t) min_t = t; + if (t > max_t) max_t = t; + sum_t += t; + dest[0] = (uint8_t)(time_offset & 0xFF); + dest[1] = (uint8_t)((time_offset >> 8) & 0xFF); + dest[2] = (uint8_t)(temp & 0xFF); + dest[3] = (uint8_t)((temp >> 8) & 0xFF); + dest += 4; + } + tx_buf->ValidPayloadLen = (uint32_t)points * 4; + + PreprocessResult_t meta; + memset(&meta, 0, sizeof(meta)); + meta.pValidData = tx_buf->pBuffer + tx_buf->HeadOffset; + meta.DataLength = tx_buf->ValidPayloadLen; + meta.ValidWidth = points; + meta.ValidHeight = 1; + meta.MinTemp = min_t; + meta.MaxTemp = max_t; + meta.AvgTemp = (int16_t)(sum_t / (points > 0 ? points : 1)); + meta.RoiTemp = meta.AvgTemp; + meta.FrameNumber = raw->FrameNumber; + + TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x01, 0 /* IS_1D */); +} + static void task_business_entry(void *pvParameters) { (void)pvParameters; @@ -358,6 +421,29 @@ static void task_business_entry(void *pvParameters) DVP_Task(); #endif + /* Handle on-demand frame request from server */ + if (g_temp_req_pending) + { + g_temp_req_pending = 0; + RawImageBuffer_t raw_img; + raw_img.pData = (uint16_t *)FrameBuffer; + raw_img.Width = SENSOR_WIDTH; + raw_img.Height = SENSOR_HEIGHT; + raw_img.FrameNumber = Ready_Frame_Count; + + TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B; + use_buffer_A = !use_buffer_A; + tx_buf->ValidPayloadLen = 0; + + if (g_temp_req_is2d) { + PreprocessResult_t meta; + if (Preprocess_Execute(&raw_img, tx_buf, &meta) == 0) + TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 0x02 /* REQUEST */, 1); + } else { + send_1d_frame_from_raw(&raw_img, tx_buf); + } + } + if (Frame_Ready_Flag) { Frame_Ready_Flag = 0; @@ -475,9 +561,34 @@ int main(void) qdx_port_init(); Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT); + +#if TEST_PATTERN_MODE + /* Set default preprocess config so trigger fires with test patterns. + * Without this, all Config2D_t fields are 0 and trigger never fires + * (roi_w==0 → immediate return 0). */ + { + Config2D_t test_cfg2d; + memset(&test_cfg2d, 0, sizeof(test_cfg2d)); + test_cfg2d.TargetWidth = 64; /* ROI extraction 64x64 */ + test_cfg2d.TargetHeight = 64; + test_cfg2d.TriggerRoiX = 0; /* Full-frame trigger area */ + test_cfg2d.TriggerRoiY = 0; + test_cfg2d.TriggerRoiW = SENSOR_WIDTH; + test_cfg2d.TriggerRoiH = SENSOR_HEIGHT; + test_cfg2d.TriggerCondition = 1; /* Max temperature */ + test_cfg2d.TriggerTemperatureThreshold = 8000; /* 80.00°C */ + test_cfg2d.TriggerBurstCount = 3; + test_cfg2d.TriggerInternalIntervalMs = 200; + test_cfg2d.NGioDelay = 200; + Preprocess_Settings_Change(&test_cfg2d, NULL, NULL); + printf("Test default config loaded: trigger thresh=8000 ROI=full burst=3\r\n"); + } +#endif + TcpLogic_Init(MACAddr, NULL); TcpLogic_RegisterConfigCallback(OnConfigUpdate); TcpLogic_RegisterDetectionCallback(OnDetectionResult); + TcpLogic_RegisterTempFrameRequestCallback(OnTempFrameRequest); DBG_APP("TcpLogic_Start...\r\n"); TcpLogic_Start();