子设备不直接连接物联网平台,需要通过网关与物联网平台连接。子设备连接网关后,网关查询与当前子设备间的拓扑关系,将子设备的信息上报云端,代理子设备接入物联网平台。

开发子设备

由于子设备不直接连接物联网平台,所以无需为子设备安装物联网平台设备端SDK。子设备的设备端由厂商自行开发。

网关获取子设备证书

网关需上报子设备的设备证书(ProductKey、DeviceName和DeviceSecret),用于子设备连接认证。网关获取子设备的证书信息,需由网关厂商自行实现,阿里云不提供代码支持。网关获取子设备证书信息的三种方式:

  • 网关从子设备上直接获取子设备证书信息。

    在网关与子设备之间定义协议,实现网关发现子设备,获取子设备的设备证书。该协议由网关厂商与子设备厂商自行定义。

  • 在网关上预置子设备证书信息。

    网关厂商可以在网关上提供某种配置方式,预置子设备的证书信息。该功能由网关厂商自行实现。

  • 网关通过子设备动态注册获取子设备证书信息。
    1. 在物联网平台创建子设备,将设备的SN码或MAC地址作为DeviceName。设备创建成功后,开启该设备的动态注册功能。
    2. 开发网关时,实现网关通过某种协议发现子设备,获取子设备的型号(model)和唯一标识(SN码或MAC地址);并实现子设备型号(model)与阿里云物联网平台ProductKey的映射。
    3. 通过物联网平台的动态注册功能,从云端获取子设备的DeviceSecret。

子设备通过网关接入物联网平台

