useHooks 文件依賴關系

說明:protocol.js 不是類,而是導出一組常量和兩個派生名映射;deviceProtocol.js 里的 DeviceProtocol 才是運行時的協(xié)議協(xié)調層。下面按「在插件里各自干什么」說明,并配上對應代碼。


1. protocol.js:協(xié)議「字典層」(靜態(tài)數據)

作用:把業(yè)務上用的屬性名/動作名MIoT 的 siid/piid(讀屬性)或 siid/aiid(發(fā)動作) 綁在一起。插件里所有和云端、設備打交道的名,都應以這里為準,避免在業(yè)務代碼里寫裸數字。

  • PROP_LIST(讀/寫/訂閱的屬性)
    每個 key 是你在 UI/邏輯里用的名字;value 是數組:[siid, piid, 默認value, 是否參與云端拉取]。注釋寫明與 DeviceProtocol 里初始化一致。
/**
 * deviceProtocol類中初始化用到的數據
 * 與PROP_LIST_FORMAT相對應
 * value為Array,第一項為SIID、第二項為PIID,第三項為默認值,第四項為是否加入請求列表獲取數據
 */
export const PROP_LIST = {
    'deviceStatus': [2, 1, 400],
    'errorCode': [2, 2, 0],
    'workMode': [2, 3, 0],
    // ... 略 ...
    'stopMessage': [2, 31, '']

}
  • EVENT_LIST(要下發(fā)的動作/事件)
    每項是 [siid, aiid],供寫 action 時用。
/**
 * 定義的ACTION,第一項為SIID,第二項為AIID
 */
export const EVENT_LIST = {
    powerSwitch: [2, 1],                    //開關
    startWork: [2, 2],                      //啟動程序
    workControl: [2, 3],                    //工作啟停  0暫停 1運行 2放棄當前任務
    // ...
    historyControl: [2, 11],
}
  • PROP_LIST_NAME / EVENT_LIST_NAME
    PROP_LIST / EVENT_LIST改成和 key 相同的字符串,方便在「按名字發(fā)指令」時做校驗、避免拼錯業(yè)務名(注釋里點到了 sendSetProperty / sendAction 的 name 規(guī)范)。
/**
 * 根據PROP_LIST處理后的prop字典
 * 用于sendSetProperty時,name數值的規(guī)范問題
 */
export const PROP_LIST_NAME = { ...PROP_LIST }
Object.keys(PROP_LIST_NAME).map(key => {
    PROP_LIST_NAME[key] = key
})

/**
 * 根據EVENT_LIST處理后的event字典
 * 用于sendAction時,name數值的規(guī)范問題
 */
export const EVENT_LIST_NAME = { ...EVENT_LIST }
Object.keys(EVENT_LIST_NAME).map(key => {
    EVENT_LIST_NAME[key] = key
})

在插件流程中的位置protocol.js 本身不跑邏輯;它是單一數據源——DeviceProtocol 的映射表、訂閱列表、拉屬性列表、發(fā) action / set_property 的編碼,都從這里取數。


2. DeviceProtocol 類:運行時「協(xié)議與狀態(tài)橋梁」

作用:在插件進程里做一個單例協(xié)調器——用 protocol.js 建「云端 key ? 業(yè)務字段名」的 Map訂閱設備/云端推送、從云端拉全量屬性;用 mitt 把結果寫成 setState,并響應寫屬性 / 發(fā)動作的請求(通常由 useProtocol 一類 hook 通過 emit 調用)。

2.1 單例 + 與外部 store 的綁定

