Develop with TSL models
A Thing Specification Language (TSL) model is a data model that Alibaba Cloud IoT Platform defines for a product. You can use the Android Link SDK to report properties and events from a device and receive instructions from IoT Platform to set properties and call services.
Background information
For more information about the TSL model feature of IoT Platform, see What is a TSL model?.
For more information about the TSL model data format, see Device properties, events, and services.
Usage notes
When you call the TSL model APIs mentioned in this topic, the onSuccess callback only indicates that the message was sent from the device successfully. It does not mean the corresponding task was executed successfully. Do not rely on onSuccess to execute business logic on your device.
In the Android Link software development kit (SDK), the TSL model object obtained by calling
getDeviceThing()is IThing. For more information about IThing, see the IThing API Reference.The TSL model feature is disabled in the Android Link SDK by default. To use this feature, see Step 5 of the sample code for the device-specific certificate authentication method.
To write business logic based on the execution result of a TSL model instruction, you must process mobile terminated messages. For an example, see the
onNotifyfunction of theIConnectNotifyListenerinterface in theInitManager.javaclass of the demo.For a sample code implementation, see the
TSLActivity.javafile in the demo.
Report device properties
Report properties:
// Report from the device Map<String, ValueWrapper> reportData = new HashMap<>(); // The identifier is the property identifier defined in IoT Platform, and valueWrapper is the property value. // ValueWrapper valueWrapper = new ValueWrapper.BooleanValueWrapper(1); // For example, a Boolean variable with a value of 1. // reportData.put(identifier, valueWrapper); // This is an example. For more information, see the demo. LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() { @Override public void onSuccess(String alinkId, Object o) { // The message is sent from the device successfully. // alinkId indicates the messageId of the message. } public void onError(String alinkId, AError aError) { // Failed to report the property. // alinkId indicates the messageId of the message. } });NoteStarting from version 1.7.3.1, the
thingPropertyPostcallback interface returns the ID of the current mobile originated message in thealinkIdfield.To check whether a property message reported by the device has reached the cloud, subscribe to the
/sys/${productKey}/${deviceName}/thing/event/property/post_replytopic. Then, monitor theonNotifyinterface of theIConnectNotifyListenerclass (seeInitManager.javain the demo). This interface returns thealinkIdof thereplymessage. If thealinkIdof the mobile originated message is the same as that of the mobile terminated message, the mobile originated message has been processed by the server-side.
Retrieve properties:
This operation retrieves property data cached on the local device, not real-time property data from the cloud.
// Get the value of the corresponding TSL model property by its identifier (default module, not a user-defined module). String identifier = "******"; LinkKit.getInstance().getDeviceThing().getPropertyValue(identifier); // Get the property list of the default module (not a user-defined module). LinkKit.getInstance().getDeviceThing().getProperties()
Report device events
HashMap<String, ValueWrapper> hashMap = new HashMap<>();
// TODO: Modify this code as needed.
// 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) {
// The message is sent from the device successfully.
// alinkId indicates the messageId of the message.
}
public void onError(String alinkId, AError aError) {
// Failed to report the event.
// alinkId indicates the messageId of the message.
}
});Starting from version 1.2.3, the
thingEventPostcallback interface returns the ID of the current mobile originated message in thealinkIdfield.To check whether an event message reported by the device has reached the cloud, subscribe to the
/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_replytopic. Then, monitor theonNotifyinterface of theIConnectNotifyListenerclass (seeInitManager.javain the demo). This interface returns thealinkIdof thereplymessage. If thealinkIdof the mobile originated message is the same as that of the mobile terminated message, the mobile originated message has been processed by the server-side.
Retrieve the event list of the default module (not a user-defined module):
This operation retrieves local events on the device, not real-time events reported to IoT Platform.
LinkKit.getInstance().getDeviceThing().getEvents()Receive properties and services from the cloud
To view the definition of a service, see the Service API Reference.
Retrieve the service list of the default module (not a user-defined module):
LinkKit.getInstance().getDeviceThing().getServices()Service invocation on devices supports synchronous and asynchronous modes. The listener for this service invocation is also used to set and retrieve device properties. This listener handles the delivery of services from IoT Platform.
Asynchronous invocation
Configuration method
callType="async"Register a listener to process services for the device. When IoT Platform triggers an asynchronous service invocation, the mobile terminated request is sent to the registered listener.
onProcesshandles the service invocation that the device receives from the cloud. The first parameter is the identifier of the service to be called. You can perform different business operations based on the identifier.An identifier is the identifier of a service. For more information, see Identifier.
After the device receives the mobile terminated service invocation from IoT Platform, it performs the corresponding operation based on the instruction. After the operation is complete, it reports the property status change.
Related code
List<Service> serviceList = thing.getServices(); // Set a handler to process property delivery and asynchronous services in the default module. For asynchronous services in user-defined modules, see the "Register modular services" section. 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)) { /* The cloud sends properties to the device. */ // TODO 1: Call the API of the actual device to set its properties. Determine whether the properties are set successfully as needed. // TODO 2: After setting the properties of the actual device, report the values of the set properties. This is test code and is simplified to directly return a success message. boolean isSetPropertySuccess = true; if (isSetPropertySuccess){ if (result instanceof InputParams) { // TODO 3: Parse the property data sent from the server-side. Map<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((InputParams) result).getData(); // data.get() // Respond to the cloud that the data is received successfully. 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)){ // No user processing is required. } else { // The cloud sends a service to the device. // TODO: Process different services based on your requirements. The processing logic is related to the specific service. OutputParams outputParams = new OutputParams(); // Example: 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 + "]"); } };Synchronous call
Configuration method
callType="sync"First, register a listener for mobile terminated data. For information about how to register the listener, see the
notifyListenerin the Connection status and mobile terminated message listener section of Authentication and connection.When the cloud triggers a service invocation, you can receive the downstream invocation in
onNotify.After you receive the mobile terminated service invocation from the cloud, you must process the service on the device. After processing is complete, the device must reply to the cloud by publishing the processing result.
NoteThe latest version of the Android Link SDK supports custom revert-RPC (RRPC). If you upgraded your SDK from an earlier version, note that RRPC is the channel used for synchronous service delivery.
Related code
private static IConnectNotifyListener notifyListener = new IConnectNotifyListener() { @Override public void onNotify(String connectId, String topic, AMessage aMessage) { String data = new String((byte[]) aMessage.data); // Sample data returned by the server-side: 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. Process the topics of synchronous services that start with /ext/rrpc/ //Example topic: /ext/rrpc/1138654706478941696//a1ExY4afKY1/testDevice/user/get //AppLog.d(TAG, "receive Message=" + new String((byte[]) aMessage.data)); //Sample data to be sent to the server-side: {"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; } // The cloud sent a synchronous service. A response is required. Otherwise, the server-side displays a call timeout error. // TODO: Specify the parameters as needed. This is for reference only. request.payloadObj = "{\"id\":\"" + alinkdId + "\", \"code\":\"200\"" + ",\"data\":{\"aa\":1} }"; LinkKit.getInstance().publish(request, new IConnectSendListener() { @Override public void onResponse(ARequest aRequest, AResponse aResponse) { // The response is successful. } @Override public void onFailure(ARequest aRequest, AError aError) { // The response failed. } }); } else if (ConnectSDK.getInstance().getPersistentConnectId().equals(connectId) && !TextUtils.isEmpty(topic) && topic.startsWith("/sys/" + DemoApplication.productKey + "/" + DemoApplication.deviceName + "/rrpc/request/")) { // 2. Process the topics of synchronous services that start with /sys/ // AppLog.d(TAG, "receive Message=" + new String((byte[]) aMessage.data)); // Sample data to be sent to the server-side: {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"} MqttPublishRequest request = new MqttPublishRequest(); // 0 and 1 are supported. The default value is 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: Specify the parameters as needed. This is for reference only. 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. Process other messages. /** * TODO * Process services based on the subscribed topic. */ } //TODO: Implement other callbacks. }
Modular TSL models
Usage notes
Only iot-device-manager 1.7.5.2 and later support modular TSL models.
In the default module of a TSL model, you do not need to add a prefix to the identifier of a property, event, or service. For example, for a property named
lightSwitch, the identifier islightSwitch.In a user-defined module, you must add the module name as a prefix to the identifier of a property, event, or service. For example, for the
lightSwitchproperty in themyBlockmodule, the identifier must bemyBlock:lightSwitch.
Report properties of a modular TSL model
The following example shows how to report the lightSwitch property in the myBlock module:
Map<String, ValueWrapper> reportData = new HashMap<>();
// The identifier is the property identifier defined in IoT Platform, and valueWrapper is the property value.
String identifier = "myBlock:lightSwitch";
reportData.put(identifier, valueWrapper); // This is an example. For more information, see the demo.
LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, new IPublishResourceListener() {
@Override
public void onSuccess(String resID, Object o) {
// The property is reported successfully.
}
@Override
public void onError(String resId, AError aError) {
// Failed to report the property.
}
});Report events of a modular TSL model
The following example shows how to report the OnDetect event in the map module:
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) { // The event is reported successfully.
}
@Override
public void onError(String resId, AError aError) { // Failed to report the event.
}
});Register modular services
You must register the services in a user-defined module with the SDK before you can listen for the corresponding callbacks. For example, to subscribe to the VehDtcService service in the map module, use the following method:
thing.setServiceHandler("map:VehDtcService", resRequestHandler);In this example, resRequestHandler is an instance of a handler that processes TSL model messages. For more information, see the TSLActivity.java file in the demo.
Send instructions to a device
To send instructions from IoT Platform, use the Operations and Maintenance (O&M) feature to control devices and send data. For more information, see Online debugging in O&M.
To send instructions from the server-side, call a northbound control API using OpenAPI. For more information, see Cloud API reference.