This topic describes how to call the API operations of Link SDK for C to implement the Thing Specification Language (TSL) model feature for a device. In this example, a sample code file named ./demos/data_model_basic_demo.c is used.
Background information
For more information about the TSL model feature, see Example.
The device tag feature is available only for devices that are connected to IoT Platform over Message Queuing Telemetry Transport (MQTT). For more information about MQTT connection-related code when you configure the feature, see Overview.
Step 1: Initialize the SDK
Add header files.
…… …… #include "aiot_dm_api.h"
Add the underlying dependency and configure the log output feature.
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile); aiot_state_set_logcb(demo_state_logcb);
Call the aiot_dm_init operation to create a client instance named
data-model
and initialize the default parametersdm_handle = aiot_dm_init(); if (dm_handle == NULL) { printf("aiot_dm_init failed"); return -1;}
Step 2: Configure required features
Call the aiot_dm_setopt operation to configure the following items:
Associate with an MQTT connection handle.
ImportantBefore you configure device tag-specific parameters, make sure that device verification information is specified. For more information, see Example.
Sample code:
aiot_dm_setopt(dm_handle, AIOT_DMOPT_MQTT_HANDLE, mqtt_handle);
Parameters:
Parameter
Example
Description
AIOT_DMOPT_MQTT_HANDLE
mqtt_handle
You must establish an MQTT connection. This parameter is used to associate with the MQTT connection handle.
Configure a callback function to process TSL messages.
Define the callback function.
static void demo_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_handler, type = %d\r\n", recv->type); switch (recv->type) { /* Send a response from IoT Platform after the device submits properties, submits events, requests desired properties, or deletes desired properties. */ case AIOT_DMRECV_GENERIC_REPLY: { demo_dm_recv_generic_reply(dm_handle, recv, userdata); } break; /* Configure properties. */ case AIOT_DMRECV_PROPERTY_SET: { demo_dm_recv_property_set(dm_handle, recv, userdata); } break; /* Asynchronously call services */ case AIOT_DMRECV_ASYNC_SERVICE_INVOKE: { demo_dm_recv_async_service_invoke(dm_handle, recv, userdata); } break; /* Synchronously call services */ case AIOT_DMRECV_SYNC_SERVICE_INVOKE: { demo_dm_recv_sync_service_invoke(dm_handle, recv, userdata); } break; /* Send downstream binary data */ case AIOT_DMRECV_RAW_DATA: { demo_dm_recv_raw_data(dm_handle, recv, userdata); } break; /* When you synchronously call services to send binary data, the rrpc_id parameter is added to the binary data. */ case AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE: { demo_dm_recv_raw_sync_service_invoke(dm_handle, recv, userdata); } break; /* Send a response from IoT Platform after the device submits binary data. */ case AIOT_DMRECV_RAW_DATA_REPLY: { demo_dm_recv_raw_data_reply(dm_handle, recv, userdata); } break; default: break; } }
Configure a callback function to process TSL messages.
Sample code:
aiot_dm_setopt(dm_handle, AIOT_DMOPT_RECV_HANDLER, (void *)demo_dm_recv_handler);
Parameters:
Parameter
Example
Description
AIOT_DMOPT_RECV_HANDLER
demo_dm_recv_handler
This function is called when a TSL message is received.
Specify whether IoT Platform returns responses.
You can use the following code to specify whether IoT Platform returns responses after receiving device messages:
Sample code:
uint8_t post_reply = 1; …… …… aiot_dm_setopt(dm_handle, AIOT_DMOPT_POST_REPLY, (void *)&post_reply);
Parameters:
Parameter
Example
Description
AIOT_DMOPT_POST_REPLY
1
Valid values:
1: returns responses
0: does not return responses
If the value of the AIOT_DMOPT_POST_REPLY parameter is 1, you must specify the logic to process response messages.
You can specify the processing logic based on your business requirements. In this example, response messages are printed.
ICA Standard Data Format (Alink JSON)
Response messages are contained in the
recv->data.generic_reply
structure. The messages use the Alink data format. For more information, see Device properties, events, and services.static void demo_dm_recv_generic_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata){ printf("demo_dm_recv_generic_reply msg_id = %d, code = %d, data = %.*s, message = %.*s\r\n", recv->data.generic_reply.msg_id, recv->data.generic_reply.code, recv->data.generic_reply.data_len, recv->data.generic_reply.data, recv->data.generic_reply.message_len, recv->data.generic_reply.message); }
Custom data format
Response messages are contained in the
recv->data.raw_data
structure. Before the messages are sent, you must upload a parsing script to IoT Platform. For more information, see Message parsing.static void demo_dm_recv_raw_data_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_async_service_invoke receive reply for up_raw msg, data len = %d\r\n", recv->data.raw_data.data_len); }
Step 3: Submit device properties
After you connect the device to IoT Platform over MQTT, call the aiot_dm_send operation to submit device properties. The properties to be sent are stored in a specified location by using the aiot_dm_msg_t structure. This structure is an input parameter of the aiot_dm_send operation.
Write code to submit device properties based on the following device data formats:
ICA standard data format (Alink JSON)
Add a TSL feature.
Log on to the IoT Platform console. On the Feature Definition tab of the Product Details page, add the properties to the default TSL module or a custom module. For example, you can create a custom module named
demo_extra_block
. For more information, see Add a TSL feature.The following table describes the TSL properties that are added in this example.
TSL module
Feature
Identifier
Data type
Data definition
Read/write type
Default module
Night light switch 1
LightSwitch
bool
0: on
1: off
Read and write
Power
Power
text
Data length: 10,240 characters
Read and write
Operating current
WF
int32
Valid values: 0 to 10.
Step size: 1
Unit: A
Read and write
demo_extra_block
Night light switch 2
NightLightSwitch
bool
0: on
1: off
Read and write
In this example, you can also add the features by importing the TSL file. For more information, see Batch add TSL features.
Specify the content of a property message.
Sample code:
Default module:
/* Submit a property. */ demo_send_property_post(dm_handle, "{\"LightSwitch\": 0}"); /* Submit multiple properties at a time. */ demo_send_property_batch_post(dm_handle, "{\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518000}],\"WF\": [{\"value\": 3,\"time\":1612684518000}]}}");
Custom module:
demo_send_property_post(dm_handle, "{\"demo_extra_block:NightLightSwitch\": 1}");
Sample messages:
Submitted TSL messages are in the JSON format. The following table describes sample messages and the Alink formats of the messages. For more information, see Device properties, events, and services.
NoteYou need to only focus on message content, which is indicated by the params parameter in the Alink data. The Link SDK encapsulates and processes the messages.
Message type
Example
Alink format
One property
{\"LightSwitch\": 0}
{ "id": "123", "version": "1.0", "sys":{ "ack":0 }, "params": { "LightSwitch": 0 }, "method": "thing.event.property.post" }
Multiple properties
{\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518000}],\"WF\": [{\"value\": 3,\"time\":1612684518000}]}}
{ "id": 123, "version": "1.0", "sys":{ "ack":0 }, "method": "thing.event.property.batch.post", "params": { "properties": { "Power": [{ "value": "on", "time": 1612684518000 }, ], "WF": [{ "value": 3, "time": 1612684518000 }, ] }, }
After multiple properties are submitted, IoT Platform sends a response to confirm that the message is received. To receive the response, you must subscribe to the
/sys/a18wP******/LightSwitch/thing/event/property/batch/post_reply
topic.Sample code:
aiot_mqtt_sub(mqtt_handle, "/sys/a18wP******/LightSwitch/thing/event/property/batch/post_reply", NULL, 1, NULL);
Description:
a18wP******
indicates the ProductKey of the device.LightSwitch
indicates the DeviceName of the device.
Define a function to submit properties.
/* Submit a property. */ int32_t demo_send_property_post(void *dm_handle, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_POST; msg.data.property_post.params = params; return aiot_dm_send(dm_handle, &msg); } /* Submit multiple properties at a time. */ int32_t demo_send_property_batch_post(void *dm_handle, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_BATCH_POST; msg.data.property_post.params = params; return aiot_dm_send(dm_handle, &msg); }
Custom data format
Upload a data parsing script to IoT Platform.
For more information, see What is data parsing?
Write code to submit binary device data.
Sample code:
{ aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_DATA; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); }
When you use TSL models for communication, make sure that the number of messages does not exceed the threshold.
For more information about communication limits, see Limits.
If the number of messages exceeds the threshold, log on to the IoT Platform console to view accumulated messages. For more information, see View and monitor consumer groups.
Step 4: Configure device properties
You can call the SetDeviceProperty or SetDevicesProperty operation to send a property configuration command to the device. The device calls the aiot_mqtt_recv operation to receive the command and calls the demo_dm_recv_handler
callback function to process the command.
When you specify the processing logic of the callback function, take note of the following items:
Usage notes:
The received message is used as an input parameter of the aiot_dm_recv_handler_t callback function. The message is stored in a specified location by using the aiot_dm_recv_t structure.
The command content in the received message varies based on the device data format. The following table describes the command fields.
Device data format
Field in the received message
Description
ICA standard data format (Alink JSON)
recv->data.property_set.params
This field is the same as the params parameter in the Alink data. For more information, see Device properties, events, and services.
Custom data format
recv->data.raw_data
You must parse data on the device. For more information, see What is data parsing?
Recommended processing logic:
Update device properties.
Submit the updated properties.
The processing logic of the sample code:
In this example, the received message is printed. To submit the updated properties, you can delete the comment symbols (
/*
and*/
) below TODO.ICA standard data format (Alink JSON)
static void demo_dm_recv_property_set(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_property_set msg_id = %ld, params = %.*s\r\n", (unsigned long)recv->data.property_set.msg_id, recv->data.property_set.params_len, recv->data.property_set.params); /* TODO: The following sample code shows how to return a response after IoT Platform sends a command to configure properties. To run the sample code, uncomment the sample code. */ /* { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_SET_REPLY; msg.data.property_set_reply.msg_id = recv->data.property_set.msg_id; msg.data.property_set_reply.code = 200; msg.data.property_set_reply.data = "{}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } */ }
Custom data format
static void demo_dm_recv_raw_data(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_raw_data raw data len = %d\r\n", recv->data.raw_data.data_len); /* TODO: The following sample code shows how to send binary data. If you want to send binary data, you must configure a data parsing script in the IoT Platform console. */ /* { aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_DATA; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); } */ }
Step 5: Submit device events
After you connect the device to IoT Platform over MQTT, call the aiot_dm_send operation to submit device properties. The events to be sent are stored in a specified location by using the aiot_dm_msg_t structure. This structure is an input parameter of the aiot_dm_send operation.
Write code to submit device properties based on the following device data formats:
If your device data is in a custom format, you can use the same API operation to submit events and properties. For more information, see Step 3.
If your device data is in the ICA standard data format (Alink JSON), perform the following steps:
Add a TSL model.
Log on to the IoT Platform console. On the Feature Definition tab of the Product Details page, add the events to the product. For more information, see Add a TSL feature.
The following table describes the TSL events that are added in this example.
Feature
Identifier
Event type
Output parameters
Error
Error
Error
Parameter name: Error code
Parameter identifier: ErrorCode
Data type: enum
Enumeration items:
0: An error occurred on the device hardware.
1: An error occurred on the device software.
2: An unknown error occurred.
In this example, you can also add the features by importing the TSL file. For more information, see Batch add TSL features.
Specify the content of the event message.
Sample code:
demo_send_event_post(dm_handle, "Error", "{\"ErrorCode\": 0}");
Sample messages:
Submitted TSL messages are in the JSON format. The following table describes sample messages and the Alink formats of the messages. For more information, see Device properties, events, and services.
NoteYou need to only focus on message content, which is indicated by the params parameter in the Alink data. The Link SDK encapsulates and processes the messages.
Example
Alink format
{\"ErrorCode\": 0}
{ "id": "123", "version": "1.0", "params": { "ErrorCode": 0 }, "method": "thing.event.alarm.post" }
Define a function to submit events.
int32_t demo_send_event_post(void *dm_handle, char *event_id, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_EVENT_POST; msg.data.event_post.event_id = event_id; msg.data.event_post.params = params; return aiot_dm_send(dm_handle, &msg); }
Step 6: Call device services
You can call the InvokeThingService or InvokeThingsService operation to send a service invocation command to the device. The device calls the aiot_mqtt_recv operation to receive the command and calls the demo_dm_recv_handler
callback function to process the command.
When you specify the processing logic of the callback function, take note of the following items:
Usage notes:
The received message is used as an input parameter of the aiot_dm_recv_handler_t callback function. The message is stored in a specified location by using the aiot_dm_recv_t structure.
The command content in the received message varies based on the device data format. The following table describes the command fields.
Device data format
Field in a synchronous message
Field in an asynchronous message
Description
ICA standard data format (Alink JSON)
recv->data.sync_service_invoke
recv->data.async_service_invoke
This field is the same as the params parameter in the Alink data. For more information, see Device properties, events, and services.
Custom data format
recv->data.raw_data
recv->data.raw_service_invoke
You must upload a script to parse the command content. For more information, see What is data parsing?
If the defined callback function does not have logic to respond to service calls, you must configure a function to specify the logic. When you configure the function to send a response, make sure that the values of the following parameters in the response are the same as those in the request.
Synchronous call:
rrpc_id
andmsg_id
.Asynchronous call:
msg_id
.
When you send a response to IoT Platform, take note of the timeout period.
Synchronous call: After the device receives the request, send a response within 8 seconds. Otherwise, IoT Platform determines that the call failed regardless of whether the device received the request.
Asynchronous call: You can specify a timeout period based on your business requirements. No limit is applied.
Recommended processing logic:
Process the command.
Return a response to IoT Platform.
The processing logic of the sample code:
In this example, the received message is printed. To submit the updated properties, you can delete the comment symbols (
/*
and*/
) below TODO.ICA standard data format (Alink JSON)
Synchronous call:
static void demo_dm_recv_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_sync_service_invoke msg_id = %ld, rrpc_id = %s, service_id = %s, params = %.*s\r\n", (unsigned long)recv->data.sync_service_invoke.msg_id, recv->data.sync_service_invoke.rrpc_id, recv->data.sync_service_invoke.service_id, recv->data.sync_service_invoke.params_len, recv->data.sync_service_invoke.params); /* TODO: The following sample code shows how to return a response after IoT Platform synchronously calls device services. */ { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_SYNC_SERVICE_REPLY; msg.data.sync_service_reply.rrpc_id = recv->data.sync_service_invoke.rrpc_id; msg.data.sync_service_reply.msg_id = recv->data.sync_service_invoke.msg_id; msg.data.sync_service_reply.code = 200; msg.data.sync_service_reply.service_id = "SetLightSwitchTimer"; msg.data.sync_service_reply.data = "{}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } }
Asynchronous call:
static void demo_dm_recv_async_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_async_service_invoke msg_id = %ld, service_id = %s, params = %.*s\r\n", (unsigned long)recv->data.async_service_invoke.msg_id, recv->data.async_service_invoke.service_id, recv->data.async_service_invoke.params_len, recv->data.async_service_invoke.params); /* TODO: The following sample code shows how to return a response after IoT Platform asynchronously calls device services. To run the sample code, uncomment the sample code. */ /* { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_ASYNC_SERVICE_REPLY; msg.data.async_service_reply.msg_id = recv->data.async_service_invoke.msg_id; msg.data.async_service_reply.code = 200; msg.data.async_service_reply.service_id = "ToggleLightSwitch"; msg.data.async_service_reply.data = "{\"dataA\": 20}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } */ }
Custom data format
The operation to asynchronously call services based on the binary data format works the same way the operation to configure properties based on the binary data format. For more information, see Parse messages that are sent to custom topics.
The following sample code shows how to synchronously call services based on the binary data format:
static void demo_dm_recv_raw_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_raw_sync_service_invoke raw sync service rrpc_id = %s, data_len = %d\r\n", recv->data.raw_service_invoke.rrpc_id, recv->data.raw_service_invoke.data_len); /* TODO: The following example shows how to return a response after IoT Platform sends the command to synchronously call services. To run the sample code, uncomment the sample code.*/ /* { aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_SERVICE_REPLY; msg.data.raw_service_reply.rrpc_id = recv->data.raw_service_invoke.rrpc_id; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); } */ }
Step 7: Close the connection
MQTT connections are applied to devices that remain persistently connected. You can manually disconnect devices from IoT Platform.
In this example, the main thread is used to set parameters and establish a connection. After the connection is established, you can put the main thread to sleep.
Call the aiot_mqtt_disconnect operation to disconnect the device from IoT Platform.
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
Step 8: Exit the program
Call the aiot_dm_deinit operation to delete the data-model
client instance and release the corresponding resources.
res = aiot_dm_deinit(&dm_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dm_deinit failed: -0x%04X\n", -res);
return -1;
}
What to do next
After you configure the sample code file, compile the file to generate an executable file. In this example, the ./output/data-model-basic-demo executable file is generated.
For more information, see Compilation and running.
For more information about the execution result, see View logs.