物模型开发

物模型是阿里云物联网平台为产品定义的数据模型。您可以通过Android Link SDK,实现设备端上报属性和事件,并接收物联网平台发送的设置属性和调用服务的指令。

背景信息

使用说明

重要

调用下文提及的物模型相关接口后,回调中onSuccess仅代表对应消息从设备发出成功,不代表消息对应的任务执行成功。设备执行业务逻辑请勿依赖onSuccess

  • Android Link SDK中,通过getDeviceThing(),获取的物模型对象即为IThing。IThing的详细说明,请参见 IThing ApiReference

  • Android Link SDK默认未开启物模型功能,如需使用,请参见一机一密设备认证方式的示例代码Step 5。

  • 调用物模型接口后,如果您需根据物模型指令的执行结果编写业务的处理逻辑,请参考Demo的InitManager.java类中的函数 IConnectNotifyListener接口onNotify,处理下行消息。

  • 具体代码实现请参见Demo中的TSLActivity.java

设备上报属性

  • 上报属性:

    // 设备上报
    Map<String, ValueWrapper> reportData = new HashMap<>();
    // identifier为物联网平台定义的属性标识符,valueWrapper为属性的值
    // ValueWrapper valueWrapper = new ValueWrapper.BooleanValueWrapper(1);   //以布尔型变量值1为例
    // reportData.put(identifier, valueWrapper); // 参考示例,更多使用可参考Demo 
    LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
     @Override
    public void onSuccess(String alinkId, Object o) {
            // 消息从设备发出成功
            // alinkId表示该消息的messageId
    }
    public void onError(String alinkId, AError aError) {
            // 属性上报失败    
            // alinkId表示该消息的messageId
    }
    });
     
    说明
    • 从1.7.3.1版本开始,thingPropertyPost的回调接口通过alinkId字段透出当前所发送的上行消息的ID。

    • 如果要观察设备上报的属性消息是否已到达云端,可以在订阅 /sys/${productKey}/${deviceName}/thing/event/property/post_reply消息后,关注IConnectNotifyListener类(参考Demo的InitManager.java)的接口onNotify。该接口会透出reply消息的alinkId,如果上下行消息的alinkId一致,表示上行消息已经被服务端处理。

  • 获取属性:

    获取的是缓存在设备本地的属性数据,不是云端实时的属性数据。

    // 通过identifier获取物模型对应属性的值(默认模块, 非用户自定义模块)
    String identifier = "******";
    LinkKit.getInstance().getDeviceThing().getPropertyValue(identifier);
    // 获取默认模块(非用户自定义模块)的属性列表
    LinkKit.getInstance().getDeviceThing().getProperties()

设备上报事件

HashMap<String, ValueWrapper> hashMap = new HashMap<>();
// TODO 您需根据业务实际情况修改该代码
// hashMap.put("ErrorCode", new ValueWrapper.IntValueWrapper(0));
OutputParams params = new OutputParams(hashMap);
LinkKit.getInstance().getDeviceThing().thingEventPost(identifier, params, new IPublishResourceListener() {
 @Override
public void onSuccess(String alinkId, Object o) {
        // 消息从设备发出成功
        // alinkId表示该消息的messageId
}
public void onError(String alinkId, AError aError) {
        // 事件上报失败
        // alinkId表示该消息的messageId
}
});
说明
  • 从1.2.3版本开始,thingEventPost的回调接口通过alinkId字段透出当前所发送的上行消息的ID。

  • 如果要观察设备上报的事件消息是否已到达云端,可以在订阅 /sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply消息后,关注IConnectNotifyListener类(参考Demo的InitManager.java)的接口onNotify。该接口会透出reply消息的alinkId,如果上下行消息的alinkId一致,表示上行消息已经被服务端处理。

获取默认模块(非用户自定义模块)的事件列表:

获取的是设备本地的事件,不是上报到物联网平台的实时事件。

LinkKit.getInstance().getDeviceThing().getEvents()