構造時把調用方傳進來的 store(常見是帶 setState 的協(xié)議狀態(tài)或頁面狀態(tài))掛到 this.deviceState;若已有單例則直接復用。進入后臺再回前臺時,若設備在線會防抖再拉一次云端全量,避免斷線或狀態(tài)陳舊。

    constructor(store) {
        console.log('onNewDeviceProtocol')
        this.deviceState = store
        // 單例模式
        if (DeviceProtocol.instance) {
            // this.debounceInitProtocolState()
            return DeviceProtocol.instance
        } else {
            this.initDeviceProtocol()
            DeviceProtocol.instance = this

            let isPrevStateBackground = false
            this.appStateListener = AppState.addEventListener('change', (appState) => {
                console.log('appStateListenerChange', appState)
                // 首次進入跳過
                if (appState === 'background') {
                    isPrevStateBackground = true
                }
                if (appState === 'active' && isPrevStateBackground) {
                    // this.deviceState.setState({ loadingVisible: true })
                    Device.isOnline && this.debounceInitProtocolState()
                }
            })
        }
    }
    initDeviceProtocol() {
        this.protocolMap = this.initProtocolMap()
        this.createEventEmitter()
        this.subscribeMessage()

        // 注釋防止離線模式數據重置
        Device.isOnline && this.debounceInitProtocolState()
        // this.debounceInitProtocolState()
    }

2.2 protocolMap:統(tǒng)一鍵名

prop.siid.piid / event.siid.aiid 映射成 PROP_LIST / EVENT_LIST 的 key,供推送和云端返回時反查業(yè)務字段名。

    /**
    * 映射字段與siid、piid、aiid。
    * 格式為 prop|event.siid.piid|aiid=>定義的字段名
    */
    initProtocolMap() {
        const pMap = new Map()
        Object.keys(PROP_LIST).map(item => {
            pMap.set(`prop.${PROP_LIST[item][0]}.${PROP_LIST[item][1]}`, item)
        })

        // 只需要映射屬性字段,不需要映射事件
        Object.keys(EVENT_LIST).map(item => {
            pMap.set(`event.${EVENT_LIST[item][0]}.${EVENT_LIST[item][1]}`, item)
        })
        console.log(pMap)
        return pMap
    }

2.3 訂閱:設備側推送 → 合并后 setState

createSubscribeDataPROP_LIST/EVENT_LIST 拼出要訂閱的字符串;subscribeMessage 里一邊 DeviceEvent.deviceReceivedMessages 收推送,一邊 subscribeMessages 向設備聲明訂閱。收到的 messages 里若 key 在 protocolMap 里,就聚成 toRefreshData,再 emit('setState', ...)。

    createSubscribeData() {
        const subscribeData = []
        Object.keys(PROP_LIST).map(item => {
            subscribeData.push(`prop.${PROP_LIST[item][0]}.${PROP_LIST[item][1]}`)
        })
        Object.keys(EVENT_LIST).map(item => {
            subscribeData.push(`event.${EVENT_LIST[item][0]}.${EVENT_LIST[item][1]}`)
        })
        return subscribeData
    }
    subscribeMessage() {
        const { remove } = DeviceEvent.deviceReceivedMessages.addListener(
            (device, messages = new Map()) => {
                // ...
                const toRefreshData = {}
                messages.forEach((value, key) => {
                    if (this.protocolMap.has(key)) {
                        console.log(`subscribeMessage---${this.protocolMap.get(key)}=>${value}`)
                        toRefreshData[this.protocolMap.get(key)] = value
                    }
                })
                if (Object.keys(toRefreshData).length > 0) {
                    this.deviceProtocolEmitter.emit('setState', toRefreshData)
                }
            },
        )
        this.removeDeviceReceivedMessages = remove
        Device.getDeviceWifi().subscribeMessages(this.createSubscribeData())
        // ...
    }

2.4 全量拉?。?code>get_properties 分段 + 再 setState

