控制 Wi-Fi 设备
更新时间:2025/02/18
小米 IoT 平台设备功能都是基于 Spec 模型来进行开发的。其连接与控制设备都是通过 Spec 功能定义中的属性(Properties)、事件(Event)或者方法(Action)来进行处理的。在 IoT 后台 - 产品 - 功能定义处可看到对应产品的具体功能定义。
也可以通过如下代码获取产品 Spec 功能定义的 JSON String:
Service.spec
.getSpecString(Device.deviceID)
.then((res) => {
logger.i('spec string: ', res);
})
.catch((error) => {
logger.e('getSpecString error', error);
});
在开发者平台修改功能定义后,需要 2 小时才会同步更新。
连接与控制流程

在照明类产品中,以控制灯开启/关闭的属性是 siid = 2, piid = 1 为例。
在用户点击扩展程序关灯按钮后:
- 插件调用
setPropertiesValue
将 siid = 2, piid = 1 的属性值修改为 true; - 请求发送到 IoT 平台服务端后,服务端发送下行 RPC 指令到设备;
- 这条指令(
set_properties 2 1 true
)最终由模组回复到 mcu 的get_down
的轮询请求中; - 当设备正确处理请求后,mcu 回复
result 2 1 0
给模组告知请求已成功处理; - 最后一个数字 0 用于设备告知插件该请求的处理状态码:
code
; - 0 代表请求处理成功;
- 1 代表请求已经发送给设备,具体的处理结果需要等待设备发送
properties_changed
指令,一般只有网关才可能回复此code
; - 其他代表失败。
- 服务端得知
code = 0
,将此code
回复到插件setPropertiesValue
请求的then
回调内; - 插件判断
code = 0
,可以认为操作已经成功,展示灯已打开;
插件订阅相关属性/事件后,可以在 deviceReceivedMessages
回调中收到设备主动发出的属性/事件变更:
- 设备上报
properties_changed 2 1 true
之后,平台服务端发送推送给插件(如果插件已经订阅的话); - 插件收到属性变更的回调后,在界面上展示灯已打开。
获取属性值
扩展程序可通过以下代码获取设备的各个属性值:
/**
* 这里的 siid 和 piid 的获取,可以通过解析 getSpecString 获取的 spec 信息。
* 也可以通过开发者平台上功能定义处查询该设备对应的设备属性 siid 和 piid 值。
*/
const params = [
{ did: Device.deviceID, siid: 1, piid: 1 },
{ did: Device.deviceID, siid: 2, piid: 1 }
];
Service.spec
.getPropertiesValue(params)
.then((res) => {
logger.i('getPropertiesValue success ', res);
// 注意判断 res 数组中每一个对象里面是否有 code
// 如果有 code 说明数据获取失败了
if (res?.length) {
res.forEach((item) => {
if (item.code) {
// 具体错误码见下文 **Spec 请求错误码**
logger.e('getPropertiesValue spec error: ', item);
}
});
}
})
.catch((error) => {
// 一般是网络原因或者请求参数问题
logger.e('getPropertiesValue error ', error);
});
监听属性/事件变化
获取设备属性值是一次网络请求,一般用于初始化属性值。
设备还会由于遥控器控制、其他用户操作等原因,使得其属性值发生改变。由于设备属性值发生变化,设备会上报 properties_changed
。扩展程序可以监听属性值的变化,这样就不需要不断轮询 getPropertiesValue
来获取属性值。在属性值发生变化后,小米 IoT 平台会通过推送透传给扩展程序。
import { Component } from 'react';
import { Device, DeviceEvent } from 'miot';
import Service from 'miot/Service';
class SwitchSub extends Component {
constructor(props) {
super(props);
// 第 1 步:初始化 state 数据
// 这里以开关 prop.2.1 为例
this.state = {
switch: false
};
}
componentDidMount() {
// 第 2 步:使用网络请求初始化属性值
Service.spec
.getPropertiesValue([{ did: Device.deviceID, siid: 2, piid: 1 }])
.then((res) => {
this.onReceiveData(res);
logger.i('Initial value: ', res);
})
.catch((err) => {
logger.e("Init prop's value error: ", err);
});
// 第 3 步:订阅属性值变化
Device.getDeviceWifi()
.subscribeMessages('prop.2.1')
.then((sub) => {
logger.i('Sub props successfully');
this.subscription = sub;
})
.catch((err) => {
logger.e('Sub props failed: ', err);
});
// 第 4 步:接受属性值变化回调
this.event = DeviceEvent.deviceReceivedMessages.addListener(
(device, map, data) => {
this.onReceiveData(data);
}
);
}
onReceiveData(data) {
// 在此格式化 data 数据并 setState
// 注意:初始化属性值请求(getPropertiesValue)和订阅的属性值变化(deviceReceivedMessages)
// 返回的数据格式不同
// 第 5 步:格式化数据并 setState
if (!data || !data.length) return;
for (let d of data) {
if (d.key === 'prop.2.1' || (d.siid === 2 && d.piid === 1)) {
let value = d.value;
if (Array.isArray(value)) {
value = value[0];
}
if (
value !== undefined &&
typeof value === 'boolean' &&
this.state.switch.value !== value
) {
this.setState({
switch: value
});
}
break;
}
}
}
componentWillUnmount() {
// 第 6 步:在 componentWillUnmount 中移除监听
this.subscription && this.subscription.remove();
this.event && this.event.remove();
}
}
设备上报事件(event_occured
)和 properties_changed
一样也是可以被扩展程序订阅的。事件订阅具体用法和 properties_changed
保持一致。
比如 siid = 2, eiid = 1020 的事件,只需要将上述 prop.2.1
替换成 event.2.1020
即可。
第二步初始化时,事件不像属性一样可以获取到具体的 value 值,不过可以获取事件上报的历史记录(SDS)。
具体使用参考属性/事件历史记录。
控制设备
控制设备一般来说是修改设备的属性值(set_properties)或者执行设备功能定义中的方法(action)。
修改设备的属性值
const params = [
{
did: Device.deviceID,
siid: 2,
piid: 1,
value: true
}
];
Service.spec
.setPropertiesValue(params)
.then((result) => {
if (result instanceof Array && result.length >= 1) {
const res = result[0];
if (res.code === 0) {
// code === 0 说明设置成功,可直接 setState
logger.i('setPropertiesValue success: ', res);
} else {
// 其他 code 可等待订阅返回结果
// 具体错误码见下文 **Spec 请求错误码**
logger.i('setPropertiesValue with code: ' + res);
}
} else {
// 其他异常
logger.w('setPropertiesValue unknown error: ' + res);
}
})
.catch((err) => {
logger.e('setPropertiesValue error: ' + err);
});
执行设备功能定义中的方法
Service.spec
.doAction({
did: Device.deviceID,
siid: 2,
aiid: 1,
in: [true, 'shanghai'] // 在 in 数组内按顺序填入功能定义中需要的参数值
})
.then((res) => {
if (res?.length && res[0].code) {
// 具体错误码见下文 **Spec 请求错误码**
logger.e('doAction error: ', res);
} else {
logger.i('doAction success: ', res);
}
})
.catch((err) => {
logger.e('doAction error: ', err);
});
云端缓存与 RPC
扩展程序获取设备属性值的时候,为减小设备压力,默认会直接读取云端缓存值,而不是直接下发请求到设备获取。云端缓存值仅在设备上报 properties_changed
的时候会更新。某些执行设备方法(doAction
)的产品逻辑上,设备内部会更新属性值,但是没上报 properties_changed
。这种情况缓存值也不会更新。
如果没有缓存值,IoT 云端会下发请求到设备(RPC 请求)获取属性值,然后将设备返回的值透传给扩展程序。
开发者在 getPropertiesValue
获取属性值的时候。如果不想使用 SDK 默认的获取策略,可以修改 getPropertiesValue
函数第二个参数 datasource
:
// 自行构造参数
const params = [];
/**
*
* datasource=1 优先从服务器缓存读取,没有读取到下发 rpc;不能保证取到的一定是最新值。(默认)
* datasource=2 直接下发 rpc,每次都是设备返回的最新值。会增加上报量和设备压力。
* datasource=3 直接读缓存;没有缓存的 code 是 -70xxxx;可能取不到值。
* 建议 datasource=1 或 datasource=3,以减轻后台服务的压力。
*
*/
const datasource = 1;
Service.spec
.getPropertiesValue(params, datasource)
.then((res) => logger.i('res: ', res))
.catch((err) => logger.e('err: ', err));
Spec 问题排查
- 插件网络请求数据可以在插件控制台打印,抓包查看请求数据等;
- 插件 Spec 请求报错可以查看下文 Spec 请求错误码 进行排查;
- 如果碰到 Spec 报错,且根据错误码也排查不出问题的情况,请检查是否使用了非白名单用户测试白名单固件;
- 开发者平台是否发送 RPC 请求到设备以及设备是否正确回复可以在开发者平台查询通信日志数据;
- 日志数据可能会延迟一个小时左右;
- 下行日志指的是 IoT 服务端(插件)发送给设备的请求;
- 上行日志指的是设备上报给 IoT 服务端的请求。
- 排查固件日志是否正常。
Spec 请求错误码
调用接口后,若开发者收到扩展程序返回的错误码(-70xxxyzzz),开发者可按照如下内容排查问题。
错误码格式:
- xxx:HTTP 标准状态码
- y:出现错误的位置
- zzz:错误码
值 | 出错的位置 | 值 | 出错的位置 |
---|---|---|---|
0 | 插件 | 1 | 开放平台 |
2 | 设备云 | 3 | 设备 |
4 | MIOT-SPEC |
错误码 | 说明 | 故障解决 |
---|---|---|
-705001000 | 开放平台服务器内部错误 | 可能的原因:
|
-706012000 | 三方云服务器内部错误 | 云对云特有原因: 请检查三方云的回应是否正常。
|
-705004000 | MIoT Spec 服务器内部错误 | 可能的原因:
|
-705005000 | 订阅 ID(subscriptionId) 编码错误 | - |
-705006000 | notify error | - |
-704002000 | 设备错误(通用) 三方云请求参数不正确 | 此错误一般是设备对扩展程序的 Spec 操作返回了错误码导致。请自行在控制台 - 运营 - 通信日志查看设备的原始错误码。 可能的原因:
|
-702000000 | OK | - |
-702010000 | accept | - |
-704010000 | 未认证 | BLE 设备特有原因:
|
-704000000 | 错误的请求 | 云对云/独立 App 特有原因:
|
-704000001 | 错误的请求体 | 可能的原因:
|
-704001000 | 定时只支持小米设备 | - |
-702022036 | 操作正在处理中 | 可能的原因:
|
-704042010 |
| - |
-704042009 | 未找到场景 | - |
-704042012 | 场景无权限 | - |
-704090001 | 未找到设备 | 可能的原因:
|
-704042001 | 未找到设备 | - |
-704042011 | 设备离线 | 可能的原因:
|
-704012901 | token 不存在或过期 | - |
-704012902 | token 非法 | - |
-704012903 | 授权过期 | - |
-704012904 | 设备未授权控制能力给小爱 | 可能的原因:
|
-704012905 | 设备未绑定 | 可能的原因:
|
-704012906 | 认证失败 | - |
-704040002 | 服务不存在 | 可能的原因:
|
-706010002 | 远程服务异常 | 可能的原因:
|
-706010004 | MIoT 错误 | - |
-706010005 | 属性缓存失败 | - |
-704040003 | 属性不存在 | 可能的原因:
|
-704030013 | 属性不可读 | 可能的原因:
|
-704030023 | 属性不可写 | 可能的原因:
|
-704030033 | 属性不可上报 | 可能的原因:
|
-704030992 | 请求过于频繁,本次请求被拒绝 | 可能的原因:
|
-704220043 | 属性值不正确 | 可能的原因:
|
-705201013 | 读属性失败 | - |
-706012013 | 读属性失败 | 云对云特有原因:
|
-706012014 | 读属性失败 | - |
-705201023 | 写属性失败 | - |
-706012023 | 写属性失败 | 云对云特有原因:
|
-705201033 | 上报属性失败 | - |
-706012033 | 上报属性失败 | - |
-706012043 | 订阅失败 | - |
-704040004 | 事件不存在 | 可能的原因:
|
-704222034 | 事件参数数量不匹配 | 可能的原因:
|
-704040005 | 方法不存在 | 可能的原因:
|
-705201015 | 方法执行失败 | - |
-706012015 | 方法执行失败 | 可能的原因:
0 code,请在控制台 - 运营 - 通信日志查询设备通信日志并结合固件日志排查问题。 |
-704220035 | 方法输入参数错误 | 可能的原因:
|
-704220025 | 方法输入参数数量不匹配 | 可能的原因:
|
-704222035 | 方法输出参数数量不匹配或参数错误 | 可能的原因:
|
-704044006 | 未找到功能定义 | 可能的原因:
|
-705204006 | 非法的功能定义 | 可能的原因:
int32 ,其数值范围最大值不能大于 2147483648 ,最小值不能小于 -2147483647 。 |
-705204007 | 实例未加载 | - |
-704041007 | cloud not found | - |
-704220008 | 非法的 ID(SIID、PIID、EIID、AIID) | 可能的原因:
|
-704220009 | 非法的 uid | - |
-704220010 | 非法的订阅 ID(subscriptionId) | - |
-704053100 | 无法执行此操作 | 可能的原因:
设备在 RPC 指令的回复中,回复了非 |
-704083036 | 操作超时 | 可能的原因:
|
-704040999 | 功能未上线 | - |
-704013101 | 红外设备不支持此操作 | - |
-704053101 | 摄像机休眠中 | - |
上一篇:
下一篇: