AI 驱动的家庭场景识别:自动切换影音、休息与工作模式界面

AI 驱动的家庭场景识别:自动切换影音、休息与工作模式界面 AI 驱动的家庭场景识别自动切换影音、休息与工作模式界面一、引子模式切换不应该是一个按钮在智能家居产品评审会上产品经理指着设计稿说这里加一个模式切换按钮用户点击后弹出四个选项影音模式、阅读模式、休息模式、工作模式。技术上行得通交互上是个灾难。模式切换按钮是场景自动化的反面——它把根据上下文自动判断应该进入什么模式的智能需求退化成了请你手动告诉我你想干什么的传统交互。真正的场景自动化应该是无感的。用户走进客厅、打开投影仪、拉上窗帘——这三个动作的组合已经告诉系统我要看电影了。系统的任务不是弹出确认框问是否开启影音模式而是静默执行灯光调暗到 10%、氛围灯设为暖色调、音响切到 HDMI ARC、空调设为静音模式。只在首次执行时给出一个轻微的通知已自动切换到影音模式点击可调整。场景识别的技术本质是一个多传感器融合的意图分类问题。输入是设备状态变化、时间、位置、人体感应等多维信号输出是一个场景标签和置信度。难点不在分类算法本身而在分类的时机——既不能反应太慢用户都开始看电影了灯光还没暗也不能反应太快用户只是路过客厅拿个东西就被判定为要休息。二、底层机制多信号融合的场景分类器场景识别需要一组弱信号的组合推理单个信号都不可靠但多个信号的交叉验证可以大幅提升置信度。关键设计决策在于置信度分级执行策略。不是所有场景识别结果都应该自动执行。将置信度分为三档高置信度 0.85投影仪开机 窗帘关闭 时间是晚上 人在客厅持续 10 秒以上 → 自动切换影音模式通知已切换至影音模式中置信度0.6 - 0.85窗帘关闭 时间晚上 但投影仪未开 → 建议切换检测到你可能想看视频需要切换到影音模式吗低置信度 0.6仅窗帘关闭 → 忽略等待更多信号分类器的核心输入信号权重如下设备主动操作用户点了什么权重 0.4人体传感器数据权重 0.25时间上下文权重 0.2历史行为模式权重 0.15三、生产级代码场景识别引擎/** * 家庭场景识别引擎 * * 基于多传感器信号融合的意图分类器。 * 输入原始传感器事件流 * 输出场景标签 置信度 执行策略 */ // 场景标签 type SceneLabel | movie_time // 影音模式 | reading // 阅读模式 | resting // 休息模式 | working // 工作模式 | dining // 用餐模式 | leaving // 离家模式 | sleeping // 睡眠模式 | unknown; // 无法识别 // 传感器事件 interface SensorEvent { type: device_state | presence | time | audio; deviceId?: string; property?: string; value: number | boolean | string; timestamp: number; } // 场景识别结果 interface SceneRecognition { label: SceneLabel; confidence: number; // 0-1 strategy: auto | suggest | ignore; reasoning: string[]; // 可解释的推理步骤 suggestedActions: SceneAction[]; } // 场景动作 interface SceneAction { deviceId: string; action: string; params: Recordstring, any; priority: number; } /** * 场景识别器 * * 使用加权规则引擎而非深度学习模型确保 * 1. 推理过程可解释调试时能看懂为什么触发 * 2. 冷启动友好不需要训练数据 * 3. 延迟极低 50ms适合实时场景 */ class SceneRecognizer { // 滑动窗口保留最近 30 秒的传感器事件 private eventWindow: SensorEvent[] []; private readonly WINDOW_MS 30000; // 场景识别规则表 // 每条规则定义了一个场景的信号组合和权重 private readonly rules: SceneRule[] [ { label: movie_time, signals: [ { type: device_state, value: [projector_power_on, tv_power_on], weight: 0.3 }, { type: device_state, value: [curtain_closed], weight: 0.2 }, { type: time, value: [evening, night], weight: 0.15 }, { type: presence, value: [person_in_living_room], weight: 0.2 }, { type: audio, value: [movie_sound], weight: 0.15 }, ], }, { label: sleeping, signals: [ { type: time, value: [late_night], weight: 0.3 }, { type: presence, value: [person_in_bedroom], weight: 0.25 }, { type: device_state, value: [all_lights_off], weight: 0.25 }, { type: device_state, value: [bedroom_curtain_closed], weight: 0.2 }, ], }, { label: leaving, signals: [ { type: device_state, value: [lock_locked], weight: 0.3 }, { type: presence, value: [no_one_home], weight: 0.4 }, { type: time, value: [morning], weight: 0.15 }, { type: device_state, value: [all_lights_off], weight: 0.15 }, ], }, { label: reading, signals: [ { type: device_state, value: [reading_lamp_on], weight: 0.4 }, { type: presence, value: [person_stationary], weight: 0.2 }, { type: time, value: [evening], weight: 0.2 }, { type: audio, value: [silence], weight: 0.2 }, ], }, ]; // 历史场景记录用于模式学习 private sceneHistory: { label: SceneLabel; timestamp: number }[] []; /** * 接收传感器事件并触发场景识别 * * 使用去抖动策略事件发生后等待 2 秒 * 收集窗口内的所有相关事件再统一分析 */ private debounceTimer: number | null null; onSensorEvent(event: SensorEvent): void { this.eventWindow.push(event); // 清理过期事件 const cutoff Date.now() - this.WINDOW_MS; this.eventWindow this.eventWindow.filter( (e) e.timestamp cutoff ); // 去抖2 秒内连续事件只触发一次识别 if (this.debounceTimer) clearTimeout(this.debounceTimer); this.debounceTimer window.setTimeout(() { this.recognize(); }, 2000); } /** * 执行场景识别 */ recognize(): SceneRecognition { let bestLabel: SceneLabel unknown; let bestConfidence 0; let bestReasoning: string[] []; let bestActions: SceneAction[] []; // 遍历所有场景规则 for (const rule of this.rules) { let confidence 0; const reasoning: string[] []; const matchedSignals: string[] []; // 检查每条信号 for (const signal of rule.signals) { const isMatched this.matchSignal( signal, this.eventWindow ); if (isMatched) { confidence signal.weight; matchedSignals.push(this.describeSignal(signal)); } else { reasoning.push(未检测到: ${this.describeSignal(signal)}); } } // 应用历史模式加成 const historyBoost this.getHistoryBoost(rule.label); confidence historyBoost; reasoning.unshift( 场景「${rule.label}」匹配度: ${(confidence * 100).toFixed(0)}%, 匹配信号: ${matchedSignals.join(, ) || 无} ); if (confidence bestConfidence) { bestConfidence confidence; bestLabel rule.label; bestReasoning reasoning; bestActions this.getActionsForScene(rule.label); } } // 置信度封顶在 1.0 bestConfidence Math.min(bestConfidence, 1.0); return { label: bestLabel, confidence: bestConfidence, strategy: this.determineStrategy(bestConfidence, bestLabel), reasoning: bestReasoning, suggestedActions: bestActions, }; } /** * 信号匹配检查 * * 在事件窗口中查找匹配的传感器事件 */ private matchSignal( signal: SceneSignal, events: SensorEvent[] ): boolean { const matchingEvents events.filter((e) { if (e.type ! signal.type) return false; // 值匹配支持数组多值匹配 if (Array.isArray(signal.value)) { return signal.value.some((v) this.valueMatches(e.value, v)); } return this.valueMatches(e.value, signal.value); }); return matchingEvents.length 0; } /** * 值匹配逻辑 */ private valueMatches( actual: number | boolean | string, expected: number | boolean | string ): boolean { // 字符串模糊匹配 if (typeof actual string typeof expected string) { return actual.toLowerCase().includes(expected.toLowerCase()); } return actual expected; } /** * 信号描述用于可解释性 */ private describeSignal(signal: SceneSignal): string { const descriptions: Recordstring, string { projector_power_on: 投影仪已开机, curtain_closed: 窗帘已关闭, all_lights_off: 全部灯光已关闭, lock_locked: 门锁已上锁, no_one_home: 家中无人, person_in_living_room: 客厅有人, person_in_bedroom: 卧室有人, person_stationary: 人处于静止状态, evening: 当前时段傍晚, night: 当前时段夜间, late_night: 当前时段深夜, morning: 当前时段早晨, silence: 环境安静, reading_lamp_on: 阅读灯已开启, }; return descriptions[String(signal.value)] || String(signal.value); } /** * 历史模式加成 * * 最近 7 天同一时段该场景出现频次越高加成越大 */ private getHistoryBoost(label: SceneLabel): number { const now new Date(); const currentHour now.getHours(); const dayOfWeek now.getDay(); // 查找最近 7 天同一时段的历史 const recentMatches this.sceneHistory.filter((h) { const hDate new Date(h.timestamp); const hourDiff Math.abs(hDate.getHours() - currentHour); return h.label label hourDiff 1; }); // 7 天中出现了 4 天以上加成 0.1 return recentMatches.length 4 ? 0.1 : 0; } /** * 确定执行策略 */ private determineStrategy( confidence: number, label: SceneLabel ): auto | suggest | ignore { if (confidence 0.55) return ignore; // 离家模式即使中等置信度也自动执行安全优先 if (label leaving confidence 0.7) return auto; // 睡眠模式需要高置信度 if (label sleeping confidence 0.85) return auto; // 通用策略 if (confidence 0.85) return auto; if (confidence 0.55) return suggest; return ignore; } /** * 获取场景对应的操作序列 */ private getActionsForScene(label: SceneLabel): SceneAction[] { const actionMap: Recordstring, SceneAction[] { movie_time: [ { deviceId: living-light-main, action: setBrightness, params: { value: 5 }, priority: 1 }, { deviceId: living-light-ambient, action: setColorTemp, params: { value: 3200 }, priority: 2 }, { deviceId: curtain-living, action: setPosition, params: { value: 0 }, priority: 1 }, { deviceId: speaker-main, action: setInput, params: { value: hdmi_arc }, priority: 3 }, { deviceId: ac-living, action: setMode, params: { value: quiet }, priority: 4 }, ], sleeping: [ { deviceId: all-lights, action: setPower, params: { value: false }, priority: 1 }, { deviceId: curtain-bedroom, action: setPosition, params: { value: 0 }, priority: 1 }, { deviceId: thermostat, action: setTemperature, params: { value: 26 }, priority: 2 }, ], leaving: [ { deviceId: all-lights, action: setPower, params: { value: false }, priority: 1 }, { deviceId: all-curtains, action: setPosition, params: { value: 0 }, priority: 1 }, { deviceId: ac-all, action: setPower, params: { value: false }, priority: 2 }, { deviceId: lock-front, action: lock, params: {}, priority: 0 }, ], }; return actionMap[label] || []; } /** * 记录场景切换用于历史学习 */ recordScene(label: SceneLabel): void { this.sceneHistory.push({ label, timestamp: Date.now() }); // 保留最近 30 天数据 const cutoff Date.now() - 30 * 24 * 3600 * 1000; this.sceneHistory this.sceneHistory.filter( (h) h.timestamp cutoff ); } } // 类型补充 interface SceneSignal { type: device_state | presence | time | audio; value: string | number | boolean | (string | number | boolean)[]; weight: number; } interface SceneRule { label: SceneLabel; signals: SceneSignal[]; }四、边界分析误触发是最大的信任杀手场景识别的错误分两类——误报false positive和漏报false negative。误报的代价远大于漏报。系统在用户没有看电影时自动关了灯用户会觉得这破系统又在自作主张。系统在用户看电影时没关灯用户会觉得哦可能没识别到手动关一下就好。设计上应该偏保守宁可漏报 20%也不能误报超过 5%。连续场景切换抖动用户从客厅走到卧室可能触发影音→行走→休息三次连续切换。UI 闪烁会让用户困惑。解决方案是引入最小场景停留时间——一旦切换到一个场景至少停留 30 秒才能再次切换。多成员家庭的冲突爸爸在客厅看电影影音模式妈妈在卧室看书阅读模式系统应该按哪个场景答案是按设备归属性——每个场景的操作限定在目标房间的设备范围内。客厅的影音模式不应该影响卧室的灯光。五、总结场景自动化应该是无感切换而非弹出模式选择对话框多传感器融合的置信度分级auto/suggest/ignore是场景识别的正确交互模型加权规则引擎优于深度学习模型可解释性在场景识别中比准确率更重要误报false positive比漏报false negative更伤害用户信任连续切换需要 30 秒最小停留时间防止 UI 抖动历史模式加成7 天同时段频次可以提升 10% 的识别准确率场景操作应限定在目标房间范围内避免多成员场景冲突