HarmonyOS开发:智能家居架构——设备控制面板设计
HarmonyOS开发:智能家居架构——设备控制面板设计
📌 核心要点:智能家居App的架构设计决定了整个项目的可扩展性和可维护性,设备控制面板的组件化方案是核心,鸿蒙分布式能力让跨设备控制成为可能。
背景与动机
你接到一个智能家居App的需求。第一个想法是什么?写个页面,放几个按钮,点一下开灯,再点一下关灯——搞定?
等你做到20种设备、50个控制项、3种屏幕尺寸的时候,你就知道什么叫"灾难"了。灯的控制面板和空调的不一样,空调的和窗帘的又不一样,每个设备一套UI,代码量爆炸。更别提用户在手机上控制完,又想在平板上、在智慧屏上接着控制——你难道每个端都重写一遍?
智能家居的架构设计,不是UI画得好不好看的问题,而是整个系统能不能活过三个版本的问题。
鸿蒙的分布式能力天然适合智能家居场景——设备发现、设备迁移、跨端控制,这些在Android上要写一堆适配代码的能力,在鸿蒙上就是API调一下的事。但前提是,你的架构得对。架构不对,分布式能力用不上;架构对了,加设备加功能就是加个组件的事。
核心原理
整体架构分层
智能家居App的架构分四层:设备层 → 通信层 → 业务层 → 展示层。
flowchart TD
A[展示层 Presentation] --> B[业务层 Business]
B --> C[通信层 Communication]
C --> D[设备层 Device]
A1[设备控制面板组件] --> A
A2[场景联动面板组件] --> A
A3[设备分组面板组件] --> A
A4[定时任务面板组件] --> A
B1[设备管理服务] --> B
B2[场景引擎服务] --> B
B3[定时调度服务] --> B
B4[消息推送服务] --> B
C1[MQTT通信] --> C
C2[CoAP通信] --> C
C3[BLE通信] --> C
C4[SoftAP配网] --> C
D1[WiFi设备] --> D
D2[BLE设备] --> D
D3[Zigbee网关] --> D
D4[红外设备] --> D
classDef present fill:#1565C0,color:#fff,stroke:#0D47A1
classDef business fill:#2E7D32,color:#fff,stroke:#1B5E20
classDef comm fill:#E65100,color:#fff,stroke:#BF360C
classDef device fill:#6A1B9A,color:#fff,stroke:#4A148C
class A,A1,A2,A3,A4,present
class B,B1,B2,B3,B4,business
class C,C1,C2,C3,C4,comm
class D,D1,D2,D3,D4,device
为什么要分层?因为每一层的变化频率不一样。设备协议经常变(今天用MQTT,明天可能换CoAP),但业务逻辑相对稳定(控制设备、场景联动这些逻辑不会变)。分层之后,改通信协议不影响业务逻辑,换设备类型不影响UI展示。
设备控制面板组件化
设备控制面板是智能家居App的核心UI。灯有开关和亮度,空调有温度和模式,窗帘有开合度,扫地机有清扫模式——每种设备的控制项都不一样。
如果每种设备写一个页面,100种设备你就得写100个页面。维护?不存在的。
组件化的思路是:把控制面板拆成原子组件,然后根据设备类型动态组合。
| 原子组件 | 适用设备 | 属性 |
|---|---|---|
| SwitchToggle | 灯、插座、开关 | on/off |
| SliderControl | 调光灯、窗帘、音量 | min/max/step |
| EnumSelector | 空调模式、风扇档位 | options[] |
| ColorPicker | RGB灯 | hue/saturation/brightness |
| ProgressRing | 湿度、温度、电量 | value/max |
一个空调的控制面板 = SwitchToggle + SliderControl(温度)+ EnumSelector(模式)+ EnumSelector(风速)。动态组合,不需要写死页面。
鸿蒙分布式能力
鸿蒙的分布式能力在智能家居场景下有三个关键应用:
- 设备迁移:在手机上控制到一半,无缝迁移到平板继续控制
- 跨端协同:手机控制面板 + 智慧屏状态展示,多端联动
- 分布式数据:设备状态在多端实时同步,任何一端操作,其他端立刻更新
这些能力的底层是鸿蒙的分布式软总线,应用层通过@ohos.distributedDeviceManager和@ohos.distributedData来使用。
代码实战
基础用法:设备控制面板原子组件
先定义原子组件,这是整个面板系统的基石。
// components/panel/SliderControl.ets
// 滑块控制组件 - 用于调光灯亮度、窗帘开合度、音量等连续值控制
@Component
export struct SliderControl {
// 控制项名称,如"亮度"、"温度"
label: string = ''
// 当前值
@Prop value: number = 0
// 最小值
min: number = 0
// 最大值
max: number = 100
// 步长
step: number = 1
// 单位,如"%"、"°C"
unit: string = '%'
// 值变化回调
onChange?: (value: number) => void
build() {
Row() {
// 左侧标签
Text(this.label)
.fontSize(14)
.fontColor('#333333')
.width(60)
// 中间滑块
Slider({
value: this.value,
min: this.min,
max: this.max,
step: this.step,
style: SliderStyle.InSet
})
.selectedColor('#007DFF')
.trackColor('#E0E0E0')
.width('50%')
.onChange((value: number) => {
this.onChange?.(value)
})
// 右侧数值显示
Text(`${Math.round(this.value)}${this.unit}`)
.fontSize(14)
.fontColor('#666666')
.width(60)
.textAlign(TextAlign.End)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
}
}
// components/panel/EnumSelector.ets
// 枚举选择组件 - 用于空调模式、风扇档位等离散值选择
@Component
export struct EnumSelector {
label: string = ''
@Prop current: string = ''
options: Array<{ key: string, label: string }> = []
onSelect?: (key: string) => void
build() {
Row() {
Text(this.label)
.fontSize(14)
.fontColor('#333333')
.width(60)
Row() {
ForEach(this.options, (option: { key: string, label: string }) => {
Text(option.label)
.fontSize(13)
.fontColor(this.current === option.key ? '#FFFFFF' : '#333333')
.backgroundColor(this.current === option.key ? '#007DFF' : '#F5F5F5')
.borderRadius(16)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.margin({ right: 8 })
.onClick(() => {
this.onSelect?.(option.key)
})
})
}
.layoutWeight(1)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
}
}
进阶用法:设备面板动态组合
根据设备类型,动态组合不同的原子组件,生成对应的控制面板。
// panels/DevicePanelFactory.ets
// 设备面板工厂 - 根据设备类型动态生成控制面板
import { SwitchToggle } from '../components/panel/SwitchToggle'
import { SliderControl } from '../components/panel/SliderControl'
import { EnumSelector } from '../components/panel/EnumSelector'
import { ColorPicker } from '../components/panel/ColorPicker'
// 设备属性定义
interface DeviceProperty {
key: string // 属性标识,如'brightness'
type: 'switch' | 'slider' | 'enum' | 'color' // 属性类型
label: string // 显示名称
config: Record<string, Object> // 类型相关配置
}
// 设备类型定义
interface DeviceType {
typeId: string
name: string
icon: Resource
properties: DeviceProperty[]
}
// 预定义设备类型
const DEVICE_TYPES: Record<string, DeviceType> = {
'light-dimmable': {
typeId: 'light-dimmable',
name: '调光灯',
icon: $r('app.media.ic_light'),
properties: [
{ key: 'power', type: 'switch', label: '开关', config: {} },
{ key: 'brightness', type: 'slider', label: '亮度', config: { min: 0, max: 100, step: 1, unit: '%' } }
]
},
'air-conditioner': {
typeId: 'air-conditioner',
name: '空调',
icon: $r('app.media.ic_ac'),
properties: [
{ key: 'power', type: 'switch', label: '开关', config: {} },
{ key: 'temperature', type: 'slider', label: '温度', config: { min: 16, max: 30, step: 1, unit: '°C' } },
{ key: 'mode', type: 'enum', label: '模式', config: {
options: [
{ key: 'cool', label: '制冷' },
{ key: 'heat', label: '制热' },
{ key: 'auto', label: '自动' },
{ key: 'dry', label: '除湿' }
]
}},
{ key: 'fanSpeed', type: 'enum', label: '风速', config: {
options: [
{ key: 'low', label: '低' },
{ key: 'mid', label: '中' },
{ key: 'high', label: '高' },
{ key: 'auto', label: '自动' }
]
}}
]
},
'curtain': {
typeId: 'curtain',
name: '窗帘',
icon: $r('app.media.ic_curtain'),
properties: [
{ key: 'position', type: 'slider', label: '开合度', config: { min: 0, max: 100, step: 1, unit: '%' } }
]
}
}
// 设备面板组件 - 动态渲染
@Component
export struct DevicePanel {
// 设备类型ID
deviceTypeId: string = ''
// 设备当前状态
@Prop deviceState: Record<string, Object> = {}
// 属性变更回调
onPropertyChange?: (key: string, value: Object) => void
build() {
Column() {
// 根据设备类型获取属性定义,动态渲染对应组件
if (this.deviceTypeId && DEVICE_TYPES[this.deviceTypeId]) {
ForEach(DEVICE_TYPES[this.deviceTypeId].properties, (prop: DeviceProperty) => {
// 根据属性类型渲染不同组件
if (prop.type === 'switch') {
SwitchToggle({
label: prop.label,
isOn: (this.deviceState[prop.key] as boolean) || false,
onToggle: (value: boolean) => {
this.onPropertyChange?.(prop.key, value)
}
})
} else if (prop.type === 'slider') {
SliderControl({
label: prop.label,
value: (this.deviceState[prop.key] as number) || 0,
min: (prop.config['min'] as number) || 0,
max: (prop.config['max'] as number) || 100,
step: (prop.config['step'] as number) || 1,
unit: (prop.config['unit'] as string) || '',
onChange: (value: number) => {
this.onPropertyChange?.(prop.key, value)
}
})
} else if (prop.type === 'enum') {
EnumSelector({
label: prop.label,
current: (this.deviceState[prop.key] as string) || '',
options: (prop.config['options'] as Array<{ key: string, label: string }>) || [],
onSelect: (key: string) => {
this.onPropertyChange?.(prop.key, key)
}
})
}
})
}
}
.width('100%')
.padding(16)
}
}
完整示例:智能家居主页面
把设备面板、场景联动、设备分组整合到一起,形成完整的智能家居主页面,并接入鸿蒙分布式能力。
// pages/SmartHomePage.ets
// 智能家居主页面 - 整合设备控制、场景联动、设备分组
import { DevicePanel } from '../panels/DevicePanelFactory'
import { deviceManager } from '@kit.DistributedServiceKit'
import { distributedData } from '@kit.ArkData'
// 设备信息
interface SmartDevice {
deviceId: string
name: string
typeId: string
roomName: string
isOnline: boolean
state: Record<string, Object>
}
// 场景信息
interface Scene {
sceneId: string
name: string
icon: Resource
actions: Array<{ deviceId: string, property: string, value: Object }>
}
@Entry
@Component
struct SmartHomePage {
// 设备列表
@State devices: SmartDevice[] = []
// 场景列表
@State scenes: Scene[] = []
// 当前选中房间
@State currentRoom: string = '全部'
// 分布式数据同步
private kvManager?: distributedData.KVManager
private kvStore?: distributedData.SingleKVStore
aboutToAppear() {
this.loadDevices()
this.loadScenes()
this.initDistributedData()
}
// 初始化分布式数据,实现多端设备状态同步
async initDistributedData() {
try {
const kvConfig: distributedData.KVManagerConfig = {
bundleName: 'com.example.smarthome',
context: getContext(this)
}
this.kvManager = distributedData.createKVManager(kvConfig)
const storeConfig: distributedData.SingleKVStoreConfig = {
securityLevel: distributedData.SecurityLevel.S1
}
this.kvStore = await this.kvManager.getKVStore('device_state', storeConfig)
// 监听远端数据变更,同步设备状态
this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_REMOTE,
(data: distributedData.ChangeNotification) => {
// 远端设备状态变更,更新本地UI
data.updateEntries.forEach((entry: distributedData.Entry) => {
const deviceId = entry.key
const state = JSON.parse(entry.value.value as string) as Record<string, Object>
this.updateDeviceState(deviceId, state)
})
})
} catch (err) {
console.error(`分布式数据初始化失败: ${JSON.stringify(err)}`)
}
}
// 更新设备状态(本地+远端同步)
updateDeviceState(deviceId: string, state: Record<string, Object>) {
const index = this.devices.findIndex(d => d.deviceId === deviceId)
if (index >= 0) {
this.devices[index].state = { ...this.devices[index].state, ...state }
}
}
// 控制设备属性
async controlDevice(deviceId: string, property: string, value: Object) {
// 1. 先乐观更新本地UI
this.updateDeviceState(deviceId, { [property]: value })
// 2. 下发控制指令到设备
try {
// 实际项目中调用通信层发送MQTT/CoAP指令
await this.sendDeviceCommand(deviceId, property, value)
// 3. 同步到分布式数据,其他端自动更新
if (this.kvStore) {
const device = this.devices.find(d => d.deviceId === deviceId)
if (device) {
await this.kvStore.put(deviceId, JSON.stringify(device.state))
}
}
} catch (err) {
// 控制失败,回滚UI
console.error(`设备控制失败: ${JSON.stringify(err)}`)
// 重新加载设备真实状态
this.loadDevices()
}
}
// 发送设备控制指令(模拟,实际调用通信层)
async sendDeviceCommand(deviceId: string, property: string, value: Object): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => resolve(), 100)
})
}
// 加载设备列表
async loadDevices() {
// 实际项目从本地数据库或云端加载
this.devices = [
{
deviceId: 'light-001', name: '客厅主灯', typeId: 'light-dimmable',
roomName: '客厅', isOnline: true, state: { power: true, brightness: 80 }
},
{
deviceId: 'ac-001', name: '卧室空调', typeId: 'air-conditioner',
roomName: '卧室', isOnline: true, state: { power: true, temperature: 26, mode: 'cool', fanSpeed: 'auto' }
},
{
deviceId: 'curtain-001', name: '客厅窗帘', typeId: 'curtain',
roomName: '客厅', isOnline: true, state: { position: 60 }
}
]
}
// 加载场景列表
loadScenes() {
this.scenes = [
{ sceneId: 'scene-home', name: '回家模式', icon: $r('app.media.ic_scene_home'), actions: [] },
{ sceneId: 'scene-away', name: '离家模式', icon: $r('app.media.ic_scene_away'), actions: [] },
{ sceneId: 'scene-sleep', name: '睡眠模式', icon: $r('app.media.ic_scene_sleep'), actions: [] }
]
}
build() {
Navigation() {
Column() {
// 顶部场景快捷入口
Row() {
ForEach(this.scenes, (scene: Scene) => {
Column() {
Image(scene.icon).width(36).height(36)
Text(scene.name).fontSize(12).fontColor('#333333').margin({ top: 4 })
}
.width(72)
.height(80)
.justifyContent(FlexAlign.Center)
.backgroundColor('#F5F5F5')
.borderRadius(12)
.margin({ right: 12 })
.onClick(() => {
// 执行场景
})
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
// 房间筛选Tab
Tabs({ index: 0 }) {
TabContent().tabBar('全部')
TabContent().tabBar('客厅')
TabContent().tabBar('卧室')
TabContent().tabBar('厨房')
}
.width('100%')
.height(44)
.onChange((index: number) => {
const rooms = ['全部', '客厅', '卧室', '厨房']
this.currentRoom = rooms[index]
})
// 设备列表
List({ space: 12 }) {
ForEach(
this.devices.filter(d => this.currentRoom === '全部' || d.roomName === this.currentRoom),
(device: SmartDevice) => {
ListItem() {
Column() {
// 设备头部:名称 + 在线状态
Row() {
Text(device.name).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
Blank()
Circle({ width: 8, height: 8 })
.fill(device.isOnline ? '#4CAF50' : '#BDBDBD')
Text(device.isOnline ? '在线' : '离线')
.fontSize(12).fontColor(device.isOnline ? '#4CAF50' : '#BDBDBD')
}
.width('100%')
.padding({ left: 16, right: 16, top: 12 })
// 设备控制面板(动态组合)
DevicePanel({
deviceTypeId: device.typeId,
deviceState: device.state,
onPropertyChange: (key: string, value: Object) => {
this.controlDevice(device.deviceId, key, value)
}
})
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(16)
.shadow({ radius: 4, color: '#1A000000', offsetY: 2 })
}
}
)
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 8 })
}
.width('100%')
.height('100%')
.backgroundColor('#F8F8F8')
}
.title('智能家居')
.titleMode(NavigationTitleMode.Mini)
}
}
踩坑与注意事项
1. 面板组件的性能陷阱
设备列表一多,ForEach渲染就慢。50个设备,每个设备3个控制项,就是150个组件实例。滑块拖动的时候,每个onChange都触发状态更新,UI卡成PPT。
解法: 对设备列表做懒加载,用LazyForEach替代ForEach。滑块组件加防抖,拖动过程中不触发状态更新,松手后才下发指令。
// 滑块防抖 - 拖动中不触发,松手后才下发
Slider({ value: this.value })
.onChange((value: number) => {
// 只更新本地显示,不下发
this.localValue = value
})
.onGesture(() => {
// 手势结束时才下发指令
this.onChange?.(this.localValue)
})
2. 设备状态同步的竞态问题
两个人同时控制同一个设备——你在手机上调空调温度到26°,你老婆在平板上同时调到24°——谁的指令生效?如果两个指令几乎同时到达设备,设备可能执行后到的那个,但两端的UI显示不一致。
解法: 设备状态以设备实际上报为准,不做乐观更新。控制指令下发后,等待设备上报新状态再更新UI。或者用分布式数据做冲突合并——后写入的覆盖先写入的,但UI上要提示"设备状态已被其他端修改"。
3. 分布式数据初始化时机
KVManager和KVStore的初始化是异步的,如果在aboutToAppear里初始化,页面渲染时数据可能还没准备好。用户看到空列表,以为没有设备,其实数据还在加载。
解法: 加loading状态,数据没加载完显示骨架屏或加载动画。KVStore初始化完成后再加载数据。
4. 组件化不是万能药
组件化解决的是"不同设备类型复用UI"的问题,但解决不了"同一设备类型不同品牌差异"的问题。同样是空调,A品牌支持"自清洁",B品牌不支持——你的EnumSelector怎么处理?
解法: 设备类型的属性定义不能写死在客户端,要从服务端动态下发。每个设备注册时上报自己支持的属性列表,客户端根据属性列表动态渲染面板。这样加新品牌、新功能就不需要发版了。
5. 面板组件的触摸冲突
滑块(Slider)和列表(List)的触摸事件会冲突——用户在滑块上横向拖动,列表也跟着横向滚动(如果有横向滚动的话)。即使没有横向滚动,滑块的垂直拖动也可能被List的滚动事件截获。
解法: 给Slider外层加.hitTestBehavior(HitTestMode.Block),阻止触摸事件冒泡到List。
HarmonyOS 6适配说明
HarmonyOS 6对智能家居场景做了几项重要增强:
-
分布式能力增强:新增
@ohos.distributedDeviceManager的设备发现API,支持基于设备类型的过滤发现,不再需要遍历所有设备再筛选。 -
KVStore性能优化:分布式数据的同步延迟从秒级降到百毫秒级,多端状态同步更实时。新增批量写入API,减少同步次数。
-
面板组件系统:HarmonyOS 6引入了
@ohos.arkui.UIExtension,支持跨进程渲染面板组件。这意味着设备厂商可以提供自己的控制面板组件,你的App直接加载就行,不用自己写。 -
无感迁移:
UIAbility的迁移API新增onContinue回调,支持迁移时携带设备控制上下文。用户在手机上控制到一半,迁移到平板后控制面板自动恢复到之前的操作状态。
适配代码示例:
// HarmonyOS 6 无感迁移适配
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit'
export default class SmartHomeAbility extends UIAbility {
// 迁移时保存控制上下文
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
// 保存当前正在控制的设备ID和操作状态
wantParam['currentDeviceId'] = 'ac-001'
wantParam['controlState'] = JSON.stringify({ temperature: 26, mode: 'cool' })
return AbilityConstant.OnContinueResult.AGREE
}
// 对端接收迁移数据
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// 恢复控制上下文
const deviceId = want.parameters?.['currentDeviceId'] as string
const stateStr = want.parameters?.['controlState'] as string
// 通知UI恢复控制面板状态
}
}
}
总结
智能家居App的架构设计,核心就三件事:分层解耦、组件化面板、分布式协同。
分层解耦让通信协议和业务逻辑互不干扰,组件化面板让新设备接入只是加配置的事,分布式协同让多端控制变得自然。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐ 组件化方案不难,但分布式数据同步和状态一致性需要深入理解 |
| 使用频率 | ⭐⭐⭐⭐⭐ 智能家居是鸿蒙的核心场景,架构设计贯穿整个项目生命周期 |
| 重要程度 | ⭐⭐⭐⭐⭐ 架构决定上限,没有好的架构,后期加功能就是加灾难 |
记住一句话:架构不是设计出来的,是重构出来的。 但如果你一开始就按分层+组件化的思路来,后面重构的次数会少很多。
- 点赞
- 收藏
- 关注作者
评论(0)