網(wǎng)關(guān)與子設(shè)備
不具備IP地址的設(shè)備可以作為網(wǎng)關(guān)的子設(shè)備接入物聯(lián)網(wǎng)平臺(tái),實(shí)現(xiàn)與物聯(lián)網(wǎng)平臺(tái)的通信。本文介紹子設(shè)備管理的相關(guān)功能。
背景信息
網(wǎng)關(guān)子設(shè)備管理提供子設(shè)備動(dòng)態(tài)注冊(cè)、獲取云端網(wǎng)關(guān)下子設(shè)備列表、添加子設(shè)備、刪除子設(shè)備、子設(shè)備上下線、監(jiān)聽子設(shè)備禁用和刪除的消息、代理子設(shè)備數(shù)據(jù)上下行的能力。
網(wǎng)關(guān)是一個(gè)直連設(shè)備,網(wǎng)關(guān)使用子設(shè)備管理的功能前,需已連接到阿里云物聯(lián)網(wǎng)平臺(tái)。網(wǎng)關(guān)設(shè)備開發(fā)的詳細(xì)信息,請(qǐng)參見認(rèn)證與連接、基于MQTT Topic通信。
網(wǎng)關(guān)子設(shè)備管理相關(guān)接口,請(qǐng)參見設(shè)備IGateway。
使用說明
接入前:
子設(shè)備的發(fā)現(xiàn)與連接功能由廠商自行實(shí)現(xiàn)。
子設(shè)備接入前,需先獲取子設(shè)備認(rèn)證信息,具體操作,請(qǐng)參見子設(shè)備的認(rèn)證信息獲取。
接入時(shí):
網(wǎng)關(guān)通過調(diào)用Link SDK的添加子設(shè)備接口,代理子設(shè)備接入物聯(lián)網(wǎng)平臺(tái),然后調(diào)用Link SDK的子設(shè)備上線接口,將設(shè)備狀態(tài)通知到物聯(lián)網(wǎng)平臺(tái)。
接入后:
子設(shè)備上線后,網(wǎng)關(guān)需要將子設(shè)備的狀態(tài)信息上報(bào)至物聯(lián)網(wǎng)平臺(tái),以保證子設(shè)備在物聯(lián)網(wǎng)平臺(tái)的狀態(tài)與當(dāng)前子設(shè)備的狀態(tài)一致。如果使用物模型定義子設(shè)備功能,還需將物模型的屬性上報(bào)至物聯(lián)網(wǎng)平臺(tái)。
子設(shè)備離線時(shí),網(wǎng)關(guān)需要調(diào)用子設(shè)備離線接口通知物聯(lián)網(wǎng)平臺(tái)該子設(shè)備已離線。
當(dāng)在線的子設(shè)備屬性發(fā)生變化時(shí),需要實(shí)時(shí)通知物聯(lián)網(wǎng)平臺(tái)。
當(dāng)網(wǎng)關(guān)離線并再次上線時(shí)(例如網(wǎng)絡(luò)連接斷開,或者網(wǎng)關(guān)重啟),網(wǎng)關(guān)需再次調(diào)用相關(guān)接口,使子設(shè)備上線。如果網(wǎng)關(guān)無法確認(rèn)子設(shè)備的屬性與網(wǎng)關(guān)離線前上報(bào)至物聯(lián)網(wǎng)平臺(tái)的屬性是否一致,網(wǎng)關(guān)還需將子設(shè)備的最新屬性上報(bào)至物聯(lián)網(wǎng)平臺(tái)。
當(dāng)網(wǎng)關(guān)接收到來自物聯(lián)網(wǎng)平臺(tái)對(duì)子設(shè)備的控制指令時(shí),需自行實(shí)現(xiàn)將該消息轉(zhuǎn)換成子設(shè)備識(shí)別的格式并發(fā)送給子設(shè)備。
如果發(fā)送控制指令至離線的子設(shè)備,物聯(lián)網(wǎng)平臺(tái)將直接返回失敗。
開發(fā)過程
定義網(wǎng)關(guān)產(chǎn)品并完成開發(fā):具體操作,請(qǐng)參見網(wǎng)關(guān)與子設(shè)備。
接入子設(shè)備:具體操作,請(qǐng)參見子設(shè)備上線。
子設(shè)備的認(rèn)證信息獲取
子設(shè)備由網(wǎng)關(guān)代理接入物聯(lián)網(wǎng)平臺(tái),需使用子設(shè)備的認(rèn)證信息進(jìn)行驗(yàn)證。網(wǎng)關(guān)獲取子設(shè)備認(rèn)證信息的方式如下:
網(wǎng)關(guān)從子設(shè)備獲取設(shè)備認(rèn)證信息。
由網(wǎng)關(guān)與子設(shè)備之間定義一套協(xié)議,當(dāng)網(wǎng)關(guān)發(fā)現(xiàn)并連接子設(shè)備后,可以獲取子設(shè)備的認(rèn)證信息。該協(xié)議由網(wǎng)關(guān)廠商與子設(shè)備廠商自行定義與實(shí)現(xiàn)。
網(wǎng)關(guān)預(yù)置子設(shè)備的認(rèn)證信息。
網(wǎng)關(guān)設(shè)備預(yù)先得知要連接的子設(shè)備,且網(wǎng)關(guān)提供了指定的配置方式輸入子設(shè)備的認(rèn)證信息。該功能由網(wǎng)關(guān)廠商實(shí)現(xiàn)。
網(wǎng)關(guān)通過動(dòng)態(tài)注冊(cè)獲取子設(shè)備的認(rèn)證信息。
網(wǎng)關(guān)可以通過指定協(xié)議發(fā)現(xiàn)并連接子設(shè)備,并獲取子設(shè)備的型號(hào)(model)以及唯一標(biāo)識(shí)(比如SN、MAC地址),動(dòng)態(tài)獲取子設(shè)備的DeviceSecret。
由于子設(shè)備需要在阿里云物聯(lián)網(wǎng)平臺(tái)進(jìn)行產(chǎn)品定義(物聯(lián)網(wǎng)平臺(tái)會(huì)為子設(shè)備生成ProductKey),網(wǎng)關(guān)可以建立子設(shè)備型號(hào)(model)和阿里云物聯(lián)網(wǎng)平臺(tái)ProductKey的映射(網(wǎng)關(guān)廠家在網(wǎng)關(guān)上實(shí)現(xiàn)該映射),并將設(shè)備的唯一標(biāo)識(shí)作為阿里云物聯(lián)網(wǎng)平臺(tái)的DeviceName,然后通過阿里云物聯(lián)網(wǎng)平臺(tái)提供的動(dòng)態(tài)注冊(cè)功能獲取子設(shè)備的DeviceSecret,從而得到完整的子設(shè)備的認(rèn)證信息。
子設(shè)備動(dòng)態(tài)注冊(cè)
子設(shè)備廠商需要在物聯(lián)網(wǎng)平臺(tái)開啟子設(shè)備的動(dòng)態(tài)注冊(cè)功能,具體操作,請(qǐng)參見開啟動(dòng)態(tài)注冊(cè)。開啟動(dòng)態(tài)注冊(cè)后,為子設(shè)備預(yù)置唯一標(biāo)識(shí)(例如SN、MAC地址)。
子設(shè)備動(dòng)態(tài)注冊(cè)的代碼示例如下:
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 {
// 子設(shè)備動(dòng)態(tài)注冊(cè)結(jié)果
ResponseModel<List<DeviceInfo>> response = JSONObject.parseObject(aResponse.data.toString(), new TypeReference<ResponseModel<List<DeviceInfo>>>() {
}.getType());
if (response != null && "200".equals(response.code)) {
/** 獲取deviceSecret, 存儲(chǔ)到本地,在添加子設(shè)備的時(shí)候需要使用deviceSecret
*/
// deviceSecret = response.data.get("deviceSecret");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(ARequest aRequest, AError aError) {
// 子設(shè)備動(dòng)態(tài)注冊(cè)失敗
}
});
獲取子設(shè)備列表
獲取網(wǎng)關(guān)已在物聯(lián)網(wǎng)平臺(tái)注冊(cè)的子設(shè)備。代碼示例如下:
LinkKit.getInstance().getGateway().gatewayGetSubDevices(new IConnectSendListener() {
@Override
public void onResponse(ARequest aRequest, AResponse aResponse) {
//獲取子設(shè)備列表結(jié)果
try {
ResponseModel<List<DeviceInfo>> response = JSONObject.parseObject(aResponse.data.toString(), new TypeReference<ResponseModel<List<DeviceInfo>>>() {
}.getType());
//TODO,根據(jù)實(shí)際應(yīng)用場(chǎng)景處理
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(ARequest aRequest, AError aError) {
//獲取子設(shè)備列表失敗
}
});
添加子設(shè)備
網(wǎng)關(guān)發(fā)現(xiàn)并連接了一個(gè)新的子設(shè)備,并獲取到子設(shè)備的認(rèn)證信息后,可以通知物聯(lián)網(wǎng)平臺(tái)網(wǎng)關(guān)需要添加一個(gè)子設(shè)備。代碼示例如下:
網(wǎng)關(guān)重啟并連接到物聯(lián)網(wǎng)平臺(tái)后,對(duì)連接的子設(shè)備需要再次調(diào)用添加子設(shè)備方法。
final DeviceInfo info = new DeviceInfo();
info.productKey = productKey; //設(shè)備證書的ProductKey(必填)
info.deviceName = deviceName; //設(shè)備證書的DeviceName(必填)
info.deviceSecret= deviceSecret; //設(shè)備證書DeviceSecret(必填)
LinkKit.getInstance().getGateway().gatewayAddSubDevice(info, new ISubDeviceConnectListener() {
@Override
public String getSignMethod() {
//使用的簽名方法
return "hmacsha1";
}
@Override
public String getSignValue() {
//獲取簽名,用戶使用deviceSecret獲得簽名結(jié)果
Map<String, String> signMap = new HashMap<>();
signMap.put("productKey", info.productKey);
signMap.put("deviceName", info.deviceName);
signMap.put("clientId", getClientId());
return SignUtils.hmacSign(signMap, info.deviceSecret);
}
@Override
public String getClientId() {
//clientId,可為任意值
return "id";
}
@Override
public Map<String, Object> getSignExtraData() {
return null;
}
@Override
public void onConnectResult(boolean isSuccess, ISubDeviceChannel iSubDeviceChannel, AError aError) {
//添加結(jié)果
if (isSuccess) {
//子設(shè)備添加成功,接下來可以做子設(shè)備上線的邏輯
// subDevOnline(null);
}
}
@Override
public void onDataPush(String s, AMessage message) {
//收到子設(shè)備下行數(shù)據(jù),topic=" + s + ", data=" + message
//例如禁用或刪除子設(shè)備、屬性設(shè)置和服務(wù)調(diào)用等,返回的數(shù)據(jù)message.data是byte[]
}
});
刪除子設(shè)備
此處指刪除網(wǎng)關(guān)和子設(shè)備的拓?fù)潢P(guān)系。代碼示例如下:
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; //產(chǎn)品型號(hào)(必填)
deviceInfo.deviceName = deviceName; //設(shè)備標(biāo)識(shí)(必填)
LinkKit.getInstance().getGateway().gatewayDeleteSubDevice(deviceinfo, new ISubDeviceRemoveListener() {
@Override
public void onSuceess() {
//成功刪除子設(shè)備,刪除之前可先做下線操作
}
@Override
public void onFailed(AError aError) {
//刪除子設(shè)備失敗
}
});
子設(shè)備上線
調(diào)用子設(shè)備上線之前,請(qǐng)確保已完成子設(shè)備添加。網(wǎng)關(guān)發(fā)現(xiàn)子設(shè)備連上網(wǎng)關(guān)之后,需要通知物聯(lián)網(wǎng)平臺(tái)子設(shè)備上線,子設(shè)備上線后可以執(zhí)行子設(shè)備的訂閱、發(fā)布等操作。代碼示例如下:
由于接口調(diào)用都是異步的,子設(shè)備上線接口不能在子設(shè)備添加的相關(guān)代碼的下一行調(diào)用,需在子設(shè)備添加成功的回調(diào)里面調(diào)用。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; //產(chǎn)品型號(hào)(必填)
deviceInfo.deviceName = deviceName; //設(shè)備標(biāo)識(shí)(必填)
LinkKit.getInstance().getGateway().gatewaySubDeviceLogin(deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
//代理子設(shè)備上線成功
//上線之后可訂閱、刪除和禁用的下行通知
// subDevDisable(null);
}
@Override
public void onFailed(AError aError) {
ALog.d(TAG, "onFailed() called with: aError = [" + aError + "]");
}
});
子設(shè)備下線
當(dāng)子設(shè)備離線之后,網(wǎng)關(guān)需要通知物聯(lián)網(wǎng)平臺(tái)子設(shè)備離線,以避免物聯(lián)網(wǎng)平臺(tái)向子設(shè)備發(fā)送數(shù)據(jù)。子設(shè)備下線之后不可以進(jìn)行子設(shè)備的發(fā)布、訂閱、取消訂閱等操作。代碼示例如下:
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; //設(shè)備證書ProductKey(必填)
deviceInfo.deviceName = deviceName; //設(shè)備證書DeviceName(必填)
LinkKit.getInstance().getGateway().gatewaySubDeviceLogout(deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
//代理子設(shè)備下線成功
}
@Override
public void onFailed(AError aError) {
//代理子設(shè)備下線失敗
}
});
監(jiān)聽禁用子設(shè)備
網(wǎng)關(guān)設(shè)備可以在云端控制子設(shè)備,如禁用子設(shè)備、啟用子設(shè)備、刪除網(wǎng)關(guān)與子設(shè)備的拓?fù)潢P(guān)系。目前服務(wù)端只支持禁用子設(shè)備的下行通知。服務(wù)端在禁用子設(shè)備時(shí),會(huì)對(duì)子設(shè)備做下線處理,后續(xù)網(wǎng)關(guān)將不能代理子設(shè)備和云端做通信。代碼示例如下:
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; //產(chǎn)品型號(hào)(必填)
deviceInfo.deviceName = deviceName; //設(shè)備標(biāo)識(shí)(必填)
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) {
//子設(shè)備禁用通知
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 + "]");
}
});
代理子設(shè)備物模型初始化
子設(shè)備物模型初始化必須在子設(shè)備添加到網(wǎng)關(guān)下,且子設(shè)備已經(jīng)登錄的情況下才可以調(diào)用。代碼示例如下:
說明由于接口調(diào)用都是異步的,子設(shè)備物模型初始化接口不能在相關(guān)代碼的下一行調(diào)用,而是要放到子設(shè)備登錄成功的回調(diào)里面調(diào)用。
DeviceInfo deviceInfo = new DeviceInfo(); deviceInfo.productKey = productKey; deviceInfo.deviceName = deviceName; // deviceInfo.deviceSecret = "xxxx"; Map<String, ValueWrapper> subDevInitState = new HashMap<>(); // subDevInitState.put(); //TODO 用戶根據(jù)實(shí)際情況設(shè)置 String tsl = null;// 用戶根據(jù)實(shí)際情況設(shè)置,默認(rèn)為空,直接從云端獲取最近的TSL LinkKit.getInstance().getGateway().initSubDeviceThing(tsl, deviceInfo, subDevInitState, new IDMCallback<InitResult>() { @Override public void onSuccess(InitResult initResult) { // 物模型初始化成功之后,可以做服務(wù)注冊(cè),上報(bào)等操作 } @Override public void onFailure(AError aError) { // 子設(shè)備初始化失敗 } });
子設(shè)備物模型使用接口和直連設(shè)備的物模型使用一致,通過獲取IThing實(shí)例實(shí)現(xiàn)。代碼示例如下:
// 獲取IThing實(shí)例 IThing thing = LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).first; // thing有可能為空,如子設(shè)備未登錄、物模型未初始化、子設(shè)備未添加到網(wǎng)關(guān)、子設(shè)備處于離線狀態(tài)等。 // error信息在 LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).second 返回 // 參考示例,注意判空 thing.thingPropertyPost(reportData, new IPublishResourceListener() { @Override public void onSuccess(String s, Object o) { // 設(shè)備上報(bào)狀態(tài)成功 } @Override public void onError(String s, AError aError) { // 設(shè)備上報(bào)狀態(tài)失敗 } });
子設(shè)備物模型銷毀,反初始化物模型。后續(xù)如果需要重新使用,需重新進(jìn)行登錄、物模型初始化流程。代碼示例如下:
LinkKit.getInstance().getGateway().uninitSubDeviceThing(mBaseInfo);
網(wǎng)關(guān)代理子設(shè)備的上下行通信
使用網(wǎng)關(guān)的通道執(zhí)行子設(shè)備的數(shù)據(jù)上下行。代碼示例如下:
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 設(shè)備證書ProductKey(必填)
deviceInfo.deviceName = deviceName; // 設(shè)備證書DeviceName(必填)
String topic = xxx;
String publishData = xxx;
// 訂閱
LinkKit.getInstance().getGateway().gatewaySubDeviceSubscribe(topic, deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子設(shè)備訂閱成功
}
@Override
public void onFailed(AError aError) {
// 代理子設(shè)備訂閱失敗
}
});
//發(fā)布
LinkKit.getInstance().getGateway().gatewaySubDevicePublish(topic, publishData, deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子設(shè)備發(fā)布成功
}
@Override
public void onFailed(AError aError) {
// 代理子設(shè)備發(fā)布失敗
}
});
// 取消訂閱
LinkKit.getInstance().getGateway().gatewaySubDeviceUnsubscribe(topic, deviceinfo, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子設(shè)備取消訂閱成功
}
@Override
public void onFailed(AError aError) {
// 代理子設(shè)備取消訂閱事變
}
});