本文以 miot-sdk RN插件 项目为例,完整展示从设备协议初始化、首次数据拉取,到 MobX 状态驱动 UI,以及与米家宿主(Native)交互的全部核心代码。
一、协议层初始化与首次数据拉取(含完整代码)
1. 建立 MobX 设备状态树
// productionState.js(关键片段)
import { createDeviceStore } from './deviceStore';
import { PROP_LIST } from './deviceProtocol/protocol';
function initDefaultValue() {
const defaultValues = {};
Object.keys(PROP_LIST).forEach(key => {
defaultValues[key] = PROP_LIST[key][2]; // 默认值取数组第三项
});
return defaultValues;
}
const state = {
deviceState: createDeviceStore(initDefaultValue()),
componentState: createComponentStore(),
};
function createDeviceStore(defaultValues) {
const store = makeAutoObservable({
...defaultValues,
// 其他 observable 属性、action 等
});
// 关键:挂载协议方法
addMQTTMethod(store);
return store;
}
2. addMQTTMethod:挂载协议单例和发送方法
// productionState.js
import DeviceProtocol from './deviceProtocol/deviceProtocol';
export function addMQTTMethod(store) {
// 创建协议单例(传入 store 引用)
const protocol = new DeviceProtocol(store);
// 将发送方法挂载到 store 上,供 UI 调用
store.sendSetProperty = (params) => {
protocol.deviceProtocolEmitter.emit('protocolSetProperty', params);
};
store.sendAction = (params) => {
protocol.deviceProtocolEmitter.emit('protocolAction', params);
};
}
3. DeviceProtocol 构造函数(首次初始化)
// deviceProtocol.js
class DeviceProtocol {
constructor(store) {
if (DeviceProtocol.instance) {
return DeviceProtocol.instance;
}
this.deviceState = store;
this.initDeviceProtocol();
DeviceProtocol.instance = this;
}
initDeviceProtocol() {
this.initProtocolMap(); // 建立 siid.piid → 业务字段映射
this.createEventEmitter(); // 创建 mitt 事件总线
this.subscribeMessage(); // 订阅设备上报
// 设备在线则立即拉取首屏数据(leading: true)
if (Device.isOnline) {
this.debounceInitProtocolState(); // 内部使用 lodash.debounce,leading: true
}
}
initProtocolMap() {
this.protocolMap = new Map();
Object.entries(PROP_LIST).forEach(([key, value]) => {
const [siid, piid] = value;
this.protocolMap.set(`prop.${siid}.${piid}`, key);
});
}
createEventEmitter() {
this.deviceProtocolEmitter = mitt(); // 创建内部事件总线
// 监听 setState 事件,直接写入 MobX
this.deviceProtocolEmitter.on('setState', (newState) => {
this.deviceState.setState(newState);
});
// 监听协议动作,调用云端方法
this.deviceProtocolEmitter.on('protocolSetProperty', (params) => {
this.callSetProperty(params);
});
// ... 类似 protocolAction
}
}
🔔 关于 emit 和事件总线
上述代码中的 deviceProtocolEmitter = mitt() 创建了一个极简的发布/订阅事件总线。
-
emit('setState', toRefreshData)表示:在该总线上触发名为'setState'的事件,并携带toRefreshData作为参数。 - 谁在监听?
createEventEmitter()中注册的on('setState', (state) => { this.deviceState.setState(state) })会收到这条消息,并执行 MobX 的setState更新 store。
为什么要用 emit 而不直接调用 setState?
因为协议层有多个不同来源(订阅回调、get_properties 成功回调、sendAction 失败后的刷新等)需要更新设备状态。统一使用 emit('setState', data) 发事件,再由唯一监听者 onSetState 负责写入 MobX,可以解耦数据来源与存储逻辑,避免到处持有 store 引用。同时,同一个 emitter 也用于发送 protocolAction、protocolSetProperty 等控制指令,保持内部通信一致。
4. subscribeMessage:订阅设备推送(完整代码)
// deviceProtocol.js
subscribeMessage() {
// 监听原生层推送的消息
const { remove } = DeviceEvent.deviceReceivedMessages.addListener(
(device, messages = new Map()) => {
const toRefreshData = {};
messages.forEach((value, key) => {
// key 格式如 "prop.2.1",通过 protocolMap 转为业务字段名
if (this.protocolMap.has(key)) {
toRefreshData[this.protocolMap.get(key)] = value;
}
});
if (Object.keys(toRefreshData).length > 0) {
this.deviceProtocolEmitter.emit('setState', toRefreshData);
}
},
);
this.removeSubscription = remove;
// 向原生层注册订阅(告诉云端需要推送哪些属性和事件)
const subscribeData = this.createSubscribeData(); // 从 PROP_LIST/EVENT_LIST 生成
Device.getDeviceWifi().subscribeMessages(subscribeData)
.catch(err => console.error('订阅失败', err));
}
createSubscribeData() {
const props = Object.values(PROP_LIST).map(([siid, piid]) => `prop.${siid}.${piid}`);
const events = Object.values(EVENT_LIST).map(([siid, eiid]) => `event.${siid}.${eiid}`);
return [...props, ...events];
}
5. 首次拉取设备属性(initProtocolStateFromCloud)
// deviceProtocol.js
async initProtocolStateFromCloud() {
// 筛选需要主动拉取的属性(规则:数组长度≤3 或 第4项为 true)
const toFetch = Object.entries(PROP_LIST)
.filter(([_, def]) => def.length <= 3 || def[3] === true)
.map(([key, [siid, piid]]) => ({ siid, piid, key }));
// 分批,每批最多30个
const batchSize = 30;
const batches = [];
for (let i = 0; i < toFetch.length; i += batchSize) {
batches.push(toFetch.slice(i, i + batchSize));
}
const results = await Promise.all(batches.map(batch => {
const params = batch.map(({ siid, piid }) => ({ siid, piid }));
return Device.getDeviceWifi().callMethodFromCloud('get_properties', params);
}));
const newState = {};
results.forEach(res => {
res.result.forEach(item => {
const key = toFetch.find(({ siid, piid }) =>
siid === item.siid && piid === item.piid
)?.key;
if (key) newState[key] = item.value;
});
});
newState.loadingVisible = false;
this.deviceProtocolEmitter.emit('setState', newState);
}
首屏数据闭环示意:
createDeviceStore → addMQTTMethod → new DeviceProtocol →
subscribeMessage (监听+订阅) → debounceInitProtocolState (leading) →
initProtocolStateFromCloud → get_properties → emit('setState') →
deviceState.setState → UI 通过 observer 自动刷新
二、与米家宿主的交互(Native 上下文代码示例)
1. 插件入口
// index.js
import { Package, DarkMode } from 'miot';
import Root from './root';
DarkMode.preparePluginOwnDarkMode();
Package.entry(Root, () => console.log('root loaded'));
2. 获取设备信息与调用宿主功能
// main/page/DeviceInfo/index.js
import { Device, Host } from "miot";
// 设备名称展示及修改
{
title: '设备名称',
showArrow: Device.isOwner,
rightExtend: miotSdkProp.Device.name, // 动态名称
onPress: () => {
if (Device.isOwner) {
Host.ui.openChangeDeviceName(); // 调用宿主改名界面
}
}
},
{
title: '设备类型',
rightExtend: Device.device.displayName // 静态展示名
},
{
title: '设备编号',
rightExtend: Device.deviceID
}
3. 退出插件
// main/page/index.js
<HeaderBackButton onPress={Package.exit} />
三、MobX 状态管理与界面使用(含完整 Hook 和组件示例)
1. 定义全局 Store 与 Context
// productionState.js
import React, { createContext, useContext } from 'react';
const state = {
deviceState: createDeviceStore(initDefaultValue()),
componentState: createComponentStore(),
};
const StateContext = createContext(state);
export const StateContextProvider = ({ children }) => (
<StateContext.Provider value={state}>{children}</StateContext.Provider>
);
const useStores = () => {
const stores = useContext(StateContext);
if (!stores) throw new Error('useStores must be used within a StoreProvider');
return stores;
};
export const useDeviceState = () => useStores().deviceState;
export const useComponentState = () => useStores().componentState;
2. 在根组件注入 Provider
// root.js
import { StateContextProvider } from './main/useHooks/productionState';
export default function Root() {
return (
<SafeAreaProvider>
<StateContextProvider>
<MenuProvider>
<App />
</MenuProvider>
</StateContextProvider>
</SafeAreaProvider>
);
}
3. 界面读取状态(完整组件示例)
示例 A:根据设备状态切换页面
// main/page/Home/index.js
import { observer } from 'mobx-react-lite';
import { useDeviceState, useComponentState } from '../../useHooks/productionState';
const PageSwitcher = observer(({ navigation }) => {
const deviceState = useDeviceState();
const componentState = useComponentState();
if ([3, 4].includes(deviceState.deviceStatus) ||
(deviceState.notification && deviceState.deviceStatus === 1)) {
return <WorkingStatus />;
}
if ([0, 1, 2, 5, 6, 400].includes(deviceState.deviceStatus)) {
return <DeviceController navigation={navigation} />;
}
return null;
});
示例 B:动态控制 Tab 禁用状态
// main/page/index.js
const App = observer((props) => {
const deviceState = useDeviceState();
const tabList = [
{ name: '设备' },
{ name: '菜谱', disabled: [400, 5, 6].includes(deviceState.deviceStatus) }
];
// ... 渲染 Tabs
});
四、下发指令(写属性 / 调动作)完整代码
// 任意 UI 组件中
const SomeControl = observer(() => {
const deviceState = useDeviceState();
const turnOnLight = () => {
deviceState.sendSetProperty({
name: 'light', // 必须与 PROP_LIST 中的 key 一致
value: 1,
resolveCallback: () => console.log('成功'),
rejectCallback: (err) => console.error(err),
});
};
const startWork = () => {
deviceState.sendAction({
name: 'startWork', // 必须与 EVENT_LIST 中的 key 一致
value: { mode: 2 },
resolveCallback: () => {},
rejectCallback: () => {},
});
};
return <Button onPress={turnOnLight} title="开灯" />;
});
内部调用链:
sendSetProperty → deviceProtocolEmitter.emit('protocolSetProperty') →
callSetProperty → Device.getDeviceWifi().callMethodFromCloud('set_properties', params)
五、协议字段定义(PROP_LIST 示例)
// main/useHooks/deviceProtocol/protocol.js
export const PROP_LIST = {
'deviceStatus': [2, 1, 400], // [siid, piid, 默认值]
'errorCode': [2, 2, 0],
'workMode': [2, 3, 0],
'temperature1': [2, 5, 0],
'power': [2, 7, 0],
'step': [2, 8, 0],
'toWorkConfig': [2, 9, ''], // 字符串默认值
// 若需要首次主动拉取,可加第4个参数 true,如:
'someImportant': [3, 1, 0, true],
};
六、完整数据流总结图(文字版)
| 方向 | 触发点 | 核心代码 | 终点 |
|---|---|---|---|
| 首次拉取 | debounceInitProtocolState |
initProtocolStateFromCloud → get_properties → emit('setState')
|
MobX deviceState
|
| 设备上报 | DeviceEvent.deviceReceivedMessages |
subscribeMessage 监听 → protocolMap 转换 → emit('setState')
|
MobX deviceState
|
| UI 下发 |
sendSetProperty / sendAction
|
callMethodFromCloud('set_properties'/'action') |
云端 |
| UI 响应 |
observer 组件使用 useDeviceState()
|
MobX 自动通知重渲染 | 界面刷新 |
关键文件索引:
-
productionState.js– MobX store、Context、addMQTTMethod -
deviceProtocol.js– 协议单例、订阅、get_properties、callMethodFromCloud,以及内部mitt事件总线 -
protocol.js–PROP_LIST/EVENT_LIST与 SIID/PIID 映射
七、useHooks 目录角色与依赖关系
为了更清晰地理解各模块职责,下面列出 main/useHooks 下主要文件的作用及其依赖关系。
1. 目录角色一览
| 文件 / 目录 | 作用(一句话) |
|---|---|
deviceProtocol/protocol.js |
纯配置:属性表 PROP_LIST、事件表 EVENT_LIST 及相关名称常量,不依赖本目录其它业务文件。 |
deviceProtocol/deviceProtocol.js |
协议实现:DeviceProtocol 类,负责订阅/拉取/下发,内部用 protocol.js 做 siid/piid 映射,用 mitt 发事件。 |
productionState.js |
全局设备状态(MobX):创建 store、挂 sendSetProperty / sendAction / setState,内部 new DeviceProtocol(store),页面用 useDeviceState()。 |
mockState.js |
本地 Mock 用的 MobX store,与真实协议解耦(主要配合调试/演示)。 |
useMiotSdk/index.js |
封装 MIOT Device / 设备改名监听等小 hook,不直接依赖 deviceProtocol。 |
useDeviceData.js |
占位/未完成的小 hook,与其它模块几乎无耦合。 |
2. 依赖关系图(谁引用谁)

