【共创季稿事节】基于 HarmonyOS 6.1 意图框架的智能家居一步直达方案
文章目录

每日一句正能量
这个世上最温暖的成全莫过于让别人的欢喜落地有声。
当别人告诉你一件开心的事,你不只是说“挺好的”,而是真正看见、听见、回应那份喜悦。“落地有声”意味着那份欢喜没有被忽略、被敷衍,而是被接住并放大了。
人生不是单行道,也不是完美实验,而是一场可以不断调整节奏、换角度、换方向、允许失误的旅途。
导读
本文以“打开客厅灯”为主线,演示如何借助 HarmonyOS 6.1 Intents Kit,将传统的多级页面操作压缩为“语音或搜索一句话直达设备控制”,并给出架构设计、意图建模、ArkTS 代码、风险控制、异常处理与测试思路。
七个关键词
HarmonyOS 6.1、Intents Kit、智能家居、语音直达、全局搜索、ArkTS、设备控制
一、为什么智能家居需要“一步直达”
传统智能家居应用的典型操作链路是:
- 解锁手机;
- 找到并打开家庭应用;
- 进入“我的家”;
- 选择房间;
- 找到目标设备;
- 点击开关或进入控制面板;
- 等待设备状态刷新。
这条链路在首次使用时并不算复杂,但在“每天重复几十次”的高频场景中,任何一次多余点击都会被放大。更麻烦的是,用户往往并不关心应用的信息架构,他真正想完成的是一个业务动作,例如:
- 打开客厅灯;
- 把卧室空调调到 26 ℃;
- 关闭书房窗帘;
- 查询空气净化器滤芯寿命;
- 启动“回家”场景。
用户表达的是目标,而不是“打开哪个页面”。因此,一步直达的核心不是再加一个 DeepLink,而是让系统理解“动作、对象、位置和参数”,再把结构化意图交给应用执行。
HarmonyOS 的 Intents Kit 面向的正是这类问题:应用将可执行的业务能力声明为系统可理解的意图,系统入口可通过语音、搜索等方式识别用户需求,再把匹配后的意图与参数分发给应用。这样,应用从“等待用户逐层寻找功能”转变为“直接承接用户目标”。
说明:不同 SDK 版本的装饰器、配置文件字段和发布流程可能调整,本文代码以工程化设计与 ArkTS 实现思路为主。接入实际项目时,应以当前 HarmonyOS 6.1 SDK、DevEco Studio 模板和官方 Intents Kit 文档为准。
二、目标交互:从六步压缩到一步

以“打开客厅灯”为例,目标交互如下:
2.1 语音入口
用户说:
“小艺,打开客厅灯。”
系统完成意图识别后,将结构化参数传给应用:
intentName = controlDevice
room = 客厅
device = 灯
action = ON
应用不再打开首页,而是直接调用设备控制服务。控制成功后,系统返回:
“客厅灯已打开。”
2.2 搜索入口
用户在系统搜索中输入:
打开客厅灯
搜索结果直接展示“打开客厅灯”的服务结果卡。点击后可立即执行,或在高风险场景中先进行确认。
2.3 失败时也要一步到位
“一步直达”并不等于“一律静默执行”。系统必须区分以下情况:
- 设备离线:明确提示设备当前不可用;
- 设备重名:展示候选设备;
- 权限失效:引导重新授权;
- 高风险动作:要求二次确认;
- 网络超时:允许重试,并避免重复执行;
- 状态不确定:提示“指令已发送,设备状态待确认”。
三、整体架构设计

