认证与连接

更新时间:2024-03-07 05:37:11

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

前提条件

背景信息

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

  • 设备密钥:

    认证方式

    注册方式

    说明

    认证方式

    注册方式

    说明

    一机一密

    不涉及

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

    一型一密

    预注册

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

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

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

    免预注册

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

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

    • 设备通过动态注册获取ClientIDDeviceToken的组合。

    说明

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

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

说明

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

一机一密

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

AppLog.setLevel(ALog.LEVEL_DEBUG);

final LinkKitInitParams params = new LinkKitInitParams();

String productKey = "${YourProductKey}";
String deviceName = "${YourDeviceName}";
String deviceSecret = "${YourDeviceSecret}";
String productSecret = "";

//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();
clientConfig.receiveOfflineMsg = false;//cleanSession=1 不接受离线消息
//mqtt接入点信息
clientConfig.channelHost = "${YourMqttHostUrl}:8883";
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(new IConnectNotifyListener() {
    @Override
    public void onNotify(String s, String s1, AMessage aMessage) {
        //TODO: 处理下行消息的回调,请参考文档
    }

    @Override
    public boolean shouldHandle(String s, String s1) {
        return true; //TODO 根据实际情况设置,请参考文档
    }

    @Override
    public void onConnectStateChange(String connectId, ConnectState connectState) {
        // 对应连接类型的连接状态变化回调,具体连接状态参考SDK ConnectState
        AppLog.d(TAG, "onConnectStateChange() called with: connectId = [" + connectId + "], connectState = [" + connectState + "]");
    
        //首次连云可能失败。对于首次连云失败,SDK会报出ConnectState.CONNECTFAIL这种状态。对于这种场景,用户可以尝试若干次后退出,也可以一直重试直到连云成功
        //TODO: 以下是首次建连时用户主动重试的一个参考实现,用户可以打开下面注释使能下述代码
//      if(connectState == ConnectState.CONNECTFAIL){
//          try{
//              Thread.sleep(5000);
//              PersistentNet.getInstance().reconnect();
//          }catch (Exception e){
//              AppLog.d(TAG, "exception is " + e);
//          };
//          AppLog.d(TAG, "onConnectStateChange() try to reconnect when connect failed");
//      }
    
        //SDK连云成功后,后续如果网络波动导致连接断开时,SDK会抛出ConnectState.DISCONNECTED这种状态。在这种情况下,SDK会自动尝试重连,重试的间隔是1s、2s、4s、8s...128s...128s,到了最大间隔128s后,会一直以128s为间隔重连直到连云成功。
    }
});

//对于一型一密免预注册的设备, 设备连云时要用上deviceToken和clientId
//Step7: 一型一密免预注册设置,默认关闭
// 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(getAppContext(), params, new ILinkKitConnectListener() {
    @Override
    public void onError(AError error) {
        ALog.d(TAG, "onError() called with: error = [" + (error) + "]");
    }

    @Override
    public void onInitDone(Object data) {
        ALog.d(TAG, "onInitDone() called with: data = [" + data + "]");
        //TODO 开始用户自己的业务
    }
});

一型一密

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

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

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

  • 请确保已执行下面示例代码中的step 1、step 2、step 3。

  • 动态注册成功或失败之后,需要断开当前的动态注册长连接,具体参考step 4。

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

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

区别

预注册

免预注册

区别

预注册

免预注册

通信协议

MQTT、HTTPS

MQTT

地域支持

  • 基于MQTT的动态注册,物联网平台支持的所有地域。

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

华东2(上海)、华北2(北京)

返回的设备密钥

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

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

添加设备

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

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

使用次数限制

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

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

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

说明

具体代码实现,请参见Demo中的DemoApplication.java

示例代码如下:

String productKey = "${YourProductKey}";
String deviceName = "${YourDeviceName}";
String deviceSecret = "${YourDeviceSecret}";
String productSecret = "";
MqttInitParams initParams = new MqttInitParams(productKey, productSecret, deviceName, deviceSecret, MqttConfigure.MQTT_SECURE_MODE_TLS);

