认证与连接

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

前提条件

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

  • 设备密钥

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

    认证方式

    注册方式

    说明

    相关文档

    一机一密

    不涉及

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

    一机一密

    一型一密

    预注册

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

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

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

    一型一密

    说明

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

    免预注册

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

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

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

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

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

背景信息

说明

具体代码实现请参见Demo中的InitManager.java。

Demo使用说明

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

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

  • 参数

    一机一密

    一型一密(预注册)

    一型一密(免预注册)

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

    需要填写

    需要填写

    需要填写

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

    需要填写

    需要填写

    需要填写

    productSecret (产品密钥)

    需要填写

    需要填写

    deviceSecret (设备密钥)

    需要填写

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

    需要填写,内容为字符串regnwl

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

    需要填写

    需要填写

    mqttHost (MQTT接入域名)

    需要填写(上海区域除外),需要添加端口号信息。

    • 使用新版公共实例、企业实例接入的用户,控制台的实例详情中带有端口号信息,可直接拷贝使用。格式如下: {instanceid}.mqtt.iothub.aliyuncs.com:443

    • 使用旧版实例的用户,控制台的实例详情中没有端口号信息, 请拷贝后自行添加端口号信息。实例接入参考格式如下:

      {YourProductKey}.iot-as-mqtt.{region}.aliyuncs.com:443具体请参见查看实例终端节点

一机一密

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

        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();
        // 默认开启物模型功能,物模型初始化(包含请求云端物模型)完成后才返回onInitDone
        ioTDMConfig.enableThingModel = true;
        // 默认不开启网关功能,开启之后,初始化的时候会初始化网关模块,获取云端网关子设备列表
        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: 如果这个字段为空, 则表示为需要预注册的一型一密(需要实现创建设备)
        
        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连接:

    配置项

    说明

    相关代码

    保活时间

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

    说明
    • 当前SDK默认保活时间为65秒。

    • 您可设置的心跳周期在30 ~ 1200秒。

    // 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());

安卓设备如何快速重连?

引用import com.aliyun.alink.linksdk.channel.core.persistent.PersistentNet; 坐标后,可以通过PersistentNet.getInstance().reconnect();来实现快速重连。

如果想自定义一个重连时间,可以启动一个定时器,定时器到期后调用PersistentNet.getInstance().reconnect();,可以让断开的MQTT连接立刻重连。

阿里云首页 物联网平台 相关技术圈