Files
fire_leave_tool/docs/业务逻辑梳理.md
T
2026-05-12 15:45:50 +08:00

15 KiB
Raw Blame History

动火离人微服务业务逻辑梳理

本文档基于当前代码整理,目标是帮助快速回忆这个微服务现在“实际在做什么”,以及哪些逻辑是设计上存在但当前代码里已经弱化或未真正生效的。

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 包里,监听:

  • 按钮 GPIO111
  • 蜂鸣器 GPIO109

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_checkfire_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_leaveperson_count 参数没有直接用于报警判断

    • 当前代码写死判断 PersonCount == 0
    • 如果业务上原本支持“少于 N 人也报警”,那这部分目前没有实现
  5. param.fire_leave.confidenceparam.fire_check.confidence

    • 配置结构里有
    • 但实际启动命令仍统一使用 deviceData.Confidence

如果后面你继续描述前端和后端的预期业务,我可以再基于这份文档继续补一版“设计业务逻辑 vs 当前代码实现差异”文档,专门帮你找现在代码和你脑中业务之间的偏差。