IoT Platform allows you to specify a desired value for a device property. This way, you can cache the desired value in IoT Platform and remotely control a device by using the value. This topic describes how to specify a desired value for a device property in the IoT Platform console to control the status of a bulb.
Background information
After a bulb is connected to IoT Platform, make sure that the device remains online to control the bulb status. The bulb in the IoT Platform console can be in the On (1) or Off (0) state. The bulb may fail to remain online in actual scenarios.
You can specify a desired value for the bulb and store the value in IoT Platform. After the device goes online, the device can read the desired property value that is stored in IoT Platform and update the property value. Then, the updated property value is submitted to IoT Platform and displayed on the Status tab in the IoT Platform console.
Create a product and add a device
Specify and query a desired property value in IoT Platform
You can call IoT Platform API operations to specify a desire property value for a device or obtain the latest desired property values.
For more information, see API operations. In this example, IoT Platform SDK for Java is used.
- Call the SetDeviceDesiredProperty operation to specify a desired value for a device property.
DefaultProfile profile = DefaultProfile.getProfile( "<RegionId>", // The region ID. "<accessKey>", // The AccessKey ID of the Alibaba Cloud account. "<accessSecret>"); // The AccessKey secret of the Alibaba Cloud account. IAcsClient client = new DefaultAcsClient(profile); // Create an API request and configure the required parameters. SetDeviceDesiredPropertyRequest request = new SetDeviceDesiredPropertyRequest(); request.setIotInstanceId("iot-060***"); request.setDeviceName("Lamp"); request.setProductKey("g4r***"); //The identifiers and desired values that you want to specify for the properties. request.setItems("{\"LightStatus\": 1}"); request.setVersions("{\"LightStatus\": 0}"); // Send the request and handle the response or exception. try { SetDeviceDesiredPropertyResponse response = client.getAcsResponse(request); System.out.println(new Gson().toJson(response)); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { System.out.println("ErrCode:" + e.getErrCode()); System.out.println("ErrMsg:" + e.getErrMsg()); System.out.println("RequestId:" + e.getRequestId()); }
- Call the QueryDeviceDesiredProperty operation to query the desired property values of the device.
DefaultProfile profile = DefaultProfile.getProfile( "<RegionId>", // The region ID. "<accessKey>", // The AccessKey ID of the Alibaba Cloud account. "<accessSecret>"); // The AccessKey secret of Alibaba Cloud account. IAcsClient client = new DefaultAcsClient(profile); // Create an API request and configure the required parameters. QueryDeviceDesiredPropertyRequest request = new QueryDeviceDesiredPropertyRequest(); request.setIotInstanceId("iot-06****"); request.setProductKey("g4rm****"); request.setDeviceName("Lamp"); // The identifiers of the properties that you want to query. If you do not specify a property identifier, the desired values of all properties except the read-only properties are queried. List<String> identifierList = new ArrayList<String>(); identifierList.add("LightStatus"); request.setIdentifiers(identifierList); // Send the request and handle the response or exception. try { QueryDeviceDesiredPropertyResponse response = client.getAcsResponse(request); System.out.println(new Gson().toJson(response)); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { System.out.println("ErrCode:" + e.getErrCode()); System.out.println("ErrMsg:" + e.getErrMsg()); System.out.println("RequestId:" + e.getRequestId()); }
For more information about how to configure the parameters in the code, see Use IoT Platform SDK for Java.
After a desired property value is specified for the Lamp device, the value is displayed on the Status tab.
Device-side development
The bulb can obtain the desired property values in the following scenarios:
- If the bulb goes from offline to online, the device requests the desired property value that is cached in IoT Platform.
- If the bulb is online, the device receives the desired property value that is pushed by IoT Platform in real time.
For more information, see Download device SDKs.
This topic provides complete device-side sample code. For more information, see Appendix: Sample code to configure a device.
- Specify the device certificate, region, and MQTT endpoint.
/** * The certificate information about the device. */ private static String productKey = "******"; private static String deviceName = "********"; private static String deviceSecret = "**************"; /** * The MQTT connection information. */ private static String regionId = "******"; ...... /** * Configure parameters for MQTT initialization. */ config.channelHost = deviceInfo.productKey + ".iot-as-mqtt." + region + ".aliyuncs.com:1883";
Note- For information about how to obtain the device certificate information, see the "Create a product and add a device" section of this topic.
- regionId specifies the ID of the region where your service resides. You can view the region in the upper-left corner of the IoT Platform console. For information about the format of region IDs, see Supported regions.
- channelHost specifies the endpoint that you want to use to connect your device to IoT Platform over MQTT. For more information about how to obtain region IDs, see View the endpoint of an instance.
- Add the following methods. These methods can be used to change the actual property
values of the bulb and automatically report the changes to IoT Platform.
/** * When the device handles a property value change, the methods are called in the following scenarios: * Scenario 1: After the device is connected to IoT Platform, the device automatically requests to pull the latest desired property value from IoT Platform. * Scenario 2: When the device is online, the device receives the desired property values from the property.set topic to which IoT Platform pushes the values. * @param identifier: the property identifier. * @param value: the desired property value. * @param needReport: specifies whether to report the property value to IoT Platform by using the property.post topic. * In Scenario 2, the property report capability is integrated into the processing function and the needReport parameter is set to false. * @return */ private boolean handlePropertySet(String identifier, ValueWrapper value, boolean needReport) { ALog.d(TAG, "The device handles property changes= [" + identifier + "], value = [" + value + "]"); // Check whether the property settings are configured as expected based on the response. In this example, a success message is returned. 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, "Report the property 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) { // The property value is reported. ALog.d(TAG, "Report success onSuccess() called with: s = [" + s + "], o = [" + o + "]"); } public void onError(String s, AError aError) { // The property value fails to be reported. ALog.d(TAG, "Report failure onError() called with: s = [" + s + "], aError = [" + JSON.toJSONString(aError) + "]"); } }); }
- If the bulb is online and a desired property value is specified for the bulb in the
IoT Platform console, IoT Platform pushes the value to the bulb. The bulb processes
the message and changes the status.
In the following code, the
connectNotifyListener()
method is called to process the message. For information about the Alink protocol, see Devices submit property data to IoT Platform.After the device receives an asynchronous message from IoT Platform, the device calls the
mCommonHandler()
method and then thehandlePropertySet
method to update the property value./** * Register a function to respond to service calls and property settings. * When IoT Platform calls a service from the device, the device must respond to the call and return a response. */ 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, "Received an asynchronous service call from IoT Platform " + serviceIdentifier); try { if (SERVICE_SET.equals(serviceIdentifier)) { Map<String, ValueWrapper> data = (Map<String, ValueWrapper>)((InputParams)result).getData(); ALog.d(TAG, "Received asynchronous downstream data " + data); //Configure the property of the device and then report the property value to IoT Platform. boolean isSetPropertySuccess = handlePropertySet("LightStatus", data.get("LightStatus"), false); if (isSetPropertySuccess) { if (result instanceof InputParams) { // Return a response to IoT Platform. The response indicates that the data is received. 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 { // The device operation varies by service. ALog.d(TAG, "Return a response to IoT Platform."); OutputParams outputParams = new OutputParams(); // outputParams.put("op", new ValueWrapper.IntValueWrapper(20)); itResResponseCallback.onComplete(serviceIdentifier, null, outputParams); } } catch (Exception e) { e.printStackTrace(); ALog.d(TAG, "The format of the returned data is invalid"); } } public void onSuccess(Object o, OutputParams outputParams) { ALog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]"); ALog.d(TAG, "The service was registered"); } public void onFail(Object o, ErrorInfo errorInfo) { ALog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]"); ALog.d(TAG, "Service registration failed."); } };
- If the bulb is offline and a desired property value is specified for the bulb in the
IoT Platform console, the value is stored in IoT Platform.
After the bulb goes online, the bulb requests the desired property value from IoT Platform and calls the
handlePropertySet()
method to update the property value.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(); // Request the latest desired property value from IoT Platform. 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) { // No desired value is specified. } 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); } } }
Verify the migration result
Run the sample code based on the following scenarios to verify the online or offline state of the bulb. You can change the device property value by specifying a desired property value in IoT Platform.
- If the bulb is online, you can change the bulb status in IoT Platform. The bulb responds to the change in real time.
- If the bulb is offline, you can also change the bulb status in IoT Platform. In this case, the desired property value in IoT Platform and the latest device property value are different.
- If the bulb goes from offline to online, the bulb requests the desired property value. The latest property value is immediately synchronized to the desired value.
Appendix: Sample code to configure a device
package com.aliyun.alink.devicesdk.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.alink.apiclient.utils.StringUtils;
import com.aliyun.alink.dm.api.BaseInfo;
import com.aliyun.alink.dm.api.DeviceInfo;
import com.aliyun.alink.dm.api.InitResult;
import com.aliyun.alink.dm.model.RequestModel;
import com.aliyun.alink.dm.utils.IDGeneraterUtils;
import com.aliyun.alink.linkkit.api.ILinkKitConnectListener;
import com.aliyun.alink.linkkit.api.IoTMqttClientConfig;
import com.aliyun.alink.linkkit.api.LinkKit;
import com.aliyun.alink.linkkit.api.LinkKitInitParams;
import com.aliyun.alink.linksdk.cmp.api.ConnectSDK;
import com.aliyun.alink.linksdk.cmp.connect.channel.MqttPublishRequest;
import com.aliyun.alink.linksdk.cmp.core.base.ARequest;
import com.aliyun.alink.linksdk.cmp.core.base.AResponse;
import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSendListener;
import com.aliyun.alink.linksdk.tmp.api.InputParams;
import com.aliyun.alink.linksdk.tmp.api.OutputParams;
import com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper;
import com.aliyun.alink.linksdk.tmp.devicemodel.Service;
import com.aliyun.alink.linksdk.tmp.listener.IPublishResourceListener;
import com.aliyun.alink.linksdk.tmp.listener.ITResRequestHandler;
import com.aliyun.alink.linksdk.tmp.listener.ITResResponseCallback;
import com.aliyun.alink.linksdk.tmp.utils.ErrorInfo;
import com.aliyun.alink.linksdk.tools.AError;
import com.aliyun.alink.linksdk.tools.ALog;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LampDemo {
private static final String TAG = "LampDemo";
private final static String SERVICE_SET = "set";
private final static String SERVICE_GET = "get";
public static String DESIRED_PROPERTY_GET = "/sys/${productKey}/${deviceName}/thing/property/desired/get";
public static String DESIRED_PROPERTY_GET_REPLY = "/sys/${productKey}/${deviceName}/thing/property/desired/get_reply";
public static String METHOD_GET_DESIRED_PROPERTY = "thing.property.desired.get";
public static void main(String[] args) {
/**
* The certificate information about the device.
*/
String productKey = "****";
String deviceName = "Lamp";
String deviceSecret = "****";
/**
* The MQTT connection information.
*/
String regionId = "cn-shanghai";
LampDemo manager = new LampDemo();
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.productKey = productKey;
deviceInfo.deviceName = deviceName;
deviceInfo.deviceSecret = deviceSecret;
manager.init(deviceInfo, regionId);
}
public void init(final DeviceInfo deviceInfo, String region) {
LinkKitInitParams params = new LinkKitInitParams();
/**
* Configure the parameters for MQTT initialization.
*/
IoTMqttClientConfig config = new IoTMqttClientConfig();
config.productKey = deviceInfo.productKey;
config.deviceName = deviceInfo.deviceName;
config.deviceSecret = deviceInfo.deviceSecret;
config.channelHost = deviceInfo.productKey + ".iot-as-mqtt." + region + ".aliyuncs.com:1883";
/**
* Specify whether to receive offline messages.
* The cleanSession field that corresponds to the MQTT connection.
*/
config.receiveOfflineMsg = false;
params.mqttClientConfig = config;
/**
* Configure the initialization and pass in the certificate information about the device.
*/
params.deviceInfo = deviceInfo;
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();
// Request the latest desired property value from IoT Platform.
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) {
// No desired value is specified.
} 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 + "]");
}
});
}
});
}
/**
* When the device handles a property value change, the methods are called in the following scenarios:
* Scenario 1: After the device is reconnected to IoT Platform, the device automatically requests to pull the latest desired property value from IoT Platform.
* Scenario 2: When the device is online, the device receives the desired property values from the property.set topic to which IoT Platform pushes the values.
* @param identifier: the property identifier.
* @param value: the desired property value.
* @param needReport: specifies whether to report the property value to IoT Platform by using the property.post topic.
* In Scenario 2, the property report capability is integrated into the processing function and the needReport parameter is set to false.
* @return
*/
private boolean handlePropertySet(String identifier, ValueWrapper value, boolean needReport) {
ALog.d(TAG, "The device handles property changes= [" + identifier + "], value = [" + value + "]");
// Check whether the property settings are configured as expected based on the response. In this example, a success message is returned.
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, "Report property 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) {
// The property value is reported.
ALog.d(TAG, "Report success onSuccess() called with: s = [" + s + "], o = [" + o + "]");
}
public void onError(String s, AError aError) {
// The property value fails to be reported.
ALog.d(TAG, "Report failure onError() called with: s = [" + s + "], aError = [" + JSON.toJSONString(aError) + "]");
}
});
}
/**
* Register a function to respond to service calls and property settings.
* When IoT Platform calls a service from the device, the device must respond to the call and return a response.
*/
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, "Received an asynchronous service call from IoT Platform " + serviceIdentifier);
try {
if (SERVICE_SET.equals(serviceIdentifier)) {
Map<String, ValueWrapper> data = (Map<String, ValueWrapper>)((InputParams)result).getData();
ALog.d(TAG, "Received asynchronous downstream data " + data);
// Configure the property of the device and then report the property value to IoT Platform.
boolean isSetPropertySuccess =
handlePropertySet("LightStatus", data.get("LightStatus"), false);
if (isSetPropertySuccess) {
if (result instanceof InputParams) {
// Return a response to IoT Platform.
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 {
// The device operation varies by service.
ALog.d(TAG, "The returned values corresponding to the service.");
OutputParams outputParams = new OutputParams();
// outputParams.put("op", new ValueWrapper.IntValueWrapper(20));
itResResponseCallback.onComplete(serviceIdentifier, null, outputParams);
}
} catch (Exception e) {
e.printStackTrace();
ALog.d(TAG, "The format of the returned data is invalid");
}
}
public void onSuccess(Object o, OutputParams outputParams) {
ALog.d(TAG, "onSuccess() called with: o = [" + o + "], outputParams = [" + outputParams + "]");
ALog.d(TAG, "The service was registered");
}
public void onFail(Object o, ErrorInfo errorInfo) {
ALog.d(TAG, "onFail() called with: o = [" + o + "], errorInfo = [" + errorInfo + "]");
ALog.d(TAG, "Service registration failed.");
}
};
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);
}
}
}
}