本示例Demo中,java/src/main/com.aliyun.iot.api.common.openApi目录下的DeviceTopoManager文件中包含网关管理拓扑关系和子设备动态注册的代码。在该文件中,配置网关和子设备信息,建立拓扑关系和实现子设备动态注册。

  1. 子设备接入网关。
    子设备连接网关,网关发现子设备、发现子设备上下线和将来自物联网平台的消息发送给子设备等功能,均由网关厂商与子设备厂商定义协议实现。
  2. 网关查询与当前子设备是否有拓扑关系。
         /**
         * 获取网关下topo关系,查询网关与子设备是否已经存在topo关系
         */
        private void getGWDeviceTopo() {
            LinkKit.getInstance().getGateway().gatewayGetSubDevices(new IConnectSendListener() {
    
                @Override
                public void onResponse(ARequest request, AResponse aResponse) {
                    ALog.i(TAG, "获取网关的topo关系成功 : " + JSONObject.toJSONString(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 request, AError error) {
                    ALog.i(TAG, "获取网关的topo关系失败 : " + JSONObject.toJSONString(error));
                }
            });
        }
  3. (可选)动态注册子设备。如果网关已获得子设备的设备证书信息,则忽略此步。
    说明 需先在物联网平台控制台,子设备所属产品的产品详情页,打开动态注册开关。

    由网关向物联网平台上报子设备的ProductKey和DeviceName进行注册。物联网平台校验子设备ProductKey和DeviceName通过后,动态下发子设备的DeviceSecret。

    动态注册子设备代码示例:

         /**
         * 子设备动态注册获取设备deviceSecret。
         * 预注册子设备时,可以使用子设备的MAC地址或SN序列号等作为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, "子设备注册成功 : " + JSONObject.toJSONString(response));
                }
    
                @Override
                public void onFailure(ARequest request, AError error) {
                    ALog.i(TAG, "子设备注册失败 : " + JSONObject.toJSONString(error));
                }
            });
        }

    关于设备动态注册的详细说明,请参见子设备动态注册

  4. 添加子设备与网关的拓扑关系。
        /**
         * 待添加拓扑关系的子设备信息
         */
        private void gatewayAddSubDevice() {
            BaseInfo baseInfo1 = new BaseInfo();
            baseInfo1.productKey = "a1j7SyR****";
            baseInfo1.deviceName = "safasdf";
            String deviceSecret = "7lzCJIWHmGFpZpDKbJdVucDHUz6C****";
    
            LinkKit.getInstance().getGateway().gatewayAddSubDevice(baseInfo1, new ISubDeviceConnectListener() {
                @Override
                public String getSignMethod() {
                    // 使用的签名方法
                    return "hmacsha1";
                }
    
                @Override
                public String getSignValue() {
                    // 获取签名,用户使用 deviceSecret 获得签名结果
                    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) {
    
    
                    // 添加结果
                    if (isSuccess) {
                        // 子设备添加成功,接下来可以做子设备上线的逻辑
                        ALog.i(TAG, "topo关系添加成功 : " + JSONObject.toJSONString(iSubDeviceChannel));
                    } else {
                        ALog.i(TAG, "topo关系添加失败 : " + JSONObject.toJSONString(aError));
                    }
                }
    
                @Override
                public void onDataPush(String s, AMessage aMessage) {
    
                }
            });
        }
    云端确认子设备和网关存在拓扑关系,子设备便可复用网关的物理通道与物联网平台进行通信。
  5. 子设备上线。
         /**
         * 调用子设备上线接口之前,请确保已建立topo关系。网关发现子设备连接之后,
         * 需要告知云端子设备上线。子设备上线之后可以执行子设备的订阅、发布等操作。
         */
        public void gatewaySubDeviceLogin(){
    
            BaseInfo baseInfo1 = new BaseInfo();
            baseInfo1.productKey = "a1j7SyR****";
            baseInfo1.deviceName = "safasdf";
    
            LinkKit.getInstance().getGateway().gatewaySubDeviceLogin(baseInfo1, new ISubDeviceActionListener() {
                @Override
                public void onSuccess() {
                    // 代理子设备上线成功
                    // 上线之后可订阅、发布消息,并可以删除和禁用子设备
                    // subDevDisable(null);
                    // subDevDelete(null);
                }
                @Override
                public void onFailed(AError aError) {
                    ALog.d(TAG, "onFailed() called with: aError = [" + aError + "]");
                }
            });
    
        }
    
    }

附录:代码Demo

由网关发现并上报子设备信息,建立子设备与物联网平台的逻辑通道,及子设备复用网关物理通道接入物联网平台的完整示例代码如下:

package com.aliyun.iot.api.common.openApi;

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;
import static java.awt.SystemColor.info;

public class DeviceTopoManager {
    private static String regionId = "cn-shanghai";
    private static final String TAG = "TOPO";

    //网关设备
    private static String GWproductKey = "a1BxptK****";
    private static String GWdeviceName = "XMtrv3yvftEHAzrTfX1U";
    private static String GWdeviceSecret = "19xJNybifnmgcK057vYhazYK4b64****";


    public static void main(String[] args) {
        /**
         * mqtt连接信息
         */
        DeviceTopoManager manager = new DeviceTopoManager();

        /**
         * 服务器端的java http 客户端使用TSLv1.2。
         */
        System.setProperty("https.protocols", "TLSv2");

        manager.init();
    }

    public void init() {
        LinkKitInitParams params = new LinkKitInitParams();
        /**
         * 设置 Mqtt 初始化参数
         */
        IoTMqttClientConfig config = new IoTMqttClientConfig();
        config.productKey = GWproductKey;
        config.deviceName = GWdeviceName;
        config.deviceSecret = GWdeviceSecret;
        config.channelHost = GWproductKey + ".iot-as-mqtt." + regionId + ".aliyuncs.com:1883";
        /**
         * 是否接受离线消息
         * 对应 mqtt 的 cleanSession 字段
         */
        config.receiveOfflineMsg = false;
        params.mqttClientConfig = config;
        ALog.setLevel(LEVEL_DEBUG);
        ALog.i(TAG, "mqtt connetcion info=" + params);

        /**
         * 设置初始化,传入设备证书信息
         */
        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);


                //获取网关下topo关系,查询网关与自设备是否已经存在topo关系
                //如果已经存在,则直接上线子设备
                getGWDeviceTopo();

                //子设备动态注册获取设备deviceSecret,如果网关已获得子设备证书则忽略此步,直接添加topo关系
                //预注册设备时,可以使用设备的MAC地址或SN序列号等作为DeviceName
                gatewaySubDevicRegister();

                //待添加拓扑关系 子设备信息
                gatewayAddSubDevice();

                //子设备上线
                gatewaySubDeviceLogin();

            }
        });
    }

    /**
     * 获取网关的topo关系,查询网关与子设备是否已经存在topo关系
     */
    private void getGWDeviceTopo() {
        LinkKit.getInstance().getGateway().gatewayGetSubDevices(new IConnectSendListener() {

            @Override
            public void onResponse(ARequest request, AResponse aResponse) {
                ALog.i(TAG, "获取网关的topo关系成功 : " + JSONObject.toJSONString(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 request, AError error) {
                ALog.i(TAG, "获取网关的topo关系失败 : " + JSONObject.toJSONString(error));
            }
        });
    }

    /**
     * 子设备动态注册获取设备deviceSecret,如果网关已获得子设备证书则忽略此步
     * 预注册设备时,可以使用设备的MAC地址或SN序列号等作为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, "子设备注册成功 : " + JSONObject.toJSONString(response));
            }

            @Override
            public void onFailure(ARequest request, AError error) {
                ALog.i(TAG, "子设备注册失败 : " + JSONObject.toJSONString(error));
            }
        });
    }

    /**
     * 待添加拓扑关系 子设备信息
     */
    private void gatewayAddSubDevice() {
        BaseInfo baseInfo1 = new BaseInfo();
        baseInfo1.productKey = "a1j7SyR****";
        baseInfo1.deviceName = "safasdf";
        String deviceSecret = "7lzCJIWHmGFpZpDKbJdVucDHUz6C****";

        LinkKit.getInstance().getGateway().gatewayAddSubDevice(baseInfo1, new ISubDeviceConnectListener() {
            @Override
            public String getSignMethod() {
                // 使用的签名方法
                return "hmacsha1";
            }

            @Override
            public String getSignValue() {
                // 获取签名,用户使用 deviceSecret 获得签名结果
                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) {


                // 添加结果
                if (isSuccess) {
                    // 子设备添加成功,接下来可以做子设备上线的逻辑
                    ALog.i(TAG, "topo关系添加成功 : " + JSONObject.toJSONString(iSubDeviceChannel));
                } else {
                    ALog.i(TAG, "topo关系添加失败 : " + JSONObject.toJSONString(aError));
                }
            }

            @Override
            public void onDataPush(String s, AMessage aMessage) {

            }
        });
    }

    public void  gatewayDeleteSubDevice(){
        BaseInfo baseInfo1 = new BaseInfo();
        baseInfo1.productKey = "a1j7SyR****";
        baseInfo1.deviceName = "safasdf";

        LinkKit.getInstance().getGateway().gatewayDeleteSubDevice(baseInfo1, new ISubDeviceRemoveListener() {
            @Override
            public void onSuceess() {
                // 成功删除子设备,删除之前可先做下线操作
            }
            @Override
            public void onFailed(AError aError) {
                // 删除子设备失败
            }
        });
    }

    /**
     * 调用子设备上线之前,请确保已建立topo关系。网关发现子设备连接后,
     * 需要告知云端子设备上线,子设备上线之后可以执行子设备的订阅、发布等操作。
     */
    public void gatewaySubDeviceLogin(){

        BaseInfo baseInfo1 = new BaseInfo();
        baseInfo1.productKey = "a1j7SyR****";
        baseInfo1.deviceName = "safasdf";

        LinkKit.getInstance().getGateway().gatewaySubDeviceLogin(baseInfo1, new ISubDeviceActionListener() {
            @Override
            public void onSuccess() {
                // 代理子设备上线成功
                // 上线之后可订阅、发布消息,并可以删除和禁用子设备
                // subDevDisable(null);
                // subDevDelete(null);
            }
            @Override
            public void onFailed(AError aError) {
                ALog.d(TAG, "onFailed() called with: aError = [" + aError + "]");
            }
        });

    }

}