Skip to content

Repository files navigation

device-ingest · 外设接入层

多端口 UDP 外设接入 → 插件式协议解析 → 统一归一化 → 设备健康 → 指令下发,一个可独立构建、可独立部署、可整包复用的 C++ 模块。

它只负责回答一个问题:"各说各话的外设,怎么变成一份标准数据?" —— 至于这份数据画在哪张图上、进哪个台账,是宿主的事。

status language build license


📌 当前状态(请先读这一段)

S0–S3 已落地并验收通过:多接入点 UDP 接收 → 插件式解析 → 统一归一化 → 设备健康 → 进程内队列 → 单帧合并 → 广播,整条数据面已通;库、最小宿主、设备模拟器、零依赖自测都能独立构建运行。 S4(指令下发)与 S5(主仓切换)未开始。

阶段 内容 状态
需求确认 37 条需求(7 个域)逐条带验收标准 ✅ 已冻结
接口契约 接入点配置、归一化对象、事件名、REST、指令报文 ✅ 已冻结
抽仓设计 目录结构、依赖反转、代码迁移映射、迁移顺序 ✅ 已冻结
S0 仓骨架 独立 CMake(不需要 vcpkg,json 单头副本内置)、公开面、examples/host_demo ✅ 已实现
S1 acc+hub 多接入点 UDP 接收、热增删、故障隔离、进程内队列、单帧合并、广播出口 ✅ 已实现
S2 prs+nrm 插件式解析器体系(含异常隔离)、归一化与字段级降级、既有 4 类 kind 兼容、原始透传 ✅ 已实现
S3 hlt+fan 设备台账、心跳超时判离线/恢复上线、丢包与乱序精确统计、模拟器注入 ✅ 已实现
S4 cmd 指令下发(回执/重传/状态机/操作日志)+ 指令回声端 ⏳ 待设备指令集
S5 主仓接入 删除主仓自有 UDP 接收代码,改由本模块承担 ⏳ 未开始
S6 压测验收 1000×1 Hz × 10 min,接收零丢弃,端到端 P95 ⏳ 未开始
S7 发布 发布指南 + subtree 脚本 ⏳ 未开始

现状一句话:能收包、能解析、能归一、能算健康、能合并分发、能被独立验收; 还没有指令下发(S4)与主仓切换(S5)。

先跑起来看(三条命令,不需要任何外设、不需要主仓):

cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
powershell -ExecutionPolicy Bypass -File scripts\acceptance.ps1 -SkipBuild

最后一条会跑完 零依赖自测(150 项断言)+ UDP 真链路验收(27 项断言): 三接入点并行、既有 4 类 kind 字段级兼容、原始透传、丢包率与注入值一致、离线判定、零丢弃。 操作细节与排障见 doc/构建与排障.md

为什么先出文档再出代码:本模块的接口形状由外设报文格式设备指令集决定, 这两项至今仅"序号/时间戳"与"上报周期"有结论(见 §已知阻塞项)。 先把边界、契约和迁移路径写死,等协议到位只换解析器实现,不动架构—— S0–S3 正是在"没有任何设备协议文档"的前提下完成的,这本身就是对该设计的一次验证。


🎯 它解决什么问题

外设(无人机、雷达、光电、电子侦察……)通常这样上报数据:

  • 多端口 / 多组播地址并行,每类设备一套;
  • 每类设备协议都不一样,且经常没有文档
  • 数据必须实时上图,还要能判断设备在线/离线/丢包/乱序
  • 有时还要反向给设备下指令(含回执与超时重传)。

把这些塞进地图渲染模块,会立刻付三个代价:地图模块不再能"整目录拷走"、多实例地图各收一份数据、排障时分不清"没收到"还是"没画对"。

所以本模块独立存在,并遵守一条硬边界:

只做数据入口,不做渲染、不做业务。 不含地图类型、不含 SQL、不含业务后端依赖;所有出口都是反向接口(宿主实现)。


🧩 模块边界

外设(多端口/多组播 UDP)
      │
      ▼
┌───────────────────────────────────────────────────────────┐
│  device-ingest                                            │
│                                                           │
│   acc 接入点 ──▶ prs 解析器 ──▶ nrm 归一化 ──▶ fan 队列      │
│   (多端口并行)  (插件热插拔)  (统一对象)   (单帧合并)    │
│                      │              │                     │
│                      │              ├──▶ hlt 设备健康       │
│                      │              │    (心跳/丢包/乱序)  │
│   cmd 指令下发 ◀──────┴──────────────┘                     │
│   (回执 + 超时重传)                                        │
│                                                           │
│   出口:ISink / ILogSink(宿主实现)· EventHub(WS 广播)    │
│   入口:IDeviceSource(宿主提供模拟所需事实)                 │
└───────────────────────────┬───────────────────────────────┘
                            │ 归一化事件
                            ▼
                  宿主(业务 / 地图 / 台账 / 告警)
不做
多接入点 UDP 接收、热增删、故障隔离 地图渲染、图层、抽稀(属消费侧)
插件式协议解析、原始透传 业务逻辑(阶段、台账、评级)
统一归一化对象(坐标/单位/时间戳) 历史存储与时序回放(本轮不做)
设备台账、心跳超时、丢包与乱序统计 设备管理界面
指令下发、回执匹配、超时重传、操作日志 外部消息中间件(进程内队列)

📚 文档

