简介:用于鸿蒙系统的智能家居APP完整工程源码,面向鸿蒙应用开发者与物联网爱好者,针对传统智能家居控制分散、设备协同难等痛点,展示如何基于ArkTS与分布式架构实现设备联动、环境监测、安防控制等常见智能家居场景。压缩包共815个文件,总量仅1.29MB,其中包含766个SVG矢量图标、20个ETS页面/逻辑文件、11个JSON5与8个JSON配置、PNG图片及TS工具文件,结构清晰,便于按模块研读。资源覆盖首页、模块管理、设备选择、天气图标等多个核心页面,ETS源码可直接在DevEco Studio中导入运行;项目采用模块化组织,可将各页面独立拆解学习。已有390人学习,适合希望通过实际工程快速理解鸿蒙Ability、跨设备调用与UI开发的中初级开发者。借助此项目可复用常用组件、掌握从配置文件到页面交互的完整实现思路。 一年前我还在用MQTT加自定义协议栈写智能家居APP,每接入一种新设备,都要在设备端、云端和手机端各补一套适配逻辑,做久了就会发现,光通信链路就吃掉一大半工作量。后来我把整个方案迁移到鸿蒙系统,基于分布式软总线重做了一版智能家居APP源码,设备发现、连接管理、指令下发这些原本最费劲的部分,系统底层已经帮我处理掉了。这篇文章不打算讲空洞的鸿蒙优势,而是把这套源码从架构分层、数据模型、配网流程到状态同步完整拆开来看,也会把真机调试中踩过的坑一一列出来。刚转鸿蒙开发的工程师可以从这里快速建立项目感,正在做智能硬件选型的团队也能用它评估技术路线。
1. 选型逻辑:鸿蒙到底解决了智能家居的什么问题
1.1 传统方案里,连接成本怎么拖垮迭代速度
传统智能家居APP的链路通常是:设备端Wi-Fi模块连上路由器,云端负责设备注册、鉴权和指令转发,手机APP通过云服务器间接操作设备。这套架构听起来成熟,实际写起来却非常沉重。我最早的项目采MQTT加JSON指令,半年后光通信模块就有两千多行代码,要处理心跳、重连、消息幂等、乱序、离线缓存、主题订阅等十几个问题。每加一种新设备,还要在协议层加字段、在UI层加面板、在云端加产品定义,开发节奏被通信复杂度拖得很慢。
设备发现是最折磨人的环节。路由器一开启AP隔离,设备就消失;设备重新获取IP后,APP要等旧连接超时才能重新找到它;多设备同时上报状态时,偶尔还会出现串台。用户感知到的就是两个词:卡顿、不稳定。真正做过这类产品的人应该明白,这其实不是设备质量问题,而是自维护分布式网络的成本被APP开发商低估了。
1.2 系统级分布式能力带来的架构变化
鸿蒙与Android/iOS的本质区别在于,它把“设备网络”做成了系统基础设施。同一个鸿蒙账号下,或者通过扫码、碰一碰建立信任关系的设备,会自动组网并互相感知,APP不需要关心设备IP、端口,也不必自己维护连接池。设备在线状态由系统统一维护,业务层通过API查询即可;状态变化时,系统通过公共事件或回调通知到APP,APP专心做UI和业务逻辑就行。
我在源码里最大程度利用了这套能力。设备管理服务只在顶层封装了一层业务接口,底层网络通信全部走系统能力。代码仓库里再也没有传统项目的connect、disconnect、heartbeat这些方法,取而代之的是查设备列表、注册状态监听、下发指令这种语义明确的接口。项目迁移之后,原本两千多行的通信模块压缩到三百行左右,而且基本不需要再改动,因为网络链路的复杂度被收拢到系统层去了。
1.3 这套源码定位在什么场景
这套智能家居APP源码不是为了演示HarmonyOS有多少API,而是围绕真实家庭场景做的一套可运行方案,覆盖几个典型需求:照明和窗帘控制、空调和风扇调节、温湿度传感器数据展示、门锁和摄像头状态查看,以及最基础的离家/回家场景联动。APP是控制中枢,也是场景配置入口;设备通过配网接入分布式网络,数据交互在本地局域网内完成,不需要云端参与。源码没有后端服务组件,但预留了云端接入的接口位置,需要做远程访问的团队可以在这个基础上自行扩展。
2. 源码架构:先看懂分层再动手改,否则很容易改乱
2.1 工程目录与模块职责
这套源码基于DevEco Studio创建,开发语言是ArkTS,UI框架是ArkUI。工程目录组织上,我按职责分成了六个区域:
entry/src/main/ets/ ├── entryability/ │ └── EntryAbility.ets // 应用入口,初始化当前设备信息 ├── pages/ │ ├── Index.ets // 首页:设备列表与在线状态 │ ├── DeviceControl.ets // 设备控制面板 │ ├── AddDevice.ets // 配网引导页 │ └── ScenePage.ets // 场景自动化页面 ├── controller/ │ └── DeviceController.ets // 业务控制器,页面与服务的中间层 ├── service/ │ ├── DeviceDiscoveryService.ets // 设备发现与配网封装 │ ├── DeviceCommandService.ets // 指令下发与结果回调 │ └── StateSyncService.ets // 状态上报与同步 ├── model/ │ ├── Device.ets // 设备数据模型 │ └── SceneRule.ets // 场景规则模型 └── common/ ├── constants.ets // 全局常量与枚举 └── logger.ets // 日志工具封装这个分层有个硬性约定:controller层和pages层不允许直接调用鸿蒙系统API,所有系统能力必须收口在service层。原因很简单,鸿蒙的系统API还处于快速迭代阶段,真机版本之间可能有差异。如果不做这层隔离,系统接口变了就得满项目找调用点改;只要service层是唯一依赖点,升级适配时改动范围可控。
2.2 设备模型与状态的拆分
数据模型设计上,一个容易踩的坑是把设备的静态信息和运行时状态混在一起。源码里把这两个概念分开了。Device类保存设备ID、名称、类型、能力列表这些元信息;运行时状态单独存在state字段里,比如开关、亮度、温度、湿度这些动态数值。
export class Device { deviceId: string; // 设备唯一ID deviceName: string; // 设备显示名称 deviceType: number; // 设备类型,对应枚举值 isOnline: boolean; // 在线状态,业务层推断结果 capabilities: number[]; // 能力列表,决定UI显示哪种控制面板 state: Record<string, string>; // 运行时状态,如 power=on, brightness=60 }这样做的好处是,当设备状态刷新的时候,不需要重建整个Device对象,只需改state字段并通知UI更新。而设备能力列表作为静态配置,在设备绑定时一次性拉起,后续控制面板根据它来动态渲染,不用为每种设备单独写死面板布局。
2.3 页面与控制器的事件流模式
ArkUI页面和控制器之间,源码采用了一种很朴素的事件流模式:页面调用控制器的方法发起操作,控制器执行完后通过回调或AppStorage更新UI。没有引入重型的响应式框架,因为智能家居业务的状态节点有限,场景联动复杂但交互链条简单,保持直接的事件调用反而更容易排查问题。
页面订阅设备状态用的是ArkUI的AppStorage或LocalStorage绑定,当StateSyncService收到新状态并更新对应key时,页面绑定的UI组件会自动刷新。这里有一条值得注意的经验:不要在多个页面里各写一套状态更新逻辑,统一收口到StateSyncService的onStateChanged回调里,这样状态流转路径全局唯一,出现数据异常时追责链路会很清晰。
3. 配网与设备发现:源码里最容易被低估的关卡
3.1 完整配网链路拆解
智能家居设备没有屏幕和键盘,让它接入家庭Wi-Fi本身就是一个工程问题。源码的配网流程分成五步:手机靠近设备后,通过BLE低功耗蓝牙或者设备自发的SoftAP热点建立近距离连接;手机把Wi-Fi的SSID和密码传给设备;设备拿到凭据后连接家庭路由器;设备接入成功后注册到鸿蒙分布式网络;APP通过发现接口找到设备并完成绑定,写入本地数据库。
实际编码中最容易忽略的是第一步的连接方式选择。BLE配网省电但步骤多,SoftAP兼容性最好但需要手机手动切换热点。源码默认走了SoftAP方案,原因是在开发调试阶段,大多数设备模组对SoftAP的支持比BLE要成熟。生产环境如果设备端BLE协议栈稳定,建议两种方式都保留,用户偏好哪个走哪个。
3.2 设备发现服务的封装思路
设备发现封装在DeviceDiscoveryService里,核心代码不复杂,但有几处设计值得借鉴。获取信任设备列表之后,必须做一次类型过滤,只保留业务相关的家居设备类型,否则手机会把同一账号下的平板、手机、智慧屏全列出来,首页直接乱套。
过滤完成后,源码会把系统设备对象转换成自己的Device数据模型,并维护一个本地设备表。这个表是运行时内存态加持久化存储的双层结构:内存态保证UI响应速度,持久化保证APP重启后不需要重新扫描。设备变更、重命名、删除这些操作都走同一个服务接口,避免数据源分裂。
3.3 配网失败排查经验清单
配网是整个APP中用户遇到问题最多的地方,真机测试时我积累了一份问题排查对照表,源码注释里也保留了这版:
| 现象 | 可能原因 | 快速检查方法 |
|---|---|---|
| APP搜不到设备 | 设备没进入配网模式 | 看设备指示灯,必要时断电重启进入SoftAP |
| 配网超时 | Wi-Fi频段不匹配 | 很多IoT模组只支持2.4GHz,手机连的却是5GHz |
| 设备连上但不上报 | 账号信任关系未建立 | 在系统超级终端查看已发现设备列表 |
| 配网成功后仍离线 | 路由器开了AP隔离 | 进路由器管理页关闭AP隔离或用访客网络测试 |
| 状态偶尔丢失 | 设备进入休眠 | 按设备协议配置保活参数或调整上报间隔 |
配网页的交互设计也要配合这些坑。源码里AddDevice页面在进入前会让用户手动确认当前Wi-Fi是否为2.4GHz频段,如果不确定就给引导说明。不要指望配网失败后再让用户自查,那是非常差的产品体验。
4. 控制指令链路与状态同步:从点击到刷新的完整闭环
4.1 指令协议为什么设计成统一格式
控制指令不统一,是智能家居APP后期维护最头疼的问题。每个设备厂商都希望用自己的协议格式,但APP侧如果跟着设备走,就会变成一坨if else。源码里把所有控制指令统一成一种格式,设备端按同一套规则解析。
{ "cmd": "set_property", "params": { "property": "power", "value": "on" }, "requestId": "a1b2c3d4" }cmd描述动作,params描述参数,requestId是命令的唯一标识,用于把设备的响应匹配回发送方。这套协议的核心思想是“面向属性而非面向设备”,开关、亮度、色温、温度、百分比、模式这些公共属性全部抽象出来。新增一种设备时,只要它上报的属性在前面列过,APP侧就不需要为它单独实现指令封装。
4.2 指令下发与回调的可靠性处理
DeviceCommandService在发送指令后做三件事:等待响应、超时处理、失败重试。超时时间默认设5秒,这是一个综合了多款设备实测得到的值。太短对执行机械运动的设备不公平,比如窗帘电机从收到指令到反馈完成可能要两三秒;太长又会让用户感觉点击后没有反应。5秒左右对绝大多数设备来说都是一个合适的中间值。
失败重试只做一次,而且在重试前会先去查一次设备在线状态。如果设备已经离线,再发一次指令没有意义,直接提示用户检查设备连接。整套逻辑绕开了“一直转圈”和“无限重试”这两个典型体验问题。
4.3 状态上报的去重与过期处理
设备状态上报有两种来源:一种是对控制指令的响应,另一种是设备自身条件变化后的主动上报,比如温湿度传感器每30秒推一次数据。不管哪种来源,源码都统一走StateSyncService的onStateChanged回调。回调做四件事:更新Device.state字段;检查版本号,丢弃过期事件;刷新对应页面UI;匹配场景规则,触发联动。
版本号的引入解决的是“状态回跳”问题。场景是:手机发指令把灯调成红色,但设备端因某种原因延迟上报了旧状态“白色”,导致UI先变红又跳回白。给每个状态事件加一个递增版本号,只有当前版本高于设备模型里记录的值才执行更新,过期事件直接丢弃,问题就解决了。
5. 真机调试踩过的坑:这些问题不实测根本发现不了
5.1 系统回调线程里直接改UI,卡顿和闪退轮着来
鸿蒙很多系统回调不在主线程,如果在回调里直接修改UI组件,轻则渲染延迟,重则闪退。我第一版代码就犯了这个问题,设备状态上报后UI刷新有明显的掉帧感,后来统一在回调里包了一层主线程调度工具方法才解决。只要涉及系统回调更新UI的场景,命名都要带明显的runOnUIThread标识,防止后人踩同一个坑。
5.2 “在线”是业务推断结果,不是客观事实
测试过程中我遇到一个很迷惑的现象:设备明明能正常控制,APP却显示离线。排查半天,发现是因为设备在某个时间段内没有上报任何状态,服务把“静默”误判成了“离线”。后来改成双重判定条件:超过心跳间隔加实际控制失败,才把设备标记为离线。这个经验很重要,尤其是做门锁、传感器这类很久不主动上报的设备,误判离线会直接影响用户信任。
5.3 多设备并发上报导致状态互相覆盖
几个传感器同时上报数据时,如果一个全局变量承载“当前状态”,后到的数据一定会覆盖先到的,最后页面上显示的是最后一个设备的状态,其他全丢。解决办法很直接:按deviceId分片存储,每个设备维护独立的状态记录,状态回调里先定位设备实例再更新对应的state字段,不搞全局状态。
5.4 配网时热点切换导致的异常
手机连接设备SoftAP热点后,很多机型会自动把网络切回4G或5G,因为设备热点默认没有外网。这一切,配网页面和APP的网络请求全部异常。解决方法是配网流程中显式管理Wi-Fi连接,锁定在当前热点,同时页面提示用户不要手动切换网络,配网完成后再恢复原Wi-Fi连接。
5.5 DevEco Studio版本升级带来的编译问题
鸿蒙开发工具的版本迭代非常快,API版本差异也大。我遇到过API在某个版本运行正常,升级DevEco Studio后直接编译报错的情况。给团队的建议是锁定开发工具版本,不要跟随IDE自动升级,源码里配置的兼容版本范围保持明确,等一个功能完整发布后再统一评估升级。这个问题属于工具链层面的隐形坑,文档里很少提到,但实际开发中几乎一定会遇到。
6. 二次开发怎么扩展:从新增设备到接入云端
6.1 接入一种新设备类型完整路径
基于这套源码扩展新设备,核心工作是配置而非编码。假设要接入一款支持开关和亮度调节的吸顶灯,流程是:在DeviceModel的capabilities枚举中追加新的能力组合;协议层确认灯具上报的属性是否落在已有公共属性集合内,如果属性已覆盖就不需要动协议层;UI层新增一个控制面板组件并注册到面板路由表。设备发现和指令下发服务不用改,因为它们已经按能力列表和数据协议做了通用化处理。
6.2 云端能力如何留接口
远程控制、语音助手、消息推送这些能力必须依赖云端。源码里预留了一个CloudService抽象层,定义了几个典型方法:登录、获取远程设备列表、下发指令、接收推送。实现类可以挂在服务端后台,然后通过工厂方法注入到controller层。这样本地局域网场景走软总线能力,远程场景走云端通道,两套逻辑互不干扰。做生产级产品时,建议优先跑通本地场景再考虑云端,因为设备端联调的复杂度远高于云端接口开发。
6.3 场景自动化模块的扩展方向
源码里的场景自动化是基础版:一个SceneRule包括触发条件、执行动作和有效时间段,条件来自设备状态事件,动作是对指令的封装。规则匹配全部在本地执行,断网下也能生效。后续扩展开的方向包括:定时触发、地理位置围栏、多条件AND/OR组合,以及场景执行日志。机制不用改,重点扩展的是条件表达式的灵活度和动作序列的编排能力。
把配网、控制指令、状态同步这三个闭环跑通之后,再往场景自动化方向走,每一步都有明确的技术输入和输出,不会出现做完不知道下一步做什么的情况。这套源码的初衷就是把最耗时的通信基础设施搭好,让业务开发能把精力放到真正影响用户体验的地方去。
本文还有配套的精品资源,点击获取