设备期望属性的应用

更新时间:

设备期望属性的应用

在物联网解决方案中,下发属性时如果设备不在线会导致下发失败。如果想要更优雅和高效的完成属性的下发呢?平台提供设置期望属性值功能,通过缓存设备属性的期望值,实现从物联网平台云端控制设备属性值。

本文介绍设置期望属性值,实现从物联网平台控制灯泡状态的相关操作。

场景信息

开关设备接入物联网平台后,若需从物联网平台控制灯泡工作状态(1:打开;0:关闭),需要灯泡一直保持连网在线。实际情况下,灯泡可能无法一直在线。

您可在物联网平台设置设备期望属性值,使其存储在物联网平台云端。设备在线后,可读取物联网平台存储的期望属性值,来更新自身属性值。然后,设备会将更新后的属性值上报至物联网平台。

image13.png

整体流程

1.创建产品和设备

2.使用Postman模拟设备

3.设置和获取期望属性

4.设备端开发

创建产品和设备

  1. 进入Tuya物联网平台阿里云计算巢

  2. 服务实例页签的全托管服务下,找到对应的实例,单击实例卡片。

image14.png

  1. 在左侧导航栏,选择产品开发 > 产品,单击创建产品,创建一个产品:灯泡测试。

image15.png

  1. 产品创建成功后,单击继续开发前往功能定义,为产品添加自定义功能并发布。如图所示,本示例添加属性工作状态(LightStatus)

image16.png

  1. 在左侧导航栏,选择设备管理 > 设备列表,单击添加设备,在灯泡产品下添加设备:LampTest。设备添加成功后,获取设备证书信息(ProductKey、DeviceName和DeviceSecret)。

image17.png

您可在设备列表,单击设备LampTest对应的查看进入设备详情页面,查看运行状态连接参数

image18.png

使用Postman模拟设备

1.您可携带MQTT连接参数在Postman创建对应的MQTT连接,具体步骤可以参考使用Postman模拟设备

image19.png

2.完成对/sys/${productKey}/${deviceName}/thing/service/property/set下发属性topic的订阅

image20.png

设置和获取期望属性值

您可通过平台提供的Java签名示例代码生成URL语法后调用物联网平台云端API,设置设备期望属性值。

1.在示例代码OpenAPISignatureDemo类中找到对应的代码块,完成对SetDeviceDesiredProperty方法参数的替换。

        //方法二:已经有参数Map,使用参数进行签名
        Map<String, String> map = new HashMap<String, String>();
        // 公共参数
        map.put("AccessKeyId", accessKey);
        map.put("SignatureMethod", "HMAC-SHA1");
        map.put("Timestamp", formattedDateTime);
        map.put("SignatureNonce", signatureOnce);
        // 接口请求参数
        map.put("Action", "SetDeviceDesiredProperty");
        map.put("Versions", "{}");
        map.put("Items", "{\"LightStatus\":13}");
        map.put("ProductKey", "xZluAsUunlbuhDdG");
        map.put("DeviceName", "LampTest");
        try {
            String signature = SignatureUtils.generate(httpMethod, map, secretKey);
            System.out.println("[方法二:开始生成]======根据参数生成签名,并且生成curl命令");
            System.out.println("curl " + "'" + host+"/?"+buildQueryParam(map) + "&Signature=" + signature + "'");
        } catch (Exception e) {
            System.out.println("生成签名失败"+e.getMessage());
            e.printStackTrace();
        }

执行后会得到如下URL语法请求

curl 'https://si-d6e8d812acb848958054.tuyacloud.com:8686/?Action=SetDeviceDesiredProperty&Versions=%7B%7D&SignatureNonce=c653621a65e444b18786e0f096e92b72&AccessKeyId=xMr9wgwXQLhv5AUa65o03mcD&SignatureMethod=HMAC-SHA1&Items=%7B%22LightStatus%22%3A1%7D&Timestamp=2024-11-19T10%3A24%3A29Z&ProductKey=xZluAsUunlbuhDdG&DeviceName=LampTest&Signature=d3RCeZ%2FSk2orvOn8XHzVn3jh8NM%3D'

设备在线时,设备监听/sys/${productKey}/${deviceName}/thing/service/property/set可以直接收到设置的属性

image21.png

设备离线时,设备无法收到消息,在设备上线后通过对/sys/${productKey}/${deviceName}/thing/property/desired/get推送对应的上行消息,