//动态注册step1: 确定一型一密的类型(免预注册, 还是非免预注册)
//case 1: 如果registerType里面填写了regnwl, 表明设备的一型一密方式为免预注册(即无需创建设备)
//case 2: 如果这个字段为空, 或填写"register", 则表示为需要预注册的一型一密(需要实现创建设备)
initParams.registerType = "";

//动态注册step2: 设置动态注册的注册接入点域名
MqttConfigure.mqttHost = "${YourMqttHostUrl}:8883";;

//动态注册step3: 对于企业实例, 或者2021年07月30日之后(含当日)开通的物联网平台服务下公共实例
//对于2021年07月30日之前(不含当日)开通的物联网平台服务下公共实例,该字段为空字符串""
MqttConfigure.registerInstanceId = "${YourInstanceId}";

final Object lock = new Object();
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) {
        ALog.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 deviceSecret = jsonObject.getString("deviceSecret");

            // 一型一密免预注册返回
            String clientId = jsonObject.getString("clientId");
            String deviceToken = jsonObject.getString("deviceToken");

            //TODO: 请用户保存用户密钥,不要在此做连云的操作,要等step 4执行完成后再做连云的操作(例如在其onSuccess分支中进行连云)

            //让等待的api继续执行
            synchronized (lock){
                lock.notify();
            }

        } catch (Exception e) {
        }

    }

    @Override
    public void onFailed(com.aliyun.alink.linksdk.channel.core.base.ARequest request, com.aliyun.alink.linksdk.channel.core.base.AError error) {
        ALog.e(TAG, "onFailed() called with: request = [" + request + "], error = [" + error + "]");
        //让等待的api继续执行
        synchronized (lock){
            lock.notify();
        }
    }

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

try{
    //等待下行报文,一般1s内就有回复
    synchronized (lock){
        lock.wait(3000);
    }
    
    //step 4: 退出动态注册
    //不要在LinkKit.getInstance().deviceDynamicRegister回调中执行下述函数,否则会报错
    LinkKit.getInstance().stopDeviceDynamicRegister(10 * 1000, null, new IMqttActionListener() {
        @Override
        public void onSuccess(IMqttToken iMqttToken) {
            ALog.d(TAG, "onSuccess() called with: iMqttToken = [" + iMqttToken + "]");
            // TODO: 在此处参考一机一密进行连云和初始化
        }

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

};

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;
//对于企业版实例, 或2021年07月30日之后(含当日)开通的物联网平台服务下公共实例,通过如下方式指定实例ID,其中${实例id}替换为具体ID值,格式为iot-*******
MqttConfigure.extraMqttClientIdItems=",instanceId=" + "${实例id}";   

示例代码中的参数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();
    // 对应 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
            AppLog.d(TAG, "onConnectStateChange() called with: connectId = [" + connectId + "], connectState = [" + connectState + "]");
        
            //首次连云可能失败。对于首次连云失败,SDK会报出ConnectState.CONNECTFAIL这种状态。对于这种场景,用户可以尝试若干次后退出,也可以一直重试直到连云成功
            //TODO: 以下是首次建连时用户主动重试的一个参考实现,用户可以打开下面注释使能下述代码
    //      if(connectState == ConnectState.CONNECTFAIL){
    //          try{
    //              Thread.sleep(5000);
    //              PersistentNet.getInstance().reconnect();
    //          }catch (Exception e){
    //              AppLog.d(TAG, "exception is " + e);
    //          };
    //          AppLog.d(TAG, "onConnectStateChange() try to reconnect when connect failed");
    //      }
        
            //SDK连云成功后,后续如果网络波动导致连接断开时,SDK会抛出ConnectState.DISCONNECTED这种状态。在这种情况下,SDK会自动尝试重连,重试的间隔是1s、2s、4s、8s...128s...128s,到了最大间隔128s后,会一直以128s为间隔重连直到连云成功。
        }
    }
    // 注册下行监听,包括长连接的状态和下行的数据
    LinkKit.getInstance().registerOnPushListener(notifyListener);
    // 取消下行监听,需要确保和注册的是同一个对象
    // LinkKit.getInstance().unRegisterOnPushListener(notifyListener);
                
    说明

    下行数据的回调onNotify,默认是在UI线程透出。自lp-iot-linkkit1.7.3版本起,您可以通过PersistentConnect.mNotifyReceivedMsgOnMainThread = false;选择将下行消息通过非UI线程透出。对于下行消息密集,或者UI线程业务繁忙的场景,建议将该配置项设置为false

  • 日志开关:

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

    PersistentNet.getInstance().openLog(true);
    ALog.setLevel(ALog.LEVEL_DEBUG);
       

    安卓SDK提供了一个全量的拦截器,支持您重写拦截器的log函数,实现自定义的日志处理。例如:将日志通过持久化保存到特定文件中。

    日志输出示例代码:

            ALog.setLogDispatcher(new ILogDispatcher() {
                @Override
                public void log(int level, String prefix, String msg) {
                    switch (level){
                        case LEVEL_DEBUG:
                            System.out.println("debug:"+ prefix + msg);
                            break;
                        case LEVEL_INFO:
                            System.out.println("info:" + prefix + msg);
                            break;
                        case LEVEL_ERROR:
                            System.out.println("error:" + prefix + msg);
                            break;
                        case LEVEL_WARNING:
                            System.out.println("warnings:" + prefix + msg);
                            break;
                        default:
                            System.out.println("other:" + prefix + msg);
                    }
                }
            });
  • 获取SDK的版本号:

    ALog.i(TAG, "sdk version = " + LinkKit.getInstance().getSDKVersion());

