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 个关键点依次为:noseleftEyerightEyeleftEarrightEarleftShoulderrightShoulderleftElbowrightElbowleftWristrightWristleftHiprightHipleftKneerightKneeleftAnklerightAnkle

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=INFO1=WARN2=ERROR

4.3 工具函数

脚本内置了两个工具函数:

  • calcOverlapRatio(personBox, objBox):计算人物框与物体框的交叠比例(IoU-like),返回值 0.0~1.0
  • detectFurnitureUnderPerson(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);   // ← 新增
    }
}

运行效果

  1. 系统启动时,dispatchSceneConfig() 读取 balcony.json,下发给 onSceneInitgSceneConfig.balcony 被赋值
  2. 每次「阳台摄像头」拍照分析后,onSceneAnalysis 被调用
  3. deviceName 包含 "阳台" → 路由到 checkBalcony()
  4. 当检测到单人站立超过 10 分钟且无移动时:TTS 播报提醒 "您在阳台站了10分钟了,外面风大,回屋里吧?"
  5. 之后每隔 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 修改阈值
  • 场景处理函数相互独立:每个场景有独立的配置、状态和报警逻辑
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