本文介绍如何进行 SDK 初始化,建立设备与云端的连接。

设备认证

SDK支持的设备认证方案如下:

设备密钥认证

设备密钥认证方式指设备在连接物联网平台时需要使用平台颁发的ProductKey、DeviceName、DeviceSecret进行设备认证,每个设备的设备密钥不能一样,否则会出现一个设备上线导致另外一个设备下线的情况。

注:有时候设备密钥也被称为三元组。

开发者需要实现初始化失败的设备连接平台重试逻辑,比如设备并未联网,初始化的时候连接物联网平台会失败,那么开发者需要再次对SDK进行初始化。 如果SDK初始化成功,也即设备成功连接物联网平台,之后如果因为网络原因导致连接断开,那么SDK会自动尝试连接物联网平台。

初始化代码如下所示:

/**
 * 设置设备三元组信息
 */
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey;// 产品类型
deviceInfo.deviceName = deviceName;// 设备名称
deviceInfo.deviceSecret = deviceSecret;// 设备密钥
//如果使用deviceToken和clientID连接物联网平台,那么设备的deviceSecret需要设置为null

//一型一密免白名单动态注册之后建联需要设置deviceToken、clientId这两个值,这两个值由deviceDynamicRegister接口返回
MqttConfigure.deviceToken = deviceToken;
MqttConfigure.clientId = clientId;

/**
 * 设置设备当前的初始状态值,属性需要和云端创建的物模型属性一致
 * 如果这里什么属性都不填,物模型就没有当前设备相关属性的初始值。
 * 用户调用物模型上报接口之后,物模型会有相关数据缓存。
 */
Map<String, ValueWrapper> propertyValues = new HashMap<>();
// 示例
// propertyValues.put("LightSwitch", new ValueWrapper.BooleanValueWrapper(0));

IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(productKey, deviceName, deviceSecret);

LinkKitInitParams params = new LinkKitInitParams();
params.deviceInfo = deviceInfo;
params.propertyValues = propertyValues;
params.mqttClientConfig = clientConfig;
/**
 * 设备初始化建联
 * onError 初始化建联失败,需要用户重试初始化。如因网络问题导致初始化失败。
 * onInitDone 初始化成功
 */
LinkKit.getInstance().init(context, params, new ILinkKitConnectListener() {
    @Override
    public void onError(AError error) {
        // 初始化失败 error包含初始化错误信息 
    }

    @Override
    public void onInitDone(Object data) {
        // 初始化成功 data 作为预留参数
    }
});
            
上面的代码会默认连接物联网平台上海站点的公共实例,如果您的实例位于其它站点或者是企业实例,那么需要对连接的域名进行更改:
  • 公共实例

    公共实例的域名格式为:{ProductKey}.iot-as-mqtt.RegionID.aliyuncs.com",其中的RegionID即代表相应的站点,其取值可以从“地域和可用区”中查看,需要注意的是物联网平台这个服务并不是所有的阿里云地域都进行了部署,因此这里填入的RegionID一定要和产品创建的地域相同,比如“华东2(上海)”的RegionID则为cn-shanghai,假设产品的ProductKey是abc,那么因此最终的域名为:abc.iot-as-mqtt.cn-shanghai.aliyuncs.com

  • 企业实例
    用户需要在企业实例中查看实例的接入URL,请参见文档“实例管理”中的“查看实例终端节点”章节了解如何查看实例详情,其中MQTT接入的域名如下图所示:mqtt_url对于使用了企业实例的用户,我们提供demo用于演示如何连接物联网平台,您下载demo之后需要将app/src/main/res/raw/deviceinfo文件中的endpoint修改为自己实例的值。

动态注册

由于每个设备的DeviceName、DeviceSecret都是不同的,有的产品在产线上为每个设备烧写不同的设备密钥存在难度,为了简化厂商产线的设备密钥烧写难度,阿里云物联网平台提供了动态注册的方案。

