全部產品
Search
文件中心

IoT Platform:物模型開發

更新時間:Jun 30, 2024

裝置可以使用物模型功能,實現屬性上報(上報裝置狀態)、事件上報(上報裝置異常或錯誤)和服務調用(通過雲端調用裝置提供的服務)。

前提條件

物聯網平台已為目標裝置完成物模型預設模組自訂模組的物模型功能定義(屬性、事件和服務定義)。具體操作,請參見添加物模型

說明

如果物模型功能定義為空白,會導致裝置上報屬性、上報事件和雲端調用服務失敗。

背景資訊

使用說明

  • 在物模型的預設模組中,屬性、事件、服務的identifier不需要加首碼。例如名為lightSwitch的屬性,identifier就是lightSwitch

  • 在物模型的自訂模組中,屬性、事件、服務的identifier要加模組名為首碼。例如myBlock模組中的lightSwitch屬性,identifier要寫成myBlock:lightSwitch

重要

調用本文提及的物模型相關介面後,回調中onSuccess僅代表對應訊息從裝置發出成功,不代表訊息對應的任務執行成功。裝置執行商務邏輯請勿依賴onSuccess

裝置上報屬性

說明
  • getDeviceThing()返回的IThing介面介紹參見 IThing ApiReference,具體代碼實現請參見Demo中的ThingSample.java。

  • 從1.2.3版本開始,thingPropertyPost的回調介面通過alinkId欄位透出當前所發送的上行訊息的ID。

  • 如果要觀察裝置上報的屬性訊息是否已到達雲端,可以在訂閱 /sys/${productKey}/${deviceName}/thing/event/property/post_reply訊息後,關注IConnectNotifyListener類(參考Demo的ThingSample.java)的介面onNotify。該介面會透出reply訊息的alinkId,如果上下行訊息的alinkId一致,表示上行訊息已經被服務端處理。

預設模組

// 裝置上報
Map<String, ValueWrapper> reportData  = new HashMap<>();
// identifier 是雲端定義的屬性的唯一標識,valueWrapper是屬性的值
// 以上報整型資料為例,我們構造如下valueWrapper
// ValueWrapper valueWrapper = new ValueWrapper.IntValueWrapper(1);
// reportData.put(identifier, valueWrapper);  // 參考樣本,更多使用可參考demo 
LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
     public void onSuccess(String alinkId, Object o) {
        // 訊息從裝置發出成功
        // alinkId表示該訊息的messageId
    }
     public void onError(String alinkId, AError aError) {
        // 屬性上報失敗    
        // alinkId表示該訊息的messageId
    }
});
               

自訂模組

以上報myBlock模組中的lightSwitch屬性為例:

Map<String, ValueWrapper> reportData  = new HashMap<>();
// identifier為物聯網平台定義的屬性的標識符,valueWrapper為屬性的值
String identifier = "myBlock:lightSwitch";
reportData.put(identifier, valueWrapper);  // 參考樣本,更多內容可參考Demo
LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
    @Override
    public void onSuccess(String resID, Object o) {
        // 屬性上報成功
    }

    @Override
    public void onError(String resId, AError aError) {
        // 屬性上報失敗
    }
});

裝置上報事件

說明
  • 從1.2.3版本開始,thingEventPost的回調介面通過alinkId欄位透出當前所發送的上行訊息的ID。

  • 如果要觀察裝置上報的事件訊息是否已到達雲端,可以在訂閱 /sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply訊息後,關注IConnectNotifyListener類(參考Demo的ThingSample.java)的介面onNotify。該介面會透出reply訊息的alinkId,如果上下行訊息的alinkId一致,表示上行訊息已經被服務端處理。

預設模組

HashMap<String, ValueWrapper> hashMap = new HashMap<>();
// TODO 使用者根據實際情況設定
// hashMap.put("ErrorCode", new ValueWrapper.IntValueWrapper(0));
OutputParams params = new OutputParams(valueWrapperMap);
LinkKit.getInstance().getDeviceThing().thingEventPost(identity, params, new IPublishResourceListener() {
    public void onSuccess(String alinkId, Object o) {
        // 訊息從裝置發出成功
        // alinkId表示該訊息的messageId
    }
    public void onError(String alinkId, AError aError) {
        // 事件上報失敗
        // alinkId表示該訊息的messageId
    }
});
                    

自訂模組

以上報myBlock模組中的OnDetect事件為例:

HashMap<String, ValueWrapper> hashMap = new HashMap<>();
hashMap.put("StoreID", new ValueWrapper.StringValueWrapper("1"));
OutputParams params = new OutputParams(hashMap);
LinkKit.getInstance().getDeviceThing().thingEventPost("myBlock:OnDetect", params, new IPublishResourceListener() {
    @Override
        public void onSuccess(String resId, Object o) { // 事件上報動作成功
    }

    @Override
        public void onError(String resId, AError aError) { // 事件上報失敗
     }
 });