云端下发属性和服务

  • 设备调用服务获取Service的定义,请参见 Service API Reference

  • 获取默认模块(非用户自定义模块)的服务列表

    LinkKit.getInstance().getDeviceThing().getServices()
  • 设备的服务调用支持同步和异步两种方式。设备属性的设置和获取也是通过该服务调用的监听方式,实现物联网平台服务的下发。

    异步调用

    设置方法

    callType="async"
    • 设备注册服务的处理监听器,当物联网平台触发异步服务调用时,下行的请求会到注册的监听器中。

    • onProcess为设备收到的云端下行的服务调用。其中,第一个参数为需调用服务对应的identifier。您可根据identifier做不同的业务处理。

      identifier为服务的标识符,更多信息,请参见标识符

    • 设备收到物联网平台的下行服务调用后,根据指令执行对应操作,操作结束后上报一条属性状态变化的通知。

    相关代码

    List<Service> serviceList = thing.getServices();
            //设置handler处理属性下发,以及默认模块中的异步服务. 如果是用户自定义模块的异步服务,请参见"模块化服务的注册"一节 
            for (int i = 0; serviceList != null && i < srviceList.size(); i++) {
                Service service = serviceList.get(i);
                LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(), mCommonHandler);
            }
            
            private ITResRequestHandler mCommonHandler = new ITResRequestHandler()  {
            
            @Override
            public void onProcess(String identify, Object result, ITResResponseCallback itResResponseCallback) {
                AppLog.d(TAG, "onProcess() called with: s = [" + identify + "], o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
    
                try {
                    if (SERVICE_SET.equals(identify)) {
                        /* 云端下发属性到设备 */
                        // TODO 1:用户需要按照真实设备的接口调用,设置设备的属性. 用户根据实际情况判断属性是否设置成功.
                        // TODO 2:设置完真实设备属性之后,上报设置完成的属性值. 这里是测试代码,做了简化,直接返回成功
                        boolean isSetPropertySuccess = true;
                        if (isSetPropertySuccess){
                            if (result instanceof InputParams) {
                                //TODO 3:解析服务端下行的属性数据
                                Map<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((InputParams) result).getData();
                                //   data.get()
                                // 响应云端 接收数据成功
                                itResResponseCallback.onComplete(identify, null, null);
                            } else {
                                itResResponseCallback.onComplete(identify, null, null);
                            }
                        } else {
                            AError error = new AError();
                            error.setCode(100);
                            error.setMsg("setPropertyFailed.");
                            itResResponseCallback.onComplete(identify, new ErrorInfo(error), null);
                        }
    
                    } else if (SERVICE_GET.equals(identify)){
                        //  不需要用户处理
                    } else {
                        // 云端下发服务到设备. 
                        // TODO: 根据不同的服务做不同的处理,跟具体的服务有关系
                        OutputParams outputParams = new OutputParams();
                        // 参考例子: outputParams.put("op", new ValueWrapper.IntValueWrapper(20));
                        itResResponseCallback.onComplete(identify,null, outputParams);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onSuccess(Object o, OutputParams outputParams) {
                AppLog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]");
            }
    
            @Override
            public void onFail(Object o, ErrorInfo errorInfo) {
                AppLog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]");
            }
        };

    同步调用

    设置方法

    callType="sync"
    1. 先注册一个下行数据监听,注册方法请参见认证与连接连接状态与下行消息监听notifyListener

      当云端触发服务调用时,用户可以在onNotify收到云端的下行服务调用。

    2. 用户收到云端的下行服务调用后,根据实际服务调用对设备做服务处理,处理之后需要设备回复云端的请求,即发布一个带有处理结果的请求到云端。

    说明

    新版本的Android Link SDK支持自定义RRPC,如果您的SDK是由低版本升级而来,请注意同步服务属性下行的通道为RRPC。

    相关代码

    private static IConnectNotifyListener notifyListener = new IConnectNotifyListener() {
          
            @Override
            public void onNotify(String connectId, String topic, AMessage aMessage) {
                String data = new String((byte[]) aMessage.data);
                // 服务端返回数据示例  data = {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
                AppLog.d(TAG, "onNotify() called with: connectId = [" + connectId + "], topic = [" + topic + "], aMessage = [" + data + "]");
    
    
                if (ConnectSDK.getInstance().getPersistentConnectId().equals(connectId) && !TextUtils.isEmpty(topic) &&
                        topic.startsWith("/ext/rrpc/")) {
                    // 1.处理/ext/rrpc开头的同步服务的topic
                    
                    //示例 topic=/ext/rrpc/1138654706478941696//a1ExY4afKY1/testDevice/user/get
                    //AppLog.d(TAG, "receice Message=" + new String((byte[]) aMessage.data));
                    //回复服务端数据示例  {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
                    MqttPublishRequest request = new MqttPublishRequest();
                    request.isRPC = false;
                    request.topic = topic;
                    String[] array = topic.split("/");
                    String resId = array[3];
                    request.msgId = resId;
    
                    String alinkdId = null;
                    try{
                        JSONObject jsonObject = JSONObject.parseObject(data);
                        alinkdId = jsonObject.getString("id");
                    }catch (Exception e){
                        AppLog.e(TAG,"parse alinkId failed, exit");
                        return;
                    }
    
                    // 云端下发了同步服务,需要做回复,否则服务端会显示调用超时
                    // TODO 用户根据实际情况填写 仅做参考
                    request.payloadObj = "{\"id\":\"" + alinkdId + "\", \"code\":\"200\"" + ",\"data\":{\"aa\":1} }";
                    LinkKit.getInstance().publish(request, new IConnectSendListener() {
                        @Override
                        public void onResponse(ARequest aRequest, AResponse aResponse) {
                            // 响应成功
                        }
    
                        @Override
                        public void onFailure(ARequest aRequest, AError aError) {
                            // 响应失败
                        }
                    });
                } else if (ConnectSDK.getInstance().getPersistentConnectId().equals(connectId) && !TextUtils.isEmpty(topic) &&
                        topic.startsWith("/sys/" + DemoApplication.productKey + "/" + DemoApplication.deviceName + "/rrpc/request/")) {
                    // 2.处理/sys开头的同步服务的topic
                    
                    // AppLog.d(TAG, "receice Message=" + new String((byte[]) aMessage.data));
                    // 回复服务端数据示例  {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
                    MqttPublishRequest request = new MqttPublishRequest();
                    // 支持 0 和 1, 默认0
                    // request.qos = 0;
                    request.isRPC = false;
                    request.topic = topic.replace("request", "response");
                    String[] array = topic.split("/");
                    String resId = array[6];
                    request.msgId = resId;
                    // TODO 用户根据实际情况填写 仅做参考
                    request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";
    
                    LinkKit.getInstance().publish(request, new IConnectSendListener() {
                        @Override
                        public void onResponse(ARequest aRequest, AResponse aResponse) {
                        }
    
                        @Override
                        public void onFailure(ARequest aRequest, AError aError) {
                        }
                    });
                } else {
                    // 3.处理其他消息
                    
                    /**
                     * TODO
                     * 根据订阅的具体 topic 做业务处理
                     */
                }
              
              //TODO: 实现其他回调
            }

模块化物模型

使用说明

  • 只有iot-device-manager的1.7.5.2及以上版本支持模块化物模型。

  • 在物模型默认的模块中,属性、事件、服务的identifier不用加前缀。例如名为lightSwitch的属性,identifier就是lightSwitch

  • 在用户自定义的模块中,属性、事件、服务的identifier要加模块名为前缀。例如myBlock模块中的lightSwitch属性,identifier要写成myBlock:lightSwitch

模块化物模型属性的上报

以上报myBlock模块中的lightSwitch属性为例:

Map<String, ValueWrapper> reportData  = new HashMap<>();
// identifier为物联网平台定义的属性的标识符,valueWrapper为属性的值
String identifier = "myBlock:lightSwitch";
reportData.put(identifier, valueWrapper);  // 参考示例,更多内容可参考Demo
LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
    @Override
    public void onSuccess(String resID, Object o) {
        // 属性上报成功
    }

    @Override
    public void onError(String resId, AError aError) {
        // 属性上报失败
    }
});

模块化物模型事件的上报

以上报map模块中的OnDetect事件为例:

 HashMap<String, ValueWrapper> hashMap = new HashMap<>();
hashMap.put("StoreID", new ValueWrapper.StringValueWrapper("1"));
OutputParams params = new OutputParams(hashMap);
LinkKit.getInstance().getDeviceThing().thingEventPost("map:OnDetect", params, new IPublishResourceListener() {
    @Override
        public void onSuccess(String resId, Object o) { // 事件上报动作成功
    }

    @Override
        public void onError(String resId, AError aError) { // 事件上报失败
     }
 });

模块化服务的注册

用户自定义模块中的服务要先向SDK注册,才能监听到相应的回调,以map模块中的VehDtcService服务为例,需要通过如下方式订阅:

thing.setServiceHandler("map:VehDtcService", resRequestHandler);

其中resRequestHandler是物模型报文处理handler的实例,请参见demo中的TSLActivity.java

向设备下发指令

  • 物联网平台下发指令:通过监控运维功能对设备进行控制和数据的下发,具体内容,请参见监控运维的在线调试

  • 服务端下发指令:通过OpenAPI,调用北向控制API下发指令。具体内容,请参见云端API参考