網關裝置及其下子裝置接入物聯網平台時,需要使用子裝置管理功能。 本文介紹如何配置和開發網關子裝置接入物聯網平台。
功能說明
網關與子裝置管理功能提供了子裝置動態註冊、擷取雲端網關子裝置列表、添加子裝置、刪除子裝置、子裝置上線、子裝置離線、監聽子裝置禁用和刪除、代理子裝置上下行通訊的能力。網關本身是一個直連裝置可直接使用上述所有能力。網關和子裝置之間的串連、資料通訊需要使用者處理。
開發說明
網關開發過程說明
廠商在物聯網平台定義網關產品時,設定節點類型為網關裝置,設定網關的身份認證方式,並根據網關功能定義Topic或物模型,並參照前面的章節中的說明對網關自身的功能進行開發。
實現子裝置的管理功能:
子裝置的發現與串連功能。
由廠商自行實現,阿里不提供網關如何發現以及如何將子裝置串連到網關的代碼實現。
子裝置認證的擷取方式。
下文介紹的擷取子裝置認證的方式可供廠商參考。
物聯網平台遠端管理網關下子裝置。
當網關發現並接入子裝置後,需要調用SDK的添加子裝置介面通知物聯網平台:先獲得該子裝置的認證資訊,然後調用SDK提供的子裝置上線介面通知物聯網平台。
說明如果一個子裝置處於離線狀態,物聯網平台遠端控制子裝置時,會直接返回失敗,不會將命令發送給網關再等待錯誤提示或者逾時提示。
同步子裝置運行狀態到物聯網平台。
當網關通知物聯網平台一個子裝置上線後,需要將子裝置的狀態資訊上報雲端,以保證子裝置在雲端的狀態與當前子裝置的狀態一致。
說明如果使用物模型定義子裝置功能時,子裝置上線需要將屬性的最新數值上報到物聯網平台。
當網關代理接入物聯網平台的子裝置離線時,網關需要調用子裝置離線介面,通知物聯網平台該子裝置已離線。
當線上子裝置的屬性發生變化時,也需要即時上報到物聯網平台。
當網關離線並再次上線時(例如網路連接斷開或網關重啟),網關需要對所有已添加到物聯網平台的子裝置,再次調用添加子裝置介面和子裝置上線介面。如果網關不知道子裝置的屬性與網關離線前上報到物聯網平台的是否一致,則網關需要將子裝置的最新屬性再次上報到物聯網平台。
子裝置接收物聯網平台下發的訊息。
當網關接收到物聯網平台下發給子裝置的控制訊息時,網關如何將該訊息轉換成子裝置識別的格式並發送給子裝置,由網關廠商進行實現。
子裝置開發說明
裝置廠商在物聯網平台定義子裝置產品,設定節點類型為網關子裝置,設定產品的身份認證方式。
阿里雲並不在子裝置上提供任何SDK,因此網關如何發現子裝置、如何串連子裝置、網關如何發現子裝置上線或者離線、網關如何將來自物聯網平台的命令發送給子裝置,均由網關廠商與子裝置廠商定義協議並實現。
網關的認證與串連
擷取子裝置認證
子裝置是通過網關代理在阿里雲物聯網平台進行註冊的,註冊時需要使用到子裝置的認證進行身分識別驗證。網關擷取子裝置認證的方式如下,網關廠家可根據自己的實際情況進行選用:
網關從子裝置擷取子裝置認證。
由網關與子裝置之間定義一套協議,當網關發現與串連子裝置後,擷取到子裝置的認證。阿里雲並不提供參考協議實現,該協議由網關廠商與子裝置廠商自行定義與實現。
網關預置子裝置的認證。
如果網關裝置可預先擷取需要串連的子裝置資訊,並且網關提供了某種配置方式輸入子裝置的認證資訊,則可以通過該方式擷取子裝置的認證。同樣,該功能由網關廠商實現。
網關通過動態註冊擷取子裝置認證。
網關可以通過某種協議發現與串連子裝置,並擷取到子裝置的型號(model)以及唯一標識(例如SN、MAC地址),但並不知道子裝置的DeviceSecret時,可通過以下步驟擷取:
子裝置在阿里雲物聯網平台進行產品定義,雲端會為子裝置產生ProductKey。
網關裝置建立子裝置型號(model)到阿里雲物聯網平台ProductKey的映射(網關廠家在網關上實現該映射),並將裝置的唯一標識作為阿里雲物聯網平台的DeviceName。
網關通過阿里雲物聯網平台提供的動態註冊功能,從雲端擷取子裝置的DeviceSecret,從而得到完整的子裝置的認證資訊。
使能網關模組
在網關初始化時,將enableGateway選項設定為true。具體配置,請參見認證與串連中一機一密的ioTDMConfig.enableGateway配置。
// 預設不開啟網關功能,開啟之後,初始化的時候會初始化網關模組,擷取雲端網關子裝置列表
ioTDMConfig.enableGateway = true;子裝置動態註冊
使用ProductKey和DeviceName動態註冊
子裝置添加到網關之前需要先進行動態註冊擷取子裝置認證資訊,您需要在物聯網平台開啟動態註冊功能。動態註冊支援同時註冊多個子裝置,用於擷取子裝置認證。
如果本地已有子裝置認證資訊,可以跳過此步驟。如果子裝置已經被綁定到其他網關裝置,動態註冊不會返回該子裝置的認證資訊。
LinkKit.getInstance().getGateway().gatewaySubDevicRegister(getSubDevList(), new IConnectSendListener() {
@Override
public void onResponse(ARequest aRequest, AResponse aResponse) {
ALog.d(TAG, "onResponse() called with: aRequest = [" + aRequest + "], aResponse = [" + (aResponse == null ? "null" : aResponse.data) + "]");
try {
// 子裝置動態註冊成功
ResponseModel<List<DeviceInfo>> response = JSONObject.parseObject(aResponse.data.toString(), new TypeReference<ResponseModel<List<DeviceInfo>>>() {
}.getType());
// 根據 response 的資料判斷是否成功 code=200
//TODO 儲存子裝置的認證資訊
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(ARequest aRequest, AError aError) {
// 子裝置動態註冊失敗
}
});
使用ProductKey、DeviceName、ProductSecret動態註冊
如果當前已經有子裝置的ProductKey、DeviceName、ProductSecret資訊,並且需要進行搶佔式動態註冊,則可以使用該方式進行子裝置動態註冊。搶佔式動態註冊是指被其它網關裝置綁定的子裝置認證也會返回。
該動態註冊方式需要預先擷取子裝置的ProductSecret,安全性會低於第一種動態註冊方式。
推薦通過物聯網平台遠程配置下發(COTA)子裝置的ProductKey、DeviceName、ProductSecret資訊到網關裝置,然後再進行搶佔式動態註冊。
應用情境
需要將子裝置從A網關綁定到B網關的情境。
代碼實現
// 該動態註冊方案需要提前知道子裝置的productSecret,安全性會比下面一種子裝置動態註冊低一點
// 這種動態註冊方式可以考慮和COTA-遠程配置下發配合使用,在雲端下發子裝置的ProductKey、DeviceName、ProductSecret,網關收到後
// 完成動態註冊
// 使用於需要搶佔綁定關係時使用
MqttPublishRequest request = new MqttPublishRequest();
final RequestModel requestModel = new RequestModel();
requestModel.id = String.valueOf(IDGenerater.generateId());
requestModel.version = "1.0";
requestModel.method = GatewayChannel.METHOD_PRESET_SUBDEV_REGITER;
request.isRPC = true;
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = new JSONArray();
for (int i = 0; i < presetSubdevList.size(); i++) {
DeviceInfo itemDev = presetSubdevList.get(i);
Map<String, String> itemMap = new HashMap<>();
itemMap.put("productKey", itemDev.productKey);
itemMap.put("deviceName", itemDev.deviceName);
itemMap.put("random", RandomStringUtil.getRandomString(10));
String sign = SignUtils.hmacSign(itemMap, itemDev.productSecret);
itemMap.put("sign", sign);
itemMap.put("signMethod", "hmacsha1");
jsonArray.add(itemMap);
}
jsonObject.put("proxieds", jsonArray);
requestModel.params = jsonObject;
request.payloadObj = requestModel.toString();
LinkKit.getInstance().getGateway().subDevicRegister(request, new IConnectSendListener() {
@Override
public void onResponse(ARequest aRequest, AResponse aResponse) {
ALog.d(TAG, "onResponse() called with: aRequest = [" + aRequest + "], aResponse = [" + aResponse + "]");
try {
showToast("收到子裝置動態結果");
ResponseModel<Map<String, List<DeviceInfo>>> responseModel = JSONObject.parseObject(aResponse.data.toString(),
new TypeReference<ResponseModel<Map<String, List<DeviceInfo>>>>() {
}.getType());
// TODO 儲存子裝置的認證資訊
ALog.d(TAG, "onResponse responseModel=" + JSONObject.toJSONString(responseModel));
// {"code":200,"data":{"failures":[],"successes":[{"deviceSecret":"xxx","productKey":"xxx","deviceName":"xxx"}]},"id":"1","message":"success","method":"thing.proxy.provisioning.product_register","version":"1.0"}
// 動態註冊成功列表
List<DeviceInfo> successList = responseModel.data.get("successes");
// 動態註冊失敗列表
List<DeviceInfo> failList = responseModel.data.get("failures");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(ARequest aRequest, AError aError) {
ALog.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
}
});
代理子裝置接入
擷取子裝置列表
擷取網關當前在物聯網平台已經有的子裝置列表。
LinkKit.getInstance().getGateway().gatewayGetSubDevices(new IConnectSendListener() {
@Override
public void onResponse(ARequest aRequest, AResponse aResponse) {
// 擷取子裝置列表結果
try {
ResponseModel<List<DeviceInfo>> response = JSONObject.parseObject(aResponse.data.toString(), new TypeReference<ResponseModel<List<DeviceInfo>>>() {
}.getType());
// TODO 根據實際應用情境處理
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(ARequest aRequest, AError aError) {
// 擷取子裝置列表失敗
}
});
添加子裝置
在擷取到子裝置認證後,可以參考以下代碼添加網關與子裝置的拓撲關係。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 認證:產品型號(必填)
deviceInfo.deviceName = deviceName; // 認證:裝置標識 (必填)
LinkKit.getInstance().getGateway().gatewayAddSubDevice(deviceinfo, new ISubDeviceConnectListener() {
@Override
public String getSignMethod() {
// 使用的簽名方法
return "hmacsha1";
}
@Override
public String getSignValue() {
// 擷取簽名,使用者使用deviceSecret獲得簽名結果
Map<String, String> signMap = new HashMap<>();
signMap.put("productKey", info.productKey);
signMap.put("deviceName", info.deviceName);
// signMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
signMap.put("clientId", getClientId());
return SignUtils.hmacSign(signMap, info.deviceSecret);
}
@Override
public String getClientId() {
// clientId 可為任意固定值,不可以是隨機值
return "id";
}
@Override
public void onConnectResult(boolean isSuccess, ISubDeviceChannel iSubDeviceChannel, AError aError) {
// 添加結果
if (isSuccess) {
// 子裝置添加成功,接下來可以做子裝置上線的邏輯
// subDevOnline(null);
}
}
@Override
public void onDataPush(String s, AMessage message) {
// 收到子裝置下行資料 topic=" + s + ", data=" + message
// 如禁用刪除已經設定、服務調用等返回的資料message.data 是byte[]
}
});
刪除子裝置
刪除網關下的子裝置。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 產品型號(必填)
deviceInfo.deviceName = deviceName; // 裝置標識 (必填)
LinkKit.getInstance().getGateway().gatewayDeleteSubDevice(deviceinfo, new ISubDeviceRemoveListener() {
@Override
public void onSuceess() {
// 成功刪除子裝置
}
@Override
public void onFailed(AError aError) {
// 刪除子裝置失敗
}
});
子裝置上線
調用子裝置上線介面前,請確保已完成子裝置添加。
由於介面調用都是非同步,子裝置上線介面不能在子裝置添加的下一行調用,而是要放到子裝置添加成功的回調中調用。
網關發現子裝置連上網關後,需要參考如下代碼將子裝置上線,子裝置上線後可以執行子裝置的訂閱、發布等操作。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; //產品型號(必填)
deviceInfo.deviceName = deviceName; //裝置標識(必填)
LinkKit.getInstance().getGateway().gatewaySubDeviceLogin(deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子裝置上線成功
// 上線之後可訂閱刪除和禁用的下行通知
// subDevDisable(null);
// subDevDelete(null);
}
@Override
public void onFailed(AError aError) {
ALog.d(TAG, "onFailed() called with: aError = [" + aError + "]");
}
});
子裝置離線
當子裝置離線之後,網關需要通知物聯網平檯子裝置離線,以避免物聯網平台雲端向子裝置發送資料。子裝置離線後,不可以進行子裝置的發布、訂閱、取消訂閱等操作。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 產品型號(必填)
deviceInfo.deviceName = deviceName; // 裝置標識(必填)
LinkKit.getInstance().getGateway().gatewaySubDeviceLogout(deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子裝置下線成功
}
@Override
public void onFailed(AError aError) {
// 代理子裝置下線失敗
}
});
監聽子裝置禁用
網關裝置可以在物聯網平台管理子裝置,例如禁用子裝置、啟用子裝置、刪除和子裝置的拓撲關係。
目前服務端只支援禁用子裝置的下行通知。服務端在禁用子裝置時會對子裝置進行下線處理,後續網關將不能代理子裝置和物聯網平台雲端通訊。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 產品型號(必填)
deviceInfo.deviceName = deviceName; // 裝置標識(必填)
LinkKit.getInstance().getGateway().gatewaySetSubDeviceDisableListener(deviceinfo, new IConnectRrpcListener() {
@Override
public void onSubscribeSuccess(ARequest aRequest) {
// 訂閱成功
}
@Override
public void onSubscribeFailed(ARequest aRequest, AError aError) {
// 訂閱失敗
}
@Override
public void onReceived(ARequest aRequest, IConnectRrpcHandle iConnectRrpcHandle) {
// 子裝置停用通知
iConnectRrpcHandle.onRrpcResponse(null, null);
}
@Override
public void onResponseSuccess(ARequest aRequest) {
Log.d(TAG, "onResponseSuccess() called with: aRequest = [" + aRequest + "]");
}
@Override
public void onResponseFailed(ARequest aRequest, AError aError) {
Log.d(TAG, "onResponseFailed() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
}
});
代理子裝置上下行通訊
物模型通訊
子裝置物模型初始化
子裝置物模型初始化必須在子裝置添加到網關,且子裝置已經登入的情況下才可以調用。
重要由於介面調用都是非同步,子裝置物模型初始化介面不能在子裝置登入的下一行調用,而是要放到子裝置登入成功的回調中調用。
DeviceInfo deviceInfo = new DeviceInfo(); deviceInfo.productKey = productKey; deviceInfo.deviceName = deviceName; // deviceInfo.deviceSecret = "xxxx"; Map<String, ValueWrapper> subDevInitState = new HashMap<>(); // subDevInitState.put(); //TODO 使用者根據實際情況設定 String tsl = null;// 使用者根據實際情況設定,預設為空白直接從雲端擷取最細的 TSL LinkKit.getInstance().getGateway().initSubDeviceThing(tsl, deviceInfo, subDevInitState, new IDMCallback<InitResult>() { @Override public void onSuccess(InitResult initResult) { // 物模型初始化成功之後可以做服務註冊上報等操作 } @Override public void onFailure(AError aError) { // 子裝置初始化失敗 } });
子裝置物模型使用介面的調用和直連裝置的物模型介面調用一致,擷取
IThing介面的實現代碼如下。// 擷取 IThing 執行個體 IThing thing = LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).first; // thing 有可能為空白,如子裝置未登入、物模型未初始化、子裝置未添加到網關、子裝置處於離線狀態等。 // error 資訊在 LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).second 返回 // 參考樣本,注意判空 thing.thingPropertyPost(reportData, new IPublishResourceListener() { @Override public void onSuccess(String s, Object o) { // 裝置上報狀態成功 } @Override public void onError(String s, AError aError) { // 裝置上報狀態失敗 } });子裝置物模型銷毀反初始化物模型。
後續如果需要重新使用,需要重新走登入、物模型初始化流程。
LinkKit.getInstance().getGateway().uninitSubDeviceThing(mBaseInfo);
基礎通訊
使用網關的通道執行子裝置的資料上下行。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 產品型號(必填)
deviceInfo.deviceName = deviceName; // 裝置標識(必填)
String topic = xxx;
String publishData = xxx;
// 訂閱
LinkKit.getInstance().getGateway().gatewaySubDeviceSubscribe(topic, deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子裝置訂閱成功
}
@Override
public void onFailed(AError aError) {
// 代理子裝置訂閱失敗
}
});
//發布
LinkKit.getInstance().getGateway().gatewaySubDevicePublish(topic, publishData, deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子裝置發布成功
}
@Override
public void onFailed(AError aError) {
// 代理子裝置發布失敗
}
});
// 取消訂閱
LinkKit.getInstance().getGateway().gatewaySubDeviceUnsubscribe(topic, deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子裝置取消訂閱成功
}
@Override
public void onFailed(AError aError) {
// 代理子裝置取消訂閱事變
}
});