雲端下發屬性和服務

預設模組

  • 裝置服務擷取Service定義參見 Service API Reference

    //擷取預設模組(非使用者自訂模組)的服務列表
    LinkKit.getInstance().getDeviceThing().getServices()                   
  • 擷取預設模組(非使用者自訂模組)的事件列表。

    LinkKit.getInstance().getDeviceThing().getEvents()
  • 裝置服務調用監聽。

    雲端在添加裝置服務時,需設定該服務的調用方式,由Service中的callType欄位表示:

    • 同步服務調用時,callType="sync"

    • 非同步服務調用時,callType="async"

    裝置屬性設定和擷取也是通過服務調用監聽方式實現雲端服務的下發。

    非同步服務調用

    先註冊服務的處理監聽器,當雲端觸發非同步服務調用時,下行的請求會到註冊的監聽器中。一個裝置會有多種服務,通常需要註冊所有服務的處理監聽器。

    onProcess是裝置收到的雲端下行的服務調用方法,第一個參數是需要調用服務對應的identifier,使用者可以根據identifier做不同的處理。雲端調用設定服務時,裝置需要在收到設定指令後,調用裝置執行真實操作,操作結束後上報一條屬性狀態變化的通知。

    說明

    identifier是在物聯網平台為產品建立屬性、事件、服務時定義的標識符,使用者可在物聯網平台控制台查看產品功能定義中屬性、事件、服務對應的identifier

        public void setServiceHandler() {
            ALog.d(TAG, "setServiceHandler() called");
            List<Service> srviceList = LinkKit.getInstance().getDeviceThing().getServices();
            for (int i = 0; srviceList != null && i < srviceList.size(); i++) {
                Service service = srviceList.get(i);
                LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(), mCommonHandler);
            }
            LinkKit.getInstance().registerOnNotifyListener(connectNotifyListener);
        }
    
        private ITResRequestHandler mCommonHandler = new ITResRequestHandler() {
            public void onProcess(String identify, Object result, ITResResponseCallback itResResponseCallback) {
                ALog.d(TAG, "onProcess() called with: s = [" + identify + "], o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
                try {
                    if (SERVICE_SET.equals(identify)) {
    
                        /** 雲端下發屬性,SDK收到後觸發的回調
                         *
                         * TODO: 使用者需要將下發的屬性值,設定到真實裝置裡面。
                         * 若設定成功,需要將isSetPropertySuccess寫為true,
                         * demo將通過itResResponseCallback這個回調,將裝置本地更新後的屬性值寫到雲平台,
                         * 雲平台的裝置詳情的物模型資料一欄屬性值將會重新整理
                         * 若設定失敗,需要將isSetPropertySuccess寫為false, demo將不更新雲平台中的屬性值
                         *
                         *  這裡假定使用者已經將屬性設定到真實裝置裡面,將isSetPropertySuccess寫為true
                         */
                        boolean isSetPropertySuccess = true;
    
                        if (isSetPropertySuccess) {
                            if (result instanceof InputParams) {
                                Map<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((InputParams) result).getData();
                                // 如果控制台下發了屬性OverTiltEnable,可以通過data.get("OverTiltEnable") 來擷取相應的屬性值
                                ALog.d(TAG, "收到下行資料 " + data);
    
                                /**
                                 * 讀取屬性的值
                                 *
                                 * 假設使用者物模型中有OverCurrentEnable這個屬性,並且使用者在控制台對OverCurrentEnable進行了下發屬性的操作
                                 * 我們下面範例程式碼示範如何從中讀取到屬性的值
                                 *
                                 *
                                 * TODO:使用者需要根據自己的物模型進行適配
                                 */
    
                                 // ValueWrapper.IntValueWrapper intValue = (ValueWrapper.IntValueWrapper) data.get("OverCurrentEnable");
                                 // if (null != intValue) {
                                 // ALog.d(TAG, "收到下行資料 " + intValue.getValue());
                                 // }
                            }
    
                            /**
                             * 向雲端上報資料
                             *
                             * errorInfo為空白,表示接收資料成功,itResResponseCallback.onComplete回調將
                             * 回複/sys/${productKey}/${deviceName}/thing/service/property/set_reply給雲端
                             * 同時,該回調會再通過/sys/${productKey}/${deviceName}/thing/service/property/post將更新後的屬性上報到雲端
                             * 表示裝置端更新該屬性成功
                             */
                            itResResponseCallback.onComplete(identify, null, null);
    
                        } else {
                            AError error = new AError();
                            error.setCode(100);
                            error.setMsg("setPropertyFailed.");
                            itResResponseCallback.onComplete(identify, new ErrorInfo(error), null);
                        }
    
                    } else if (SERVICE_GET.equals(identify)) {
                        //  初始化的時候將預設值初始化傳進來,物模型內部會直接返回雲端緩衝的值
    
                    } else {
                        /**
                         *  非同步服務下行處理
                         */
                        ALog.d(TAG, "使用者根據真實的服務返回服務的值,請參照set樣本");
                        OutputParams outputParams = new OutputParams();
                        // outputParams.put("op", new ValueWrapper.IntValueWrapper(20));
                        /**
                         * 裝置端接收到服務,並返迴響應資料給服務端
                         */
                        itResResponseCallback.onComplete(identify, null, outputParams);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    ALog.d(TAG, "TMP 返回資料格式異常");
                }
            }
    
            public void onSuccess(Object o, OutputParams outputParams) {
                ALog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]");
                ALog.d(TAG, "註冊服務成功");
            }
    
            public void onFail(Object o, ErrorInfo errorInfo) {
                ALog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]");
                ALog.d(TAG, "註冊服務失敗");
            }
        };

    同步服務調用(RRPC調用)

    1. 先註冊一個下行資料監聽,註冊方法請參見認證與串連串連狀態與下行訊息監聽notifyListener

      當雲端觸發服務調用時,使用者可以在onNotify收到雲端的下行服務調用。

    2. 使用者收到雲端的下行服務調用後,根據實際服務調用對裝置做服務處理,處理之後需要裝置回複雲端的請求,即發布一個帶有處理結果的請求到雲端。

    說明

    目前的版本添加了支援使用自訂RRPC,雲端的同步服務屬性下行是通過自訂RRPC通道下行,使用者在升級SDK之後要注意這個改動點。

        private IConnectNotifyListener connectNotifyListener = new IConnectNotifyListener() {
            public void onNotify(String connectId, String topic, AMessage aMessage) {
                ALog.d(TAG, "onNotify() called with: connectId = [" + connectId + "], topic = [" + topic + "], aMessage = [" + printAMessage(aMessage) + "]");
                try {
                    if (CONNECT_ID.equals(connectId) && !StringUtils.isEmptyString(topic) &&
                            topic.startsWith("/sys/" + productKey + "/" + deviceName + "/rrpc/request")) {
                        ALog.d(TAG, "收到雲端系統RRPC下行" + printAMessage(aMessage));
                        // ALog.d(TAG, "receice Message=" + new String((byte[]) aMessage.data));
                        // 服務端返回資料樣本  {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
                        MqttPublishRequest request = new MqttPublishRequest();
                        request.isRPC = false;
                        request.topic = topic.replace("request", "response");
                        String resId = topic.substring(topic.indexOf("rrpc/request/") + 13);
                        request.msgId = resId;
                        // TODO 使用者根據實際情況填寫,僅做參考
                        request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";
                        // aResponse.data =
                        LinkKit.getInstance().getMqttClient().publish(request, new IConnectSendListener() {
                            public void onResponse(ARequest aRequest, AResponse aResponse) {
                                ALog.d(TAG, "onResponse() called with: aRequest = [" + aRequest + "], aResponse = [" + aResponse + "]");
                            }
    
                            public void onFailure(ARequest aRequest, AError aError) {
                                ALog.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + getError(aError) + "]");
                            }
                        });
                    }
                    else if (CONNECT_ID.equals(connectId) && !TextUtils.isEmpty(topic) &&
                            topic.startsWith("/ext/rrpc/")) {
                        ALog.d(TAG, "收到雲端自訂RRPC下行");
                        // ALog.d(TAG, "receice Message=" + new String((byte[]) aMessage.data));
                        // 服務端返回資料樣本  {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
                        MqttPublishRequest request = new MqttPublishRequest();
                        // 支援 0 和 1, 預設0
                        // request.qos = 0;
                        request.isRPC = false;
                        request.topic = topic.replace("request", "response");
                        String[] array = topic.split("/");
                        String resId = array[3];
                        request.msgId = resId;
                        // TODO 使用者根據實際情況填寫,僅做參考
                        request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";
                        // aResponse.data =
                        LinkKit.getInstance().publish(request, new IConnectSendListener() {
                            @Override
                            public void onResponse(ARequest aRequest, AResponse aResponse) {
                                ALog.d(TAG, "onResponse() called with: aRequest = [" + aRequest + "], aResponse = [" + aResponse + "]");
                            }
    
                            @Override
                            public void onFailure(ARequest aRequest, AError aError) {
                                ALog.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
                            }
                        });
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

自訂模組

使用者自訂模組中的服務要先向SDK註冊,才能監聽到相應的回調,以myBlock模組中的VehDtcService服務為例,需要通過如下方式訂閱:

thing.setServiceHandler("myBlock:VehDtcService", resRequestHandler);

其中resRequestHandler是物模型報文處理handler的執行個體,詳細內容,請參見Java SDK Demo中的TSLActivity.java