initProtocolStateFromCloudPROP_LIST 里「長度≤3 或第 4 項為 true」的項組請求參數,分成多批(每批最多 30 個)callMethodFromCloud('get_properties', ...),合并結果后用 protocolMap 轉成業(yè)務字段,然后 deviceProtocolEmitter.emit('setState', resultList),并關 loading。失敗時發(fā)錯誤態(tài)、deviceStatus: 400 等,并和防抖邏輯配合。

    async initProtocolStateFromCloud() {
        console.log('initProtocolStateFromCloud')
        const paramsList = Object.values(PROP_LIST).filter(item => item.length <= 3 || item[3] === true).map(item => {
            return {
                siid: item[0],
                piid: item[1],
            }
        })
        // ... 分片 Promise.all ...
                    result.map(item => {
                        resultList[this.protocolMap.get(`prop.${item.siid}.${item.piid}`)] = item.value
                    })
                    this.deviceState.setState({
                        loadingVisible: false
                    })
                    this.deviceProtocolEmitter.emit('setState', resultList)
        // catch 中 emit deviceStatus: 400 等
    }
    // initProtocolStateFromCloud的防抖形式
    debounceInitProtocolState = _.debounce(this.initProtocolStateFromCloud, 15000, { leading: true })

2.5 事件總線:對接 UI/Hook 的「寫」和「合狀態(tài)」

createEventEmitter 里訂閱三類事件:

  • setState → 調 this.deviceState.setState(state)(把協(xié)議數據寫進你傳入的 store);
  • protocolAction → 用 EVENT_LISTaction 調云端;
  • protocolSetProperty → 用 PROP_LISTset_properties。
    createEventEmitter() {
        // ...
        const onSetState = (state) => {
            console.log('listened setState', state)
            this.deviceState.setState(state)
        }

        const onProtocolAction = (payload = { name: '', value: '', ... }) => {
            // ...
            if (!EVENT_LIST.hasOwnProperty(name)) {
                throw new Error(`event '${name}' is not defined in EVENT_LIST`)
            }
            const actionParams = {
                did: Device.deviceID,
                siid: EVENT_LIST[name][0],
                aiid: EVENT_LIST[name][1],
                in: [{ piid: EVENT_LIST[name][1], value: value }]
            }
            Device.getDeviceWifi()
                .callMethodFromCloud('action', actionParams)
            // ...
        }

        const onProtocolSetProperty = (payload) => {
            if (!PROP_LIST.hasOwnProperty(name)) {
                throw new Error(`prop '${name}' is not defined in PROP_LIST`)
            }
            const setPropertyParams = [{
                did: Device.deviceID,
                siid: PROP_LIST[name][0],
                piid: PROP_LIST[name][1],
                value: value
            }]
            Device.getDeviceWifi()
                .callMethodFromCloud('set_properties', setPropertyParams)
            // ...
        }

        this.deviceProtocolEmitter.on('protocolAction', onProtocolAction.bind(this))
        this.deviceProtocolEmitter.on('protocolSetProperty', onProtocolSetProperty.bind(this))
        this.deviceProtocolEmitter.on('setState', onSetState.bind(this))
    }

2.6 卸載時取消訂閱、清總線、拆單例

    clearAllListener() {
        this.cancelSubscribeMessage()
        this.removeDeviceReceivedListener()
        this.clearEventEmitter()
        DeviceProtocol.instance = null
        this.appStateListener.remove()
    }

3. 兩者在插件里如何「串起來」(邏輯小結)

角色 說明
protocol.js 定義本設備在 MIoT 上的全部屬性/動作siid/piid/aiid 的對應關系及默認、拉取規(guī)則。改協(xié)議或加字段主要改這里。
DeviceProtocol 單例:用上述定義建 protocolMap進插件時訂閱推送 + 拉全量;運行中把推送/拉取結果通過 setState 寫進你傳入的 store用戶操作時由上層 emit('protocolAction' | 'protocolSetProperty') 經此類轉成 callMethodFromCloud。

若你項目里還有 useProtocol.js,一般會 new DeviceProtocol(某 setState)deviceProtocolEmitter.emit(...),與上面 createEventEmitter 的三種事件一一對應;你當前 deviceProtocol.jsconstructor 接收的 store 需帶 setState 方法,與 onSetState 的調用方式一致。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容