对/sys/${productKey}/${deviceName}/thing/property/desired/get_reply完成订阅,可以获取最新的期望属性。具体消息格式可参考设备期望属性值

image22.png

设备端开发

设备获取期望属性值,有两种场景:

  • 灯泡重新上线时,主动获取物联网平台云端缓存的期望属性值。

  • 灯泡正处于上线状态,实时接收物联网平台云端推送的期望属性值。

设备端开发更多信息,请参见使用设备端SDK接入。

本文以Java 代码为例,提供了设备端Demo示例,请参见下文附录:设备端Demo代码。

  1. 填入设备证书、地域和MQTT接入地址的信息。

/**
* 设备证书信息
*/
private static String productKey = "******";
private static String deviceName = "********";
private static String deviceSecret = "**************";
/**
* MQTT连接信息
*/
private static String regionId = "******";
private static String iotInstanceId = "si-*************";

......

/**
* 设置 Mqtt 初始化参数
*/ 
config.channelHost = iotInstanceId + ".aliyun.tuyacloud.com:1883";

2.添加以下方法,用于变更实际灯泡的属性,并在属性变更后,主动将信息上报到最新属性值中。

/**
 * 真实设备处理属性变更时,在以下两个场下会被调用:
 * 场景1. 设备联网后主动获取最新的属性期望值(由设备发起,拉模式)
 * 场景2. 设备在线时接收到云端property.set推送的属性期望值(由云端发起,推模式)
 * @param identifier  属性标识符
 * @param value       期望属性值
 * @param needReport  是否通过property.post发送状态上报。
 *                    上面场景2的处理函数中已集成属性上报能力,会将needReport设置为false
 * @return
 */
private boolean handlePropertySet(String identifier, ValueWrapper value, boolean needReport) {
    ALog.d(TAG, "真实设备处理属性变更 = [" + identifier + "], value = [" + value + "]");
    // 用户根据实际情况判性是否设置成功,这里测试直接返回成功
    boolean success = true;
    if (needReport) {
        reportProperty(identifier, value);
    }
    return success;
}

private void reportProperty(String identifier, ValueWrapper value){
    if (StringUtils.isEmptyString(identifier) || value == null) {
        return;
    }

    ALog.d(TAG, "上报属性identity=" + identifier);

    Map<String, ValueWrapper> reportData = new HashMap<>();
    reportData.put(identifier, value);
    LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {

        public void onSuccess(String s, Object o) {
            // 属性上报成功
            ALog.d(TAG, "上报成功 onSuccess() called with: s = [" + s + "], o = [" + o + "]");
        }

        public void onError(String s, AError aError) {
            // 属性上报失败
            ALog.d(TAG, "上报失败onError() called with: s = [" + s + "], aError = [" + JSON.toJSONString(aError) + "]");
        }
    });
}

3.灯泡在线时,如果物联网平台设置了灯泡的期望属性值,该值将被推送到设备端。灯泡处理消息,改变属性状态。

如下代码中,将调用connectNotifyListener处理消息,相关Alink协议说明,请参见设备上报属性

收到异步下行的数据后,mCommonHandler被调用,进而调用handlePropertySet更新设备的物理属性。

/**
 * 注册服务调用(以及属性设置)的响应函数。
 * 云端调用设备的某项服务的时候,设备端需要响应该服务并回复。
 */
public void connectNotifyListener() {
    List<Service> serviceList = LinkKit.getInstance().getDeviceThing().getServices();
    for (int i = 0; serviceList != null && i < serviceList.size(); i++) {
        Service service = serviceList.get(i);
        LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(), mCommonHandler);
    }
}

