HarmonyOS开发:场景联动------自动化场景
📌 核心要点:场景联动是智能家居的灵魂,条件-动作模型驱动自动化,定时触发和事件触发两种模式,场景冲突检测避免"空调开制热又开制冷"的尴尬。
背景与动机
你每天回家,手动开灯、开空调、关窗帘------三个动作,点六次。早上出门,关灯、关空调、关窗帘------又是六次。一天两次,一个月60次,一年720次。
你不烦吗?
场景联动就是解决这个问题的。一个触发条件,自动执行一组动作。 回家模式:开客厅灯 + 空调制冷26° + 窗帘半开。离家模式:全屋关灯 + 空调关闭 + 窗帘全关。一键搞定,甚至不用按------检测到你到家了,自动触发。
但场景联动做起来没那么简单。温度高于28°自动开空调,但你已经手动关了空调------场景要不要覆盖你的手动操作?两个场景同时触发,一个开灯一个关灯------听谁的?定时场景在App被杀后台后还能不能触发?
这些问题不解决,场景联动就不是"智能",而是"智障"。
核心原理
条件-动作模型
场景联动的核心是条件-动作模型(Condition-Action):当满足条件时,执行对应的动作。
渲染错误: Mermaid 渲染失败: Parse error on line 32: ...ss A,B,C,D,E,trigger class F,H,condi -----------------------^ Expecting 'SPACE', 'AMP', 'COLON', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'NEWLINE'
触发类型
| 触发类型 | 说明 | 示例 |
|---|---|---|
| 定时触发 | 到达指定时间触发 | 每天7:00开灯、工作日8:30关空调 |
| 事件触发 | 设备状态变化触发 | 温度>28°开空调、门打开开灯 |
| 手动触发 | 用户主动点击执行 | 回家模式、离家模式、睡眠模式 |
| 地理围栏 | 进入/离开指定区域触发 | 到家自动开灯、离家自动关灯 |
条件组合
条件可以组合,支持AND和OR:
- AND:所有条件都满足才触发。如"温度>28° AND 湿度>70%" → 开空调制冷+除湿
- OR:任一条件满足就触发。如"温度>28° OR 湿度>70%" → 开空调
实际项目中AND用得更多,因为场景通常需要多个条件同时满足才有意义。
动作列表
一个场景包含多个动作,按顺序执行:
json
{
"sceneId": "scene-home",
"name": "回家模式",
"actions": [
{ "deviceId": "light-001", "property": "power", "value": true },
{ "deviceId": "ac-001", "property": "power", "value": true },
{ "deviceId": "ac-001", "property": "temperature", "value": 26 },
{ "deviceId": "ac-001", "property": "mode", "value": "cool" },
{ "deviceId": "curtain-001", "property": "position", "value": 50 }
]
}
动作之间可以有延时:开灯后等2秒再开空调,避免同时发送太多指令导致设备处理不过来。
场景冲突
两个场景同时操作同一设备的同一属性,就是冲突。比如"高温开制冷"和"低温开制热"同时满足(虽然不太可能),或者"回家模式"开灯和"睡眠模式"关灯同时触发。
冲突检测规则:新场景优先级 > 旧场景优先级。 如果优先级相同,按触发时间排序,后触发的覆盖先触发的。
代码实战
基础用法:场景规则定义与执行引擎
定义场景规则的数据结构,实现场景执行引擎。
typescript
// services/SceneEngine.ets
// 场景联动引擎 - 规则定义、条件判断、动作执行
// 条件操作符
type ConditionOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'
// 触发条件
interface SceneCondition {
type: 'timer' | 'event' | 'geofence'
// 定时条件
cronExpression?: string // cron表达式,如'0 7 * * *'每天7:00
// 事件条件
deviceId?: string // 监听的设备ID
property?: string // 监听的属性
op?: ConditionOp // 比较操作符
value?: Object // 比较值
// 条件组合
logic?: 'and' | 'or' // 多条件的逻辑关系
subConditions?: SceneCondition[] // 子条件列表
}
// 场景动作
interface SceneAction {
deviceId: string // 目标设备ID
property: string // 控制属性
value: Object // 目标值
delay?: number // 延时(毫秒)
}
// 场景定义
interface SceneRule {
sceneId: string
name: string
icon?: Resource
enabled: boolean // 是否启用
priority: number // 优先级,数值越大优先级越高
conditions: SceneCondition[] // 触发条件(AND关系)
actions: SceneAction[] // 执行动作
createdAt: number
updatedAt: number
}
// 场景执行结果
interface SceneResult {
sceneId: string
success: boolean
executedActions: number // 成功执行的动作数
failedActions: number // 失败的动作数
conflicts: string[] // 冲突的场景ID列表
}
class SceneEngine {
// 已注册的场景规则
private rules: Map<string, SceneRule> = new Map()
// 正在执行的场景
private executingScenes: Set<string> = new Set()
// 动作执行回调
private actionExecutor?: (deviceId: string, property: string, value: Object) => Promise<boolean>
// 定时器映射
private timerMap: Map<string, number> = new Map()
// 注册动作执行器(由外部注入设备控制能力)
setActionExecutor(executor: (deviceId: string, property: string, value: Object) => Promise<boolean>): void {
this.actionExecutor = executor
}
// 添加场景规则
addRule(rule: SceneRule): void {
this.rules.set(rule.sceneId, rule)
if (rule.enabled) {
this.activateRule(rule)
}
}
// 移除场景规则
removeRule(sceneId: string): void {
const rule = this.rules.get(sceneId)
if (rule) {
this.deactivateRule(rule)
this.rules.delete(sceneId)
}
}
// 启用规则
enableRule(sceneId: string): void {
const rule = this.rules.get(sceneId)
if (rule) {
rule.enabled = true
this.activateRule(rule)
}
}
// 禁用规则
disableRule(sceneId: string): void {
const rule = this.rules.get(sceneId)
if (rule) {
rule.enabled = false
this.deactivateRule(rule)
}
}
// 激活规则(注册定时器等)
private activateRule(rule: SceneRule): void {
// 对定时条件,注册定时器
rule.conditions.forEach((condition, index) => {
if (condition.type === 'timer' && condition.cronExpression) {
const timerId = this.registerCronTimer(rule.sceneId, condition.cronExpression)
this.timerMap.set(`${rule.sceneId}_timer_${index}`, timerId)
}
})
}
// 停用规则
private deactivateRule(rule: SceneRule): void {
// 清除定时器
this.timerMap.forEach((timerId, key) => {
if (key.startsWith(rule.sceneId)) {
clearInterval(timerId)
this.timerMap.delete(key)
}
})
}
// 注册cron定时器(简化版,实际项目用cron库)
private registerCronTimer(sceneId: string, cronExpression: string): number {
// 解析cron表达式,计算下次执行时间
// 这里简化为每分钟检查一次
return setInterval(() => {
if (this.matchCron(cronExpression)) {
this.executeScene(sceneId, 'timer')
}
}, 60000)
}
// 匹配cron表达式(简化版)
private matchCron(cron: string): boolean {
const now = new Date()
const parts = cron.split(' ')
// cron格式:分 时 日 月 周
if (parts.length !== 5) return false
const matchMinute = parts[0] === '*' || parseInt(parts[0]) === now.getMinutes()
const matchHour = parts[1] === '*' || parseInt(parts[1]) === now.getHours()
const matchDay = parts[2] === '*' || parseInt(parts[2]) === now.getDate()
const matchMonth = parts[3] === '*' || parseInt(parts[3]) === (now.getMonth() + 1)
const matchWeek = parts[4] === '*' || parseInt(parts[4]) === now.getDay()
return matchMinute && matchHour && matchDay && matchMonth && matchWeek
}
// 事件触发检查
onDeviceStateChange(deviceId: string, property: string, value: Object): void {
this.rules.forEach((rule) => {
if (!rule.enabled) return
// 检查事件条件
const eventConditions = rule.conditions.filter(c => c.type === 'event')
if (eventConditions.length === 0) return
const matched = eventConditions.some(condition => {
if (condition.deviceId !== deviceId || condition.property !== property) {
return false
}
return this.evaluateCondition(value, condition.op!, condition.value)
})
if (matched) {
this.executeScene(rule.sceneId, 'event')
}
})
}
// 评估条件
private evaluateCondition(actual: Object, op: ConditionOp, expected: Object): boolean {
const a = actual as number
const e = expected as number
switch (op) {
case 'eq': return actual === expected
case 'ne': return actual !== expected
case 'gt': return a > e
case 'gte': return a >= e
case 'lt': return a < e
case 'lte': return a <= e
default: return false
}
}
// 执行场景
async executeScene(sceneId: string, triggerType: string): Promise<SceneResult> {
const rule = this.rules.get(sceneId)
if (!rule || !rule.enabled) {
return { sceneId, success: false, executedActions: 0, failedActions: 0, conflicts: [] }
}
// 防止重复执行
if (this.executingScenes.has(sceneId)) {
return { sceneId, success: false, executedActions: 0, failedActions: 0, conflicts: [] }
}
this.executingScenes.add(sceneId)
// 冲突检测
const conflicts = this.detectConflicts(rule)
let executedActions = 0
let failedActions = 0
// 按序执行动作
for (const action of rule.actions) {
// 检查该动作是否与冲突场景冲突
const isConflicting = conflicts.some(conflictSceneId => {
const conflictRule = this.rules.get(conflictSceneId)
return conflictRule?.actions.some(a =>
a.deviceId === action.deviceId && a.property === action.property
)
})
if (isConflicting && conflicts.length > 0) {
// 简化处理:冲突时跳过该动作
failedActions++
continue
}
// 延时执行
if (action.delay && action.delay > 0) {
await new Promise(resolve => setTimeout(resolve, action.delay))
}
// 执行动作
if (this.actionExecutor) {
const success = await this.actionExecutor(action.deviceId, action.property, action.value)
if (success) {
executedActions++
} else {
failedActions++
}
}
}
this.executingScenes.delete(sceneId)
return {
sceneId,
success: failedActions === 0,
executedActions,
failedActions,
conflicts
}
}
// 冲突检测
private detectConflicts(rule: SceneRule): string[] {
const conflicts: string[] = []
this.rules.forEach((otherRule, otherSceneId) => {
if (otherSceneId === rule.sceneId || !otherRule.enabled) return
if (!this.executingScenes.has(otherSceneId)) return
// 检查是否有动作操作同一设备的同一属性
const hasOverlap = rule.actions.some(action =>
otherRule.actions.some(otherAction =>
otherAction.deviceId === action.deviceId &&
otherAction.property === action.property &&
otherAction.value !== action.value // 值不同才算冲突
)
)
if (hasOverlap) {
conflicts.push(otherSceneId)
}
})
return conflicts
}
// 获取所有规则
getAllRules(): SceneRule[] {
return Array.from(this.rules.values())
}
// 获取规则
getRule(sceneId: string): SceneRule | undefined {
return this.rules.get(sceneId)
}
}
export default new SceneEngine()
export type { SceneRule, SceneCondition, SceneAction, SceneResult }
进阶用法:场景联动页面
场景创建和编辑页面,支持条件配置、动作配置、冲突提示。
typescript
// pages/SceneEditPage.ets
// 场景编辑页面 - 创建和编辑自动化场景
import SceneEngine, { SceneRule, SceneCondition, SceneAction } from '../services/SceneEngine'
@Entry
@Component
struct SceneEditPage {
@State sceneName: string = ''
@State conditions: SceneCondition[] = []
@State actions: SceneAction[] = []
@State isEditing: boolean = false
@State editingSceneId: string = ''
// 可选设备列表(简化)
private devices = [
{ deviceId: 'light-001', name: '客厅主灯', properties: ['power', 'brightness'] },
{ deviceId: 'ac-001', name: '卧室空调', properties: ['power', 'temperature', 'mode'] },
{ deviceId: 'sensor-001', name: '温湿度传感器', properties: ['temperature', 'humidity'] },
{ deviceId: 'curtain-001', name: '客厅窗帘', properties: ['position'] }
]
build() {
Navigation() {
Scroll() {
Column() {
// 场景名称
this.NameSection()
// 触发条件
this.ConditionSection()
// 执行动作
this.ActionSection()
// 保存按钮
Button('保存场景')
.width('90%')
.height(48)
.backgroundColor('#007DFF')
.fontColor('#FFFFFF')
.borderRadius(24)
.margin({ top: 24, bottom: 24 })
.onClick(() => this.saveScene())
}
.width('100%')
.padding(16)
}
.width('100%')
.height('100%')
.backgroundColor('#F8F8F8')
}
.title(this.isEditing ? '编辑场景' : '创建场景')
.titleMode(NavigationTitleMode.Mini)
}
// 场景名称
@Builder NameSection() {
Column() {
Text('场景名称').fontSize(14).fontColor('#999999').margin({ bottom: 8 })
TextInput({ placeholder: '如:回家模式、睡眠模式', text: this.sceneName })
.fontSize(16)
.onChange((value: string) => { this.sceneName = value })
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
// 触发条件
@Builder ConditionSection() {
Column() {
Row() {
Text('触发条件').fontSize(14).fontColor('#999999')
Blank()
Text('添加条件')
.fontSize(14)
.fontColor('#007DFF')
.onClick(() => {
this.conditions.push({
type: 'event',
deviceId: 'sensor-001',
property: 'temperature',
op: 'gt',
value: 28
})
})
}
.width('100%')
.margin({ bottom: 12 })
// 条件列表
ForEach(this.conditions, (condition: SceneCondition, index: number) => {
Row() {
if (condition.type === 'timer') {
Text(`定时: ${condition.cronExpression || '未设置'}`)
.fontSize(14).fontColor('#333333')
} else {
const device = this.devices.find(d => d.deviceId === condition.deviceId)
Text(`当 ${device?.name || '设备'} 的 ${condition.property} ${this.getOpLabel(condition.op!)} ${condition.value}`)
.fontSize(14).fontColor('#333333')
}
Blank()
// 删除条件
Image($r('sys.media.ohos_ic_public_remove'))
.width(20).height(20).fillColor('#F44336')
.onClick(() => {
this.conditions.splice(index, 1)
})
}
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.margin({ bottom: 8 })
})
if (this.conditions.length === 0) {
Text('暂无触发条件,请添加')
.fontSize(13).fontColor('#999999')
.padding(12)
}
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.margin({ top: 12 })
}
// 执行动作
@Builder ActionSection() {
Column() {
Row() {
Text('执行动作').fontSize(14).fontColor('#999999')
Blank()
Text('添加动作')
.fontSize(14)
.fontColor('#007DFF')
.onClick(() => {
this.actions.push({
deviceId: 'light-001',
property: 'power',
value: true
})
})
}
.width('100%')
.margin({ bottom: 12 })
// 动作列表
ForEach(this.actions, (action: SceneAction, index: number) => {
Row() {
const device = this.devices.find(d => d.deviceId === action.deviceId)
Text(`${device?.name || '设备'} → ${action.property} = ${action.value}`)
.fontSize(14).fontColor('#333333')
Blank()
// 延时设置
Text(action.delay ? `延时${action.delay}ms` : '无延时')
.fontSize(12).fontColor('#999999').margin({ right: 8 })
Image($r('sys.media.ohos_ic_public_remove'))
.width(20).height(20).fillColor('#F44336')
.onClick(() => {
this.actions.splice(index, 1)
})
}
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.margin({ bottom: 8 })
})
if (this.actions.length === 0) {
Text('暂无执行动作,请添加')
.fontSize(13).fontColor('#999999')
.padding(12)
}
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.margin({ top: 12 })
}
// 获取操作符标签
private getOpLabel(op: string): string {
const labels: Record<string, string> = {
'eq': '等于', 'ne': '不等于', 'gt': '大于',
'gte': '大于等于', 'lt': '小于', 'lte': '小于等于'
}
return labels[op] || op
}
// 保存场景
private saveScene(): void {
if (!this.sceneName) {
// 提示输入名称
return
}
if (this.conditions.length === 0) {
// 提示添加条件
return
}
if (this.actions.length === 0) {
// 提示添加动作
return
}
const rule: SceneRule = {
sceneId: this.editingSceneId || `scene_${Date.now()}`,
name: this.sceneName,
enabled: true,
priority: 0,
conditions: this.conditions,
actions: this.actions,
createdAt: Date.now(),
updatedAt: Date.now()
}
SceneEngine.addRule(rule)
// 返回上一页
}
}
完整示例:场景管理主页面
场景列表展示、快捷执行、启用禁用,整合场景引擎。
typescript
// pages/SceneListPage.ets
// 场景管理主页面
import SceneEngine, { SceneRule, SceneResult } from '../services/SceneEngine'
import DeviceControlManager from '../services/DeviceControlManager'
@Entry
@Component
struct SceneListPage {
@State scenes: SceneRule[] = []
@State executingSceneId: string = ''
@State lastResult: SceneResult | null = null
aboutToAppear() {
// 注入动作执行器
SceneEngine.setActionExecutor(async (deviceId, property, value) => {
DeviceControlManager.controlDevice(deviceId, property, value)
return true
})
this.loadScenes()
}
loadScenes() {
// 加载预设场景
const presets: SceneRule[] = [
{
sceneId: 'scene-home', name: '回家模式', enabled: true, priority: 1,
conditions: [{ type: 'event', deviceId: 'geofence', property: 'status', op: 'eq', value: 'arrive_home' }],
actions: [
{ deviceId: 'light-001', property: 'power', value: true },
{ deviceId: 'ac-001', property: 'power', value: true, delay: 500 },
{ deviceId: 'ac-001', property: 'temperature', value: 26, delay: 1000 },
{ deviceId: 'curtain-001', property: 'position', value: 50, delay: 1500 }
],
createdAt: Date.now(), updatedAt: Date.now()
},
{
sceneId: 'scene-away', name: '离家模式', enabled: true, priority: 1,
conditions: [{ type: 'event', deviceId: 'geofence', property: 'status', op: 'eq', value: 'leave_home' }],
actions: [
{ deviceId: 'light-001', property: 'power', value: false },
{ deviceId: 'ac-001', property: 'power', value: false, delay: 500 },
{ deviceId: 'curtain-001', property: 'position', value: 0, delay: 1000 }
],
createdAt: Date.now(), updatedAt: Date.now()
},
{
sceneId: 'scene-sleep', name: '睡眠模式', enabled: true, priority: 2,
conditions: [{ type: 'timer', cronExpression: '0 23 * * *' }],
actions: [
{ deviceId: 'light-001', property: 'power', value: false },
{ deviceId: 'ac-001', property: 'temperature', value: 27, delay: 500 },
{ deviceId: 'curtain-001', property: 'position', value: 0, delay: 1000 }
],
createdAt: Date.now(), updatedAt: Date.now()
},
{
sceneId: 'scene-hot', name: '高温制冷', enabled: true, priority: 3,
conditions: [{ type: 'event', deviceId: 'sensor-001', property: 'temperature', op: 'gt', value: 28 }],
actions: [
{ deviceId: 'ac-001', property: 'power', value: true },
{ deviceId: 'ac-001', property: 'mode', value: 'cool', delay: 500 },
{ deviceId: 'ac-001', property: 'temperature', value: 26, delay: 1000 }
],
createdAt: Date.now(), updatedAt: Date.now()
}
]
presets.forEach(rule => SceneEngine.addRule(rule))
this.scenes = SceneEngine.getAllRules()
}
// 手动执行场景
async executeScene(sceneId: string) {
this.executingSceneId = sceneId
const result = await SceneEngine.executeScene(sceneId, 'manual')
this.lastResult = result
this.executingSceneId = ''
if (result.conflicts.length > 0) {
// 有冲突,提示用户
console.warn(`场景冲突: ${result.conflicts.join(', ')}`)
}
}
// 切换场景启用状态
toggleScene(sceneId: string, enabled: boolean) {
if (enabled) {
SceneEngine.disableRule(sceneId)
} else {
SceneEngine.enableRule(sceneId)
}
this.scenes = SceneEngine.getAllRules()
}
build() {
Navigation() {
Column() {
// 场景列表
List({ space: 12 }) {
ForEach(this.scenes, (scene: SceneRule) => {
ListItem() {
Row() {
// 场景图标和名称
Column() {
Text(scene.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
// 触发条件描述
Text(this.getConditionDesc(scene))
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 启用/禁用开关
Toggle({ type: ToggleType.Switch, isOn: scene.enabled })
.selectedColor('#007DFF')
.width(48)
.height(28)
.onChange((isOn: boolean) => {
this.toggleScene(scene.sceneId, scene.enabled)
})
// 手动执行按钮
Button('执行')
.height(32)
.fontSize(13)
.fontColor('#007DFF')
.backgroundColor('#F0F7FF')
.borderRadius(16)
.margin({ left: 8 })
.enabled(this.executingSceneId !== scene.sceneId)
.onClick(() => this.executeScene(scene.sceneId))
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
})
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 12 })
// 执行结果提示
if (this.lastResult) {
Row() {
Image(this.lastResult.success ? $r('sys.media.ohos_ic_public_ok') : $r('sys.media.ohos_ic_public_fail'))
.width(16).height(16)
.fillColor(this.lastResult.success ? '#4CAF50' : '#F44336')
Text(this.lastResult.success ? '场景执行成功' : `执行失败:${this.lastResult.failedActions}个动作未完成`)
.fontSize(13)
.fontColor(this.lastResult.success ? '#4CAF50' : '#F44336')
.margin({ left: 8 })
}
.width('100%')
.padding(12)
.backgroundColor(this.lastResult.success ? '#E8F5E9' : '#FFEBEE')
}
// 添加场景按钮
Button('+ 创建新场景')
.width('90%')
.height(48)
.backgroundColor('#007DFF')
.fontColor('#FFFFFF')
.borderRadius(24)
.margin({ top: 12, bottom: 24 })
.onClick(() => {
// 跳转到场景编辑页面
})
}
.width('100%')
.height('100%')
.backgroundColor('#F8F8F8')
}
.title('场景联动')
.titleMode(NavigationTitleMode.Mini)
}
// 获取条件描述
private getConditionDesc(scene: SceneRule): string {
return scene.conditions.map(c => {
if (c.type === 'timer') {
return `定时: ${c.cronExpression}`
} else if (c.type === 'event') {
return `当${c.deviceId}的${c.property} ${this.getOpLabel(c.op!)} ${c.value}`
} else {
return '手动触发'
}
}).join(' 且 ')
}
private getOpLabel(op: string): string {
const labels: Record<string, string> = {
'eq': '=', 'ne': '≠', 'gt': '>', 'gte': '≥', 'lt': '<', 'lte': '≤'
}
return labels[op] || op
}
}
踩坑与注意事项
1. 定时场景的后台保活
定时场景(如每天23:00执行睡眠模式)依赖App后台运行。但HarmonyOS和iOS一样,对后台App的管控很严格------App进后台一段时间后可能被杀掉,定时器就没了。
解法: 定时场景不能依赖客户端定时器,必须注册到系统的后台任务调度器。HarmonyOS用@ohos.resourceschedule.reminderAgentManager注册提醒,到时间系统会唤醒App执行场景。或者更可靠的做法:定时规则上传到服务端,服务端到时间推送通知给App,App收到通知后执行场景。
2. 事件触发的死循环
场景A:温度>28° → 开空调。场景B:空调开了 → 开窗。场景C:窗户开了 → 温度下降 → 关空调 → 温度上升 → 又触发场景A......
如果不做循环检测,场景之间可以无限触发,设备疯狂开关,直到烧坏。
解法: 加冷却时间。同一场景在执行后的一段时间内(如5分钟)不会再次触发。同时检测循环依赖------如果场景A的触发条件可能被场景B的动作影响,且场景B的触发条件可能被场景A的动作影响,就标记为潜在循环,提示用户。
3. 条件的时间窗口
"温度>28°开空调"------温度传感器每秒上报一次,28.1°、28.2°、28.3°......每次都触发场景?空调被反复开关?
解法: 条件加持续时间。温度持续>28°超过3分钟才触发。这样温度短暂波动不会误触发。实现方式:记录条件首次满足的时间,如果持续满足超过阈值才执行场景。
4. 动作执行失败的处理
场景有5个动作,第3个失败了------前2个已经执行,后2个还没执行。怎么办?
解法: 不做回滚。场景动作不是事务,部分成功也是成功。失败的动作用重试机制(最多3次),3次都失败就跳过,记录日志。下次场景触发时,失败的设备可能已经恢复了。
5. 场景规则的持久化
场景规则存在内存里,App重启就没了。用户辛辛苦苦配了10个场景,重启后全没了,你猜他会不会骂你?
解法: 场景规则必须持久化。用鸿蒙的@ohos.data.relationalStore(关系型数据库)存储规则,App启动时从数据库加载并注册到场景引擎。同时上传到云端,换设备也能恢复。
HarmonyOS 6适配说明
HarmonyOS 6对场景联动做了几项增强:
-
后台任务调度增强 :
reminderAgentManager新增ReminderRequestTimer类型,支持精确到秒的定时触发,不再只有分钟级精度。定时场景可以更精确地执行。 -
地理围栏API :新增
@ohos.geoLocationManager的地理围栏能力,支持进入/离开指定区域触发回调。场景联动可以直接使用地理围栏作为触发条件。 -
场景模板:HarmonyOS 6的智能家居Kit新增场景模板API,预置了"回家模式""离家模式""睡眠模式"等常见场景模板,用户一键套用,不用从零配置。
-
场景冲突可视化:新增场景冲突检测API,创建场景时自动检测与已有场景的冲突,返回冲突列表和冲突原因。
适配代码示例:
typescript
// HarmonyOS 6 地理围栏触发
import { geoLocationManager } from '@kit.LocationKit'
// 创建地理围栏
const geofence: geoLocationManager.Geofence = {
latitude: 39.9042, // 家的纬度
longitude: 116.4074, // 家的经度
radius: 200, // 200米范围
expiration: 0 // 永不过期
}
const request: geoLocationManager.GeofenceRequest = {
geofences: [geofence]
}
// 监听地理围栏事件
geoLocationManager.on('geofenceStatusChange', (event: geoLocationManager.GeofenceTransitionEvent) => {
if (event.transition === geoLocationManager.GeofenceTransition.GEOFENCE_TRANSITION_ENTER) {
// 进入围栏 → 触发回家模式
SceneEngine.executeScene('scene-home', 'geofence')
} else if (event.transition === geoLocationManager.GeofenceTransition.GEOFENCE_TRANSITION_EXIT) {
// 离开围栏 → 触发离家模式
SceneEngine.executeScene('scene-away', 'geofence')
}
})
总结
场景联动是智能家居从"遥控器"进化到"管家"的关键。没有场景联动,用户就是一个人肉定时器,每天重复同样的操作。有了场景联动,系统自动根据条件和时间执行动作,用户只需要享受结果。
但场景联动不是"配几个规则就完事"的。冲突检测、循环防护、冷却时间、失败处理------这些细节决定了场景联动是"智能"还是"智障"。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐ 条件-动作模型不难,但冲突检测、循环防护、后台保活需要深入设计 |
| 使用频率 | ⭐⭐⭐⭐ 场景联动是智能家居的核心卖点,用户使用频率很高 |
| 重要程度 | ⭐⭐⭐⭐⭐ 没有场景联动的智能家居只是"远程遥控",不是真正的智能 |
一句话:场景联动做得好,用户离不开;做得差,用户关掉不用。 差别就在那些细节里。