依赖要点:
-
protocol.js在最底层:只导出常量,被deviceProtocol.js和productionState.js引用。 -
deviceProtocol.js在中间:只依赖protocol.js(以及miot、mitt、RN 等外部包),实现「读属性 / 写属性 / 动作」的具体逻辑。 -
productionState.js在最上层(业务入口之一):把 MobX store 交给DeviceProtocol,并在 store 上挂sendSetProperty/sendAction(内部是deviceProtocolEmitter.emit(...))。
数据流可以理解为:
UI → sendSetProperty(store 上的方法)→ mitt → DeviceProtocol 里监听 → 查 PROP_LIST → set_properties 等。
3. 以另外一种工程(多一个层次)
在另外一个工程中,使用的是 deviceProtocol/useProtocol.js,其设计略有不同:不经过 productionState,而是用 useProtocol(routeName) 在页面级维护 protocolState,同样内部 new DeviceProtocol + 同样的 emit('protocolSetProperty') 链路。
依赖关系仍然为:
useProtocol.js → protocol.js + deviceProtocol.js(+ 页面侧 setIsloadingRef 等)。
4. 一句话记忆
-
protocol.js:字典(属性/动作编号表)。 -
deviceProtocol.js:按字典跟设备/云端打交道 + 事件总线。 -
productionState.js(微波)或useProtocol.js(其他机器):把「状态 + 发送方法」接到 React/MobX 上给界面用;mockState/useMiotSdk/useDeviceData则是旁路工具,不在主协议链上。