# 动火离人微服务业务逻辑梳理 本文档基于当前代码整理,目标是帮助快速回忆这个微服务现在“实际在做什么”,以及哪些逻辑是设计上存在但当前代码里已经弱化或未真正生效的。 ## 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 当前代码实现差异”文档,专门帮你找现在代码和你脑中业务之间的偏差。