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

背景信息

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

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

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

网关开发过程

  1. 厂商在物联网平台定义网关产品(基础版或者高级版),以及网关自身的开发。
    1. 创建产品时,节点类型需选择为网关设备,设置网关的身份认证模式。具体操作,请参见创建产品
    2. 根据网关功能定义topic或者定义物模型。具体操作,请参见什么是物模型
    3. 参照Java Link SDK其他章节中的说明对网关自身的功能进行开发。
  2. 接入子设备。
    1. 子设备的发现与连接功能由厂商自行实现。
    2. 关于子设备的认证信息,阿里云介绍了几种获取方式供厂商参考,请参见子设备的认证信息获取
    3. 当网关发现并连接一个子设备后,若想通过物联网平台对该子设备进行远程管理,需要先获得该子设备的认证信息,再调用Link SDK的添加子设备接口将其绑定到物联网平台,然后调用Link SDK的子设备上线接口将设备状态通知到物联网平台。
    4. 当网关通知物联网平台一个子设备上线之后,需要将子设备的状态信息上报云端,以保证子设备在云端的状态与当前子设备的状态一致。在使用物模型定义子设备功能时,子设备上线还需要将属性的最新数值通知云端。
    5. 当网关下已添加到物联网平台的子设备离线时,网关需要调用子设备离线接口通知物联网平台这个子设备已离线。
  3. 管理子设备。
    • 当一个在线子设备的属性发生变化时,需要实时通知物联网平台。
    • 当网关离线并再次上线时(比如网络连接断开,或者网关重启),网关需要对所有已添加到云端的子设备再次调用添加子设备接口和子设备上线接口。如果网关不知道子设备的属性与网关离线前上报到云端的是否一致,那么网关需要将子设备的最新属性再次上报云端。
    • 当网关接收到来自物联网平台对子设备的控制指令时,由网关厂商实现将该消息转换成子设备识别的格式并发送给子设备。
    说明 如果对离线的子设备进行远程控制,物联网平台将直接返回失败,而不是将命令发送给网关之后等待错误提示或者超时提示。

子设备开发过程

  • 子设备厂商在物联网平台定义子设备产品(基础版或者高级版):

    设置节点类型为设备,设置网关的身份认证模式,详细操作,请参见单个创建设备

  • 阿里云并不在子设备上提供任何SDK,因此网关如何发现子设备、连接子设备、发现子设备上线或者离线、将来自物联网平台的命令发送给子设备,均由网关厂商与子设备厂商定义协议并实现。

子设备的认证信息获取

子设备是通过网关到阿里云物联网进行注册的,注册时也需要使用到子设备的认证信息进行验证。下面是网关获取子设备的认证信息的几种方式,网关厂家根据自己的实际情况进行选用:

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

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

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

    如果网关设备预先可以得知自己需要连接的子设备,并且网关提供了某种配置方式输入子设备的认证信息,那么可以通过这种方式获取子设备的认证信息。该功能由网关厂商实现。

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

    网关可以通过某种协议发现并连接子设备,并获取到子设备的型号(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; // 设备证书 产品型号(必填)
info.deviceName = deviceName; // 设备证书 设备标识(必填)
info.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; // 设备证书 产品型号(必填)
deviceInfo.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; // 设备证书 产品型号(必填)
deviceInfo.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) {
        // 代理子设备取消订阅事变
    }
});