小蜜陪护机器人 - 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 顶层字段
| 字段 | 类型 | 说明 |
|---|---|---|
timestamp |
string | ISO 8601 时间戳,如 "2026-07-18T14:30:00" |
deviceName |
string | 设备名称,包含场景关键词,如 "客厅摄像头"、"厨房摄像头" |
clientInfo |
string | 客户端唯一标识,用于区分不同设备的状态 |
imageSize |
object | { width, height, fileKB } — 原始图片尺寸和文件大小 |
imagePath |
string | 保存的照片相对于 photoshop 目录的路径 |
personCount |
number | 检测到的人物数量 |
2.2 poses — 人物姿态关键点数组
每人一个元素:
{
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.3 poseTracking — 单人姿态追踪结果
仅 personCount === 1 时存在,由 C++ PoseTracker 在每帧间追踪同一人物的姿态变化和移动状态:
| 字段 | 类型 | 说明 |
|---|---|---|
currentPose |
string | 当前帧姿态:"站立"/"坐立"/"躺卧" |
previousPose |
string | 上一帧姿态 |
poseChanged |
boolean | 姿态是否从上帧发生变化 |
poseDurationSec |
number | 当前姿态已持续的秒数 |
currentBox |
object | 当前帧人物边界框 { x, y, width, height } |
previousBox |
object | 上一帧人物边界框 |
hasMoved |
boolean | 人物是否发生移动 |
moveDistance |
number | 人物中心点移动的像素距离 |
moveDistanceRatio |
number | 移动距离相对于图片对角线的比例 |
jointMoveDistance |
number | 所有关节点平均移动距离 |
jointMoveRatio |
number | 关节点移动距离相对于图片对角线的比例 |
poseChangeCount |
number | 姿态变化次数(累计) |
stationaryDurationSec |
number | 静止持续时间(秒) — 从上次移动/首次检测算起 |
stationaryDurationSec是报警逻辑的核心依据。C++ 侧 PoseTracker 自动计算和重置,JS 侧直接读取使用即可,无需自行计时。
2.4 objects — 物体检测结果
由 YOLOv8 目标检测模型输出,包含场景中识别到的物体:
[
{
className: "chair", // 物体类别名
classId: 56, // COCO 类别 ID
confidence: 0.87, // 检测置信度
boundingBox: { x: 100, y: 200, width: 80, height: 120 }
}
]
脚本中通过家具类别 ID (56=椅子, 57=沙发, 59=床) 判断人物是否在家具上。
2.5 fireDetections — 火灾检测结果
结构与 objects 相同,包含检测到的火焰/烟雾物体。当检测到火焰时,脚本进入火灾报警流程(独立于人数判断)。
2.6 deviceAlarmState — 设备报警初始状态
C++ 层维护的设备报警状态快照,用于 JS 侧首次初始化时继承已有的报警次数和时间:
{
lastAlarmTime: 0, // 上次报警的毫秒时间戳
alarmCount: 0 // 报警累积次数
}
3. gSceneConfig 参数在哪里设置
3.1 配置文件位置
场景配置存储在 bin/sceneconfigs/configs/ 目录下的 JSON 文件中:
| 文件 | 对应场景 | 在 JS 中的访问路径 |
|---|---|---|
living.json |
客厅 | gSceneConfig.living |
kitchen.json |
厨房 | gSceneConfig.kitchen |
bedroom.json |
卧室 | gSceneConfig.bedroom |
bathroom.json |
厕所 | gSceneConfig.bathroom |
3.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)
| 参数 | 默认值 | 说明 |
|---|---|---|
chatMinutes |
3 | 单人站立超过 N 分钟后主动 TTS 搭话 |
partyMinutes |
1 | 3 人以上聚会持续 N 分钟后搭话 |
partyRepeatMinutes |
10 | 聚会搭话后的重复间隔 |
stationaryMinutes |
30 | 静止超过 N 分钟后报警 |
noFurnitureMinutes |
5 | 躺卧/坐立且不在家具上超过 N 分钟后报警 |
repeatIntervalMinutes |
1 | 重复告警的最小间隔 |
popupAfterAlarms |
3 | 累积报警 N 次后弹出系统弹窗 |
厨房 (kitchen)
| 参数 | 默认值 | 说明 |
|---|---|---|
stationaryMinutes |
5 | 静止超过 N 分钟后报警 |
kitchenFireMinutes |
5 | 有火无人持续时间超过 N 分钟后火灾报警 |
repeatIntervalMinutes |
1 | 重复告警间隔 |
popupAfterAlarms |
3 | 弹窗累积次数 |
卧室 (bedroom)
| 参数 | 默认值 | 说明 |
|---|---|---|
stationaryMinutes |
30 | 静止超过 N 分钟后报警 |
noFurnitureMinutes |
5 | 躺卧/坐立不在床上超过 N 分钟后报警 |
repeatIntervalMinutes |
1 | 重复告警间隔 |
popupAfterAlarms |
3 | 弹窗累积次数 |
厕所 (bathroom)
| 参数 | 默认值 | 说明 |
|---|---|---|
noFurnitureMinutes |
5 | 躺卧/坐立静止超时报警 |
repeatIntervalMinutes |
1 | 重复告警间隔 |
popupAfterAlarms |
3 | 弹窗累积次数 |
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() |
检查系统是否运行中 |
printlog |
printlog(level, msg) |
日志输出(0=INFO, 1=WARN, 2=ERROR) |
4.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,下发给onSceneInit,gSceneConfig.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 修改阈值
- 场景处理函数相互独立:每个场景有独立的配置、状态和报警逻辑
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)