安卓设备快速重连

安卓SDK断开后,快速重连的默认时间为65秒。部分场景下,您需要在断开后快速重连。请参考如下代码:

LinkKit.getInstance().reconnect();

设置自定义心跳和解决电池供电及灭屏情况下的心跳不准问题

  • 通过接口MqttPingSender,可实现自定义心跳时间。更多信息,请参见TimerPingSender。通过TimerPingSender,您可以设置下一次发送心跳的时间点,以及停止发送心跳。

    说明

    发送心跳的时间点不要超过保活时间。

    示例代码如下:

    // 其中PrivateMqttPingSender实现了MqttPingSender接口
    MqttConfigure.pingSender = new PrivateMqttPingSender();
  • 部分安卓6以及更高版本的电池供电的安卓设备在灭屏情况下,会因心跳发送不及时,导致设备离线。此时,您可参考接口AlarmMqttPingSender实现自定义心跳时间。

    重要
    • 该方式会定期唤醒安卓系统,会产生额外的功耗开销。

    • 对于安卓API level >= 31的设备:

      • 需要在AndroidManifest.xml文件中添加权限:

        android.permission.SCHEDULE_EXACT_ALARM

      • 在第67行调用API PendingIntent.getBroadcast时,flag字段(最后一个参数)需要根据实际情况考虑设置为FLAG_IMMUTABLE或者FLAG_MUTABLE

    • 在电池供电同时灭屏的情况下,安卓6以及更高版本的安卓系统的网络可能会由于Doze机制自动断开(例如系统日志中会出现Netd : Destroyed 3 sockets for UidRanges),这种情况下,即使定时器准确心跳报文还是无法正常发出,请联系您的安卓系统的供应商解决该问题。

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:8883

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

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

常见问题

Android Link SDK相关问题

  • 本页导读 (1)
  • 前提条件
  • 背景信息
  • 一机一密
  • 一型一密
  • ID²认证
  • 更多设置
  • 安卓设备快速重连
  • 设置自定义心跳和解决电池供电及灭屏情况下的心跳不准问题
  • Demo使用说明
  • 常见问题