private ITResRequestHandler mCommonHandler = new ITResRequestHandler() {
    public void onProcess(String serviceIdentifier, Object result, ITResResponseCallback itResResponseCallback) {
        ALog.d(TAG, "onProcess() called with: s = [" + serviceIdentifier + "]," +
                " o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
        ALog.d(TAG, "收到云端异步服务调用 " + serviceIdentifier);
        try {
            if (SERVICE_SET.equals(serviceIdentifier)) {
                Map<String, ValueWrapper> data = (Map<String, ValueWrapper>)((InputParams)result).getData();
                ALog.d(TAG, "收到异步下行数据 " + data);
                // 设置真实设备的属性,然后上报设置完成的属性值
                boolean isSetPropertySuccess =
                        handlePropertySet("LightStatus", data.get("LightStatus"), false);
                if (isSetPropertySuccess) {
                    if (result instanceof InputParams) {
                        // 响应云端,接收数据成功
                        itResResponseCallback.onComplete(serviceIdentifier, null, null);
                    } else {
                        itResResponseCallback.onComplete(serviceIdentifier, null, null);
                    }
                } else {
                    AError error = new AError();
                    error.setCode(100);
                    error.setMsg("setPropertyFailed.");
                    itResResponseCallback.onComplete(serviceIdentifier, new ErrorInfo(error), null);
                }
            } else if (SERVICE_GET.equals(serviceIdentifier)) {
            } else {
                // 根据不同的服务做不同的处理,跟具体的服务有关系
                ALog.d(TAG, "根据真实的服务返回服务的值,请参照set示例");
                OutputParams outputParams = new OutputParams();
                // outputParams.put("op", new ValueWrapper.IntValueWrapper(20));
                itResResponseCallback.onComplete(serviceIdentifier, null, outputParams);
            }
        } catch (Exception e) {
            e.printStackTrace();
            ALog.d(TAG, "云端返回数据格式异常");
        }
    }

    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, "注册服务失败");
    }
};

4.灯泡离线后,如果物联网平台云端设置了灯的期望属性值,该值将被存储在云端。

灯泡上线后,会主动获取期望属性值,然后调用handlePropertySet更新实际设备的属性。

LinkKit.getInstance().init(params, new ILinkKitConnectListener() {
    public void onError(AError aError) {
        ALog.e(TAG, "Init Error error=" + aError);
    }
    public void onInitDone(InitResult initResult) {
        ALog.i(TAG, "onInitDone result=" + initResult);

        connectNotifyListener();

        // 获取云端最新期望属性值
        getDesiredProperty(deviceInfo, Arrays.asList("LightStatus"), new IConnectSendListener() {
            public void onResponse(ARequest aRequest, AResponse aResponse) {
                if(aRequest instanceof MqttPublishRequest && aResponse.data != null) {
                    JSONObject jsonObject = JSONObject.parseObject(aResponse.data.toString());
                    ALog.i(TAG, "onResponse result=" + jsonObject);
                    JSONObject dataObj = jsonObject.getJSONObject("data");
                    if (dataObj != null) {
                        if (dataObj.getJSONObject("LightStatus") == null) {
                            // 未设置期望值
                        } else {
                            Integer value = dataObj.getJSONObject("LightStatus").getInteger("value");
                            handlePropertySet("LightStatus", new ValueWrapper.IntValueWrapper(value), true);
                        }
                    }
                }
            }
            public void onFailure(ARequest aRequest, AError aError) {
                ALog.d(TAG, "onFailure() called with: aRequest = [" + aRequest + "], aError = [" + aError + "]");
            }
        });
    }
});

private void getDesiredProperty(BaseInfo info, List<String> properties, IConnectSendListener listener) {
    ALog.d(TAG, "getDesiredProperty() called with: info = [" + info + "], listener = [" + listener + "]");
    if(info != null && !StringUtils.isEmptyString(info.productKey) && !StringUtils.isEmptyString(info.deviceName)) {
        MqttPublishRequest request = new MqttPublishRequest();
        request.topic = DESIRED_PROPERTY_GET.replace("{productKey}", info.productKey).replace("{deviceName}", info.deviceName);
        request.replyTopic = DESIRED_PROPERTY_GET_REPLY.replace("{productKey}", info.productKey).replace("{deviceName}", info.deviceName);
        request.isRPC = true;
        RequestModel<List<String>> model = new RequestModel<>();
        model.id = String.valueOf(IDGeneraterUtils.getId());
        model.method = METHOD_GET_DESIRED_PROPERTY;
        model.params = properties;
        model.version = "1.0";
        request.payloadObj = model.toString();
        ALog.d(TAG, "getDesiredProperty: payloadObj=" + request.payloadObj);
        ConnectSDK.getInstance().send(request, listener);
    } else {
        ALog.w(TAG, "getDesiredProperty failed, baseInfo Empty.");
        if(listener != null) {
            AError error = new AError();
            error.setMsg("BaseInfoEmpty.");
            listener.onFailure(null, error);
        }
    }
}

附录:设备端Demo代码