文档 内容 适合谁读
需求专篇 37 条需求(ING-ACC/PRS/NRM/HLT/CMD/FAN/NFR)、验收准则、指标口径、决策记录、风险 产品、评审、测试
接入契约 接入点配置结构、归一化对象(字段级)、WS 事件名、REST 接口、UDP 指令与回执、解析器插件接口、错误码、降级矩阵 接入方、前端、联调
抽仓设计方案 现状耦合分析(逐行)、目录结构、依赖反转(ISink/ILogSink/IDeviceSource)、配置结构、主仓改动清单、迁移顺序 S0–S7、代码迁移映射 架构、实现者

三份文档的关系:需求说"做什么" → 契约说"长什么样" → 设计说"怎么拆、怎么搬"


🔌 关键约定速览

取值
接入方式 UDP 组播为主(一个端口只归一个接入点);也支持单播
报文格式 JSON over UDP(UTF-8,单包 ≤ 8 KB);二进制协议由该设备的解析器插件编解码
归一化对象 { deviceId, deviceType, kind, lng, lat, alt, heading, speed, battery, seq, ts, tsSource, recvAt, quality, source }
坐标与单位 WGS84 经纬度、顺序 [经度, 纬度]、高度米、速度 m/s、航向 0–360 度
时间戳 epoch 毫秒;优先设备 ts,缺失用服务端到达时刻并标 tsSource: "server"
丢包/乱序 依据设备 seq 精确统计;无 seq明确标注"不可精确统计",不给估计值
失联判定 超时 = 上报周期 × 倍数(默认 3 倍),可配置
指令通道 直接发 UDP(单播/组播),回执也走 UDP;状态机 pending → sent → acked / failed / timeout
队列 进程内(不引 Redis/NATS/Kafka),按"生产者→队列→消费者"设计以留跨进程升级口
目标规模 1000 台 × 1 Hz × 200 B(端到端 P95 ≤ 500 ms)
对外发布 REST /api/v1/ingest/*(路由由宿主挂载,模块不依赖 Web 框架)
构建开关 DEVICE_INGEST_WITH_HUB=OFF 可编译到无 Web 框架的采集机

🗺 路线图

内容 状态 主仓影响
S0 建仓骨架 + 独立 CMake + 最小宿主示例 ✅ 完成
S1 acc 多接入点接收 + hub 广播 ✅ 完成
S2 prs 解析器体系 + nrm 归一化(含既有 4 类事件兼容) ✅ 完成
S3 hlt 设备健康 + fan 队列与单帧合并 + 模拟器(丢包/乱序/停机注入) ✅ 完成
S4 cmd 指令下发(回执/重传/状态机/操作日志)+ 指令回声端 ⏳ 待设备指令集
S5 宿主接入:删除主仓自有 UDP 接收代码,改由本模块承担 ⏳ 未开始 一次性切换
S6 压测与验收:1000×1 Hz × 10 min,接收零丢弃,端到端 P95 ⏳ 未开始
S7 发布指南 + subtree 发布脚本 ⏳ 未开始

S0–S3 已完成(对应四个提交 chore(s0) / feat(s1) / feat(s2) / feat(s3)): 空环境独立构建通过(目标 G1);host_demo 可运行;device_sim 发既有 4 类 kind 的报文, 事件名与字段与主仓现状逐字段一致;丢包/乱序/离线在真 UDP 链路上与注入值一致。 证据:scripts/acceptance.ps1(27 项断言)+ tests/selftest(150 项断言); 逐条对照见 doc/代码迁移映射.md。 S4–S6 宿主零改动;风险集中在 S5 单步,且是"删除 + 适配"而非"重写逻辑"。


🚧 已知阻塞项

以下四项未关闭前,解析器(prs)与指令(cmd不能开工

  1. 各设备类型的具体报文格式(字段名、单位、字节序、文本/二进制);
  2. 设备指令集定义(指令类型、载荷格式、回执格式与 cmdId 回填约定);
  3. 各设备心跳周期是否统一(影响默认 3 倍超时是否可接受);
  4. 设备与接入点的归属关系(哪些设备走哪个端口/组播)。

🧱 技术栈(规划)

取值 说明
语言 C++17 与宿主项目保持一致
构建 CMake 3.20+(可选 vcpkg 预设) 独立构建,无宿主头文件依赖
依赖 nlohmann/json(必需)、Drogon(可选,仅 WS 广播层) DEVICE_INGEST_WITH_HUB=OFF 时不需要任何 Web 框架
数据库 日志与落库全部经 ILogSink 由宿主实现
许可 Apache-2.0 LICENSE

🤝 接入方式(规划)

三种方式,按需选一种:

# ① FetchContent(推荐:版本化引入)
FetchContent_Declare(device_ingest
  GIT_REPOSITORY https://github.com/soft-monk/device-ingest.git
  GIT_TAG        v0.1.0)
FetchContent_MakeAvailable(device_ingest)
target_link_libraries(your_app PRIVATE device_ingest)

# ② git submodule(想直接改模块代码联调)
# ③ 本地 SOURCE_DIR(联调期:改两边同时生效)

宿主需要实现两个(可留空的)反向接口,模块即可工作:

class MySink : public device_ingest::ISink   { /* onEvent / onBatch / onDeviceHealth / ... */ };
class MyLog  : public device_ingest::ILogSink { /* log / commandAudit */ };
class MySrc  : public device_ingest::IDeviceSource { /* 仅在需要内置模拟器时实现 */ };

详细接入步骤在实现落地后补入 doc/发布与接入指南.md


📄 许可

Apache License 2.0

About

外设接入层(C++17):多端口 UDP 外设接入 · 插件式协议解析 · 统一归一化 · 设备健康监测 · 指令下发;可独立构建、可独立部署,不含渲染与业务

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages