不具备IP地址的设备可以作为网关的子设备接入物联网平台,实现与物联网平台的通信。本文介绍子设备管理的相关功能。

背景信息

网关子设备管理提供子设备动态注册、获取云端网关下子设备列表、添加子设备、删除子设备、子设备上下线、监听子设备禁用和删除的消息、代理子设备数据上下行的能力。

网关是一个直连设备,网关使用子设备管理的功能前,需已连接到阿里云物联网平台。网关设备开发的详细信息,请参见认证与连接自定义MQTT Topic通信

说明 网关子设备管理相关接口,请参见设备IGateway

使用说明

  • 接入前:
    • 子设备的发现与连接功能由厂商自行实现。
    • 子设备接入前,需先获取子设备认证信息,具体操作,请参见子设备的认证信息获取
  • 接入时:

    网关通过调用Link SDK的添加子设备接口,代理子设备接入物联网平台,然后调用Link SDK的子设备上线接口,将设备状态通知到物联网平台。

  • 接入后:
    • 子设备上线后,网关需要将子设备的状态信息上报至物联网平台,以保证子设备在物联网平台的状态与当前子设备的状态一致。如果使用物模型定义子设备功能,还需将物模型的属性上报至物联网平台。
    • 子设备离线时,网关需要调用子设备离线接口通知物联网平台该子设备已离线。
    • 当在线的子设备属性发生变化时,需要实时通知物联网平台。
    • 当网关离线并再次上线时(例如网络连接断开,或者网关重启),网关需再次调用相关接口,使子设备上线。如果网关无法确认子设备的属性与网关离线前上报至物联网平台的属性是否一致,网关还需将子设备的最新属性上报至物联网平台。
    • 当网关接收到来自物联网平台对子设备的控制指令时,需自行实现将该消息转换成子设备识别的格式并发送给子设备。
    • 如果发送控制指令至离线的子设备,物联网平台将直接返回失败。

开发过程

  1. 定义网关产品并完成开发:具体操作,请参见网关与子设备
  2. 接入子设备:具体操作,请参见子设备上线

子设备的认证信息获取

子设备由网关代理接入物联网平台,需使用子设备的认证信息进行验证。网关获取子设备认证信息的方式如下:

  • 网关从子设备获取设备认证信息。

    由网关与子设备之间定义一套协议,当网关发现并连接子设备后,可以获取子设备的认证信息。该协议由网关厂商与子设备厂商自行定义与实现。

  • 网关预置子设备的认证信息。

    网关设备预先得知要连接的子设备,且网关提供了指定的配置方式输入子设备的认证信息。该功能由网关厂商实现。

  • 网关通过动态注册获取子设备的认证信息。

    网关可以通过指定协议发现并连接子设备,并获取子设备的型号(model)以及唯一标识(比如SN、MAC地址),动态获取子设备的DeviceSecret。

    由于子设备需要在阿里云物联网平台进行产品定义(物联网平台会为子设备生成ProductKey),网关可以建立子设备型号(model)和阿里云物联网平台ProductKey的映射(网关厂家在网关上实现该映射),并将设备的唯一标识作为阿里云物联网平台的DeviceName,然后通过阿里云物联网平台提供的动态注册功能获取子设备的DeviceSecret,从而得到完整的子设备的认证信息。

子设备动态注册

子设备厂商需要在物联网平台开启子设备的动态注册功能,具体操作,请参见开启动态注册。开启动态注册后,为子设备预置唯一标识(例如SN、MAC地址)。

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