动态注册指设备在出厂前烧写了ProductKey、ProductSecret,并使用设备已有的某个唯一标识用作DeviceName,通常为设备的MAC地址或者SN。由于ProductKey、ProductSecret对同一个产品的所有设备来说都是相同的,而设备即使不连接阿里云也需要烧写MAC地址或者SN,所以厂商可以将ProductKey、ProductSecret固化在设备的固件中,这就意味着设备厂商在产线上无需烧写设备密钥。

动态注册将会从云端获取设备的DeviceSecret,然后设备调用SDK初始化使用上面的设备密钥认证方式进行设备认证。设备使用设备密钥连接物联网平台成功之后,出于安全考虑平台不允许设备再次通过动态注册获取DeviceSecret,所以设备需要将收到的DeviceSecret持久化存储,确保设备进行OTA升级、配置清除之后仍然存在

下面是动态注册的代码示例:

//  #######  一型一密动态注册接口开始 ######
/**
 * 注意:动态注册成功,设备上线之后,不能再次执行动态注册,云端会返回已注册错误信息。
 *   因此用户在编程时首先需要判断设备是否已获取过deviceSecret,没有获取过的情况下再
 *   调用动态注册接口去获取deviceSecret
 */
DeviceInfo myDeviceInfo = new DeviceInfo();
myDeviceInfo.productKey = productKey;
myDeviceInfo.deviceName = deviceName;
myDeviceInfo.productSecret = productSecret;
LinkKitInitParams params = new LinkKitInitParams();
params.deviceInfo = myDeviceInfo;
// 设置动态注册请求 path 和 域名,域名使用默认即可
HubApiRequest hubApiRequest = new HubApiRequest();
hubApiRequest.path = "/auth/register/device";
LinkKit.getInstance().deviceRegister(context, params, hubApiRequest, new IConnectSendListener() {
    @Override
    public void onResponse(ARequest aRequest, AResponse aResponse) {
        // aRequest 用户的请求数据
        if (aResponse != null && aResponse.data != null) {
            ResponseModel<Map<String, String>> response = JSONObject.parseObject(aResponse.data.toString(),
                    new TypeReference<ResponseModel<Map<String, String>>>() {
                    }.getType());
            if ("200".equals(response.code) && response.data != null && response.data.containsKey("deviceSecret") &&
                    !TextUtils.isEmpty(response.data.get("deviceSecret"))) {
                deviceInfo.deviceSecret = response.data.get("deviceSecret");
                // getDeviceSecret success, to build connection.
                // 持久化 deviceSecret 初始化建联的时候需要
                // TODO  用户需要按照实际场景持久化设备的三元组信息,用于后续的连接
                // 成功获取 deviceSecret,调用初始化接口建联
                // TODO 调用设备初始化建联
            }
        }
    }

    @Override
    public void onFailure(ARequest aRequest, AError aError) {
        Log.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
    }
});
//  ####### 一型一密动态注册接口结束  ######
            
说明 目前安卓SDK仅支持在上海站点支持上面所说的动态注册功能。

免白名单动态注册

区别于动态注册,这里的免白动态注册是不需要预先在后台录入deviceName的,可以直接使用该接口创建指定deviceName的设备。该动态注册接口返回的信息不包含deviceSecret,而是包含deviceToken和clientId,这个在建联的时候需要用到。需要注意的是,需要在云端控制台开启动态注册功能。

动态注册成功或失败之后,需要断开当前的动态注册长链接。如果动态注册成功,可以在断链之后执行建联的流程,具体可参考demo实现。下面是代码示例:
MqttInitParams initParams = new MqttInitParams(productKey, productSecret, deviceName, deviceSecret, MqttConfigure.MQTT_SECURE_MODE_TLS);
initParams.registerType = "regnwl"; // 一型一密免白
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 ci = jsonObject.getString("clientId");
            String dt = jsonObject.getString("deviceToken");

            if ((!TextUtils.isEmpty(clientId) && !TextUtils.isEmpty(deviceToken)) || (!TextUtils.isEmpty(deviceSecret))) {
                // TODO by user 这里仅为测试代码,请将认证信息持久化到外部存储,确保app清除缓存或者卸载重装后仍能取到
                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;
    }
});         

