chore: upload current project state

This commit is contained in:
gqc
2026-05-12 15:45:50 +08:00
parent cd9b199e2c
commit 6056b1fdc5
25 changed files with 4051 additions and 373 deletions
@@ -0,0 +1,355 @@
# WebSocket 协议改造分析
日期:2026-05-11
参考文档:
- `https://gitea.s5s8.com/anlicheng/ekfa/raw/branch/protobuf_mst/docs/service_websocket_protocol.md`
## 1. 当前项目的通讯方式
当前实现文件:
- `connect/ws_channel.go`
- `reporter/reporter.go`
- `main.go`
当前协议特征:
1. 传输层
- WebSocket 文本帧
- 使用 JSON 编码
2. 请求响应模型
- 统一消息结构:`{id, method, params, result, error}`
- 当前请求方法主要有:
- `register`
- `subscribe`
- 当前推送/上报主要有:
- `metric_data`
- `publish`
3. 注册逻辑
- 客户端连接到 `ws://172.17.0.1:18080/ws`
- 发送 `register`
- 参数包含:
- `container_name`
- `service_id`
- `meta_data`
4. 订阅逻辑
- 发送 `subscribe`
- 参数为:
- `topic`
5. 下行消息
- 服务端下发 `publish`
- `params` 中当前代码按如下字段解析:
- `topic`
- `data`
- `content`
6. 上行数据上报
- 业务侧通过:
- `method = "metric_data"`
- `params.route_key`
- `params.metric`
- `reporter` 中事件与数据快照上报都走这个逻辑
7. 心跳与重连
- 客户端主动发 WebSocket ping frame
- 自己维护重连、重注册、重订阅
## 2. 目标协议的通讯方式
根据新文档,目标协议特征如下:
1. 传输层
- WebSocket binary frame
- 每帧格式:
- `1 byte frame_type`
- `N bytes protobuf payload`
2. 帧类型
- `0x01``REQUEST`
- `0x02``REPLY`
- `0x03``CAST`
3. protobuf 消息
- `ServiceRequest`
- `ServiceReply`
- `ServiceCast`
4. request/reply
- `register`
- 参数只有 `service_id`
- `subscribe`
- 参数只有 `topic`
- `reply`
- 成功:`result = "ok"`
- 失败:`error.code = -1`
5. cast
- service -> efka
- `metric_data`
- 字段:
- `route_key: bytes`
- `metric: bytes`
- efka -> service
- `topic_event`
- 字段:
- `topic: string`
- `content: bytes`
6. 注册前约束
- 未注册前只允许 `register`
- 其它 request 返回 `invalid request`
- cast 会被忽略
7. ping/pong
- Cowboy 直接处理 ping/pong
- service 侧无需再设计额外业务层心跳协议
## 3. 当前与目标协议的核心差异
## 编码层差异
当前:
- JSON
- 文本帧
目标:
- protobuf
- 二进制帧
影响:
- `connect/ws_channel.go` 的读写逻辑要整体重写,不能局部修补
## 消息模型差异
当前:
- 通用 JSON RPC 风格:`id + method + params/result/error`
目标:
- 分离为 `REQUEST / REPLY / CAST`
- 且由帧头字节决定消息类型
影响:
- 现有 `WSMessage` 结构体要废弃或降级为兼容层
## 注册参数差异
当前:
- `register(container_name, service_id, meta_data)`
目标:
- `register(service_id)`
影响:
- `RegisterParams` 需要改
- `container_name``meta_data` 不应继续按当前协议发送
## 下行消息差异
当前:
- 服务端下发 `publish`
- 业务代码把 `publish.params` 再反序列化
目标:
- 服务端下发 `CAST topic_event`
影响:
- `handlePublishMessage()` 这类 JSON `publish` 解析流程要删除
- 新增 `topic_event` 的 protobuf 解码与路由
## 上行上报差异
当前:
- `method = metric_data`
- `params.route_key`
- `params.metric`
目标:
- `CAST metric_data`
- `route_key``metric` 都是 `bytes`
影响:
- `reporter` 发消息时不再构造 JSON RPC
- 要构造 `ServiceCast.metric_data`
## reply 相关差异
当前:
- 请求响应靠 JSON 的 `id`
目标:
- `packet_id`
- 且只存在于 `ServiceRequest / ServiceReply`
影响:
- 当前 `callbacks map[int64]chan *WSMessage` 机制可以保留思路
- 但要替换成 protobuf `packet_id``ServiceReply`
## 4. 当前项目中需要改动的代码点
## A. `connect/ws_channel.go` 需要整体重构
需要改动:
1. 连接保持不变
- 仍然连 `/ws`
2. 写消息逻辑
- 现在 `SendRawMessage()` 直接 `json.Marshal()` 后发 `TextMessage`
- 需要改成:
- protobuf 序列化
- 前面拼接 `frame_type`
-`BinaryMessage`
3. 请求发送逻辑
- 现在 `sendRequest(method, params, waitResponse)` 是 JSON 风格
- 需要改成:
-`ServiceRequest`
-`packet_id`
- body 选择 `register``subscribe`
-`0x01 + protobuf(ServiceRequest)`
4. 读消息逻辑
- 现在 `readLoop()` 直接把整帧当 JSON 解码
- 需要改成:
- 读取二进制帧
- 取第一个字节判断帧类型
- `0x02` 解码为 `ServiceReply`
- `0x03` 解码为 `ServiceCast`
5. 下行消息分发
- 现在按 `message.Method == "publish"` 处理
- 需要改成按 `ServiceCast.body`
- `topic_event`
- 后续如协议扩展再加别的 cast
6. 回调等待机制
- 现在按 `id` 关联回调
- 需要改成按 `packet_id`
## B. 注册逻辑需要调整
当前:
- `RegisterService(containerName string)`
- 发送 `container_name + service_id + meta_data`
目标:
- 仅发送 `service_id`
建议:
-`container_name` 从协议层移除
- `service_id` 建议继续使用当前生成逻辑,但需确认上层是否有固定命名要求
## C. 订阅逻辑需要调整
当前:
- 发送 JSON `subscribe`
目标:
- 发送 protobuf `ServiceRequest.Subscribe`
建议:
- `SubscribeTopic()` 对外接口可以保留
- 内部实现改为 protobuf request
## D. 上报逻辑需要调整
当前:
- `reporter.ReportEvent()`
- `reporter.ReportDataSnapshot()`
- `GenerateEventMessage()`
- `GenerateDataMessage()`
- 最终都构造成 JSON `metric_data`
目标:
- 最终发送 protobuf `ServiceCast.metric_data`
建议:
- `GenerateEventMessage()` / `GenerateDataMessage()` 不一定保留 map 结构
- 更合理的是:
- 先生成业务 payload JSON 字符串
- 再把该字符串作为 `metric_data.metric` 的 bytes 内容
- `route_key` 也按 bytes 写入 protobuf
## E. 下行配置/告警处理需要调整
当前:
- 依赖 `publish.params.topic/data/content`
- 并把 `data``DeviceData` 解析
目标:
- 依赖 `topic_event.topic/content`
- `content` 是 bytes
建议:
- 重新梳理每个 topic 的 payload 格式
- 尤其要确认:
- `/dhlr/alert`
- `/dhlr/forbid_time`
- 设备配置下发 topic
- 不能再默认存在 `publish.data`
## 5. 改造时需要确认的外部问题
这些问题代码里无法自行推断,需要和上层协议一起确认:
1. 设备配置下发现在走哪个 topic
- 当前代码默认任何 `publish.data` 都可能是 `DeviceData`
- 新协议文档只定义了 `topic_event(topic, content)`,没定义具体业务 topic 的 payload schema
2. `content` 的业务内容格式是什么
- 是 JSON bytes
- 还是 protobuf bytes
- 还是普通字符串
3. `service_id` 的命名规则
- 现有代码自动生成 `fireleave-service-<timestamp>`
- 新链路是否要求固定 `service_id`
4. topic 命名是否需要改
- 当前项目使用:
- `/dhlr/alert`
- `/dhlr/forbid_time`
- `/dhlr/device/<uuid>/event`
- `/dhlr/device/<uuid>/data`
- 文档中的示例 topic 风格更像:
- `device/a/event`
- `device/*/event`
- 需要确认上层真实路由规范
5. 是否还需要客户端主动 ping
- 文档说明服务端会直接回复 ping/pong
- 但没有明确要求客户端是否必须主动发 ping
- 当前保留主动 ping 一般没问题,但需要确认是否有重复机制
## 6. 建议的改造顺序
1. 先生成 `service.proto` 的 Go 代码
2. 新建独立的 protobuf WebSocket 客户端层
3. 保留现有上层接口:
- `RegisterService`
- `SubscribeTopic`
- `SendAsync`
4. 先把协议层替换掉,不动业务层调用点
5. 再改下行 `topic_event` 到业务 topic 的解析分发
6. 最后联调配置下发、事件上报、重连重订阅
## 7. 对当前项目的直接结论
这次协议改造不是小修小补,而是通讯层整体替换,最核心的改动文件会是:
- `connect/ws_channel.go`
- `reporter/reporter.go`
- 可能新增:
- `connect/protocol/`
- `connect/pb/`
同时,当前项目里“配置更新不生效”的问题建议和这次协议改造一起处理,否则即使新协议打通,设备配置下发链路也还是不完整。
1.我的业务逻辑是这样的,我是末端的微服务,我负责采集数据然后上报数据,我的业务是安防的,我这边分析发现危险情况然后上报给上层,而forbid_time和alert是我联动的硬件侧的声光报警器,他们分别区分的是报警间隔时间和报警信息。我对你这句话的理解,你认为topic是对多个微服务或者设备的一个共同网络,如果改动了topic_event(topic, content)我订阅这个topic的所有设备都会触发声光报警是吗?
2.对的,主要是外层传输协议的改动。
3.可能是你不清楚现在的运行环境,我先描述一下现在的业务环境,
+70
View File
@@ -0,0 +1,70 @@
# TODO
日期:2026-05-11
## 第一阶段
1. 统一端口管理器
- 明确保留哪一套端口管理器作为唯一实现
- 替换重启链路里的端口获取逻辑
- 校验 `fire_leave``fire_check`、测温端口关系
- 清理废弃实现和重复状态
2. 修复 `service.conf` schema
- 明确 `service.conf` 的唯一 schema
- 增加 `DeviceData -> ServiceConfig` 转换
- 重写 `SaveServiceConfig()` / `SaveSingleServiceConfig()`
- 检查已有启动流程兼容性
3. 修复 `fire_leave` 参数映射
- 明确 `fire_leave` 运行时依赖字段
- 补齐 `param.fire_leave` 到运行字段的映射
- 清理主流程里对默认值的隐式依赖
- 复核 `confidence` 是否要区分双模型
## 第二阶段
1. 接入 `/dhlr/forbid_time``fire_leave` 暂停上报
- 明确 `/dhlr/forbid_time` 最终字段语义
- 增加 `fire_leave` 暂停状态存储
-`fire_leave` 主判断前接入暂停窗口检查
- 清理或降级 `reporter` 中不再适用的事件周期逻辑
2. 调整 GPIO 按钮逻辑
- 确认按钮最终由谁决定暂停时长
- 确认按钮是本地触发还是必须等待上层下发
- 移除与“事件周期切换”强耦合的旧逻辑
## 第三阶段
1. 改造 WebSocket 协议层为 protobuf binary frame
- 引入 `service.proto` 对应的 Go 类型
- 重写 websocket 读写帧逻辑
- 重写 request/reply 关联逻辑
- 重写下行 `topic_event` 分发逻辑
- 重写上行 `metric_data` 发送逻辑
2. 保留主动 ping 和失败重连
- 在新协议层中保留 ping loop
- 保留 ping 失败即重连逻辑
- 复核重连后 register 和 subscribe 恢复是否完整
## 暂不处理
1. 业务上报 ACK
- 暂不增加业务 ACK
- 协议改造时保留现有单向上报模式
- 作为已知风险保留在 `整改计划.md`
2. 运行中配置热更新链路
- 判断当前业务是否真的需要运行中热更新
- 如果需要,后续再设计完整消费链路
- 如果不需要,后续再移除误导性代码
## 收尾
1. 增加最小验证
- 增加协议层编解码基础测试
- 增加配置映射测试
- 增加报警暂停窗口逻辑测试
- 条件允许时补最小集成测试
+528
View File
@@ -0,0 +1,528 @@
# 动火离人微服务业务逻辑梳理
本文档基于当前代码整理,目标是帮助快速回忆这个微服务现在“实际在做什么”,以及哪些逻辑是设计上存在但当前代码里已经弱化或未真正生效的。
## 1. 项目定位
当前仓库是“动火离人”项目中的微服务管理程序,职责不是前端展示,也不是后端总控,而是:
- 接收后端/平台下发的设备配置
- 为每个设备启动并管理本地推理程序
- 接收推理结果和测温结果
- 按规则判断是否触发报警
- 回溯报警前后视频,拼接并上传
- 通过 WebSocket 将报警事件和数据快照发给后端,再由后端转给前端
整体链路可以理解为:
`前端配置 -> 后端下发配置 -> 微服务启动/管理推理 -> 推理结果回流微服务 -> 微服务判定报警 -> 上传视频 + websocket上报 -> 后端转发前端展示`
## 2. 当前代码里的三类核心进程
每个设备启动时,实际上会拉起 3 条本地能力:
1. `fire_leave` 推理程序
- 模型参数:`./yolov5 -m model`
- 主要用途:统计人员数量
- 结果通过本地 HTTP 服务回传
2. `fire_check` 推理程序
- 模型参数:`./yolov5 -m fire_check -R`
- 主要用途:检测火焰
- 结果通过另一套本地 HTTP 服务回传
3. 测温程序
- 启动脚本:`./release/run_thermometry.sh`
- 结果通过本地 TCP 服务回传
也就是说,当前“动火离人”并不是单模型,而是:
- 一套模型做人数量判断
- 一套模型做火焰检测
- 一套测温程序提供温度值
## 3. 配置来源与初始化
### 3.1 启动时配置来源
程序启动后先从 `/usr/local/etc/service.conf` 读取设备配置。
配置结构已经是多设备形式,核心字段包括:
- `device_uuid`
- `task_id`
- `camera_rtsp`
- `camera_ip`
- `confidence`
- `detect_area.fire_leave`
- `detect_area.fire_check`
- `param.fire_leave`
- `param.fire_check`
其中:
- `fire_leave` 检测区给人员检测使用
- `fire_check` 检测区给火焰检测使用
- 两套检测区彼此独立
### 3.2 配置落盘
初始化后,每个设备还会落盘到:
- `/data/devices/device_{deviceUUID}.json`
用于后续读取和状态持久化。
## 4. 每个设备启动时的完整流程
`main.go` 中每个设备会进入 `processDevice()`,当前流程如下:
1. 设备加入全局 `DeviceManager`
2.`fire_leave` 分配一个端口
3.`fire_check` 再分配一个端口
4. 分别启动两个本地 HTTP 接收服务
5. 等待 HTTP `/health` 就绪
6. 把前端配置的检测区域转换成推理程序需要的点串格式
7. 启动 `fire_leave` 推理进程
8. 启动 `fire_check` 推理进程
9. 启动测温程序
10. 等待测温 TCP 服务就绪
11. 创建 TCP 客户端读取温度
12. 进入主循环,持续处理:
- `fire_leave` 推理结果
- `fire_check` 推理结果
- 周期性温度读取和报警判定
## 5. 两条检测链路的当前职责
## 5.1 fire_leave 链路
当前代码里,`fire_leave` 推理结果主要只负责更新人数。
处理逻辑:
- 微服务接收 `/video/post`
- 解析 `params`
-`ClassIdx == 1`
-`Number` 作为当前人数 `PersonCount`
- 写回设备状态
也就是说,当前“离人”判断本身不是由模型直接报“报警/不报警”,而是:
- 模型只提供人数
- 微服务结合温度再做二次判定
## 5.2 fire_check 链路
当前代码里,`fire_check` 推理结果专门负责火焰报警。
处理逻辑:
- 微服务接收 `fire_check``/video/post`
- 遍历结果
-`ClassIdx == 0 && Number > 0` 时,认为检测到火焰
- 连续满足 `detection_time` 秒后触发火焰报警
火焰报警完全独立于“无人 + 高温”逻辑,不依赖温度和人数。
## 6. 动火离人报警的当前判定逻辑
这是你现在代码里的主报警逻辑,运行在定时器分支里,每 2 秒检查一次。
### 6.1 输入条件
判定使用两个实时量:
- `PersonCount == 0`
- `Temperature > TemperatureThreshold`
其中温度还做了一个简化的防抖:
- 高于阈值 `+0.5`:稳定超温
- 低于阈值 `-0.5`:稳定未超温
- 落在中间:按当前是否大于阈值判断
### 6.2 触发条件
只有同时满足以下条件才会计时:
- 无人
- 温度超阈值
满足后开始累计持续时间,达到 `DetectionTime` 后触发报警。
默认值:
- `DetectionTime` 默认 `20`
- `AlarmTime` 默认 `300`
### 6.3 冷却时间
报警触发后会进入冷却:
- `AlarmPauseUntil = now + AlarmTime`
冷却期内,即使条件继续满足,也不会重复触发。
### 6.4 当前本质
当前“动火离人报警”实际规则可以概括成一句话:
`人员检测结果为 0 + 测温超过阈值 + 持续达到设定秒数 -> 触发 fire_leave 报警`
## 7. 火焰检测报警的当前判定逻辑
火焰检测走另一套状态机。
### 7.1 输入条件
- `fire_check` 结果中出现 `ClassIdx == 0 && Number > 0`
### 7.2 持续时间
持续时间取自:
- `deviceData.FireCheckParam.DetectionTime`
默认值:
- 如果没配,默认 `2`
### 7.3 触发结果
持续检测到火焰达到时长后,触发:
- `GlobalReporter.TriggerFireCheckAlarm(deviceData, alarmTime)`
### 7.4 当前本质
当前“火焰检测报警”可以概括成:
`火焰模型连续检测到火焰达到设定秒数 -> 触发 fire_check 报警`
## 8. 报警后做了什么
无论是 `fire_leave` 还是 `fire_check`,触发后总体都会走这几步:
1. 防止同一告警重复处理
2. 去录像目录找报警前后视频
3. 拼接成一个完整 MP4
4. 复制到 `/data/upload`
5. 上传到 Nginx/后端文件接口
6. 通过 WebSocket 上报事件消息
7. 再上报一份数据快照消息
## 9. 视频回溯与拼接逻辑
当前录像目录:
- 普通动火离人视频:`/usr/data/camera/{deviceUUID}`
- 火焰检测视频:`/usr/data/camera/{deviceUUID}_fire_check`
拼接逻辑:
1. 先把报警时间对齐到最近的 10 秒片段
2.`前10秒``当前10秒``后10秒` 三段视频
3. 如果缺段,会从同目录找最接近的 mp4 作为替代
4. 至少要找到 2 段视频才继续
5.`mp4_merge` 进行拼接
6. 输出到 `/data/upload/{deviceUUID}-{日期-时间}.MP4`
所以当前上报视频本质上是“报警时刻前后约 30 秒的回溯片段”。
## 10. WebSocket 在当前系统里的作用
WebSocket 地址写死为:
- `ws://172.17.0.1:18080/ws`
主要承担 4 件事:
1. 服务注册
2. 订阅平台 topic
3. 接收配置更新
4. 上报事件与数据
### 10.1 启动后注册
启动后会注册一个服务:
- `containerName = fireleave-container`
- `serviceID = fireleave-service-{timestamp}`
### 10.2 订阅的 topic
当前代码里会订阅:
- `/dhlr/alert`
- `/dhlr/forbid_time`
### 10.3 当前业务上真正需要接收的不是设备配置
这里要和“设备配置来源”明确区分。
设备基础配置当前应以本地文件为准:
- `/usr/local/etc/service.conf`
也就是说:
- 摄像头
- 检测区域
- 置信度
- 各类基础参数
这些设备配置不是依赖 WebSocket 实时下发来生效,而是直接读取本地 `service.conf`
WebSocket 在这块更应该承担的是“控制类消息”的接收,例如:
- 报警暂停时长
- 延迟报警/允许离岗时长
- 声光报警控制
补充说明:
- 当前代码里 `ws_channel.go` 仍保留了把 `publish.data` 解析成 `DeviceData` 并写回本地的逻辑
- 但按你确认后的业务,这部分不应视为当前核心业务链路
- 也就是说,代码现状和业务认知这里存在偏差,文档以你刚确认的业务为准
## 11. 按钮状态控制的当前真实逻辑
这是你提到“有点忘了”的重点之一。
当前 GPIO 按钮监控在 `gpio_monitor` 包里,监听:
- 按钮 GPIO`111`
- 蜂鸣器 GPIO`109`
### 11.1 按钮不是启动/停止按钮
当前业务里,按钮按下后不会:
- 启动推理
- 停止推理
- 直接触发报警
按钮的真实业务含义是:
- 临时关闭“人员离开”这一类报警的上报
- 但火焰检测报警仍然保持正常上报
### 11.2 为什么要有这个按钮
这是为了解决现场的特殊用工场景。
例如后厨煲汤:
- 厨师需要很早开始生火
- 但不可能在整个过程中始终守在灶台旁
- 现场管理允许短时间离开
- 如果仍严格套用“动火离人”规则,就会不断产生不符合现场管理实际的报警
所以按钮的业务意义是:
- 在允许短暂离岗的场景下,临时屏蔽人员离开报警
- 同时保留火焰检测能力,避免真正的火焰风险漏报
### 11.3 按钮按下后的目标行为
按你的描述,正确业务应该是:
- `fire_leave` 报警在暂停窗口内不再上报
- `fire_check` 报警仍然是每次检测到火焰后都正常上报
### 11.4 当前代码和目标业务的偏差
当前代码里,按钮实际上是在切换 `reporter` 里的事件周期状态:
- `fire_leave`
- `fire_check`
并带有:
- 200ms 去抖
- 5 秒冷却
但这和你描述的真实业务目标并不一致。
也就是说:
- 现在代码实现的是“事件周期切换”
- 你要的业务其实是“人员离开报警进入暂停期”
这部分应视为待修正逻辑。
## 12. 蜂鸣器逻辑
蜂鸣器不由本地报警自动触发,而是来自平台 topic:
- 订阅 `/dhlr/alert`
收到消息后,如果内容里的:
- `type == 1`
则触发本地蜂鸣器响指定秒数,默认 3 秒。
所以当前蜂鸣器逻辑是:
- 平台推送声光报警命令
- 微服务本地点响 GPIO109
不是“本地一检测到报警就必然自己响”。
## 13. `/dhlr/forbid_time` 的真实业务目标与当前问题
按你刚补充的业务,这个 topic 的核心作用不是“事件周期切换”,而是:
- 当前端在按钮按下后,下发一个“允许人员短暂离开”的暂停时长
- 微服务收到后,进入一个人员离开报警暂停窗口
- 在这个窗口内,即使检测到人员离开,也不要触发 `fire_leave` 上报
-`fire_check` 火焰报警仍然要照常上报
### 13.1 真实业务目标
`/dhlr/forbid_time` 应该承载的是:
- 暂停人员离开报警的时长
- 或与之等价的延迟/禁止上报控制
### 13.2 当前代码现状
当前代码收到 `/dhlr/forbid_time` 后,主要做的是:
1. 保存 `minute``/data/cache/forbid_time_config.json`
2. 保存 `fire_leave_timeout` / `fire_check_timeout``/data/cache/event_cycle_timeout.json`
3. 继续服务于 `reporter` 里的事件周期逻辑
### 13.3 当前存在的问题
这和你描述的真实业务不一致,问题点在于:
- 订阅逻辑没有真正控制 `fire_leave` 报警暂停窗口
- 没有把“按钮触发的允许离岗时长”落到主报警判定里
- 暂停期内是否跳过 `fire_leave` 上报,当前主流程没有真正接进去
- `fire_check``fire_leave` 的差异化处理没有按这个业务目标落地
### 13.4 应有的目标行为
这部分后续应改成:
1. 前端按钮按下
2. 后端通过 `/dhlr/forbid_time` 下发暂停时长
3. 微服务记录 `fire_leave` 报警暂停截止时间
4. 在暂停窗口内:
- 人员离开检测继续运行
- 但不触发 `fire_leave` 报警上报
5. 火焰检测逻辑不受影响,继续正常上报
## 14. 推理程序保活与异常恢复
代码里做了基础保活。
### 14.1 心跳来源
每次推理程序向本地 `/video/post` 发结果时,都会更新一次心跳时间。
也就是说:
- 心跳不是单独接口
- 心跳就是“推理结果持续回传”
### 14.2 超时判断
如果超过 `85` 秒没有收到某个推理程序的数据,就认为需要重启。
主循环每 `30` 秒检查一次:
- `fire_leave`
- `fire_check`
如果超时,则:
1. 杀掉旧进程
2. 复用原端口重启 `yolov5`
3. 立即刷新心跳
## 15. 当前代码里值得特别记住的几个事实
### 15.1 动火离人报警不是“火焰+离人”
当前代码里 `fire_leave` 报警依赖的是:
- 无人
- 温度超阈值
不是火焰检测结果。
### 15.2 火焰报警是完全独立的第二条链路
`fire_check` 只看火焰检测结果,单独触发 `fire_check` 级别事件。
### 15.3 两套检测区域是独立配置
- `FireLeaveDetectArea`
- `FireCheckDetectArea`
前端可以分别配置。
### 15.4 前端参数里真正参与当前判定的关键值
动火离人链路:
- `param.fire_leave.detection_time`
- `param.fire_leave.temperature_threshold`
- `param.fire_leave.alarm_time`
- 人数结果来自 `fire_leave` 模型
火焰链路:
- `param.fire_check.detection_time`
- 火焰结果来自 `fire_check` 模型
### 15.5 置信度当前是共用的
当前两个 `yolov5` 进程启动时都使用:
- `deviceData.Confidence`
没有看到两套模型分别使用不同置信度字段的实现。
## 16. 结合当前代码后的业务总结
如果用最贴近当前实现的话来概括:
- 前端负责配置设备、检测区和阈值
- 后端负责配置转发、消息汇总和给前端展示
- 微服务负责拉起两套推理和一套测温程序
- `fire_leave` 模型负责人数统计
- 测温程序负责温度
- 微服务把“无人 + 超温 + 持续时长”判定为动火离人报警
- `fire_check` 模型负责火焰检测
- 微服务把“持续检测到火焰”判定为火焰报警
- 两类报警都会回溯视频、上传视频、再通过 WebSocket 上报后端
## 17. 当前代码里可能需要你后续再确认的点
这几项不是我猜测,而是从代码现状看出来“可能存在设计与实现不完全一致”的地方:
1. `ConfigUpdateChan` 已有生产者,但未看到消费者
- 说明配置热更新链路可能没做完,或在当前仓库外
2. GPIO 按钮当前实现的是事件周期切换,但真实业务需要的是“暂停 fire_leave 上报”
- 说明这部分现在是实现偏差,不只是保留能力
3. `/dhlr/forbid_time` 当前没有真正接入 `fire_leave` 报警暂停控制
- 说明“按钮按下后允许短时离岗”的核心业务还没真正落地
4. `fire_leave``person_count` 参数没有直接用于报警判断
- 当前代码写死判断 `PersonCount == 0`
- 如果业务上原本支持“少于 N 人也报警”,那这部分目前没有实现
5. `param.fire_leave.confidence``param.fire_check.confidence`
- 配置结构里有
- 但实际启动命令仍统一使用 `deviceData.Confidence`
---
如果后面你继续描述前端和后端的预期业务,我可以再基于这份文档继续补一版“设计业务逻辑 vs 当前代码实现差异”文档,专门帮你找现在代码和你脑中业务之间的偏差。
+332
View File
@@ -0,0 +1,332 @@
# 整改计划
日期:2026-05-11
本文档只保留以下内容:
- 当前确认存在的问题和风险
- 对应的修改方案、影响范围和代码落点
执行项已单独整理到 `docs/todo.md`
## 1. 推理进程重启链路失效
### 问题
当前启动和重启使用了两套不同的端口管理器:
- 启动阶段:`connect.GlobalPortManager`
- 心跳重启阶段:`portmanager.GlobalPortManager`
结果是:
- 初次启动能分到端口
- 心跳超时后重启拿不到原端口
- `yolov5` 重启流程会失败
### 风险
- 推理进程挂掉后无法自恢复
- 设备会长期失去 `fire_leave` / `fire_check` 检测能力
### 准备怎么改
统一端口管理实现,只保留一套端口管理器。
建议方案:
1. 以当前 `main.go` 正在使用的 `connect.GlobalPortManager` 为准,统一所有调用
2. 重写或合并 `connect/heartbeat.go` 中的端口获取逻辑
3. 清理重复的 `portmanager` 包引用,避免两套状态并存
### 需要修改的代码
- `main.go`
- `connect/heartbeat.go`
- `connect/port_manager.go`
- `portmanager/portmanager.go`
- `portmanager/types.go`
## 2. `service.conf` 读写 schema 不一致
### 问题
当前配置读取和写回不是同一套结构:
- `LoadServiceConfig()``[]ServiceConfig` 读取
- `SaveServiceConfig()` / `SaveSingleServiceConfig()``[]DeviceData` 写回
### 风险
- WebSocket 或其它更新路径一旦写回配置
- 下次启动可能无法按原 schema 正确读取
- 配置字段可能丢失或错位
### 准备怎么改
统一配置文件模型,明确:
- `service.conf` 是“部署输入配置”
- `device_*.json` 是“运行态持久化”
建议方案:
1. `service.conf` 始终只用 `ServiceConfig` 读写
2. 运行态状态继续写入 `/data/devices/device_{uuid}.json`
3. 如果确实需要从 WebSocket 更新配置,先把 `DeviceData` 映射回 `ServiceConfig` 再写入
### 需要修改的代码
- `connect/device_storage.go`
- `connect/ws_channel.go`
## 3. `fire_leave` 参数映射不完整
### 问题
当前 `fire_leave` 主判定读的是:
- `deviceData.DetectionTime`
- `deviceData.AlarmTime`
但新配置的真实值在:
- `deviceData.FireLeaveParam.DetectionTime`
- `deviceData.FireLeaveParam.AlarmTime`
另外 `person_count`、双模型 `confidence` 也存在未完整映射的问题。
### 风险
- 离岗报警持续时间可能一直用默认值
- 冷却时间可能一直用默认值
- 前端/平台配置和实际行为不一致
### 准备怎么改
在配置初始化阶段统一完成映射,不在运行主循环里到处兜底。
建议方案:
1. `LoadServiceConfig()` 时完成 `param.fire_leave` 到运行字段的转换
2. `ProcessDeviceDataForInference()` 或专门的 normalize 逻辑里统一补齐
3. 后续主循环尽量只读规范化后的字段
### 需要修改的代码
- `connect/device_storage.go`
- `main.go`
## 4. `/dhlr/forbid_time` 还没真正接入 `fire_leave` 暂停上报
### 问题
按当前确认后的业务:
- `/dhlr/forbid_time` 是控制 `fire_leave` 暂停上报窗口
- `fire_check` 仍要继续正常上报
但当前代码主要做的是:
- 缓存 `minute`
- 缓存 `fire_leave_timeout` / `fire_check_timeout`
- 服务于 `reporter` 里的事件周期逻辑
没有真正控制 `fire_leave` 主报警判定。
### 风险
- 按钮按下后,现场期待的“暂时允许离岗”不会真正生效
- 现场仍可能连续收到不符合预期的 `fire_leave` 报警
### 准备怎么改
`/dhlr/forbid_time` 从“事件周期配置”改为“主报警暂停窗口控制”。
建议方案:
1. 在设备级状态里增加 `fire_leave` 暂停截止时间
2. 收到 `/dhlr/forbid_time` 后更新这个截止时间
3.`fire_leave` 报警判定前先检查暂停窗口
4. 暂停期内继续更新人数、温度,但跳过 `fire_leave` 上报
5. `fire_check` 保持独立
### 需要修改的代码
- `main.go`
- `reporter/reporter.go`
- `gpio_monitor/gpio_monitor.go`
- 可能涉及 `connect/ws_channel.go` 的控制消息分发
## 5. GPIO 按钮逻辑和真实业务存在偏差
### 问题
当前按钮逻辑更接近“切换事件周期”,而不是“暂停 `fire_leave` 上报”。
### 风险
- 代码行为和现场业务理解不一致
- 后续维护时容易误判
### 准备怎么改
将 GPIO 按钮逻辑收敛为:
- 触发本地允许离岗窗口
- 或配合 `/dhlr/forbid_time` 完成暂停控制
### 需要修改的代码
- `gpio_monitor/gpio_monitor.go`
- `reporter/reporter.go`
- `main.go`
## 6. WebSocket 协议需要整体切换到 protobuf binary frame
### 问题
当前协议层是:
- JSON
- 文本帧
- `method + params + result + error`
目标协议是:
- protobuf
- binary frame
- `REQUEST / REPLY / CAST`
### 风险
- 现有 `ws_channel.go` 不能局部修补
- 如果只改部分字段,会造成协议不兼容
### 准备怎么改
按“先替换协议层,尽量少动业务层接口”的原则改。
建议方案:
1. 保留上层调用接口:
- `RegisterService`
- `SubscribeTopic`
- `SendAsync`
2. 内部把 JSON 封装替换为 protobuf 编解码
3. 下行从 `publish` 改为 `topic_event`
4. 上行从 JSON `metric_data` 改为 `ServiceCast.metric_data`
5. 内层业务 payload 继续保留当前 JSON 结构
### 需要修改的代码
- `connect/ws_channel.go`
- `reporter/reporter.go`
- 可能新增 `connect/pb/``connect/protocol/`
## 7. 主动 ping 建议保留,失败立即重连
### 问题
如果不主动 ping
- 死连接发现会变慢
- 空闲连接可能被网络设备静默回收
- 只有下次读写时才发现断连
### 风险
- `/dhlr/alert`
- `/dhlr/forbid_time`
- 事件上报链路
以上三类能力恢复变慢。
### 准备怎么改
继续保留主动 ping。
建议方案:
1. 定时发送 websocket ping frame
2. ping 失败时立即标记断连
3. 触发重连
4. 重连后重新 register / subscribe
### 需要修改的代码
- `connect/ws_channel.go`
## 8. 业务上报仍然是“只发不确认”
### 问题
当前:
- `register`
- `subscribe`
这类请求有协议层确认。
但:
- 报警事件上报
- 数据快照上报
仍然是单向发送。
### 风险
- 只能确认本地是否成功写出 websocket 消息
- 不能确认 efka 是否真正处理成功
- 不能确认上层平台是否真正收到、入库、展示
### 当前处理决定
这一项先不解决,只记录风险。
### 相关代码
- `connect/ws_channel.go`
- `reporter/reporter.go`
## 9. 运行中配置更新链路未闭环
### 问题
当前有:
- `ConfigUpdateChan` 生产者
但没有看到清晰的消费者和热更新执行链路。
### 风险
- 代码让人以为支持实时热更新
- 实际运行中可能并不会生效
### 准备怎么改
先做一次取舍:
- 要么补齐热更新
- 要么明确去掉这条未完成链路
### 需要修改的代码
- `connect/ws_channel.go`
- `main.go`
## 10. 自动化验证不足
### 问题
当前环境没有 `go`,也没有看到覆盖关键链路的自动化测试。
### 风险
- 协议层改造、重启链路改造、报警暂停逻辑改造容易引入回归
### 准备怎么改
至少补最小化验证路径。