物模型开发

设备可以使用物模型功能,实现属性上报(上报设备状态)、事件上报(上报设备异常或错误)和服务调用(通过云端调用设备提供的服务)。

前提条件

物联网平台已为目标设备完成物模型默认模块自定义模块的物模型功能定义(属性、事件和服务定义)。具体操作,请参见添加物模型

说明

如果物模型功能定义为空,会导致设备上报属性、上报事件和云端调用服务失败。

背景信息

使用说明

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

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

重要

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

设备上报属性

说明
  • getDeviceThing()返回的IThing接口介绍参见 IThing ApiReference,具体代码实现请参见Demo中的ThingSample.java。

  • 从1.2.3版本开始,thingPropertyPost的回调接口通过alinkId字段透出当前所发送的上行消息的ID。

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

默认模块

// 设备上报
Map<String, ValueWrapper> reportData  = new HashMap<>();
// identifier 是云端定义的属性的唯一标识,valueWrapper是属性的值
// 以上报整型数据为例,我们构造如下valueWrapper
// ValueWrapper valueWrapper = new ValueWrapper.IntValueWrapper(1);
// reportData.put(identifier, valueWrapper);  // 参考示例,更多使用可参考demo 
LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
     public void onSuccess(String alinkId, Object o) {
        // 消息从设备发出成功
        // alinkId表示该消息的messageId
    }
     public void onError(String alinkId, AError aError) {
        // 属性上报失败    
        // alinkId表示该消息的messageId
    }
});
               

自定义模块

以上报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) {
        // 属性上报失败
    }
});

设备上报事件

说明
  • 从1.2.3版本开始,thingEventPost的回调接口通过alinkId字段透出当前所发送的上行消息的ID。

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

默认模块

HashMap<String, ValueWrapper> hashMap = new HashMap<>();
// TODO 用户根据实际情况设置
// hashMap.put("ErrorCode", new ValueWrapper.IntValueWrapper(0));
OutputParams params = new OutputParams(valueWrapperMap);
LinkKit.getInstance().getDeviceThing().thingEventPost(identity, params, new IPublishResourceListener() {
    public void onSuccess(String alinkId, Object o) {
        // 消息从设备发出成功
        // alinkId表示该消息的messageId
    }
    public void onError(String alinkId, AError aError) {
        // 事件上报失败
        // alinkId表示该消息的messageId
    }
});
                    

自定义模块

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

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

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

云端下发属性和服务

