HarmonyOS开发:智能家居架构——设备控制面板设计

举报
Jack20 发表于 2026/06/25 21:13:01 2026/06/25
【摘要】 HarmonyOS开发:智能家居架构——设备控制面板设计📌 核心要点:智能家居App的架构设计决定了整个项目的可扩展性和可维护性,设备控制面板的组件化方案是核心,鸿蒙分布式能力让跨设备控制成为可能。 背景与动机你接到一个智能家居App的需求。第一个想法是什么?写个页面,放几个按钮,点一下开灯,再点一下关灯——搞定?等你做到20种设备、50个控制项、3种屏幕尺寸的时候,你就知道什么叫"灾难...

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(风速)。动态组合,不需要写死页面。

鸿蒙分布式能力

鸿蒙的分布式能力在智能家居场景下有三个关键应用:

  1. 设备迁移:在手机上控制到一半,无缝迁移到平板继续控制
  2. 跨端协同:手机控制面板 + 智慧屏状态展示,多端联动
  3. 分布式数据:设备状态在多端实时同步,任何一端操作,其他端立刻更新

这些能力的底层是鸿蒙的分布式软总线,应用层通过@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. 分布式数据初始化时机

KVManagerKVStore的初始化是异步的,如果在aboutToAppear里初始化,页面渲染时数据可能还没准备好。用户看到空列表,以为没有设备,其实数据还在加载。

解法: 加loading状态,数据没加载完显示骨架屏或加载动画。KVStore初始化完成后再加载数据。

4. 组件化不是万能药

组件化解决的是"不同设备类型复用UI"的问题,但解决不了"同一设备类型不同品牌差异"的问题。同样是空调,A品牌支持"自清洁",B品牌不支持——你的EnumSelector怎么处理?

解法: 设备类型的属性定义不能写死在客户端,要从服务端动态下发。每个设备注册时上报自己支持的属性列表,客户端根据属性列表动态渲染面板。这样加新品牌、新功能就不需要发版了。

5. 面板组件的触摸冲突

滑块(Slider)和列表(List)的触摸事件会冲突——用户在滑块上横向拖动,列表也跟着横向滚动(如果有横向滚动的话)。即使没有横向滚动,滑块的垂直拖动也可能被List的滚动事件截获。

解法: 给Slider外层加.hitTestBehavior(HitTestMode.Block),阻止触摸事件冒泡到List。

HarmonyOS 6适配说明

HarmonyOS 6对智能家居场景做了几项重要增强:

  1. 分布式能力增强:新增@ohos.distributedDeviceManager的设备发现API,支持基于设备类型的过滤发现,不再需要遍历所有设备再筛选。

  2. KVStore性能优化:分布式数据的同步延迟从秒级降到百毫秒级,多端状态同步更实时。新增批量写入API,减少同步次数。

  3. 面板组件系统:HarmonyOS 6引入了@ohos.arkui.UIExtension,支持跨进程渲染面板组件。这意味着设备厂商可以提供自己的控制面板组件,你的App直接加载就行,不用自己写。

  4. 无感迁移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的架构设计,核心就三件事:分层解耦、组件化面板、分布式协同。

分层解耦让通信协议和业务逻辑互不干扰,组件化面板让新设备接入只是加配置的事,分布式协同让多端控制变得自然。

维度 评价
学习难度 ⭐⭐⭐⭐ 组件化方案不难,但分布式数据同步和状态一致性需要深入理解
使用频率 ⭐⭐⭐⭐⭐ 智能家居是鸿蒙的核心场景,架构设计贯穿整个项目生命周期
重要程度 ⭐⭐⭐⭐⭐ 架构决定上限,没有好的架构,后期加功能就是加灾难

记住一句话:架构不是设计出来的,是重构出来的。 但如果你一开始就按分层+组件化的思路来,后面重构的次数会少很多。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。