小蜜陪护机器人 - scene_handler.js 场景报警脚本开发指南

发布时间:2026/7/19 7:05:29
小蜜陪护机器人 - scene_handler.js 场景报警脚本开发指南 scene_handler.js 场景报警脚本开发指南1. 概述scene_handler.js位于bin/sceneconfigs/scripts/是本项目的场景报警处理脚本由 C 场景处理引擎SceneProcessingManager托管执行。它接收每帧 AI 推理结果人物姿态、物体检测、火灾检测等结合可配置的报警参数判断是否触发语音告警TTS或弹窗提醒。脚本运行在嵌入的 JavaScript 引擎中通过两个系统回调函数与 C 层交互回调函数调用时机作用onSceneInit(configJson)系统启动时调用一次接收并存储场景配置参数onSceneAnalysis(analysisJson)每次推理拍照分析完成后接收分析结果执行报警逻辑2. analysisJson 传递了哪些参数analysisJson是一个 JSON 字符串由 C 中的buildAnalysisJson()方法src/scene_processing_manager.cpp构建并序列化后下发给 JS。function onSceneAnalysis(analysisJson) { var data JSON.parse(analysisJson); var deviceName data.deviceName; // 设备名称如 客厅摄像头 var clientInfo data.clientInfo; // 客户端唯一标识 var personCount data.personCount; // 检测到的人数 var poses data.poses; // 所有人物姿态关键点数组 var tracking data.poseTracking; // 单人姿态追踪结果仅单人时有效 var objects data.objects; // 物体检测结果数组 var fireDets data.fireDetections; // 火灾检测结果数组 var devState data.deviceAlarmState; // C 层面维护的设备报警初始状态 }2.1 顶层字段字段类型说明timestampstringISO 8601 时间戳如2026-07-18T14:30:00deviceNamestring设备名称包含场景关键词如客厅摄像头、厨房摄像头clientInfostring客户端唯一标识用于区分不同设备的状态imageSizeobject{ width, height, fileKB }— 原始图片尺寸和文件大小imagePathstring保存的照片相对于photoshop目录的路径personCountnumber检测到的人物数量2.2poses— 人物姿态关键点数组每人一个元素{ personId: 0, // 人物编号 confidence: 0.95, // 检测置信度 boundingBox: { x, y, width, height }, // 人物边界框 poseType: 站立, // 姿态类型站立/坐立/躺卧 keypoints: [ { name: nose, x: 320, y: 150, confidence: 0.9 }, { name: leftEye, x: 310, y: 140, confidence: 0.85 }, // ... 共 17 个 COCO 关键点从 nose → rightAnkle ] }17 个关键点依次为nose,leftEye,rightEye,leftEar,rightEar,leftShoulder,rightShoulder,leftElbow,rightElbow,leftWrist,rightWrist,leftHip,rightHip,leftKnee,rightKnee,leftAnkle,rightAnkle。2.3poseTracking— 单人姿态追踪结果仅personCount 1时存在由 C PoseTracker 在每帧间追踪同一人物的姿态变化和移动状态字段类型说明currentPosestring当前帧姿态站立/坐立/躺卧previousPosestring上一帧姿态poseChangedboolean姿态是否从上帧发生变化poseDurationSecnumber当前姿态已持续的秒数currentBoxobject当前帧人物边界框{ x, y, width, height }previousBoxobject上一帧人物边界框hasMovedboolean人物是否发生移动moveDistancenumber人物中心点移动的像素距离moveDistanceRationumber移动距离相对于图片对角线的比例jointMoveDistancenumber所有关节点平均移动距离jointMoveRationumber关节点移动距离相对于图片对角线的比例poseChangeCountnumber姿态变化次数累计stationaryDurationSecnumber静止持续时间秒— 从上次移动/首次检测算起stationaryDurationSec是报警逻辑的核心依据。C 侧 PoseTracker 自动计算和重置JS 侧直接读取使用即可无需自行计时。2.4objects— 物体检测结果由 YOLOv8 目标检测模型输出包含场景中识别到的物体[ { className: chair, // 物体类别名 classId: 56, // COCO 类别 ID confidence: 0.87, // 检测置信度 boundingBox: { x: 100, y: 200, width: 80, height: 120 } } ]脚本中通过家具类别 ID (56椅子,57沙发,59床) 判断人物是否在家具上。2.5fireDetections— 火灾检测结果结构与objects相同包含检测到的火焰/烟雾物体。当检测到火焰时脚本进入火灾报警流程独立于人数判断。2.6deviceAlarmState— 设备报警初始状态C 层维护的设备报警状态快照用于 JS 侧首次初始化时继承已有的报警次数和时间{ lastAlarmTime: 0, // 上次报警的毫秒时间戳 alarmCount: 0 // 报警累积次数 }3. gSceneConfig 参数在哪里设置3.1 配置文件位置场景配置存储在bin/sceneconfigs/configs/目录下的 JSON 文件中文件对应场景在 JS 中的访问路径living.json客厅gSceneConfig.livingkitchen.json厨房gSceneConfig.kitchenbedroom.json卧室gSceneConfig.bedroombathroom.json厕所gSceneConfig.bathroom3.2 JSON 配置格式每个配置文件的结构如下以living.json为例{ sceneName: 客厅, sceneDesc: 客厅场景区域配置, parameters: { chatMinutes: { name: 主动搭话时长分钟, desc: 客厅单人站立超过设定时间后主动搭话关心用户, value: 3 }, stationaryMinutes: { name: 静止时长分钟, desc: 人物持续静止超过设定时间后触发报警, value: 30 }, noFurnitureMinutes: { name: 倒地时长分钟, desc: 人物躺卧/坐立且不在家具上超过设定时间后触发报警, value: 5 }, repeatIntervalMinutes: { name: 重复告警间隔分钟, desc: 同种告警重复触发的间隔时间, value: 1 }, popupAfterAlarms: { name: 弹出提醒次数, desc: 触发告警后弹出提醒的累积次数, value: 3 } } }3.3 下发到 JS 的流程living.json / kitchen.json / bedroom.json / bathroom.json │ ▼ dispatchSceneConfig() [C] 1. 遍历 configs/ 目录下所有 *.json 2. 读取每个文件提取 parameters.{key}.value → 扁平化为 {key: value} 3. 以文件名不含 .json为键组装到根对象中 4. 序列化为 JSON 字符串 5. 调用所有脚本的 onSceneInit(configJson) │ ▼ JS: gSceneConfig JSON.parse(configJson)最终gSceneConfig的结构为{ living: { sceneName: 客厅, sceneDesc: 客厅场景区域配置, chatMinutes: 3, partyMinutes: 1, partyRepeatMinutes: 10, stationaryMinutes: 30, noFurnitureMinutes: 5, repeatIntervalMinutes: 1, popupAfterAlarms: 3 }, kitchen: { sceneName: 厨房, stationaryMinutes: 5, kitchenFireMinutes: 5, repeatIntervalMinutes: 1, popupAfterAlarms: 3 }, bedroom: { /* ... */ }, bathroom: { /* ... */ } }3.4 GUI 编辑运行时可修改系统提供了一个场景设置对话框scene_settings_dialog.h/cpp可以在运行时通过图形界面修改配置值。每个场景对应一个 Tab 页每个参数一个 SpinBox。修改保存后会立即更新到对应的 JSON 文件下次下发配置时生效。3.5 各场景参数说明客厅 (living)参数默认值说明chatMinutes3单人站立超过 N 分钟后主动 TTS 搭话partyMinutes13 人以上聚会持续 N 分钟后搭话partyRepeatMinutes10聚会搭话后的重复间隔stationaryMinutes30静止超过 N 分钟后报警noFurnitureMinutes5躺卧/坐立且不在家具上超过 N 分钟后报警repeatIntervalMinutes1重复告警的最小间隔popupAfterAlarms3累积报警 N 次后弹出系统弹窗厨房 (kitchen)参数默认值说明stationaryMinutes5静止超过 N 分钟后报警kitchenFireMinutes5有火无人持续时间超过 N 分钟后火灾报警repeatIntervalMinutes1重复告警间隔popupAfterAlarms3弹窗累积次数卧室 (bedroom)参数默认值说明stationaryMinutes30静止超过 N 分钟后报警noFurnitureMinutes5躺卧/坐立不在床上超过 N 分钟后报警repeatIntervalMinutes1重复告警间隔popupAfterAlarms3弹窗累积次数厕所 (bathroom)参数默认值说明noFurnitureMinutes5躺卧/坐立静止超时报警repeatIntervalMinutes1重复告警间隔popupAfterAlarms3弹窗累积次数4. 如何在脚本中使用4.1 读取 gSceneConfig在场景处理函数中通过场景名获取对应的配置对象使用||提供默认值function checkLivingRoom(data, tracking, objects, state, now, clientInfo) { var cfg gSceneConfig.living || {}; // 安全读取 var stationaryMin cfg.stationaryMinutes || 30; // 带默认值 var noFurnMin cfg.noFurnitureMinutes || 5; var repeatMin cfg.repeatIntervalMinutes || 1; var popupCount cfg.popupAfterAlarms || 3; // C PoseTracker 提供的计时值秒 var stationarySec tracking.stationaryDurationSec || 0; // 判断静止是否超过配置阈值 if (stationarySec stationaryMin * 60.0 !tracking.hasMoved) { // 触发报警... } }4.2 全局对象脚本中可直接使用以下全局对象对象方法说明sceneManager.alarmTts(text)语音播报告警文本sceneManager.alarmPopup(title, text)弹出系统提醒窗口sceneManager.log(msg)输出日志到控制台sceneManager.isRunning()检查系统是否运行中printlogprintlog(level, msg)日志输出0INFO,1WARN,2ERROR4.3 工具函数脚本内置了两个工具函数calcOverlapRatio(personBox, objBox)计算人物框与物体框的交叠比例IoU-like返回值 0.0~1.0detectFurnitureUnderPerson(personBox, objects)检测人物下方是否有家具椅子/沙发/床返回中文名称或空字符串4.4 场景分发逻辑onSceneAnalysis根据deviceName中包含的关键词决定分发到哪个场景处理函数if (deviceName.indexOf(客厅) 0) { checkLivingRoom(data, tracking, objects, state, now, clientInfo); } else if (deviceName.indexOf(厨房) 0) { checkKitchen(data, tracking, objects, state, now, clientInfo); } else if (deviceName.indexOf(卧室) 0) { checkBedroom(data, tracking, objects, state, now, clientInfo); } else if (deviceName.indexOf(厕所) 0) { checkBathroom(data, tracking, state, now, clientInfo); }5. 完整小例子以下是一个简化但完整的自定义场景脚本展示如何在scene_handler.js框架下添加新的场景处理假设我们要为「阳台」场景添加报警——站太久提醒用户回室内。步骤 1创建场景配置文件在bin/sceneconfigs/configs/创建balcony.json{ sceneName: 阳台, sceneDesc: 阳台场景区域配置, parameters: { standingMinutes: { name: 站立提醒时长分钟, desc: 阳台单人站立超过设定时间后提醒回室内, value: 10 }, repeatIntervalMinutes: { name: 重复提醒间隔分钟, desc: 重复提醒的最小间隔, value: 5 } } }步骤 2在 scene_handler.js 中添加场景处理函数在scene_handler.js末尾添加以下代码// ═══════════════════════════════════════════════════════════════ // 场景5: 阳台 (Balcony) — 1条规则站太久提醒 // ═══════════════════════════════════════════════════════════════ function checkBalcony(data, tracking, state, now, clientInfo) { // 1. 读取场景配置提供默认值 var cfg gSceneConfig.balcony || {}; var standingMin cfg.standingMinutes || 10; var repeatMin cfg.repeatIntervalMinutes || 5; // 2. 从 tracking 中读取 C PoseTracker 计算的静止时长 var stationarySec tracking.stationaryDurationSec || 0; // 3. 判断站立 静止超过阈值 没有移动 if (tracking.currentPose 站立 !tracking.hasMoved stationarySec standingMin * 60.0) { // 4. 检查重复间隔避免频繁提醒 var repeatMs repeatMin * 60 * 1000; if (state.lastAlarmTime 0 || (now - state.lastAlarmTime) repeatMs) { // 5. 随机选择一句提醒语 var phrases [ 您在阳台站了 standingMin 分钟了外面风大回屋里吧, 阳台上站太久了小心着凉哦您要不要进屋休息一下, 已经站了 standingMin 分钟了回客厅坐坐吧我给您放点音乐。 ]; var idx Math.floor(Math.random() * phrases.length); // 6. TTS 语音播报 sceneManager.alarmTts(phrases[idx]); sceneManager.log([ALARM-Balcony] phrases[idx]); // 7. 更新时间戳 state.lastAlarmTime now; } } }步骤 3在 onSceneAnalysis 中注册分发修改onSceneAnalysis中的分发逻辑添加阳台场景的判断function onSceneAnalysis(analysisJson) { // ... 前面的代码不变 ... // ── 根据设备名匹配场景分发 ── if (deviceName.indexOf(客厅) 0) { checkLivingRoom(data, tracking, objects, state, now, clientInfo); } else if (deviceName.indexOf(厨房) 0) { checkKitchen(data, tracking, objects, state, now, clientInfo); } else if (deviceName.indexOf(卧室) 0) { checkBedroom(data, tracking, objects, state, now, clientInfo); } else if (deviceName.indexOf(厕所) 0) { checkBathroom(data, tracking, state, now, clientInfo); } else if (deviceName.indexOf(阳台) 0) { checkBalcony(data, tracking, state, now, clientInfo); // ← 新增 } }运行效果系统启动时dispatchSceneConfig()读取balcony.json下发给onSceneInitgSceneConfig.balcony被赋值每次「阳台摄像头」拍照分析后onSceneAnalysis被调用deviceName包含阳台→ 路由到checkBalcony()当检测到单人站立超过 10 分钟且无移动时TTS 播报提醒 您在阳台站了10分钟了外面风大回屋里吧之后每隔 5 分钟repeatIntervalMinutes重复提醒一次6. 架构总结┌─────────────────────────────────────────────────────┐ │ 系统启动 │ │ dispatchSceneConfig() │ │ 读取 configs/*.json → onSceneInit(configJson) │ │ → gSceneConfig {...} │ └─────────────────────┬───────────────────────────────┘ │ ┌─────────────────────▼───────────────────────────────┐ │ 每帧推理 │ │ 拍照 → YOLOv8 推理 → buildAnalysisJson() │ │ → dispatchToScripts(analysis) │ │ → onSceneAnalysis(analysisJson) │ │ ┌──────────────────────────────┐ │ │ │ data.poseTracking │ │ │ │ data.objects │ │ │ │ data.fireDetections │ │ │ │ data.personCount │ │ │ │ data.deviceName │ │ │ └──────────┬───────────────────┘ │ │ │ │ │ ┌──────────▼───────────────────┐ │ │ │ 按 deviceName 分发到场景 │ │ │ │ 客厅/厨房/卧室/厕所/阳台 │ │ │ │ 火灾检测 │ │ │ └──────────┬───────────────────┘ │ │ │ │ │ ┌──────────▼───────────────────┐ │ │ │ 读取 gSceneConfig.xxx │ │ │ │ 对比阈值 → 触发报警 │ │ │ │ sceneManager.alarmTts() │ │ │ │ sceneManager.alarmPopup() │ │ │ └──────────────────────────────┘ │ └─────────────────────────────────────────────────────┘关键设计原则C 负责计算姿态识别、持续时间计时、移动检测JS 只做决策和行动配置参数热可调无需重启即可通过 JSON 文件或 GUI 修改阈值场景处理函数相互独立每个场景有独立的配置、状态和报警逻辑