物模型開發(fā)
設(shè)備可以使用物模型功能,實(shí)現(xiàn)屬性上報(bào)(上報(bào)設(shè)備狀態(tài))、事件上報(bào)(上報(bào)設(shè)備異常或錯誤)和服務(wù)調(diào)用(通過云端調(diào)用設(shè)備提供的服務(wù))。
前提條件
物聯(lián)網(wǎng)平臺已為目標(biāo)設(shè)備完成物模型默認(rèn)模塊或自定義模塊的物模型功能定義(屬性、事件和服務(wù)定義)。具體操作,請參見添加物模型。
如果物模型功能定義為空,會導(dǎo)致設(shè)備上報(bào)屬性、上報(bào)事件和云端調(diào)用服務(wù)失敗。
背景信息
物聯(lián)網(wǎng)平臺物模型功能的更多信息,請參見什么是物模型。
物模型數(shù)據(jù)格式的更多信息,請參見設(shè)備屬性、事件、服務(wù)。
使用說明
在物模型的默認(rèn)模塊中,屬性、事件、服務(wù)的identifier不需要加前綴。例如名為lightSwitch的屬性,identifier就是lightSwitch。
在物模型的自定義模塊中,屬性、事件、服務(wù)的identifier要加模塊名為前綴。例如myBlock模塊中的lightSwitch屬性,identifier要寫成myBlock:lightSwitch。
調(diào)用本文提及的物模型相關(guān)接口后,回調(diào)中onSuccess
僅代表對應(yīng)消息從設(shè)備發(fā)出成功,不代表消息對應(yīng)的任務(wù)執(zhí)行成功。設(shè)備執(zhí)行業(yè)務(wù)邏輯請勿依賴onSuccess
。
設(shè)備上報(bào)屬性
getDeviceThing()
返回的IThing接口介紹參見 IThing ApiReference,具體代碼實(shí)現(xiàn)請參見Demo中的ThingSample.java。從1.2.3版本開始,
thingPropertyPost
的回調(diào)接口通過alinkId
字段透出當(dāng)前所發(fā)送的上行消息的ID。如果要觀察設(shè)備上報(bào)的屬性消息是否已到達(dá)云端,可以在訂閱
/sys/${productKey}/${deviceName}/thing/event/property/post_reply
消息后,關(guān)注IConnectNotifyListener
類(參考Demo的ThingSample.java
)的接口onNotify
。該接口會透出reply
消息的alinkId
,如果上下行消息的alinkId
一致,表示上行消息已經(jīng)被服務(wù)端處理。
默認(rèn)模塊
// 設(shè)備上報(bào)
Map<String, ValueWrapper> reportData = new HashMap<>();
// identifier 是云端定義的屬性的唯一標(biāo)識,valueWrapper是屬性的值
// 以上報(bào)整型數(shù)據(jù)為例,我們構(gòu)造如下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) {
// 消息從設(shè)備發(fā)出成功
// alinkId表示該消息的messageId
}
public void onError(String alinkId, AError aError) {
// 屬性上報(bào)失敗
// alinkId表示該消息的messageId
}
});
自定義模塊
以上報(bào)myBlock模塊中的lightSwitch屬性為例:
Map<String, ValueWrapper> reportData = new HashMap<>();
// identifier為物聯(lián)網(wǎng)平臺定義的屬性的標(biāo)識符,valueWrapper為屬性的值
String identifier = "myBlock:lightSwitch";
reportData.put(identifier, valueWrapper); // 參考示例,更多內(nèi)容可參考Demo
LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
@Override
public void onSuccess(String resID, Object o) {
// 屬性上報(bào)成功
}
@Override
public void onError(String resId, AError aError) {
// 屬性上報(bào)失敗
}
});
設(shè)備上報(bào)事件
從1.2.3版本開始,
thingEventPost
的回調(diào)接口通過alinkId
字段透出當(dāng)前所發(fā)送的上行消息的ID。如果要觀察設(shè)備上報(bào)的事件消息是否已到達(dá)云端,可以在訂閱
/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply
消息后,關(guān)注IConnectNotifyListener
類(參考Demo的ThingSample.java
)的接口onNotify
。該接口會透出reply
消息的alinkId
,如果上下行消息的alinkId
一致,表示上行消息已經(jīng)被服務(wù)端處理。
默認(rèn)模塊
HashMap<String, ValueWrapper> hashMap = new HashMap<>();
// TODO 用戶根據(jù)實(shí)際情況設(shè)置
// 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) {
// 消息從設(shè)備發(fā)出成功
// alinkId表示該消息的messageId
}
public void onError(String alinkId, AError aError) {
// 事件上報(bào)失敗
// alinkId表示該消息的messageId
}
});
自定義模塊
以上報(bào)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) { // 事件上報(bào)動作成功
}
@Override
public void onError(String resId, AError aError) { // 事件上報(bào)失敗
}
});
云端下發(fā)屬性和服務(wù)
默認(rèn)模塊
設(shè)備服務(wù)獲取Service定義參見 Service API Reference。
//獲取默認(rèn)模塊(非用戶自定義模塊)的服務(wù)列表 LinkKit.getInstance().getDeviceThing().getServices()
獲取默認(rèn)模塊(非用戶自定義模塊)的事件列表。
LinkKit.getInstance().getDeviceThing().getEvents()
設(shè)備服務(wù)調(diào)用監(jiān)聽。
云端在添加設(shè)備服務(wù)時,需設(shè)置該服務(wù)的調(diào)用方式,由Service中的
callType
字段表示:同步服務(wù)調(diào)用時,
callType="sync"
。異步服務(wù)調(diào)用時,
callType="async"
。
設(shè)備屬性設(shè)置和獲取也是通過服務(wù)調(diào)用監(jiān)聽方式實(shí)現(xiàn)云端服務(wù)的下發(fā)。
異步服務(wù)調(diào)用
先注冊服務(wù)的處理監(jiān)聽器,當(dāng)云端觸發(fā)異步服務(wù)調(diào)用時,下行的請求會到注冊的監(jiān)聽器中。一個設(shè)備會有多種服務(wù),通常需要注冊所有服務(wù)的處理監(jiān)聽器。
onProcess
是設(shè)備收到的云端下行的服務(wù)調(diào)用方法,第一個參數(shù)是需要調(diào)用服務(wù)對應(yīng)的identifier,用戶可以根據(jù)identifier做不同的處理。云端調(diào)用設(shè)置服務(wù)時,設(shè)備需要在收到設(shè)置指令后,調(diào)用設(shè)備執(zhí)行真實(shí)操作,操作結(jié)束后上報(bào)一條屬性狀態(tài)變化的通知。說明identifier是在物聯(lián)網(wǎng)平臺為產(chǎn)品創(chuàng)建屬性、事件、服務(wù)時定義的標(biāo)識符,用戶可在物聯(lián)網(wǎng)平臺控制臺查看產(chǎn)品功能定義中屬性、事件、服務(wù)對應(yīng)的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)) { /** 云端下發(fā)屬性,SDK收到后觸發(fā)的回調(diào) * * TODO: 用戶需要將下發(fā)的屬性值,設(shè)置到真實(shí)設(shè)備里面。 * 若設(shè)置成功,需要將isSetPropertySuccess寫為true, * demo將通過itResResponseCallback這個回調(diào),將設(shè)備本地更新后的屬性值寫到云平臺, * 云平臺的設(shè)備詳情的物模型數(shù)據(jù)一欄屬性值將會刷新 * 若設(shè)置失敗,需要將isSetPropertySuccess寫為false, demo將不更新云平臺中的屬性值 * * 這里假定用戶已經(jīng)將屬性設(shè)置到真實(shí)設(shè)備里面,將isSetPropertySuccess寫為true */ boolean isSetPropertySuccess = true; if (isSetPropertySuccess) { if (result instanceof InputParams) { Map<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((InputParams) result).getData(); // 如果控制臺下發(fā)了屬性O(shè)verTiltEnable,可以通過data.get("OverTiltEnable") 來獲取相應(yīng)的屬性值 ALog.d(TAG, "收到下行數(shù)據(jù) " + data); /** * 讀取屬性的值 * * 假設(shè)用戶物模型中有OverCurrentEnable這個屬性,并且用戶在控制臺對OverCurrentEnable進(jìn)行了下發(fā)屬性的操作 * 我們下面示例代碼演示如何從中讀取到屬性的值 * * * TODO:用戶需要根據(jù)自己的物模型進(jìn)行適配 */ // ValueWrapper.IntValueWrapper intValue = (ValueWrapper.IntValueWrapper) data.get("OverCurrentEnable"); // if (null != intValue) { // ALog.d(TAG, "收到下行數(shù)據(jù) " + intValue.getValue()); // } } /** * 向云端上報(bào)數(shù)據(jù) * * errorInfo為空,表示接收數(shù)據(jù)成功,itResResponseCallback.onComplete回調(diào)將 * 回復(fù)/sys/${productKey}/${deviceName}/thing/service/property/set_reply給云端 * 同時,該回調(diào)會再通過/sys/${productKey}/${deviceName}/thing/service/property/post將更新后的屬性上報(bào)到云端 * 表示設(shè)備端更新該屬性成功 */ 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)) { // 初始化的時候?qū)⒛J(rèn)值初始化傳進(jìn)來,物模型內(nèi)部會直接返回云端緩存的值 } else { /** * 異步服務(wù)下行處理 */ ALog.d(TAG, "用戶根據(jù)真實(shí)的服務(wù)返回服務(wù)的值,請參照set示例"); OutputParams outputParams = new OutputParams(); // outputParams.put("op", new ValueWrapper.IntValueWrapper(20)); /** * 設(shè)備端接收到服務(wù),并返回響應(yīng)數(shù)據(jù)給服務(wù)端 */ itResResponseCallback.onComplete(identify, null, outputParams); } } catch (Exception e) { e.printStackTrace(); ALog.d(TAG, "TMP 返回?cái)?shù)據(jù)格式異常"); } } public void onSuccess(Object o, OutputParams outputParams) { ALog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]"); ALog.d(TAG, "注冊服務(wù)成功"); } public void onFail(Object o, ErrorInfo errorInfo) { ALog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]"); ALog.d(TAG, "注冊服務(wù)失敗"); } };
同步服務(wù)調(diào)用(RRPC調(diào)用)
先注冊一個下行數(shù)據(jù)監(jiān)聽,注冊方法請參見認(rèn)證與連接中連接狀態(tài)與下行消息監(jiān)聽的
notifyListener
。當(dāng)云端觸發(fā)服務(wù)調(diào)用時,用戶可以在onNotify收到云端的下行服務(wù)調(diào)用。
用戶收到云端的下行服務(wù)調(diào)用后,根據(jù)實(shí)際服務(wù)調(diào)用對設(shè)備做服務(wù)處理,處理之后需要設(shè)備回復(fù)云端的請求,即發(fā)布一個帶有處理結(jié)果的請求到云端。
說明當(dāng)前版本添加了支持使用自定義RRPC,云端的同步服務(wù)屬性下行是通過自定義RRPC通道下行,用戶在升級SDK之后要注意這個改動點(diǎn)。
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, "收到云端系統(tǒng)RRPC下行" + printAMessage(aMessage)); // ALog.d(TAG, "receice Message=" + new String((byte[]) aMessage.data)); // 服務(wù)端返回?cái)?shù)據(jù)示例 {"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 用戶根據(jù)實(shí)際情況填寫,僅做參考 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)); // 服務(wù)端返回?cái)?shù)據(jù)示例 {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"} MqttPublishRequest request = new MqttPublishRequest(); // 支持 0 和 1, 默認(rèn)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 用戶根據(jù)實(shí)際情況填寫,僅做參考 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(); } } };
自定義模塊
用戶自定義模塊中的服務(wù)要先向SDK注冊,才能監(jiān)聽到相應(yīng)的回調(diào),以myBlock模塊中的VehDtcService服務(wù)為例,需要通過如下方式訂閱:
thing.setServiceHandler("myBlock:VehDtcService", resRequestHandler);
其中resRequestHandler
是物模型報(bào)文處理handler
的實(shí)例,詳細(xì)內(nèi)容,請參見Java SDK Demo中的TSLActivity.java
。