// 断开当前动态注册连接,并重新建联
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 + "]");
                        }

                        @Override
                        public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
                            AppLog.w(TAG, "onFailure() called with: iMqttToken = [" + iMqttToken + "], throwable = [" + throwable + "]");
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
说明 免白名单的动态注册在所有部署了物联网平台服务的阿里云站点均进行了支持。

ID2设备认证

根据客户使用的物联网平台是公共实例或者企业实例,设备初始化时需要对域名进行修改。下面是连接上海站点的公共实例时SDK初始化的时候需要添加的设置设置:

/**
 * 云端创建使用 iTLS 认证方式的设备采用这种方式初始化
 * 产品需要到 ID2 授权
 */
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;
            
对于企业实例,域名前无需添加productKey,直接使用ID2的认证域名即可,ID2认证域名可以在企业实例中获取,如下图所示:id2-url然后直接将认证域名设置到clientConfig.channelHost即可。

SDK 反初始化

如果需要注销初始化,调用如下接口。反初始化为同步接口,需要确保init的时候,上一次的deinit已经执行完成。如果正在deinit的时候执行init,会返回init失败。重复init也是会返回init失败的。

// 取消注册 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);
            

更多功能

目前支持以下功能的使能,默认都是关闭的,开发者可以根据使用场景做设置

LinkKitInitParams params = new LinkKitInitParams();
// ... 其它设置参数保持和认证参数一致,以下是新增的配置参数

/**
  * 设备是否支持被生活物联网平台APP发现
  * 需要确保开发的APP具备发现该类型设备的权限
*/
IoTDMConfig ioTDMConfig = new IoTDMConfig();
// 是否启用本地通信功能,默认不开启,
// 启用之后会初始化本地通信CoAP相关模块,设备将允许被生活物联网平台的应用发现、绑定、控制,依赖enableThingModel开启
ioTDMConfig.enableLocalCommunication = false;
// 是否启用物模型功能,如果不开启,本地通信功能也不支持
// 默认不开启,开启之后init方法会等到物模型初始化(包含请求云端物模型)完成之后才返回onInitDone
ioTDMConfig.enableThingModel = false;
// 是否启用网关功能
// 默认不开启,开启之后,初始化的时候会初始化网关模块,获取云端网关子设备列表
ioTDMConfig.enableGateway = false;
// LinkKitInitParams 设置ioTDMConfig
params.ioTDMConfig = ioTDMConfig;
            

针对有休眠场景仍需要保持设备心跳建议参照 AlarmPingSender

MqttConfigure.pingSender = new UserDefinePingSender();

其他设置

日志开关

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

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

Mqtt 连接参数

  • 设置Mqtt Keep-Alive 时间
// interval 单位秒
MqttConfigure.setKeepAliveInterval(int interval);
            
  • qos设置
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;
            
  • 设置clientId

clientId定义可以参考官方 MQTT-TCP连接通信文档。

MqttConfigure.clientId = "abcdef003d27";
            
  • 设置mqtt SDK是否自动重连

automaticReconnect设置为true,mqtt连接成功之后,如果因网络抖动导致mqtt断链,SDK会自动重新建联。如果设置为false,则mqtt断开之后,SDK不会自动重新建联,需要用户根据断链状态反馈,自己触发重连逻辑

MqttConfigure.automaticReconnect = true;
            

重连实现代码参见:

if(MqttNet.getInstance().getClient() instanceof MqttAsyncClient) {
    ((MqttAsyncClient) MqttNet.getInstance().getClient()).reconnect();
}
            

获取SDK的版本号

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