默认模块

  • 设备服务获取Service定义参见 Service API Reference

    //获取默认模块(非用户自定义模块)的服务列表
    LinkKit.getInstance().getDeviceThing().getServices()                   
  • 获取默认模块(非用户自定义模块)的事件列表。

    LinkKit.getInstance().getDeviceThing().getEvents()
  • 设备服务调用监听。

    云端在添加设备服务时,需设置该服务的调用方式,由Service中的callType字段表示:

    • 同步服务调用时,callType="sync"

    • 异步服务调用时,callType="async"

    设备属性设置和获取也是通过服务调用监听方式实现云端服务的下发。

    异步服务调用

    先注册服务的处理监听器,当云端触发异步服务调用时,下行的请求会到注册的监听器中。一个设备会有多种服务,通常需要注册所有服务的处理监听器。

    onProcess是设备收到的云端下行的服务调用方法,第一个参数是需要调用服务对应的identifier,用户可以根据identifier做不同的处理。云端调用设置服务时,设备需要在收到设置指令后,调用设备执行真实操作,操作结束后上报一条属性状态变化的通知。

    说明

    identifier是在物联网平台为产品创建属性、事件、服务时定义的标识符,用户可在物联网平台控制台查看产品功能定义中属性、事件、服务对应的identifier

        public void setServiceHandler() {
            ALog.d(TAG, "setServiceHandler() called");
            List<Service> srviceList = LinkKit.getInstance().getDeviceThing().getServices();
            for (int i = 0; srviceList != null && i < srviceList.size(); i++) {
                Service service = srviceList.get(i);
                LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(), mCommonHandler);
            }
            LinkKit.getInstance().registerOnNotifyListener(connectNotifyListener);
        }
    
        private ITResRequestHandler mCommonHandler = new ITResRequestHandler() {
            public void onProcess(String identify, Object result, ITResResponseCallback itResResponseCallback) {
                ALog.d(TAG, "onProcess() called with: s = [" + identify + "], o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
                try {
                    if (SERVICE_SET.equals(identify)) {
    
                        /** 云端下发属性,SDK收到后触发的回调
                         *
                         * TODO: 用户需要将下发的属性值,设置到真实设备里面。
                         * 若设置成功,需要将isSetPropertySuccess写为true,
                         * demo将通过itResResponseCallback这个回调,将设备本地更新后的属性值写到云平台,
                         * 云平台的设备详情的物模型数据一栏属性值将会刷新
                         * 若设置失败,需要将isSetPropertySuccess写为false, demo将不更新云平台中的属性值
                         *
                         *  这里假定用户已经将属性设置到真实设备里面,将isSetPropertySuccess写为true
                         */
                        boolean isSetPropertySuccess = true;
    
                        if (isSetPropertySuccess) {
                            if (result instanceof InputParams) {
                                Map<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((InputParams) result).getData();
                                // 如果控制台下发了属性OverTiltEnable,可以通过data.get("OverTiltEnable") 来获取相应的属性值
                                ALog.d(TAG, "收到下行数据 " + data);
    
                                /**
                                 * 读取属性的值
                                 *
                                 * 假设用户物模型中有OverCurrentEnable这个属性,并且用户在控制台对OverCurrentEnable进行了下发属性的操作
                                 * 我们下面示例代码演示如何从中读取到属性的值
                                 *
                                 *
                                 * TODO:用户需要根据自己的物模型进行适配
                                 */
    
                                 // ValueWrapper.IntValueWrapper intValue = (ValueWrapper.IntValueWrapper) data.get("OverCurrentEnable");
                                 // if (null != intValue) {
                                 // ALog.d(TAG, "收到下行数据 " + intValue.getValue());
                                 // }
                            }
    
                            /**
                             * 向云端上报数据
                             *
                             * errorInfo为空,表示接收数据成功,itResResponseCallback.onComplete回调将
                             * 回复/sys/${productKey}/${deviceName}/thing/service/property/set_reply给云端
                             * 同时,该回调会再通过/sys/${productKey}/${deviceName}/thing/service/property/post将更新后的属性上报到云端
                             * 表示设备端更新该属性成功
                             */
                            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 {
                        /**
                         *  异步服务下行处理
                         */
                        ALog.d(TAG, "用户根据真实的服务返回服务的值,请参照set示例");
                        OutputParams outputParams = new OutputParams();
                        // outputParams.put("op", new ValueWrapper.IntValueWrapper(20));
                        /**
                         * 设备端接收到服务,并返回响应数据给服务端
                         */
                        itResResponseCallback.onComplete(identify, null, outputParams);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    ALog.d(TAG, "TMP 返回数据格式异常");
                }
            }
    
            public void onSuccess(Object o, OutputParams outputParams) {
                ALog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]");
                ALog.d(TAG, "注册服务成功");
            }
    
            public void onFail(Object o, ErrorInfo errorInfo) {
                ALog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]");
                ALog.d(TAG, "注册服务失败");
            }
        };

    同步服务调用(RRPC调用)

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

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

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

    说明

    当前版本添加了支持使用自定义RRPC,云端的同步服务属性下行是通过自定义RRPC通道下行,用户在升级SDK之后要注意这个改动点。

        private IConnectNotifyListener connectNotifyListener = new IConnectNotifyListener() {
            public void onNotify(String connectId, String topic, AMessage aMessage) {
                ALog.d(TAG, "onNotify() called with: connectId = [" + connectId + "], topic = [" + topic + "], aMessage = [" + printAMessage(aMessage) + "]");
                try {
                    if (CONNECT_ID.equals(connectId) && !StringUtils.isEmptyString(topic) &&
                            topic.startsWith("/sys/" + productKey + "/" + deviceName + "/rrpc/request")) {
                        ALog.d(TAG, "收到云端系统RRPC下行" + printAMessage(aMessage));
                        // ALog.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.replace("request", "response");
                        String resId = topic.substring(topic.indexOf("rrpc/request/") + 13);
                        request.msgId = resId;
                        // TODO 用户根据实际情况填写,仅做参考
                        request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";
                        // aResponse.data =
                        LinkKit.getInstance().getMqttClient().publish(request, new IConnectSendListener() {
                            public void onResponse(ARequest aRequest, AResponse aResponse) {
                                ALog.d(TAG, "onResponse() called with: aRequest = [" + aRequest + "], aResponse = [" + aResponse + "]");
                            }
    
                            public void onFailure(ARequest aRequest, AError aError) {
                                ALog.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + getError(aError) + "]");
                            }
                        });
                    }
                    else if (CONNECT_ID.equals(connectId) && !TextUtils.isEmpty(topic) &&
                            topic.startsWith("/ext/rrpc/")) {
                        ALog.d(TAG, "收到云端自定义RRPC下行");
                        // ALog.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[3];
                        request.msgId = resId;
                        // TODO 用户根据实际情况填写,仅做参考
                        request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";
                        // aResponse.data =
                        LinkKit.getInstance().publish(request, new IConnectSendListener() {
                            @Override
                            public void onResponse(ARequest aRequest, AResponse aResponse) {
                                ALog.d(TAG, "onResponse() called with: aRequest = [" + aRequest + "], aResponse = [" + aResponse + "]");
                            }
    
                            @Override
                            public void onFailure(ARequest aRequest, AError aError) {
                                ALog.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
                            }
                        });
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

自定义模块

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

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

其中resRequestHandler是物模型报文处理handler的实例,详细内容,请参见Java SDK Demo中的TSLActivity.java