子設(shè)備接入物聯(lián)網(wǎng)平臺(tái)
子設(shè)備不直接連接物聯(lián)網(wǎng)平臺(tái),需要通過網(wǎng)關(guān)與物聯(lián)網(wǎng)平臺(tái)連接。子設(shè)備連接網(wǎng)關(guān)后,網(wǎng)關(guān)查詢與當(dāng)前子設(shè)備間的拓?fù)潢P(guān)系,將子設(shè)備的信息上報(bào)物聯(lián)網(wǎng)平臺(tái),代理子設(shè)備接入物聯(lián)網(wǎng)平臺(tái)。
前提條件
您已完成以下操作:
背景信息
- 開發(fā)子設(shè)備
由于子設(shè)備不直接連接物聯(lián)網(wǎng)平臺(tái),所以無(wú)需為子設(shè)備安裝物聯(lián)網(wǎng)平臺(tái)設(shè)備端SDK。子設(shè)備的設(shè)備端由廠商自行開發(fā)。
- 本示例Demo
java/src/main/java/com/aliyun/iot/api/common/deviceApi目錄下的DeviceTopoManager文件中包含網(wǎng)關(guān)管理拓?fù)潢P(guān)系、獲取子設(shè)備證書和子設(shè)備上線的代碼。
步驟一:網(wǎng)關(guān)管理拓?fù)潢P(guān)系
網(wǎng)關(guān)接入物聯(lián)網(wǎng)平臺(tái)后,需將拓?fù)潢P(guān)系同步至物聯(lián)網(wǎng)平臺(tái),才能代理子設(shè)備與物聯(lián)網(wǎng)平臺(tái)通信。您可以直接在控制臺(tái)查看、添加拓?fù)潢P(guān)系,也可以使用示例代碼完成這一步。
- 在物聯(lián)網(wǎng)平臺(tái)控制臺(tái)的對(duì)應(yīng)實(shí)例下查看、添加網(wǎng)關(guān)與子設(shè)備的拓?fù)潢P(guān)系。
- 在左側(cè)導(dǎo)航欄,選擇 ,在列表中找到網(wǎng)關(guān)設(shè)備。
- 單擊網(wǎng)關(guān)設(shè)備對(duì)應(yīng)的子設(shè)備,進(jìn)入子設(shè)備管理頁(yè)面。查看網(wǎng)關(guān)產(chǎn)品下的子設(shè)備信息。
- 單擊添加子設(shè)備,將創(chuàng)建網(wǎng)關(guān)和子設(shè)備步驟中的子設(shè)備添加到網(wǎng)關(guān)下。
- 通過以下示例代碼查詢、添加拓?fù)潢P(guān)系。
查詢拓?fù)潢P(guān)系:
/** * 獲取網(wǎng)關(guān)下topo關(guān)系,查詢網(wǎng)關(guān)與子設(shè)備是否已經(jīng)存在topo關(guān)系。 */ private void getGWDeviceTopo() { LinkKit.getInstance().getGateway().gatewayGetSubDevices(new IConnectSendListener() { @Override public void onResponse(ARequest request, AResponse aResponse) { ALog.i(TAG, "獲取網(wǎng)關(guān)的topo關(guān)系成功 : " + JSONObject.toJSONString(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 request, AError error) { ALog.i(TAG, "獲取網(wǎng)關(guān)的topo關(guān)系失敗 : " + JSONObject.toJSONString(error)); } }); }
- 添加拓?fù)潢P(guān)系:說明
- 子設(shè)備證書信息的獲取方法請(qǐng)參見下一步。
- 物聯(lián)網(wǎng)平臺(tái)系統(tǒng)確認(rèn)子設(shè)備和網(wǎng)關(guān)的拓?fù)潢P(guān)系后,子設(shè)備便可上線,復(fù)用網(wǎng)關(guān)的物理通道與物聯(lián)網(wǎng)平臺(tái)進(jìn)行通信。
/** * 待添加拓?fù)潢P(guān)系的子設(shè)備信息。 */ private void gatewayAddSubDevice() { BaseInfo baseInfo1 = new BaseInfo(); baseInfo1.productKey = "a1j7SyR****"; baseInfo1.deviceName = "safa***"; String deviceSecret = "7lzCJIWHmGFpZpDKbJdVucDHUz6C****"; LinkKit.getInstance().getGateway().gatewayAddSubDevice(baseInfo1, new ISubDeviceConnectListener() { @Override public String getSignMethod() { // 使用的簽名方法。 return "hmacsha1"; } @Override public String getSignValue() { // 獲取簽名,用戶使用deviceSecret獲得簽名結(jié)果。 Map<String, String> signMap = new HashMap<>(); signMap.put("productKey", baseInfo1.productKey); signMap.put("deviceName", baseInfo1.deviceName); //signMap.put("timestamp", String.valueOf(System.currentTimeMillis())); signMap.put("clientId", getClientId()); return SignUtils.hmacSign(signMap, 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è)備添加成功,接下來(lái)可以做子設(shè)備上線的邏輯 ALog.i(TAG, "topo關(guān)系添加成功 : " + JSONObject.toJSONString(iSubDeviceChannel)); //子設(shè)備上線 gatewaySubDeviceLogin(); } else { ALog.i(TAG, "topo關(guān)系添加失敗 : " + JSONObject.toJSONString(aError)); } } @Override public void onDataPush(String s, AMessage aMessage) { } }); }
步驟二:獲取子設(shè)備證書
- 使用一機(jī)一密的認(rèn)證方式。
在設(shè)備創(chuàng)建成功后,在控制臺(tái)的設(shè)備詳情頁(yè)面,獲取ProductKey、DeviceName和DeviceSecret。
- 在網(wǎng)關(guān)與子設(shè)備之間定義協(xié)議,實(shí)現(xiàn)網(wǎng)關(guān)發(fā)現(xiàn)子設(shè)備,獲取子設(shè)備的設(shè)備證書。該協(xié)議由網(wǎng)關(guān)廠商與子設(shè)備廠商自行定義。
- 網(wǎng)關(guān)廠商可以在網(wǎng)關(guān)上提供某種配置方式,預(yù)置子設(shè)備的證書信息。該功能由網(wǎng)關(guān)廠商自行實(shí)現(xiàn)。
- 使用子設(shè)備動(dòng)態(tài)注冊(cè)的方式。
由網(wǎng)關(guān)向物聯(lián)網(wǎng)平臺(tái)上報(bào)子設(shè)備的ProductKey和DeviceName進(jìn)行注冊(cè)。物聯(lián)網(wǎng)平臺(tái)校驗(yàn)子設(shè)備ProductKey和DeviceName通過后,動(dòng)態(tài)下發(fā)子設(shè)備的DeviceSecret。
- 創(chuàng)建子設(shè)備時(shí),以設(shè)備的SN碼或MAC地址作為DeviceName。設(shè)備創(chuàng)建成功后,開啟產(chǎn)品的動(dòng)態(tài)注冊(cè)功能。
- 開發(fā)網(wǎng)關(guān)時(shí),實(shí)現(xiàn)網(wǎng)關(guān)通過某種協(xié)議發(fā)現(xiàn)子設(shè)備,獲取子設(shè)備的型號(hào)(model)和唯一標(biāo)識(shí)(SN碼或MAC地址);并實(shí)現(xiàn)子設(shè)備型號(hào)(model)與阿里云物聯(lián)網(wǎng)平臺(tái)ProductKey的映射。
- 通過物聯(lián)網(wǎng)平臺(tái)的動(dòng)態(tài)注冊(cè)功能,從物聯(lián)網(wǎng)平臺(tái)獲取子設(shè)備的DeviceSecret。
代碼示例:
/** * 子設(shè)備動(dòng)態(tài)注冊(cè)獲取設(shè)備deviceSecret。 * 在物聯(lián)網(wǎng)平臺(tái)上提前創(chuàng)建子設(shè)備時(shí),可以使用子設(shè)備的MAC地址或SN序列號(hào)等作為DeviceName。 */ private void gatewaySubDevicRegister() { List<BaseInfo> subDevices = new ArrayList<>(); BaseInfo baseInfo1 = new BaseInfo(); baseInfo1.productKey = "a1j7SyR***"; baseInfo1.deviceName = "safasdf"; subDevices.add(baseInfo1); LinkKit.getInstance().getGateway().gatewaySubDevicRegister(subDevices, new IConnectSendListener() { @Override public void onResponse(ARequest request, AResponse response) { ALog.i(TAG, "子設(shè)備注冊(cè)成功 : " + JSONObject.toJSONString(response)); } @Override public void onFailure(ARequest request, AError error) { ALog.i(TAG, "子設(shè)備注冊(cè)失敗 : " + JSONObject.toJSONString(error)); } }); }
關(guān)于設(shè)備動(dòng)態(tài)注冊(cè)的詳細(xì)說明,請(qǐng)參見子設(shè)備動(dòng)態(tài)注冊(cè)。
步驟三:子設(shè)備上線
/**
* 調(diào)用子設(shè)備上線接口之前,請(qǐng)確保已建立topo關(guān)系。網(wǎng)關(guān)發(fā)現(xiàn)子設(shè)備連接之后,需要告知物聯(lián)網(wǎng)平臺(tái)子設(shè)備上線。
* 子設(shè)備上線之后可以執(zhí)行子設(shè)備的訂閱、發(fā)布等操作。
*/
public void gatewaySubDeviceLogin(){
BaseInfo baseInfo1 = new BaseInfo();
baseInfo1.productKey = "a1j7SyR****";
baseInfo1.deviceName = "safasdf";
LinkKit.getInstance().getGateway().gatewaySubDeviceLogin(baseInfo1, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子設(shè)備上線成功。
// 上線之后可訂閱、發(fā)布消息,并可以刪除和禁用子設(shè)備。
// subDevDisable(null);
// subDevDelete(null);
}
@Override
public void onFailed(AError aError) {
ALog.d(TAG, "onFailed() called with: aError = [" + aError + "]");
}
});
}
}
附錄:代碼Demo
由網(wǎng)關(guān)發(fā)現(xiàn)并上報(bào)子設(shè)備信息,建立子設(shè)備與物聯(lián)網(wǎng)平臺(tái)的邏輯通道,及子設(shè)備復(fù)用網(wǎng)關(guān)物理通道接入物聯(lián)網(wǎng)平臺(tái)的完整示例代碼如下:
package com.aliyun.iot.api.common.deviceApi;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.aliyun.alink.dm.api.BaseInfo;
import com.aliyun.alink.dm.api.DeviceInfo;
import com.aliyun.alink.dm.api.InitResult;
import com.aliyun.alink.dm.api.SignUtils;
import com.aliyun.alink.dm.model.ResponseModel;
import com.aliyun.alink.linkkit.api.ILinkKitConnectListener;
import com.aliyun.alink.linkkit.api.IoTMqttClientConfig;
import com.aliyun.alink.linkkit.api.LinkKit;
import com.aliyun.alink.linkkit.api.LinkKitInitParams;
import com.aliyun.alink.linksdk.channel.gateway.api.subdevice.ISubDeviceActionListener;
import com.aliyun.alink.linksdk.channel.gateway.api.subdevice.ISubDeviceChannel;
import com.aliyun.alink.linksdk.channel.gateway.api.subdevice.ISubDeviceConnectListener;
import com.aliyun.alink.linksdk.channel.gateway.api.subdevice.ISubDeviceRemoveListener;
import com.aliyun.alink.linksdk.cmp.core.base.AMessage;
import com.aliyun.alink.linksdk.cmp.core.base.ARequest;
import com.aliyun.alink.linksdk.cmp.core.base.AResponse;
import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSendListener;
import com.aliyun.alink.linksdk.tools.AError;
import com.aliyun.alink.linksdk.tools.ALog;
import java.util.*;
import static com.aliyun.alink.linksdk.tools.ALog.LEVEL_DEBUG;
public class DeviceTopoManager {
private static String regionId = "cn-shanghai";
private static final String TAG = "TOPO";
//網(wǎng)關(guān)設(shè)備
private static String GWproductKey = "a1Bxp*********";
private static String GWdeviceName = "XMtrv3y*************";
private static String GWdeviceSecret = "19xJNybifnmgc*************";
public static void main(String[] args) {
/**
* mqtt連接信息
*/
DeviceTopoManager manager = new DeviceTopoManager();
/**
* 服務(wù)器端的java http客戶端使用TSLv1.2。
*/
System.setProperty("https.protocols", "TLSv2");
manager.init();
}
public void init() {
LinkKitInitParams params = new LinkKitInitParams();
/**
* 設(shè)置mqtt初始化參數(shù)。
*/
IoTMqttClientConfig config = new IoTMqttClientConfig();
config.productKey = GWproductKey;
config.deviceName = GWdeviceName;
config.deviceSecret = GWdeviceSecret;
config.channelHost = GWproductKey + ".iot-as-mqtt." + regionId + ".aliyuncs.com:1883";
/**
* 是否接受離線消息。
* 對(duì)應(yīng)mqtt的cleanSession字段。
*/
config.receiveOfflineMsg = false;
params.mqttClientConfig = config;
ALog.setLevel(LEVEL_DEBUG);
ALog.i(TAG, "mqtt connetcion info=" + params);
/**
* 設(shè)置初始化,傳入網(wǎng)關(guān)的設(shè)備證書信息。
*/
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = GWproductKey;
deviceInfo.deviceName = GWdeviceName;
deviceInfo.deviceSecret = GWdeviceSecret;
params.deviceInfo = deviceInfo;
/**建立連接**/
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);
//獲取網(wǎng)關(guān)下拓?fù)潢P(guān)系,查詢網(wǎng)關(guān)與子設(shè)備是否已經(jīng)存在拓?fù)潢P(guān)系。
//如果已經(jīng)存在,則直接上線子設(shè)備。
getGWDeviceTopo();
//子設(shè)備動(dòng)態(tài)注冊(cè)獲取DeviceSecret,如果設(shè)備已知設(shè)備證書則忽略此步,直接添加拓?fù)潢P(guān)系。
//在物聯(lián)網(wǎng)平臺(tái)上提前創(chuàng)建設(shè)備時(shí),可以使用設(shè)備的MAC地址或SN序列號(hào)等作為DeviceName。
gatewaySubDevicRegister();
//待添加拓?fù)潢P(guān)系的子設(shè)備信息。
gatewayAddSubDevice();
}
});
}
/**
* 獲取網(wǎng)關(guān)下拓?fù)潢P(guān)系,查詢網(wǎng)關(guān)與子設(shè)備是否已經(jīng)存在拓?fù)潢P(guān)系。
*/
private void getGWDeviceTopo() {
LinkKit.getInstance().getGateway().gatewayGetSubDevices(new IConnectSendListener() {
@Override
public void onResponse(ARequest request, AResponse aResponse) {
ALog.i(TAG, "獲取網(wǎng)關(guān)的topo關(guān)系成功 : " + JSONObject.toJSONString(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 request, AError error) {
ALog.i(TAG, "獲取網(wǎng)關(guān)的topo關(guān)系失敗 : " + JSONObject.toJSONString(error));
}
});
}
/**
* 子設(shè)備動(dòng)態(tài)注冊(cè)獲取設(shè)備deviceSecret,如果網(wǎng)關(guān)已獲得子設(shè)備證書則忽略此步。
* 在物聯(lián)網(wǎng)平臺(tái)上提前創(chuàng)建設(shè)備時(shí),可以使用設(shè)備的MAC地址或SN序列號(hào)等作為DeviceName。
*/
private void gatewaySubDevicRegister() {
List<BaseInfo> subDevices = new ArrayList<>();
BaseInfo baseInfo1 = new BaseInfo();
baseInfo1.productKey = "a1j7SyR**********";
baseInfo1.deviceName = "test123*********";
subDevices.add(baseInfo1);
LinkKit.getInstance().getGateway().gatewaySubDevicRegister(subDevices, new IConnectSendListener() {
@Override
public void onResponse(ARequest request, AResponse response) {
ALog.i(TAG, "子設(shè)備注冊(cè)成功 : " + JSONObject.toJSONString(response));
}
@Override
public void onFailure(ARequest request, AError error) {
ALog.i(TAG, "子設(shè)備注冊(cè)失敗 : " + JSONObject.toJSONString(error));
}
});
}
/**
* 待添加拓?fù)潢P(guān)系的子設(shè)備信息。
*/
private void gatewayAddSubDevice() {
BaseInfo baseInfo1 = new BaseInfo();
baseInfo1.productKey = "a1j7Sy*************";
baseInfo1.deviceName = "safasd********";
String deviceSecret = "7lzCJIWHmGF**************";
LinkKit.getInstance().getGateway().gatewayAddSubDevice(baseInfo1, new ISubDeviceConnectListener() {
@Override
public String getSignMethod() {
// 使用的簽名方法
return "hmacsha1";
}
@Override
public String getSignValue() {
// 獲取簽名,用戶使用DeviceSecret獲得簽名結(jié)果。
Map<String, String> signMap = new HashMap<>();
signMap.put("productKey", baseInfo1.productKey);
signMap.put("deviceName", baseInfo1.deviceName);
// signMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
signMap.put("clientId", getClientId());
return SignUtils.hmacSign(signMap, 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è)備添加成功,接下來(lái)可以做子設(shè)備上線的邏輯。
ALog.i(TAG, "topo關(guān)系添加成功 : " + JSONObject.toJSONString(iSubDeviceChannel));
//子設(shè)備上線
gatewaySubDeviceLogin();
} else {
ALog.i(TAG, "topo關(guān)系添加失敗 : " + JSONObject.toJSONString(aError));
}
}
@Override
public void onDataPush(String s, AMessage aMessage) {
}
});
}
public void gatewayDeleteSubDevice(){
BaseInfo baseInfo1 = new BaseInfo();
baseInfo1.productKey = "a1j7S**************";
baseInfo1.deviceName = "saf*********";
LinkKit.getInstance().getGateway().gatewayDeleteSubDevice(baseInfo1, new ISubDeviceRemoveListener() {
@Override
public void onSuceess() {
// 成功刪除子設(shè)備。刪除之前可先做下線操作。
}
@Override
public void onFailed(AError aError) {
// 刪除子設(shè)備失敗。
}
});
}
/**
* 調(diào)用子設(shè)備上線之前,請(qǐng)確保已建立拓?fù)潢P(guān)系。網(wǎng)關(guān)發(fā)現(xiàn)子設(shè)備連接后,需要告知物聯(lián)網(wǎng)平臺(tái)子設(shè)備上線。
* 子設(shè)備上線之后可以執(zhí)行子設(shè)備的訂閱、發(fā)布等操作。
*/
public void gatewaySubDeviceLogin(){
BaseInfo baseInfo1 = new BaseInfo();
baseInfo1.productKey = "a1j7SyR***********";
baseInfo1.deviceName = "safa*********";
LinkKit.getInstance().getGateway().gatewaySubDeviceLogin(baseInfo1, new ISubDeviceActionListener() {
@Override
public void onSuccess() {
// 代理子設(shè)備上線成功。
// 上線之后可訂閱、發(fā)布消息,并可以刪除和禁用子設(shè)備。
// subDevDisable(null);
// subDevDelete(null);
}
@Override
public void onFailed(AError aError) {
ALog.d(TAG, "onFailed() called with: aError = [" + aError + "]");
}
});
}
}