LinkKit.getInstance().getGateway().gatewaySubDevicRegister(getSubDevList(), new IConnectSendListener() {
    @Override
    public void onResponse(ARequest aRequest, AResponse aResponse) {
        ALog.d(TAG, "onResponse() called with: aRequest = [" + aRequest + "], aResponse = [" + (aResponse == null ? "null" : aResponse.data) + "]");
        try {
            // 子设备动态注册结果
            ResponseModel<List<DeviceInfo>> response = JSONObject.parseObject(aResponse.data.toString(), new TypeReference<ResponseModel<List<DeviceInfo>>>() {
            }.getType());
            if (response != null && "200".equals(response.code)) {
                /**  获取deviceSecret, 存储到本地,在添加子设备的时候需要使用deviceSecret
                 */
                // deviceSecret = response.data.get("deviceSecret");
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFailure(ARequest aRequest, AError aError) {
        // 子设备动态注册失败
    }
});
            

获取子设备列表

获取网关已在物联网平台注册的子设备。代码示例如下:

LinkKit.getInstance().getGateway().gatewayGetSubDevices(new IConnectSendListener() {
    @Override
    public void onResponse(ARequest aRequest, AResponse 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 aRequest, AError aError) {
        // 获取子设备列表失败
    }
});
            

添加子设备

网关发现并连接了一个新的子设备,并获取到子设备的认证信息后,可以通知物联网平台网关需要添加一个子设备。代码示例如下:

说明 网关重启并连接到物联网平台后,对连接的子设备需要再次调用添加子设备方法。
final DeviceInfo info = new DeviceInfo();
info.productKey = productKey; // 设备证书的ProductKey(必填)
info.deviceName = deviceName; // 设备证书的DeviceName(必填)
info.deviceSecret= deviceSecret; // 设备证书DeviceSecret(必填)
LinkKit.getInstance().getGateway().gatewayAddSubDevice(info, new ISubDeviceConnectListener() {
    @Override
    public String getSignMethod() {
        // 使用的签名方法
        return "hmacsha1";
    }

    @Override
    public String getSignValue() {
        // 获取签名,用户使用deviceSecret获得签名结果
        Map<String, String> signMap = new HashMap<>();
        signMap.put("productKey", info.productKey);
        signMap.put("deviceName", info.deviceName);
        signMap.put("clientId", getClientId());
        return SignUtils.hmacSign(signMap, info.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) {
            // 子设备添加成功,接下来可以做子设备上线的逻辑
            // subDevOnline(null);
        }
    }

    @Override
    public void onDataPush(String s, AMessage message) {
        // 收到子设备下行数据 topic=" + s  + ", data=" + message
        // 例如禁用或删除子设备、属性设置和服务调用等,返回的数据message.data是byte[]
    }
});
            

删除子设备

此处指删除网关和子设备的拓扑关系。代码示例如下:

final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 设备证书 产品型号(必填)
deviceInfo.deviceName = deviceName; // 设备证书 设备标识 (必填)
LinkKit.getInstance().getGateway().gatewayDeleteSubDevice(deviceinfo, new ISubDeviceRemoveListener() {
    @Override
    public void onSuceess() {
        // 成功删除子设备,删除之前可先做下线操作
    }

    @Override
    public void onFailed(AError aError) {
        // 删除子设备失败
    }
});
            

子设备上线

调用子设备上线之前,请确保已完成子设备添加。网关发现子设备连上网关之后,需要通知物联网平台子设备上线,子设备上线后可以执行子设备的订阅、发布等操作。代码示例如下:

说明 由于接口调用都是异步的,子设备上线接口不能在子设备添加的相关代码的下一行调用,需在子设备添加成功的回调里面调用。
final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 设备证书 产品型号(必填)
deviceInfo.deviceName = deviceName; // 设备证书 设备标识 (必填)
LinkKit.getInstance().getGateway().gatewaySubDeviceLogin(deviceinfo, new ISubDeviceActionListener() {
    @Override
    public void onSuccess() {
        // 代理子设备上线成功
        // 上线之后可订阅 删除和禁用的下行通知
        // subDevDisable(null);
    }

    @Override
    public void onFailed(AError aError) {
        ALog.d(TAG, "onFailed() called with: aError = [" + aError + "]");
    }
});
            

子设备下线

当子设备离线之后,网关需要通知物联网平台子设备离线,以避免物联网平台向子设备发送数据。子设备下线之后不可以进行子设备的发布、订阅、取消订阅等操作。代码示例如下:

final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 设备证书ProductKey(必填)
deviceInfo.deviceName = deviceName; // 设备证书DeviceName(必填)
LinkKit.getInstance().getGateway().gatewaySubDeviceLogout(deviceinfo, new ISubDeviceActionListener() {
    @Override
    public void onSuccess() {
        // 代理子设备下线成功
    }

    @Override
    public void onFailed(AError aError) {
        // 代理子设备下线失败
    }
});
            

监听禁用子设备

网关设备可以在云端控制子设备,如禁用子设备、启用子设备、删除网关与子设备的拓扑关系。目前服务端只支持禁用子设备的下行通知。服务端在禁用子设备时,会对子设备做下线处理,后续网关将不能代理子设备和云端做通信。代码示例如下:

final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 证书 产品型号(必填)
deviceInfo.deviceName = deviceName; // 证书 设备标识 (必填)
LinkKit.getInstance().getGateway().gatewaySetSubDeviceDisableListener(deviceinfo, new IConnectRrpcListener() {
    @Override
    public void onSubscribeSuccess(ARequest aRequest) {
        // 订阅成功
    }

    @Override
    public void onSubscribeFailed(ARequest aRequest, AError aError) {
        // 订阅失败
    }

    @Override
    public void onReceived(ARequest aRequest, IConnectRrpcHandle iConnectRrpcHandle) {
        // 子设备禁用通知
        iConnectRrpcHandle.onRrpcResponse(null, null);
    }

    @Override
    public void onResponseSuccess(ARequest aRequest) {
        Log.d(TAG, "onResponseSuccess() called with: aRequest = [" + aRequest + "]");
    }

    @Override
    public void onResponseFailed(ARequest aRequest, AError aError) {
        Log.d(TAG, "onResponseFailed() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
    }
});
            

代理子设备物模型初始化

  • 子设备物模型初始化必须在子设备添加到网关下,且子设备已经登录的情况下才可以调用。代码示例如下:
    说明 由于接口调用都是异步的,子设备物模型初始化接口不能在相关代码的下一行调用,而是要放到子设备登录成功的回调里面调用。
    DeviceInfo deviceInfo = new DeviceInfo();
    deviceInfo.productKey = productKey;
    deviceInfo.deviceName = deviceName;
    //        deviceInfo.deviceSecret = "xxxx";
    Map<String, ValueWrapper> subDevInitState = new HashMap<>();
    //        subDevInitState.put(); //TODO 用户根据实际情况设置
    String tsl = null;// 用户根据实际情况设置,默认为空,直接从云端获取最近的TSL
    LinkKit.getInstance().getGateway().initSubDeviceThing(tsl, deviceInfo, subDevInitState, new IDMCallback<InitResult>() {
        @Override
        public void onSuccess(InitResult initResult) {
            // 物模型初始化成功之后,可以做服务注册,上报等操作
        }
    
        @Override
        public void onFailure(AError aError) {
            // 子设备初始化失败
        }
    });
                
  • 子设备物模型使用接口和直连设备的物模型使用一致,通过获取IThing实例实现。代码示例如下:
    // 获取IThing实例
    IThing thing = LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).first;
    // thing有可能为空,如子设备未登录、物模型未初始化、子设备未添加到网关、子设备处于离线状态等。
    // error信息在 LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).second 返回
    // 参考示例 注意判空
    thing.thingPropertyPost(reportData, new IPublishResourceListener() {
        @Override
        public void onSuccess(String s, Object o) {
            // 设备上报状态成功
        }
    
        @Override
        public void onError(String s, AError aError) {
            // 设备上报状态失败
        }
    });
                
  • 子设备物模型销毁,反初始化物模型。后续如果需要重新使用,需重新进行登录、物模型初始化流程。代码示例如下:
    LinkKit.getInstance().getGateway().uninitSubDeviceThing(mBaseInfo);
                        

网关代理子设备的上下行通信

使用网关的通道执行子设备的数据上下行。代码示例如下:

final DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey; // 设备证书ProductKey(必填)
deviceInfo.deviceName = deviceName; // 设备证书DeviceName(必填)
String topic = xxx;
String publishData = xxx;
// 订阅
LinkKit.getInstance().getGateway().gatewaySubDeviceSubscribe(topic, deviceinfo, new ISubDeviceActionListener() {
    @Override
    public void onSuccess() {
        // 代理子设备订阅成功
    }

    @Override
    public void onFailed(AError aError) {
        // 代理子设备订阅失败
    }
});

//发布
LinkKit.getInstance().getGateway().gatewaySubDevicePublish(topic, publishData, deviceinfo, new ISubDeviceActionListener() {
    @Override
    public void onSuccess() {
        // 代理子设备发布成功
    }

    @Override
    public void onFailed(AError aError) {
        // 代理子设备发布失败
    }
});

// 取消订阅
LinkKit.getInstance().getGateway().gatewaySubDeviceUnsubscribe(topic, deviceinfo, new ISubDeviceActionListener() {
    @Override
    public void onSuccess() {
        // 代理子设备取消订阅成功
    }

    @Override
    public void onFailed(AError aError) {
        // 代理子设备取消订阅事变
    }
});