认证与连接

设备接入物联网平台之前,需通过身份认证。本文介绍如何将Android Link SDK进行初始化,实现将设备接入物联网平台。

前提条件

背景信息

Android Link SDK支持设备密钥和ID²认证进行设备身份认证。

  • 设备密钥

    设备密钥分为以下几种认证方式:

    认证方式

    注册方式

    说明

    相关文档

    一机一密

    不涉及

    每台设备烧录自己的设备证书(ProductKey、DeviceName和DeviceSecret)。

    一机一密

    一型一密

    预注册

    • 同一产品下设备烧录相同产品证书(ProductKey和ProductSecret)。

    • 产品需开启动态注册功能。

    • 设备通过动态注册获取DeviceSecret。

    一型一密

    说明

    一型一密预注册和免预注册的区别,请参见预注册和免预注册的区别

    免预注册

    • 同一产品下设备烧录相同产品证书(ProductKey和ProductSecret)。

    • 产品需开启动态注册功能。

    • 设备通过动态注册获取ClientID与DeviceToken的组合。

  • ID²认证:ID²是一种物联网设备的可信身份标识,具备不可篡改、不可伪造等安全属性。

    更多信息,请参见ID²认证

Demo使用说明

  • 如果设备初始化失败,需再次对SDK进行初始化。当设备由于网络等原因导致与物联网平台断开连接时,SDK会自动尝试与物联网平台建立连接。

  • 修改Android Link SDK Demo./app/src/main/res/raw/deviceinfo文件的参数,实现设备的快速接入。表格中提示”需要填写“的参数进行填写,为否的参数保持默认设置不需修改。

  • 参数

    一机一密

    一型一密(预注册)

    一型一密(免预注册)

    productKey (物联网平台为产品颁发的标识符)

    需要填写

    需要填写

    需要填写

    deviceName (设备在产品内的标识符)

    需要填写

    需要填写

    需要填写

    productSecret (产品密钥)

    需要填写

    需要填写

    deviceSecret (设备密钥)

    需要填写

    registerType (一型一密的方式,预注册或免预注册)

    需要填写

    instanceId (实例ID,对企业实例和新版本公共实例适用,具体请参见实例概述

    需要填写

    需要填写

    mqttHost (MQTT接入域名)

    需要填写(上海区域除外)

    需要填写(上海区域除外)

    需要填写(上海区域除外)

一机一密

一机一密的设备认证方式的示例代码如下:

        final LinkKitInitParams params = new LinkKitInitParams();

        //Step1: 构造设备认证信息
        DeviceInfo deviceInfo = new DeviceInfo();
        deviceInfo.productKey = productKey;  // 产品类型
        deviceInfo.deviceName = deviceName;  // 设备名称
        deviceInfo.deviceSecret = deviceSecret;  // 设备密钥
        deviceInfo.productSecret = productSecret;  // 产品密钥
        params.deviceInfo = deviceInfo;

        //Step2: 全局默认域名
        IoTApiClientConfig userData = new IoTApiClientConfig();
        params.connectConfig = userData;

        //Step3: 物模型缓存
        Map<String, ValueWrapper> propertyValues = new HashMap<>();
        /**
         * 物模型的数据会缓存到该字段中,不可删除或者设置为空,否则功能会异常
         * 用户调用物模型上报接口之后,物模型会有相关数据缓存。
         */
        params.propertyValues = propertyValues;

        //Step4: mqtt设置
        /**
         * 慎用
         * Mqtt 相关参数设置,包括接入点等信息,具体参见deviceinfo文件说明
         * 域名、产品密钥、认证安全模式等;
         */
        IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(productKey, deviceName, deviceSecret);
        clientConfig.receiveOfflineMsg = false;//cleanSession=1 不接受离线消息
        //mqtt接入点信息
        clientConfig.channelHost = mqttHost;
        params.mqttClientConfig = clientConfig;

        //Step5: 高阶功能配置,默认均为关闭状态
        IoTDMConfig ioTDMConfig = new IoTDMConfig();
        // 默认不开启物模型功能,开启之后init方法会等到物模型初始化(包含请求云端物模型)完成之后才返回onInitDone
        ioTDMConfig.enableThingModel = false;
        // 默认不开启网关功能,开启之后,初始化的时候会初始化网关模块,获取云端网关子设备列表
        ioTDMConfig.enableGateway = false;
        // 默认不开启,是否开启日志推送功能
        ioTDMConfig.enableLogPush = false;
        params.ioTDMConfig = ioTDMConfig;

        //Step6: 下行消息处理回调设置
        LinkKit.getInstance().registerOnPushListener(notifyListener);

        //Step7: 一型一密免预注册设置(可选)
        //对于一型一密免预注册的设备, 设备连云时要用上deviceToken和clientId
        MqttConfigure.deviceToken = DemoApplication.deviceToken;
        MqttConfigure.clientId = DemoApplication.clientId;

        //Step8: H2文件上传设置(可选)
        /**
         * 如果要用到HTTP2文件上传, 需要用户设置域名
         */
        IoTH2Config ioTH2Config = new IoTH2Config();
        ioTH2Config.clientId = "client-id";
        ioTH2Config.endPoint = "https://" + productKey + ioTH2Config.endPoint;// 线上环境
        params.iotH2InitParams = ioTH2Config;

        /**
         * 设备初始化建联
         * onError 初始化建联失败,如果因网络问题导致初始化失败,需要用户重试初始化
         * onInitDone 初始化成功
         */
        LinkKit.getInstance().init(context, params, new ILinkKitConnectListener() {
            @Override
            public void onError(AError error) {
                AppLog.d(TAG, "onError() called with: error = [" + getAErrorString(error) + "]");
                callback.onError(error);
            }

            @Override
            public void onInitDone(Object data) {
                AppLog.d(TAG, "onInitDone() called with: data = [" + data + "]");
                callback.onInitDone(data);
            }
        });

一型一密

一型一密又称动态注册,用于向物联网平台获取设备的密钥,具体分为免预注册和预注册两种方式。使用该功能前,需要确保:

  • 已在物联网平台创建产品已开启动态注册开关。

  • demo的deviceinfo文件中的deviceSecret的值为空,productSecret不为空。

  • 请确保已执行Demo中的step 1、step 2、step 3。

  • 动态注册成功或失败之后,需要断开当前的动态注册长连接。如果动态注册成功,可以在断开后执行建连的流程,具体可参考Demo中destroyRegisterConnect()的实现。

  • 为了您的设备安全,一型一密的认证方式获取到设备密钥后,请将其持久固化至设备,若需连接至物联网平台,请参考上述一机一密流程。

免预注册和预注册区别如下:

区别

免预注册

预注册

通信协议/地域支持

MQTT/华东2(上海)

MQTT/物联网平台支持的所有地域;

基于HTTPS的动态注册仅限于上海区域,不推荐使用,不在本示例代码介绍范围之内。

返回的设备密钥

设备的clientID和deviceToken,请将其持久固化至设备,以便连云等其他功能使用。具体使用方式,请参考上述一机一密示例中的Step7。

DeviceSecret

具体使用方式,请参考上述一机一密示例中的Step1。

添加设备

不需要在物联网平台预注册设备DeviceName。

需要在物联网平台预注册设备DeviceName。

使用次数限制

物联网平台允许最多5个物理设备使用同一组ProductKey、ProductSecret、DeviceName进行激活,并为不同物理设备下发不同的ClientID、DeviceToken。

  • 同一组设备证书只能用于激活一个物理设备。若DeviceName名下已激活物理设备A,但物理设备B需要使用该DeviceName,则您可以在物联网平台上删除设备A,使设备A的DeviceSecret作废,再使用原DeviceName重新添加设备,激活物理设备B。

  • 若设备因丢失DeviceSecret等原因需要重新激活,需您调用ResetThing接口,重置设备状态为未激活,然后将设备重新联网激活。此时,物联网平台下发的DeviceSecret不变。

示例代码如下:

        MqttInitParams initParams = new MqttInitParams(productKey, productSecret, deviceName, deviceSecret, MqttConfigure.MQTT_SECURE_MODE_TLS);

        //动态注册step1: 确定一型一密的类型(免预注册, 还是非免预注册)
        //case 1: 如果registerType里面填写了regnwl, 表明设备的一型一密方式为免预注册(即无需创建设备)
        //case 2: 如果这个字段为空, 则表示为需要预注册的一型一密(需要实现创建设备)
        // docs: https://help.aliyun.com/document_detail/132111.html?spm=a2c4g.11186623.6.600.4e073f827Y7a8y
        initParams.registerType = registerType;

        //动态注册step2: 设置动态注册的注册接入点域名
        MqttConfigure.mqttHost = mqttHost;

        //动态注册step3: 如果用户所用的实例为新版本的公共实例或者企业实例(控制台中有实例详情的页面), 需设置动态注册的实例id
        MqttConfigure.registerInstanceId = instanceId;

        LinkKit.getInstance().deviceDynamicRegister(this, initParams, new IOnCallListener() {
            @Override
            public void onSuccess(com.aliyun.alink.linksdk.channel.core.base.ARequest request, com.aliyun.alink.linksdk.channel.core.base.AResponse response) {
                AppLog.i(TAG, "onSuccess() called with: request = [" + request + "], response = [" + response + "]");
                // response.data is byte array
                try {
                    String responseData = new String((byte[]) response.data);
                    JSONObject jsonObject = JSONObject.parseObject(responseData);
                    String pk = jsonObject.getString("productKey");
                    String dn = jsonObject.getString("deviceName");
                    // 非一型一密免预注册返回
                    String ds = jsonObject.getString("deviceSecret");
                    // 一型一密免预注册返回
                    String ci = jsonObject.getString("clientId");
                    String dt = jsonObject.getString("deviceToken");

                    clientId = ci;
                    deviceToken = dt;
                    deviceSecret = ds;

                    // 持久化 clientId & deviceToken 初始化建联的时候需要
                    // 这里仅为测试代码,请将认证信息持久化到外部存储,确保app清除缓存或者卸载重装后仍能取到
                    SharedPreferences preferences = getSharedPreferences("deviceAuthInfo", 0);
                    if ((!TextUtils.isEmpty(clientId) && !TextUtils.isEmpty(deviceToken)) || (!TextUtils.isEmpty(deviceSecret))) {
                        showToast("一型一密成功");
                        SharedPreferences.Editor editor = preferences.edit();
                        editor.putString("deviceId", productKey + deviceName);
                        editor.putString("clientId", clientId);
                        editor.putString("deviceToken", deviceToken);
                        editor.putString("deviceSecret", deviceSecret);
                        editor.commit();
                        try {
                            Thread.sleep(2000);
                        } catch (Exception e){

                        }
                        destroyRegisterConnect(true);
                    } else {
                        showToast("一型一密失败,返回信息无效 " + responseData);
                        destroyRegisterConnect(false);
                    }
                } catch (Exception e) {
                    showToast("一型一密失败,返回数据信息无效");
                    e.printStackTrace();
                    destroyRegisterConnect(false);
                }

            }

            @Override
            public void onFailed(com.aliyun.alink.linksdk.channel.core.base.ARequest request, com.aliyun.alink.linksdk.channel.core.base.AError error) {
                AppLog.w(TAG, "onFailed() called with: request = [" + request + "], error = [" + error + "]");
                showToast("一型一密失败 " + error);
                destroyRegisterConnect(false);
            }

            @Override
            public boolean needUISafety() {
                return false;
            }
        });

    /**
     * 注意该接口不能在动态注册回调线程里面调用,mqtt 通道会报 Disconnecting is not allowed from a callback method (32107)
     * @param needConnect
     */
    private void destroyRegisterConnect(final boolean needConnect) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    LinkKit.getInstance().stopDeviceDynamicRegister(10 * 1000, null, new IMqttActionListener() {
                        @Override
                        public void onSuccess(IMqttToken iMqttToken) {
                            AppLog.d(TAG, "onSuccess() called with: iMqttToken = [" + iMqttToken + "]");
                            if (needConnect) {
                                connect();
                            }
                        }

                        @Override
                        public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
                            AppLog.w(TAG, "onFailure() called with: iMqttToken = [" + iMqttToken + "], throwable = [" + throwable + "]");
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

ID²认证

/**
 * 在物联网平台创建使用iTLS认证方式的设备,采用该方式初始化
 * 产品需要ID²授权
 */
IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(productKey, deviceName, deviceSecret);
clientConfig.channelHost = productKey + ".itls.cn-shanghai.aliyuncs.com:1883";
clientConfig.productSecret = productSecret;
clientConfig.secureMode = 8;
linkKitInitParams.mqttClientConfig = clientConfig;
 

示例代码中的参数clientConfig.channelHost为设备的接入域名。

更多设置

您可以设置以下参数,实现设备接入相关的更多设置。

  • MQTT连接:

    配置项

    说明

    相关代码

    保活时间

    设置设备的保活时间。通过该设置实现设备与物联网平台保持长连接。

    // interval 单位秒
    MqttConfigure.setKeepAliveInterval(int interval);

    QoS等级

    设置QoS(Quality of Service)等级,即物联网平台与设备之间保证交付信息的协议。仅支持:

    • 0:最多一次。

    • 1:最少一次。

    MqttPublishRequest request = new MqttPublishRequest();
    // 支持 0 和 1, 默认0
    request.qos = 0;
    request.isRPC = false;
    request.topic = topic.replace("request", "response");
    String resId = topic.substring(topic.indexOf("rrpc/request/")+13);
    request.msgId = resId;
    // TODO 用户根据实际情况填写
    request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";

    离线消息

    通过cleanSession,设置是否接收离线消息。

    IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(productKey, deviceName, deviceSecret);
    // 对应 receiveOfflineMsg = !cleanSession, 默认不接受离线消息 
    clientConfig.receiveOfflineMsg = true;
     

    客户端ID

    通过clientId设置客户端ID。clientId的详细说明,请参见clientId说明

    MqttConfigure.clientId = "abcdef******";

    重连机制

    通过automaticReconnect参数的值,设置是否重连。

    • true:重连。

    • false:不重连。

    参数设置:

    MqttConfigure.automaticReconnect = true;
  • SDK反初始化:

    如果需要注销初始化,调用如下反初始化接口。该接口为同步接口。

    说明

    SDK进行初始化时,需确保最近一次的反初始化已执行完成。否则,会导致重复初始化失败。

    // 取消注册 notifyListener,notifyListener对象需和注册的时候是同一个对象
    LinkKit.getInstance().unRegisterOnPushListener(notifyListener);
    LinkKit.getInstance().deinit();
  • 连接状态监听:

    如果需要监听设备的上下线信息,物联网平台下发的所有数据,可以设置以下监听器。

    IConnectNotifyListener notifyListener = new IConnectNotifyListener() {
        @Override
        public void onNotify(String connectId, String topic, AMessage aMessage) {
            // 下行数据回调
            // connectId连接类型Topic下行Topic; aMessage下行数据
            // 数据解析如下:
            //String pushData = new String((byte[]) aMessage.data);
            // pushData 示例  {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
            // method 服务类型; params 下推数据内容    
       }
        @Override
        public boolean shouldHandle(String connectId, String topic) {
            // 选择是否不处理某个Topic的下行数据
            // 如果不处理某个Topic,则onNotify不会收到对应Topic的下行数据
            return true; //TODO 根据实际情况设置
        }
        @Override
        public void onConnectStateChange(String connectId, ConnectState connectState) {
            // 对应连接类型的连接状态变化回调,具体连接状态参考SDK ConnectState
        }
    }
    // 注册下行监听,包括长连接的状态和下行的数据
    LinkKit.getInstance().registerOnPushListener(notifyListener);
    // 取消下行监听,需要确保和注册的是同一个对象
    // LinkKit.getInstance().unRegisterOnPushListener(notifyListener);
                
  • 日志开关:

    打开SDK内部日志输出开关:

    PersistentNet.getInstance().openLog(true);
    ALog.setLevel(ALog.LEVEL_DEBUG);
       
  • 获取SDK的版本号:

    ALog.i(TAG, "sdk version = " + LinkKit.getInstance().getSDKVersion());
阿里云首页 设备接入Link SDK 相关技术圈