Flutter+OpenHarmony 智能家居开发 Day9|批量设备控制 + 智能场景联动全流程
表格问题场景原因解决方案批量执行时部分设备没反应MQTT 并发超限、设备离线加 100ms 间隔,离线设备自动过滤,执行后展示失败列表场景重启 APP 后丢失未存入 ObjectBox所有场景必须调用 putScene 持久化鸿蒙开发板批量执行闪退后台进程被杀死配置 KEEP_BACKGROUND_RUNNING 权限执行场景后 UI 不刷新未调用 notifyListeners执行完调用 ref
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
大家好~经过 Day8 的 MQTT 实时通信开发,我们的智能家居 APP 已经实现了设备状态毫秒级双向同步,不管是 APP 控制设备,还是设备主动上报状态,都能做到实时刷新、本地持久化,彻底解决了 “操作延迟、状态不同步” 的核心痛点。但在实际家居使用场景中,单一设备控制已经无法满足需求:比如下班回家,需要同时打开客厅灯光、空调、窗帘;离家时,需要一键关闭所有设备;睡眠模式下,需要关闭主灯、开启夜灯、调低空调温度。
这些高频场景,靠手动逐个控制设备既繁琐又低效,这就是 Day9 要解决的核心问题:实现批量设备控制 + 智能场景联动,让用户通过一键触发,完成多设备的同步操作,同时支持场景自定义编辑、本地持久化、MQTT 批量指令下发,结合 OpenHarmony 多终端适配,打造真正 “智能化、自动化” 的家居体验。
Day9 完全延续前 8 天「逐步骤拆解 + 逐行代码解析 + 全场景踩坑解决」的风格,100% 适配 CSDN 发布格式,所有代码块直接复制可高亮显示,无任何多余标签、无文件生成形式,全程围绕实际开发落地,从场景模型设计、本地持久化、批量控制逻辑、MQTT 批量指令、UI 场景面板到鸿蒙多终端适配,每一步都讲清「为什么做、怎么做、会遇到什么问题、怎么解决」,代码可直接复用,兼顾新手入门与工程化实践。
本文承接 Day3–Day8 完整项目架构,重点扩展批量操作能力、场景联动逻辑、多设备并发控制,适合 Flutter + 鸿蒙跨平台开发者、智能家居产品开发者,学完本篇,你的 APP 将从 “单设备可控” 升级为 “全场景智能”,功能完整性直接对标市面主流智能家居 APP。
一、Day9 核心目标(方向清晰,不做无用功)
Day9 在已有网络请求、本地 ObjectBox 持久化、MQTT 实时通信、设备状态同步基础上,聚焦批量控制与场景联动,所有开发围绕以下目标推进:
- 设计智能场景数据模型,支持场景名称、图标、绑定设备、执行动作、触发条件,并适配 ObjectBox 本地持久化;
- 实现场景本地 CRUD(新增、编辑、删除、查询),支持自定义场景,数据永久保存不丢失;
- 封装批量设备控制逻辑,支持按设备类型、房间、自定义分组批量选中、批量执行开关 / 调节指令;
- 基于 MQTT 实现批量指令并发下发,保证多设备指令不冲突、不丢失、延迟低;
- 实现一键执行场景:点击场景卡片→自动遍历绑定设备→批量下发 MQTT 指令→实时同步状态→更新 UI;
- 扩展 DeviceProvider,统一管理场景列表、批量选中状态、场景执行状态,实现状态全局共享;
- 开发场景 UI 面板:场景列表、场景编辑页、批量设备选择页、执行状态动效;
- 处理批量控制异常:设备离线过滤、指令发送失败重试、中断恢复、并发冲突解决;
- 完成 OpenHarmony 专属适配:DAYU200 开发板批量指令稳定性、平板 / 手机场景布局自适应、后台执行保活;
- 优化性能:百台设备批量选择不卡顿、批量 MQTT 发送无阻塞、场景执行秒级响应;
- 规范工程结构:场景逻辑独立模块化,与原有设备、MQTT、本地库解耦,便于后续扩展语音控制、定时触发。
二、前置准备(无缝衔接 Day8,不脱节)
Day9不新增任何外部依赖,完全复用 Day3–Day8 已集成的库(dio、provider、objectbox、mqtt_client、connectivity_plus 等),只做逻辑扩展与模型新增,开发前先确认基础环境:
2.1 已有能力回顾(必须确认)
- 数据层:BaseDevice / 空调 / 灯光 / 窗帘模型、ObjectBox 本地库、DeviceRepository(网络 + 本地);
- 通信层:MQTT 工具类、主题规范、消息模型、双向状态同步;
- 业务层:DeviceProvider 全局状态管理、设备筛选 / 搜索、离线兜底;
- UI 层:设备列表、设备卡片、状态实时刷新、鸿蒙多端布局;
- 权限层:鸿蒙网络、存储、后台保活权限已配置。
2.2 无新依赖(直接开发)
pubspec.yaml 完全保持 Day8 版本不动,无需添加任何库,避免版本冲突与编译问题。
2.3 智能场景核心规则(先看懂再开发)
- 一个场景 = 一组设备 + 一组动作例:回家场景 = {客厅灯:开,空调:开 26℃, 窗帘:开}
- 批量控制 = 遍历选中设备 + 串行 / 并行下发 MQTT 指令
- 场景必须本地持久化:重启 APP、断网都能正常使用;
- 离线设备自动跳过:不阻塞整体执行,执行后给出结果提示;
- 执行状态实时反馈:哪个设备成功、哪个失败,UI 直观展示。
2.4 文件夹结构(统一规范)
新增场景模块,严格按分层架构放置:
plaintext
lib/
├── core/
│ ├── mqtt/ // 原有MQTT工具
│ ├── objectbox/ // 原有本地库
│ └── constants/ // 新增场景常量
├── data/
│ ├── models/ // 新增Scene场景模型
│ └── repositories/ // 扩展DeviceRepository支持批量
├── domain/
│ └── providers/ // 扩展DeviceProvider支持场景
└── ui/
├── pages/
│ ├── scene/ // 场景列表页、场景编辑页
│ └── batch/ // 批量设备选择页
└── widgets/ // 场景卡片、批量选择组件
三、场景数据模型设计(本地持久化核心)
我们先创建场景模型,并标记为@Entity实现 ObjectBox 本地存储,这是 Day9 最基础的一步。
3.1 场景常量(统一图标 / 类型)
文件路径:lib/core/constants/scene_constants.dart
dart
// 场景类型常量
class SceneConstants {
// 预设场景类型
static const String sceneTypeHome = "home"; // 回家
static const String sceneTypeLeave = "leave"; // 离家
static const String sceneTypeSleep = "sleep"; // 睡眠
static const String sceneTypeMovie = "movie"; // 观影
static const String sceneTypeCustom = "custom"; // 自定义
// 场景对应图标
static Map<String, String> sceneIcons = {
sceneTypeHome: "assets/icons/ic_home.png",
sceneTypeLeave: "assets/icons/ic_leave.png",
sceneTypeSleep: "assets/icons/ic_sleep.png",
sceneTypeMovie: "assets/icons/ic_movie.png",
sceneTypeCustom: "assets/icons/ic_custom.png",
};
// 场景名称
static Map<String, String> sceneNames = {
sceneTypeHome: "回家模式",
sceneTypeLeave: "离家模式",
sceneTypeSleep: "睡眠模式",
sceneTypeMovie: "观影模式",
sceneTypeCustom: "自定义场景",
};
}
3.2 场景动作模型(设备 + 动作)
一个场景包含多个动作,每个动作对应:设备 ID、设备类型、执行指令(开 / 关 / 参数)。文件路径:lib/data/models/scene_action_model.dart
dart
import 'package:json_annotation/json_annotation.dart';
import 'package:objectbox/objectbox.dart';
part 'scene_action_model.g.dart';
@Entity()
class SceneAction {
@Id()
int obxId = 0;
// 绑定的设备ID
final String deviceId;
// 设备名称
final String deviceName;
// 设备类型
final String deviceType;
// 执行动作:on/off/setParam
final String action;
// 动作参数:温度、亮度、位置
final Map<String, dynamic> actionParams;
SceneAction({
required this.deviceId,
required this.deviceName,
required this.deviceType,
required this.action,
required this.actionParams,
});
factory SceneAction.fromJson(Map<String, dynamic> json) =>
_$SceneActionFromJson(json);
Map<String, dynamic> toJson() => _$SceneActionToJson(this);
}
3.3 场景主模型(ObjectBox 实体)
文件路径:lib/data/models/scene_model.dart
dart
import 'package:json_annotation/json_annotation.dart';
import 'package:objectbox/objectbox.dart';
import 'package:smart_home_flutter/data/models/scene_action_model.dart';
part 'scene_model.g.dart';
@Entity()
class SmartScene {
@Id()
int obxId = 0;
// 场景唯一标识
@Unique()
final String sceneId;
// 场景类型:home/leave/sleep/custom
final String sceneType;
// 场景名称
final String sceneName;
// 图标路径
final String iconPath;
// 场景描述
final String desc;
// 绑定的动作列表
final List<SceneAction> actions;
// 创建时间
final int createTime;
// 是否启用
bool isEnable;
SmartScene({
required this.sceneId,
required this.sceneType,
required this.sceneName,
required this.iconPath,
required this.desc,
required this.actions,
required this.createTime,
this.isEnable = true,
});
factory SmartScene.fromJson(Map<String, dynamic> json) =>
_$SmartSceneFromJson(json);
Map<String, dynamic> toJson() => _$SmartSceneToJson(this);
}
3.4 生成 ObjectBox 映射文件
执行命令(必须执行):
bash
运行
flutter pub run build_runner build --delete-conflicting-outputs
四、扩展本地库:场景本地 CRUD
在原有 ObjectBox 管理类中添加场景相关操作,实现场景本地存储。
文件路径:lib/core/objectbox/objectbox_instance.dart
dart
// 新增:场景Box获取
Box<SmartScene> get sceneBox => store.box<SmartScene>();
Box<SceneAction> get sceneActionBox => store.box<SceneAction>();
// 场景-查询所有
List<SmartScene> getAllScenes() {
return sceneBox.query().order(SmartScene_.createTime, flags: Order.descending).build().find();
}
// 场景-添加/更新
void putScene(SmartScene scene) {
sceneBox.put(scene);
// 同步存储动作
for (var action in scene.actions) {
sceneActionBox.put(action);
}
}
// 场景-删除
void deleteScene(SmartScene scene) {
sceneBox.remove(scene.obxId);
// 删除关联动作
final actions = sceneActionBox.query(SceneAction_.deviceId.equals(scene.sceneId)).build().find();
for (var a in actions) {
sceneActionBox.remove(a.obxId);
}
}
五、批量控制核心逻辑(MQTT 批量下发)
这是 Day9技术核心:批量遍历设备→构建 MQTT 指令→并发 / 串行下发→结果回收。
文件路径:lib/domain/repositories/device_repository_impl.dart
dart
// 新增:批量控制设备
Future<Map<String, bool>> batchControlDevices(List<SceneAction> actions) async {
Map<String, bool> result = {};
final isOnline = await _connectivity.checkConnectivity() != ConnectivityResult.none;
if (!isOnline) {
throw Exception("网络异常,无法执行批量控制");
}
for (var action in actions) {
try {
// 构建MQTT控制消息
final msg = MqttControlMessage.fromDeviceAndParams(
device: await _getLocalDeviceById(action.deviceId),
controlParams: action.actionParams,
clientId: _mqttManager.clientId,
);
// 下发MQTT指令
await _mqttManager.publishMessage(
topic: MqttConstants.generateControlTopic(action.deviceId),
message: msg,
);
result[action.deviceId] = true;
await Future.delayed(Duration(milliseconds: 100)); // 防抖限流
} catch (e) {
result[action.deviceId] = false;
print("批量控制失败:${action.deviceId} => $e");
}
}
return result;
}
// 辅助:从本地查询设备
Future<BaseDevice> _getLocalDeviceById(String deviceId) async {
final list = await getDevicesFromLocal();
return list.firstWhere((e) => e.deviceId == deviceId);
}
六、扩展 DeviceProvider(场景全局状态)
文件路径:lib/domain/providers/device_provider.dart
dart
// 场景相关状态
List<SmartScene> _sceneList = [];
List<BaseDevice> _batchSelectedDevices = [];
bool _isSceneExecuting = false;
Map<String, bool> _sceneExecuteResult = {};
List<SmartScene> get sceneList => _sceneList;
List<BaseDevice> get batchSelectedDevices => _batchSelectedDevices;
bool get isSceneExecuting => _isSceneExecuting;
Map<String, bool> get sceneExecuteResult => _sceneExecuteResult;
// 加载所有场景
Future<void> loadScenes() async {
_sceneList = _objectBox.getAllScenes();
notifyListeners();
}
// 保存场景
Future<void> saveScene(SmartScene scene) async {
_objectBox.putScene(scene);
await loadScenes();
}
// 删除场景
Future<void> deleteScene(SmartScene scene) async {
_objectBox.deleteScene(scene);
await loadScenes();
}
// 切换设备选中状态
void toggleDeviceSelect(BaseDevice device) {
if (_batchSelectedDevices.contains(device)) {
_batchSelectedDevices.remove(device);
} else {
_batchSelectedDevices.add(device);
}
notifyListeners();
}
// 清空选中
void clearBatchSelect() {
_batchSelectedDevices.clear();
notifyListeners();
}
// 执行场景
Future<void> executeScene(SmartScene scene) async {
_isSceneExecuting = true;
_sceneExecuteResult.clear();
notifyListeners();
try {
_sceneExecuteResult = await _deviceRepository.batchControlDevices(scene.actions);
} catch (e) {
print("场景执行失败:$e");
} finally {
_isSceneExecuting = false;
// 刷新设备列表
refreshDeviceList();
notifyListeners();
}
}
七、UI 层实现:场景面板 + 批量选择
7.1 场景列表首页 Widget
文件路径:lib/ui/pages/scene/scene_list_page.dart
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smart_home_flutter/domain/providers/device_provider.dart';
import 'package:smart_home_flutter/data/models/scene_model.dart';
import 'package:smart_home_flutter/ui/widgets/scene_card_widget.dart';
class SceneListPage extends StatefulWidget {
const SceneListPage({super.key});
@override
State<SceneListPage> createState() => _SceneListPageState();
}
class _SceneListPageState extends State<SceneListPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<DeviceProvider>(context, listen: false).loadScenes();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("智能场景")),
body: Consumer<DeviceProvider>(
builder: (context, provider, child) {
if (provider.sceneList.isEmpty) {
return const Center(child: Text("暂无场景,点击+创建"));
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: provider.sceneList.length,
itemBuilder: (context, index) {
final scene = provider.sceneList[index];
return SceneCardWidget(scene: scene);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) => const SceneEditPage()));
},
child: const Icon(Icons.add),
),
);
}
}
7.2 场景卡片 Widget
文件路径:lib/ui/widgets/scene_card_widget.dart
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smart_home_flutter/data/models/scene_model.dart';
import 'package:smart_home_flutter/domain/providers/device_provider.dart';
import 'package:smart_home_flutter/core/constants/scene_constants.dart';
class SceneCardWidget extends StatelessWidget {
final SmartScene scene;
const SceneCardWidget({super.key, required this.scene});
@override
Widget build(BuildContext context) {
final provider = Provider.of<DeviceProvider>(context);
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Image.asset(SceneConstants.sceneIcons[scene.sceneType] ?? "", width: 40, height: 40),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(scene.sceneName, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
Text(scene.desc, style: const TextStyle(fontSize: 12, color: Colors.grey)),
],
),
),
ElevatedButton(
onPressed: provider.isSceneExecuting
? null
: () => provider.executeScene(scene),
child: provider.isSceneExecuting
? const CircularProgressIndicator(size: 16)
: const Text("执行"),
),
],
),
),
);
}
}
7.3 批量设备选择页面
文件路径:lib/ui/pages/batch/batch_select_page.dart
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smart_home_flutter/domain/providers/device_provider.dart';
import 'package:smart_home_flutter/data/models/device_model.dart';
class BatchSelectPage extends StatelessWidget {
const BatchSelectPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("选择设备")),
body: Consumer<DeviceProvider>(
builder: (context, provider, child) {
return ListView.builder(
itemCount: provider.deviceList.length,
itemBuilder: (context, index) {
final device = provider.deviceList[index];
final selected = provider.batchSelectedDevices.contains(device);
return ListTile(
title: Text(device.name),
subtitle: Text(device.room),
trailing: Checkbox(
value: selected,
onChanged: (_) => provider.toggleDeviceSelect(device),
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context);
},
child: const Icon(Icons.check),
),
);
}
}
八、OpenHarmony 专属适配(关键必做)
8.1 鸿蒙后台保活(场景执行不被杀)
在module.json5中添加后台持续运行权限:
json
"requestPermissions": [
{"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"}
]
8.2 鸿蒙平板布局适配
使用LayoutBuilder自适应手机 / 平板 / 开发板:
dart
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
// 平板:网格布局
return GridView.count(crossAxisCount: 2);
} else {
// 手机:列表布局
return ListView();
}
},
)
8.3 MQTT 批量指令优化
- 鸿蒙设备 TCP 不稳定 → 自动切 WebSocket
- 批量下发增加 100ms 间隔 → 避免 Broker 限流
- 离线设备自动跳过 → 不阻塞执行流程
九、高频问题与解决方案(全是踩坑总结)
表格
| 问题场景 | 原因 | 解决方案 |
|---|---|---|
| 批量执行时部分设备没反应 | MQTT 并发超限、设备离线 | 加 100ms 间隔,离线设备自动过滤,执行后展示失败列表 |
| 场景重启 APP 后丢失 | 未存入 ObjectBox | 所有场景必须调用 putScene 持久化 |
| 鸿蒙开发板批量执行闪退 | 后台进程被杀死 | 配置 KEEP_BACKGROUND_RUNNING 权限 |
| 执行场景后 UI 不刷新 | 未调用 notifyListeners | 执行完调用 refreshDeviceList |
| 重复点击导致重复下发 | 未加执行锁 | Provider 中加 isSceneExecuting 互斥 |
十、Day9 总结(核心收获)
Day9 我们完整实现了智能家居批量控制 + 场景联动全功能,核心成果:
- 自定义智能场景,本地持久化永不丢失;
- 一键执行多设备同步控制,支持空调 / 灯光 / 窗帘等多类型设备;
- MQTT 批量指令安全下发,异常捕获、离线过滤、执行反馈;
- 鸿蒙多终端完美适配,手机 / 平板 / 开发板都能流畅运行;
- 功能模块化,可直接扩展定时场景、语音触发、地理围栏等高级能力。
至此,你的 Flutter+OpenHarmony 智能家居 APP 已经具备:设备管理 + 本地离线 + 实时同步 + 批量控制 + 场景联动 = 完整商业化基础功能。
下一篇 Day10,我们将实现设备定时任务 + 鸿蒙系统级通知,让 APP 真正实现 “全自动智能”。
更多推荐



所有评论(0)