本文把方案拆成五层。
3.1 系统入口层
负责承接用户表达,包括:
- 小艺语音;
- 系统全局搜索;
- 服务卡片;
- 应用内搜索;
- 快捷指令。
入口层只负责采集用户表达,不直接控制设备。
3.2 意图框架层
负责把自然语言转换为结构化意图:
- 识别意图名称;
- 抽取房间、设备、动作和数值;
- 处理同义词;
- 生成标准化参数;
- 选择适合的结果呈现方式。
例如,“开一下客厅的灯”和“把客厅主灯打开”最终都应归一化为同一个动作。
3.3 应用编排层
这是应用侧最关键的一层,包括:
IntentDispatcher:接收并分发意图;SceneResolver:解析设备、房间和家庭;RiskPolicy:判断是否需要二次确认;ResultPresenter:统一生成语音、搜索卡片和应用页面结果。
3.4 设备领域层
负责业务规则,而不是界面跳转:
- 读取设备列表;
- 解析设备唯一标识;
- 检查在线状态;
- 下发控制指令;
- 刷新状态缓存;
- 写入审计日志;
- 做幂等保护。
3.5 设备接入层
屏蔽不同设备协议差异,可接入:
- 家庭中枢;
- 云端 IoT 平台;
- 局域网设备;
- 蓝牙设备;
- 测试环境中的模拟设备。
四、意图模型设计

