設(shè)備期望屬性的應(yīng)用
設(shè)備期望屬性的應(yīng)用
在物聯(lián)網(wǎng)解決方案中,下發(fā)屬性時如果設(shè)備不在線會導(dǎo)致下發(fā)失敗。如果想要更優(yōu)雅和高效的完成屬性的下發(fā)呢?平臺提供設(shè)置期望屬性值功能,通過緩存設(shè)備屬性的期望值,實現(xiàn)從物聯(lián)網(wǎng)平臺云端控制設(shè)備屬性值。
本文介紹設(shè)置期望屬性值,實現(xiàn)從物聯(lián)網(wǎng)平臺控制燈泡狀態(tài)的相關(guān)操作。
場景信息
開關(guān)設(shè)備接入物聯(lián)網(wǎng)平臺后,若需從物聯(lián)網(wǎng)平臺控制燈泡工作狀態(tài)(1:打開;0:關(guān)閉),需要燈泡一直保持連網(wǎng)在線。實際情況下,燈泡可能無法一直在線。
您可在物聯(lián)網(wǎng)平臺設(shè)置設(shè)備期望屬性值,使其存儲在物聯(lián)網(wǎng)平臺云端。設(shè)備在線后,可讀取物聯(lián)網(wǎng)平臺存儲的期望屬性值,來更新自身屬性值。然后,設(shè)備會將更新后的屬性值上報至物聯(lián)網(wǎng)平臺。
整體流程
1.創(chuàng)建產(chǎn)品和設(shè)備
2.使用Postman模擬設(shè)備
3.設(shè)置和獲取期望屬性
4.設(shè)備端開發(fā)
創(chuàng)建產(chǎn)品和設(shè)備
進(jìn)入Tuya物聯(lián)網(wǎng)平臺阿里云計算巢。
在服務(wù)實例頁簽的全托管服務(wù)下,找到對應(yīng)的實例,單擊實例卡片。
在左側(cè)導(dǎo)航欄,選擇產(chǎn)品開發(fā) > 產(chǎn)品,單擊創(chuàng)建產(chǎn)品,創(chuàng)建一個產(chǎn)品:燈泡測試。
產(chǎn)品創(chuàng)建成功后,單擊繼續(xù)開發(fā)前往功能定義,為產(chǎn)品添加自定義功能并發(fā)布。如圖所示,本示例添加屬性工作狀態(tài)(LightStatus)。
在左側(cè)導(dǎo)航欄,選擇設(shè)備管理 > 設(shè)備列表,單擊添加設(shè)備,在燈泡產(chǎn)品下添加設(shè)備:LampTest。設(shè)備添加成功后,獲取設(shè)備證書信息(ProductKey、DeviceName和DeviceSecret)。
您可在設(shè)備列表,單擊設(shè)備LampTest對應(yīng)的查看進(jìn)入設(shè)備詳情頁面,查看運行狀態(tài)和連接參數(shù)
使用Postman模擬設(shè)備
1.您可攜帶MQTT連接參數(shù)在Postman創(chuàng)建對應(yīng)的MQTT連接,具體步驟可以參考使用Postman模擬設(shè)備。
2.完成對/sys/${productKey}/${deviceName}/thing/service/property/set下發(fā)屬性topic的訂閱
設(shè)置和獲取期望屬性值
您可通過平臺提供的Java簽名示例代碼生成URL語法后調(diào)用物聯(lián)網(wǎng)平臺云端API,設(shè)置設(shè)備期望屬性值。
1.在示例代碼OpenAPISignatureDemo類中找到對應(yīng)的代碼塊,完成對SetDeviceDesiredProperty方法參數(shù)的替換。
//方法二:已經(jīng)有參數(shù)Map,使用參數(shù)進(jìn)行簽名
Map<String, String> map = new HashMap<String, String>();
// 公共參數(shù)
map.put("AccessKeyId", accessKey);
map.put("SignatureMethod", "HMAC-SHA1");
map.put("Timestamp", formattedDateTime);
map.put("SignatureNonce", signatureOnce);
// 接口請求參數(shù)
map.put("Action", "SetDeviceDesiredProperty");
map.put("Versions", "{}");
map.put("Items", "{\"LightStatus\":13}");
map.put("ProductKey", "xZluAsUunlbuhDdG");
map.put("DeviceName", "LampTest");
try {
String signature = SignatureUtils.generate(httpMethod, map, secretKey);
System.out.println("[方法二:開始生成]======根據(jù)參數(shù)生成簽名,并且生成curl命令");
System.out.println("curl " + "'" + host+"/?"+buildQueryParam(map) + "&Signature=" + signature + "'");
} catch (Exception e) {
System.out.println("生成簽名失敗"+e.getMessage());
e.printStackTrace();
}
執(zhí)行后會得到如下URL語法請求
curl 'https://si-d6e8d812acb848958054.tuyacloud.com:8686/?Action=SetDeviceDesiredProperty&Versions=%7B%7D&SignatureNonce=c653621a65e444b18786e0f096e92b72&AccessKeyId=xMr9wgwXQLhv5AUa65o03mcD&SignatureMethod=HMAC-SHA1&Items=%7B%22LightStatus%22%3A1%7D&Timestamp=2024-11-19T10%3A24%3A29Z&ProductKey=xZluAsUunlbuhDdG&DeviceName=LampTest&Signature=d3RCeZ%2FSk2orvOn8XHzVn3jh8NM%3D'
設(shè)備在線時,設(shè)備監(jiān)聽/sys/${productKey}/${deviceName}/thing/service/property/set可以直接收到設(shè)置的屬性
設(shè)備離線時,設(shè)備無法收到消息,在設(shè)備上線后通過對/sys/${productKey}/${deviceName}/thing/property/desired/get推送對應(yīng)的上行消息,
對/sys/${productKey}/${deviceName}/thing/property/desired/get_reply完成訂閱,可以獲取最新的期望屬性。具體消息格式可參考設(shè)備期望屬性值
設(shè)備端開發(fā)
設(shè)備獲取期望屬性值,有兩種場景:
燈泡重新上線時,主動獲取物聯(lián)網(wǎng)平臺云端緩存的期望屬性值。
燈泡正處于上線狀態(tài),實時接收物聯(lián)網(wǎng)平臺云端推送的期望屬性值。
設(shè)備端開發(fā)更多信息,請參見使用設(shè)備端SDK接入。
本文以Java 代碼為例,提供了設(shè)備端Demo示例,請參見下文附錄:設(shè)備端Demo代碼。
填入設(shè)備證書、地域和MQTT接入地址的信息。
/**
* 設(shè)備證書信息
*/
private static String productKey = "******";
private static String deviceName = "********";
private static String deviceSecret = "**************";
/**
* MQTT連接信息
*/
private static String regionId = "******";
private static String iotInstanceId = "si-*************";
......
/**
* 設(shè)置 Mqtt 初始化參數(shù)
*/
config.channelHost = iotInstanceId + ".aliyun.tuyacloud.com:1883";
2.添加以下方法,用于變更實際燈泡的屬性,并在屬性變更后,主動將信息上報到最新屬性值中。
/**
* 真實設(shè)備處理屬性變更時,在以下兩個場下會被調(diào)用:
* 場景1. 設(shè)備聯(lián)網(wǎng)后主動獲取最新的屬性期望值(由設(shè)備發(fā)起,拉模式)
* 場景2. 設(shè)備在線時接收到云端property.set推送的屬性期望值(由云端發(fā)起,推模式)
* @param identifier 屬性標(biāo)識符
* @param value 期望屬性值
* @param needReport 是否通過property.post發(fā)送狀態(tài)上報。
* 上面場景2的處理函數(shù)中已集成屬性上報能力,會將needReport設(shè)置為false
* @return
*/
private boolean handlePropertySet(String identifier, ValueWrapper value, boolean needReport) {
ALog.d(TAG, "真實設(shè)備處理屬性變更 = [" + identifier + "], value = [" + value + "]");
// 用戶根據(jù)實際情況判性是否設(shè)置成功,這里測試直接返回成功
boolean success = true;
if (needReport) {
reportProperty(identifier, value);
}
return success;
}
private void reportProperty(String identifier, ValueWrapper value){
if (StringUtils.isEmptyString(identifier) || value == null) {
return;
}
ALog.d(TAG, "上報屬性identity=" + identifier);
Map<String, ValueWrapper> reportData = new HashMap<>();
reportData.put(identifier, value);
LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
public void onSuccess(String s, Object o) {
// 屬性上報成功
ALog.d(TAG, "上報成功 onSuccess() called with: s = [" + s + "], o = [" + o + "]");
}
public void onError(String s, AError aError) {
// 屬性上報失敗
ALog.d(TAG, "上報失敗onError() called with: s = [" + s + "], aError = [" + JSON.toJSONString(aError) + "]");
}
});
}
3.燈泡在線時,如果物聯(lián)網(wǎng)平臺設(shè)置了燈泡的期望屬性值,該值將被推送到設(shè)備端。燈泡處理消息,改變屬性狀態(tài)。
如下代碼中,將調(diào)用connectNotifyListener
處理消息,相關(guān)Alink協(xié)議說明,請參見設(shè)備上報屬性。
收到異步下行的數(shù)據(jù)后,mCommonHandler
被調(diào)用,進(jìn)而調(diào)用handlePropertySet
更新設(shè)備的物理屬性。
/**
* 注冊服務(wù)調(diào)用(以及屬性設(shè)置)的響應(yīng)函數(shù)。
* 云端調(diào)用設(shè)備的某項服務(wù)的時候,設(shè)備端需要響應(yīng)該服務(wù)并回復(fù)。
*/
public void connectNotifyListener() {
List<Service> serviceList = LinkKit.getInstance().getDeviceThing().getServices();
for (int i = 0; serviceList != null && i < serviceList.size(); i++) {
Service service = serviceList.get(i);
LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(), mCommonHandler);
}
}
private ITResRequestHandler mCommonHandler = new ITResRequestHandler() {
public void onProcess(String serviceIdentifier, Object result, ITResResponseCallback itResResponseCallback) {
ALog.d(TAG, "onProcess() called with: s = [" + serviceIdentifier + "]," +
" o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
ALog.d(TAG, "收到云端異步服務(wù)調(diào)用 " + serviceIdentifier);
try {
if (SERVICE_SET.equals(serviceIdentifier)) {
Map<String, ValueWrapper> data = (Map<String, ValueWrapper>)((InputParams)result).getData();
ALog.d(TAG, "收到異步下行數(shù)據(jù) " + data);
// 設(shè)置真實設(shè)備的屬性,然后上報設(shè)置完成的屬性值
boolean isSetPropertySuccess =
handlePropertySet("LightStatus", data.get("LightStatus"), false);
if (isSetPropertySuccess) {
if (result instanceof InputParams) {
// 響應(yīng)云端,接收數(shù)據(jù)成功
itResResponseCallback.onComplete(serviceIdentifier, null, null);
} else {
itResResponseCallback.onComplete(serviceIdentifier, null, null);
}
} else {
AError error = new AError();
error.setCode(100);
error.setMsg("setPropertyFailed.");
itResResponseCallback.onComplete(serviceIdentifier, new ErrorInfo(error), null);
}
} else if (SERVICE_GET.equals(serviceIdentifier)) {
} else {
// 根據(jù)不同的服務(wù)做不同的處理,跟具體的服務(wù)有關(guān)系
ALog.d(TAG, "根據(jù)真實的服務(wù)返回服務(wù)的值,請參照set示例");
OutputParams outputParams = new OutputParams();
// outputParams.put("op", new ValueWrapper.IntValueWrapper(20));
itResResponseCallback.onComplete(serviceIdentifier, null, outputParams);
}
} catch (Exception e) {
e.printStackTrace();
ALog.d(TAG, "云端返回數(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ù)失敗");
}
};
4.燈泡離線后,如果物聯(lián)網(wǎng)平臺云端設(shè)置了燈的期望屬性值,該值將被存儲在云端。
燈泡上線后,會主動獲取期望屬性值,然后調(diào)用handlePropertySet
更新實際設(shè)備的屬性。
LinkKit.getInstance().init(params, new ILinkKitConnectListener() {
public void onError(AError aError) {
ALog.e(TAG, "Init Error error=" + aError);
}
public void onInitDone(InitResult initResult) {
ALog.i(TAG, "onInitDone result=" + initResult);
connectNotifyListener();
// 獲取云端最新期望屬性值
getDesiredProperty(deviceInfo, Arrays.asList("LightStatus"), new IConnectSendListener() {
public void onResponse(ARequest aRequest, AResponse aResponse) {
if(aRequest instanceof MqttPublishRequest && aResponse.data != null) {
JSONObject jsonObject = JSONObject.parseObject(aResponse.data.toString());
ALog.i(TAG, "onResponse result=" + jsonObject);
JSONObject dataObj = jsonObject.getJSONObject("data");
if (dataObj != null) {
if (dataObj.getJSONObject("LightStatus") == null) {
// 未設(shè)置期望值
} else {
Integer value = dataObj.getJSONObject("LightStatus").getInteger("value");
handlePropertySet("LightStatus", new ValueWrapper.IntValueWrapper(value), true);
}
}
}
}
public void onFailure(ARequest aRequest, AError aError) {
ALog.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
}
});
}
});
private void getDesiredProperty(BaseInfo info, List<String> properties, IConnectSendListener listener) {
ALog.d(TAG, "getDesiredProperty() called with: info = [" + info + "], listener = [" + listener + "]");
if(info != null && !StringUtils.isEmptyString(info.productKey) && !StringUtils.isEmptyString(info.deviceName)) {
MqttPublishRequest request = new MqttPublishRequest();
request.topic = DESIRED_PROPERTY_GET.replace("{productKey}", info.productKey).replace("{deviceName}", info.deviceName);
request.replyTopic = DESIRED_PROPERTY_GET_REPLY.replace("{productKey}", info.productKey).replace("{deviceName}", info.deviceName);
request.isRPC = true;
RequestModel<List<String>> model = new RequestModel<>();
model.id = String.valueOf(IDGeneraterUtils.getId());
model.method = METHOD_GET_DESIRED_PROPERTY;
model.params = properties;
model.version = "1.0";
request.payloadObj = model.toString();
ALog.d(TAG, "getDesiredProperty: payloadObj=" + request.payloadObj);
ConnectSDK.getInstance().send(request, listener);
} else {
ALog.w(TAG, "getDesiredProperty failed, baseInfo Empty.");
if(listener != null) {
AError error = new AError();
error.setMsg("BaseInfoEmpty.");
listener.onFailure(null, error);
}
}
}