一个可维护的设备控制意图,至少应包含以下字段:
export enum DeviceAction {
ON = 'ON',
OFF = 'OFF',
SET_LEVEL = 'SET_LEVEL',
SET_TEMPERATURE = 'SET_TEMPERATURE',
OPEN = 'OPEN',
CLOSE = 'CLOSE'
}
export interface DeviceIntent {
intentName: string;
homeId?: string;
room?: string;
device?: string;
deviceId?: string;
action: DeviceAction;
value?: number;
requestId: string;
source: 'VOICE' | 'SEARCH' | 'CARD' | 'APP';
}
4.1 为什么要保留 requestId
语音入口或搜索结果可能因为网络重试而重复触发。若“打开灯”重复执行通常没有严重后果,但“打开门锁”“启动烤箱”等动作不能依赖运气。requestId 用于:
- 幂等校验;
- 链路追踪;
- 故障排查;
- 审计记录;
- 重试去重。
4.2 为什么不能只传设备名称
家庭中可能同时存在:
- 客厅主灯;
- 客厅灯带;
- 客厅落地灯;
- 两个都被用户简称为“灯”的设备。
因此,设备解析应遵循以下优先级:
- 明确的
deviceId; - 家庭 + 房间 + 设备名;
- 最近使用设备;
- 唯一模糊匹配;
- 返回候选列表让用户确认。
五、意图配置示例
不同 SDK 版本的意图声明方式可能存在差异。下面使用接近工程配置的 JSON5 形式说明字段设计,重点是语义建模方法。
{
"intents": [
{
"name": "controlDevice",
"description": "控制家庭中的灯、空调、窗帘等设备",
"parameters": [
{
"name": "home",
"type": "string",
"required": false
},
{
"name": "room",
"type": "string",
"required": false
},
{
"name": "device",
"type": "string",
"required": true
},
{
"name": "action",
"type": "string",
"required": true,
"enum": [
"ON",
"OFF",
"OPEN",
"CLOSE",
"SET_LEVEL",
"SET_TEMPERATURE"
]
},
{
"name": "value",
"type": "number",
"required": false
}
],
"examples": [
"打开客厅灯",
"关闭卧室空调",
"把书房窗帘打开",
"把客厅灯调到百分之五十",
"把卧室空调调到二十六度"
]
}
]
}
5.1 同义词归一化
自然语言中的动作表达非常丰富:
const ACTION_ALIASES: Record<string, DeviceAction> = {
'打开': DeviceAction.ON,
'开启': DeviceAction.ON,
'开一下': DeviceAction.ON,
'关掉': DeviceAction.OFF,
'关闭': DeviceAction.OFF,
'调亮': DeviceAction.SET_LEVEL,
'调暗': DeviceAction.SET_LEVEL,
'升温': DeviceAction.SET_TEMPERATURE,
'降温': DeviceAction.SET_TEMPERATURE
};
对于窗帘,“打开”应该映射为 OPEN;对于灯,“打开”应该映射为 ON。所以归一化不能只看动词,还要结合设备类型。
六、ArkTS 核心实现
6.1 统一结果模型
export enum CommandStatus {
SUCCESS = 'SUCCESS',
NEED_CONFIRM = 'NEED_CONFIRM',
NEED_SELECT = 'NEED_SELECT',
OFFLINE = 'OFFLINE',
FORBIDDEN = 'FORBIDDEN',
TIMEOUT = 'TIMEOUT',
FAILED = 'FAILED'
}
export interface DeviceCandidate {
id: string;
name: string;
roomName: string;
online: boolean;
}
export interface CommandResult {
status: CommandStatus;
message: string;
deviceId?: string;
deviceName?: string;
candidates?: DeviceCandidate[];
retryable?: boolean;
}
6.2 设备仓库
export interface SmartDevice {
id: string;
homeId: string;
roomName: string;
name: string;
type: 'LIGHT' | 'AIR_CONDITIONER' | 'CURTAIN' | 'LOCK';
online: boolean;
ownerUserId: string;
}
export class DeviceRepository {
private devices: SmartDevice[] = [
{
id: 'light-living-main',
homeId: 'home-001',
roomName: '客厅',
name: '主灯',
type: 'LIGHT',
online: true,
ownerUserId: 'user-001'
},
{
id: 'curtain-study',
homeId: 'home-001',
roomName: '书房',
name: '窗帘',
type: 'CURTAIN',
online: true,
ownerUserId: 'user-001'
}
];
async findCandidates(
userId: string,
homeId: string | undefined,
room: string | undefined,
deviceKeyword: string
): Promise<SmartDevice[]> {
const keyword = deviceKeyword.trim();
return this.devices.filter((item: SmartDevice) => {
const ownerMatched = item.ownerUserId === userId;
const homeMatched = homeId ? item.homeId === homeId : true;
const roomMatched = room ? item.roomName.includes(room) : true;
const deviceMatched =
item.name.includes(keyword) ||
this.matchTypeAlias(item.type, keyword);
return ownerMatched && homeMatched && roomMatched && deviceMatched;
});
}
private matchTypeAlias(type: SmartDevice['type'], keyword: string): boolean {
const aliases: Record<SmartDevice['type'], string[]> = {
LIGHT: ['灯', '照明', '灯光'],
AIR_CONDITIONER: ['空调'],
CURTAIN: ['窗帘', '电动帘'],
LOCK: ['门锁', '智能锁']
};
return aliases[type].some((item: string) => keyword.includes(item));
}
}
6.3 风险策略
设备控制不能只考虑“能不能执行”,还要判断“是否适合直接执行”。
export class RiskPolicy {
needConfirmation(
device: SmartDevice,
action: DeviceAction,
source: DeviceIntent['source']
): boolean {
if (device.type === 'LOCK') {
return true;
}
if (source === 'VOICE' &&
device.type === 'AIR_CONDITIONER' &&
action === DeviceAction.ON) {
return false;
}
return false;
}
}
真实项目中建议把以下动作列为高风险:
- 门锁开锁;
- 燃气阀开启;
- 烤箱、取暖器等高功率设备启动;
- 安防撤防;
- 涉及儿童或老人安全的设备操作。
6.4 设备命令服务
export class DeviceCommandService {
private executedRequests: Set<string> = new Set<string>();
async execute(
requestId: string,
device: SmartDevice,
action: DeviceAction,
value?: number
): Promise<CommandResult> {
if (this.executedRequests.has(requestId)) {
return {
status: CommandStatus.SUCCESS,
message: '该指令已执行,无需重复操作',
deviceId: device.id,
deviceName: device.name
};
}
if (!device.online) {
return {
status: CommandStatus.OFFLINE,
message: `${device.roomName}${device.name}当前离线`,
deviceId: device.id,
deviceName: device.name,
retryable: true
};
}
this.validateValue(device, action, value);
try {
await this.sendToDevice(device, action, value);
this.executedRequests.add(requestId);
return {
status: CommandStatus.SUCCESS,
message: this.buildSuccessMessage(device, action, value),
deviceId: device.id,
deviceName: device.name
};
} catch (error) {
return {
status: CommandStatus.FAILED,
message: '设备控制失败,请稍后重试',
deviceId: device.id,
deviceName: device.name,
retryable: true
};
}
}
private validateValue(
device: SmartDevice,
action: DeviceAction,
value?: number
): void {
if (action === DeviceAction.SET_LEVEL) {
if (value === undefined || value < 0 || value > 100) {
throw new Error('亮度值必须在 0 到 100 之间');
}
}
if (action === DeviceAction.SET_TEMPERATURE) {
if (device.type !== 'AIR_CONDITIONER') {
throw new Error('当前设备不支持温度设置');
}
if (value === undefined || value < 16 || value > 30) {
throw new Error('空调温度必须在 16 到 30 摄氏度之间');
}
}
}
private async sendToDevice(
device: SmartDevice,
action: DeviceAction,
value?: number
): Promise<void> {
// 此处替换为真实家庭中枢、云端 IoT SDK 或局域网协议调用。
await new Promise<void>((resolve) => {
setTimeout(() => resolve(), 120);
});
}
private buildSuccessMessage(
device: SmartDevice,
action: DeviceAction,
value?: number
): string {
const prefix = `${device.roomName}${device.name}`;
switch (action) {
case DeviceAction.ON:
return `${prefix}已打开`;
case DeviceAction.OFF:
return `${prefix}已关闭`;
case DeviceAction.OPEN:
return `${prefix}已打开`;
case DeviceAction.CLOSE:
return `${prefix}已关闭`;
case DeviceAction.SET_LEVEL:
return `${prefix}亮度已调到${value}%`;
case DeviceAction.SET_TEMPERATURE:
return `${prefix}已调到${value}摄氏度`;
default:
return `${prefix}控制成功`;
}
}
}
6.5 意图分发器
export class IntentDispatcher {
constructor(
private repository: DeviceRepository,
private commandService: DeviceCommandService,
private riskPolicy: RiskPolicy
) {}
async dispatch(
userId: string,
intent: DeviceIntent
): Promise<CommandResult> {
if (intent.intentName !== 'controlDevice') {
return {
status: CommandStatus.FAILED,
message: '暂不支持该意图'
};
}
if (!intent.device || intent.device.trim().length === 0) {
return {
status: CommandStatus.NEED_SELECT,
message: '请告诉我要控制哪个设备'
};
}
const candidates = await this.repository.findCandidates(
userId,
intent.homeId,
intent.room,
intent.device
);
if (candidates.length === 0) {
return {
status: CommandStatus.FAILED,
message: '没有找到匹配的设备'
};
}
if (candidates.length > 1) {
return {
status: CommandStatus.NEED_SELECT,
message: '找到多个设备,请选择一个',
candidates: candidates.map((item: SmartDevice) => ({
id: item.id,
name: item.name,
roomName: item.roomName,
online: item.online
}))
};
}
const device = candidates[0];
if (this.riskPolicy.needConfirmation(
device,
intent.action,
intent.source
)) {
return {
status: CommandStatus.NEED_CONFIRM,
message: `是否确认控制${device.roomName}${device.name}?`,
deviceId: device.id,
deviceName: device.name
};
}
return this.commandService.execute(
intent.requestId,
device,
intent.action,
intent.value
);
}
}
6.6 页面或意图处理入口
@Entry
@Component
struct IntentResultPage {
@State resultText: string = '正在执行设备指令…';
@State loading: boolean = true;
private dispatcher: IntentDispatcher = new IntentDispatcher(
new DeviceRepository(),
new DeviceCommandService(),
new RiskPolicy()
);
async aboutToAppear(): Promise<void> {
const intent: DeviceIntent = {
intentName: 'controlDevice',
room: '客厅',
device: '灯',
action: DeviceAction.ON,
requestId: `${Date.now()}-voice`,
source: 'VOICE'
};
const result = await this.dispatcher.dispatch('user-001', intent);
this.resultText = result.message;
this.loading = false;
}
build() {
Column({ space: 20 }) {
Text('智能家庭')
.fontSize(28)
.fontWeight(FontWeight.Bold)
if (this.loading) {
LoadingProgress()
.width(48)
.height(48)
}
Text(this.resultText)
.fontSize(22)
.fontWeight(FontWeight.Medium)
Button('返回家庭')
.onClick(() => {
// 返回应用主页面
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(24)
}
}
七、完整执行时序

“打开客厅灯”的完整执行过程如下:
- 用户通过语音或搜索表达目标;
- 系统匹配
controlDevice意图; - 系统抽取
room=客厅、device=灯、action=ON; - 应用校验账号、家庭和设备归属;
- 应用解析唯一设备;
- 风险策略判断是否需要二次确认;
- 设备控制服务下发指令;
- 设备返回执行结果;
- 应用更新状态缓存并写入日志;
- 系统以语音、卡片或页面反馈结果。
八、结果页设计

结果页不应只是“控制成功”四个字,而应包含:
- 设备名称和房间;
- 当前在线状态;
- 最新设备状态;
- 执行动作;
- 撤销入口;
- 继续控制入口;
- 指令来源;
- 失败时的重试方式。
对于语音场景,建议优先给出简短反馈:
“客厅灯已打开。”
对于搜索卡片,可展示更完整的信息:
客厅主灯
在线 · 已打开
[关闭] [继续控制]
九、异常与边界场景
9.1 设备重名
用户说“打开灯”,但家中有多个灯。此时不要随机选择,应返回候选项:
{
status: CommandStatus.NEED_SELECT,
message: '找到多个灯,请选择一个',
candidates: [
{ id: '1', roomName: '客厅', name: '主灯', online: true },
{ id: '2', roomName: '卧室', name: '床头灯', online: true }
]
}
9.2 设备离线
应区分“指令发送失败”和“设备确认离线”:
- 明确离线:提示检查设备供电和网络;
- 云端超时:提示稍后重试;
- 局域网不可达:尝试云端链路;
- 状态未知:提示“指令已发送,设备状态待确认”。
9.3 参数缺失
用户说“把空调调低一点”,缺少目标温度。可以采用两种策略:
- 结合当前温度做相对调节;
- 追问“要调到多少度”。
不要把“调低一点”硬编码成固定温度,否则可能不符合用户预期。
9.4 权限失效
家庭成员权限可能被撤销。执行前应重新校验:
- 用户是否仍属于该家庭;
- 是否拥有目标设备控制权限;
- 是否允许从语音入口执行;
- 是否存在儿童模式或访客限制。
9.5 高风险动作
对于开锁、撤防、启动高功率设备等动作,建议使用:
- 二次确认;
- 生物识别;
- 可信设备校验;
- 地理围栏;
- 时间段策略;
- 审计日志。
十、性能优化
一步直达的用户感知非常敏感。即使最终控制成功,等待时间过长也会让用户误以为系统没有识别。
建议把链路拆成以下指标:
| 指标 | 建议目标 | 说明 |
|---|---|---|
| 意图匹配耗时 | < 150 ms | 不含大模型远端推理 |
| 本地设备解析 | < 50 ms | 优先使用本地索引 |
| 权限校验 | < 100 ms | 缓存家庭成员关系 |
| 指令下发 | < 500 ms | 取决于设备协议 |
| 首次反馈 | < 800 ms | 可先反馈“正在打开” |
| 最终状态确认 | < 2 s | 超时后进入待确认状态 |
10.1 本地索引
把设备名称、房间、别名和最近使用记录建立本地索引,避免每次都拉取全量设备列表。
10.2 状态缓存
设备状态应带时间戳:
interface CachedDeviceState {
deviceId: string;
online: boolean;
powerOn?: boolean;
updatedAt: number;
}
缓存只用于快速展示,真正执行高风险动作前仍需校验服务端状态。
10.3 分阶段反馈
当设备链路较慢时,可先反馈:
“正在打开客厅灯。”
收到最终结果后再更新为:
“客厅灯已打开。”
这样可以降低用户的不确定感。
十一、测试方案
11.1 功能测试
至少覆盖:
- 精确设备名;
- 房间 + 设备名;
- 设备别名;
- 动作同义词;
- 数值参数;
- 多设备重名;
- 设备离线;
- 权限不足;
- 网络超时;
- 重复请求;
- 高风险确认。
11.2 示例测试用例
interface IntentTestCase {
utterance: string;
expectedRoom?: string;
expectedDevice: string;
expectedAction: DeviceAction;
expectedValue?: number;
}
const cases: IntentTestCase[] = [
{
utterance: '打开客厅灯',
expectedRoom: '客厅',
expectedDevice: '灯',
expectedAction: DeviceAction.ON
},
{
utterance: '把卧室空调调到26度',
expectedRoom: '卧室',
expectedDevice: '空调',
expectedAction: DeviceAction.SET_TEMPERATURE,
expectedValue: 26
},
{
utterance: '关掉书房窗帘',
expectedRoom: '书房',
expectedDevice: '窗帘',
expectedAction: DeviceAction.CLOSE
}
];
11.3 体验测试
技术正确不等于体验优秀,还应关注:
- 用户是否需要重复表达;
- 设备歧义时是否容易选择;
- 失败原因是否清晰;
- 是否能快速撤销;
- 语音反馈是否过长;
- 搜索卡片是否展示了关键状态;
- 高风险确认是否打断过度。
十二、与传统 DeepLink 方案的区别
DeepLink 擅长的是“打开某个页面”,例如:
smarthome://device/detail?id=light-living-main
它解决的是路由问题,但不能天然解决:
- 用户说了什么;
- 用户真正想做什么;
- “打开”是打开页面还是打开设备;
- 设备名称如何消歧;
- 参数如何抽取;
- 高风险动作如何确认;
- 执行结果如何反馈。
意图框架的价值在于把入口从“页面地址”升级为“业务目标”。两者并非互斥:
- 意图框架负责理解目标;
- 应用服务负责执行目标;
- DeepLink 可作为结果页或详情页跳转手段。
十三、工程落地建议
13.1 不要把业务逻辑写进 UIAbility
UIAbility 应负责生命周期和入口承接,设备解析、权限判断和命令执行应放到独立服务中,便于:
- 单元测试;
- 多入口复用;
- 服务卡片复用;
- 后续接入智能体;
- 日志与审计统一。
13.2 先支持少量高频意图
不要一开始就覆盖所有设备和所有句式。可先选择:
- 开灯 / 关灯;
- 空调温度调节;
- 窗帘开合;
- 常用场景启动。
把高频链路做稳定,再逐步扩展。
13.3 让失败也可恢复
失败反馈必须告诉用户下一步能做什么:
- “设备离线,请检查电源”;
- “找到两个客厅灯,请选择”;
- “当前账号没有控制权限”;
- “网络超时,是否重试”。
13.4 建立可观测性
建议记录:
interface IntentAuditLog {
requestId: string;
userId: string;
intentName: string;
source: string;
targetDeviceId?: string;
action: string;
status: string;
durationMs: number;
createdAt: number;
}
注意日志中不要记录原始敏感语音、家庭地址或不必要的个人信息。
十四、总结
智能家居应用真正的竞争力,不只是“能连接多少设备”,而是用户完成一次控制需要付出多少操作成本。
基于 HarmonyOS 6.1 Intents Kit,可以把“打开应用—进入家庭—选择房间—找到设备—执行控制”的多步操作,压缩为一句话或一次搜索。要把这个能力做好,关键不在于增加一个入口,而在于建立完整的业务闭环:
- 用标准意图描述可执行能力;
- 把自然语言映射为结构化参数;
- 用统一领域服务解析设备并执行命令;
- 对重名、离线、权限和高风险动作做显式处理;
- 用语音、卡片和页面返回可理解、可恢复的结果;
- 通过幂等、缓存、日志和性能指标保障工程质量。
当应用从“让用户寻找功能”转变为“主动承接用户目标”,智能家居才真正具备一步直达的体验价值。
转载自:https://blog.csdn.net/u014727709/article/details/162996006
欢迎 👍点赞✍评论⭐收藏,欢迎指正
更多推荐

所有评论(0)