本文以C Link SDK中的Demo文件./demos/data_model_basic_demo.c为例,介绍如何调用Link SDK的API,使设备可使用物模型的功能。

背景信息

  • 物模型功能的更多信息,请参见概述

  • 物模型功能基于MQTT接入,开发过程中涉及MQTT接入的代码说明,请参见MQTT接入

步骤一:初始化

  1. 添加头文件。
    ……
    ……
    
    #include "aiot_dm_api.h"
  2. 配置底层依赖和日志输出。
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. 调用aiot_dm_init,创建data-model客户端实例,并初始化默认参数。
        dm_handle = aiot_dm_init();
        if (dm_handle == NULL) {
            printf("aiot_dm_init failed");
            return -1;

步骤二:配置功能

调用aiot_dm_setopt,配置以下功能。

  1. 关联MQTT连接的句柄。
    注意 配置物模型功能参数前,请确保已配置设备认证信息等相关参数。具体操作,请参见MQTT配置连接参数
    • 示例代码:
          aiot_dm_setopt(dm_handle, AIOT_DMOPT_MQTT_HANDLE, mqtt_handle);
    • 相关参数:
      配置项 示例值 说明
      AIOT_DMOPT_MQTT_HANDLE mqtt_handle 物模型功能的请求基于MQTT连接,通过该配置项,关联MQTT连接句柄。
  2. 编写物模型消息的回调函数。
    1. 定义物模型消息的回调函数。
      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) {
      
              /* 对属性上报、事件上报、获取期望属性值或者删除期望属性值指令的应答 */
              case AIOT_DMRECV_GENERIC_REPLY: {
                  demo_dm_recv_generic_reply(dm_handle, recv, userdata);
              }
              break;
      
              /* 属性设置 */
              case AIOT_DMRECV_PROPERTY_SET: {
                  demo_dm_recv_property_set(dm_handle, recv, userdata);
              }
              break;
      
              /* 异步调用服务 */
              case AIOT_DMRECV_ASYNC_SERVICE_INVOKE: {
                  demo_dm_recv_async_service_invoke(dm_handle, recv, userdata);
              }
              break;
      
              /* 同步调用服务 */
              case AIOT_DMRECV_SYNC_SERVICE_INVOKE: {
                  demo_dm_recv_sync_service_invoke(dm_handle, recv, userdata);
              }
              break;
      
              /* 下行二进制数据 */
              case AIOT_DMRECV_RAW_DATA: {
                  demo_dm_recv_raw_data(dm_handle, recv, userdata);
              }
              break;
      
              /* 二进制格式的同步调用服务, 比单纯的二进制数据消息多了个rrpc_id */
              case AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE: {
                  demo_dm_recv_raw_sync_service_invoke(dm_handle, recv, userdata);
              }
              break;
      
              /* 上行二进制数据后, 物联网平台的回复报文 */
              case AIOT_DMRECV_RAW_DATA_REPLY: {
                  demo_dm_recv_raw_data_reply(dm_handle, recv, userdata);
              }
              break;
      
              default:
                  break;
          }
      }
    2. 配置物模型消息的回调函数。
      • 示例代码:
            aiot_dm_setopt(dm_handle, AIOT_DMOPT_RECV_HANDLER, (void *)demo_dm_recv_handler);                   
      • 相关参数:
        配置项 示例值 说明
        AIOT_DMOPT_RECV_HANDLER demo_dm_recv_handler 接收到物模型消息时,调用该函数。
  3. 配置物联网平台是否应答报文。
    您可以通过以下设置,要求物联网平台在接收到设备消息后,是否应答。
    • 示例代码:
          uint8_t post_reply = 1;
      
          ……
          ……
      
         aiot_dm_setopt(dm_handle, AIOT_DMOPT_POST_REPLY, (void *)&post_reply);
    • 相关参数:
      配置项/参数 示例 说明
      AIOT_DMOPT_POST_REPLY 1 取值:
      • 1:应答报文。
      • 0:不应答报文。
    • 如果配置项AIOT_DMOPT_POST_REPLY的值为1,需为应答报文编写处理逻辑。

      您可根据业务需要编写处理逻辑,示例代码仅做打印处理。

      • ICA标准数据格式(Alink JSON)ICA标准数据格式

        应答报文的消息在recv->data.generic_reply中,与Alink数据格式相同,更多信息,请参见设备属性、事件、服务

        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);
        }
      • 透传/自定义格式透传/自定义格式

        应答报文消息在recv->data.raw_data中,消息收发前,您需在物联网平台上传解析脚本,更多信息请参见数据解析流程

        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);
        
        }

步骤三:上报属性

设备建立MQTT连接后,调用aiot_dm_send,向物联网平台发送属性消息。要发送的属性消息,通过aiot_dm_msg_t结构体,存放于指定位置,作为aiot_dm_send的入参。

根据以下不同的设备数据格式类型,为上报属性消息,编写代码。

ICA标准数据格式(Alink JSON)

  1. 添加物模型。
    • 上报属性前,您需登录物联网平台控制台,在产品详情页面的功能定义页签,为默认模块或创建的自定义模块(例如:demo_extra_block)的物模型,添加属性。具体操作,请参见单个添加物模型

      本文示例代码对应添加的物模型属性如下。

      物模型模块名称 功能名称 标识符 数据类型 数据定义 读写类型
      默认模块 夜灯开关1 LightSwitch bool(布尔型)
      • 0:开
      • 1:关
      读写
      电源 Power text(字符串) 数据长度:10240 读写
      工作电流 WF int32(整数型)
      • 取值范围:0~10
      • 步长:1
      • 单位:安/A
      读写
      demo_extra_block 夜灯开关2 NightLightSwitch bool(布尔型)
      • 0:开
      • 1:关
      读写
    • 您也可以直接导入本示例的物模型TSL文件,具体操作,请参见批量添加物模型

  2. 设置属性消息的内容。
    • 示例代码:
      • 默认模块:
        /* 单个上报属性的内容 */
                demo_send_property_post(dm_handle, "{\"LightSwitch\": 0}");
        /* 批量上报属性的内容 */
                demo_send_property_batch_post(dm_handle, "{\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518}],\"WF\": [{\"value\": 3,\"time\":1612684518}]}}");
      • 自定义模块:
                demo_send_property_post(dm_handle, "{\"demo_extra_block:NightLightSwitch\": 1}");
    • 示例消息说明:
      • 物模型上报消息为JSON格式。以下为上报消息的示例及其对应的Alink数据格式,更多信息,请参见设备上报属性
        说明 为了方便使用,您只需关注设备上报的数据(即Alink数据格式中params中的内容),Link SDK会对上报的消息封装处理。
        消息类型 上报消息的示例 对应的Alink格式
        单个属性
        {\"LightSwitch\": 0}
        {
          "id": "123",
          "version": "1.0",
          "sys":{
              "ack":0
          },
          "params": {
                 "LightSwitch": 0
            },
          "method": "thing.event.property.post"
        }
        批量属性
        {\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518}],\"WF\": [{\"value\": 3,\"time\":1612684518}]}}
        {
          "id": 123, 
          "version": "1.0",
          "sys":{
              "ack":0
          }, 
          "method": "thing.event.property.batch.post", 
          "params": {
            "properties": {
                  "Power": [{
                        "value": "on", 
                        "time": 1612684518
                    },
                  ], 
                  "WF": [{
                        "value": 3, 
                        "time": 1612684518
                        },
                  ]
              }, 
        }
      • 批量上报属性消息后,物联网平台基于批量上报的消息,发送应答报文。如果您需确认物联网平台已接收到该上报的消息,需订阅Topic /sys/a18wP******/LightSwitch/thing/event/property/batch/post_reply

        示例代码:

            aiot_mqtt_sub(mqtt_handle, "/sys/a18wP******/LightSwitch/thing/event/property/batch/post_reply", NULL, 1,
                          NULL);
        其中:
        • a18wP******为设备的ProductKey。
        • LightSwitch为设备的DeviceName。
  3. 定义上报属性消息的函数。
    /* 单个属性上报 */
    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);
    }
    
    /* 批量属性上报 */
    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);
    }

透传/自定义格式

  1. 在物联网平台上传物模型数据解析脚本。
    需更多信息,请参见透传/自定义格式的消息
  2. 编写设备上报二进制消息的代码。
    示例代码:
        {
            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);
        }
说明

使用物模型通信的过程中,请确保通信量不超过阈值

  • 通信限制的更多信息,请参见连接通信
  • 如果通信超过阈值,请登录物联网平台查看堆积消息。更多信息,请参见查看和监控消费组

步骤四:设置属性

您可以调用物联网平台的云端API SetDevicePropertySetDevicesProperty,向设备发送属性设置指令。设备端调用aiot_mqtt_recv接收该指令,根据回调函数demo_dm_recv_handler的设置,执行对应处理。

您可以参考以下内容,编写回调函数的处理逻辑:

  • 注意事项:
    • 接收的消息作为类型aiot_dm_recv_handler_t的入参,通过aiot_dm_recv_t结构体存放于您指定的位置。

    • 不同的设备数据格式,其接收的设置属性指令内容,所在的字段如下表所示:
      设备数据格式 接收的消息中的字段 说明
      ICA标准数据格式(Alink JSON) recv->data.property_set.params

      与Alink格式数据中params的值一致,更多信息,请参见属性消息

      透传/自定义格式 recv->data.raw_data

      设备端需解析数据,更多信息,请参见透传/自定义格式的消息

  • 建议的处理逻辑:
    1. 更新设备属性。
    2. 上报更新后的属性。
  • 示例代码的处理逻辑:

    示例代码仅打印接收的消息,如需演示上报设置的属性,请取消示例代码中TODO下代码的注释符号(/**/),查看效果。

    • ICA标准数据格式(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: 以下代码演示如何对物联网平台的属性设置指令进行应答, 您可取消注释查看演示效果 */
          /*
          {
              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");
              }
          }
          */
      }
    • 透传/自定义格式
      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: 以下代码演示如何发送二进制格式数据, 如需使用,需在物联网平台部署对应的数据透传脚本。 */
          /*
          {
              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);
          }
          */
      }

步骤五:上报事件

设备建立MQTT连接后,调用aiot_dm_send,向物联网平台发送事件消息。要发送的事件消息,通过aiot_dm_msg_t结构体,存放于指定位置,作为aiot_dm_send的入参。

根据以下不同的设备数据格式类型,为上报事件,编写代码。

  • 如果您的设备数据格式为透传/自定义格式,设备上报事件与上报属性的接口一致,具体流程,请参见步骤三中的上报透传/自定义格式的消息
  • 如果您的设备数据格式为ICA标准数据格式(Alink JSON),设备端代码编写的流程,请参见下文。
  1. 添加物模型。
    • 上报事件前,您需登录物联网平台控制台,在产品详情页面的功能定义页签,为设备的产品添加事件。具体操作,请参见单个添加物模型

      本文示例代码对应添加的物模型事件如下。

      功能名称 标识符 事件类型 输出参数
      报错 Error 故障
      • 参数名称:错误码
      • 参数标识符:ErrorCode
      • 数据类型:enum(枚举型)
      • 枚举项目:
        • 0:设备硬件故障
        • 1:设备软件故障
        • 2:未知错误
    • 您也可以直接导入本示例的物模型TSL文件,具体操作,请参见批量添加物模型

  2. 设置事件消息内容。
    • 示例代码:
              demo_send_event_post(dm_handle, "Error", "{\"ErrorCode\": 0}");
    • 示例消息说明:

      物模型上报消息为JSON格式。以下为上报消息的示例及其对应的Alink数据格式,更多信息,请参见设备上报事件

      说明 为了方便使用,您只需关注设备上报的数据(即Alink数据格式中params中的内容),Link SDK会对上报的消息封装处理。
      上报事件的示例 对应的Alink格式
      {\"ErrorCode\": 0}
      {
        "id": "123",
        "version": "1.0",
        "params": {
              "ErrorCode": 0
        },
        "method": "thing.event.alarm.post"
      }
  3. 定义上报事件消息的函数。
    
    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);
    }

步骤六:调用服务

您可以调用物联网平台的云端API InvokeThingServiceInvokeThingsService,向设备发送调用服务的指令。设备端调用aiot_mqtt_recv接收该指令,根据回调函数demo_dm_recv_handler的设置,执行对应处理。

您可以参考以下内容,编写回调函数的处理逻辑:

  • 注意事项:

    • 接收的消息作为类型aiot_dm_recv_handler_t的入参,通过aiot_dm_recv_t结构体存放于您指定的位置。

    • 不同的设备数据格式,其接收的调用服务指令内容,所在的字段如下表所示:
      设备数据格式 同步消息的字段 异步消息的字段 说明
      ICA标准数据格式(Alink JSON) recv->data.sync_service_invoke recv->data.async_service_invoke 字段中数据的格式与Alink格式数据中params的值一致,更多信息,请参见服务消息
      透传/自定义格式 recv->data.raw_data recv->data.raw_service_invoke

      需自行上传解析脚本,解析指令内容。更多信息,请参见透传/自定义格式的消息

    • 如果您定义的回调函数不对调用服务指令做应答,自行编写应答处理逻辑函数时,请确保应答消息中的以下信息与物联网平台请求中的该参数值一致。
      • 同步:rrpc_idmsg_id
      • 异步:msg_id
    • 向物联网平台返回服务的应答报文时,需注意超时时间:
      • 同步:接收同步服务消息后,请在8秒内做出应答。否则,即使设备端已收到消息,也视其失败。
      • 异步:您可根据业务需要,自定义异步调用超时的代码逻辑。物联网平台未对此做限制。
  • 建议的处理逻辑:

    1. 执行服务指令。
    2. 向物联网平台返回服务的应答报文。
  • 示例代码的处理逻辑:

    示例代码仅打印接收的消息,如需演示上报设置的属性,请取消示例代码中TODO下代码的注释符号(/**/),查看效果。

    • ICA标准数据格式(Alink JSON)
      • 同步:
        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: 以下代码演示如何对来自物联网平台的同步调用服务进行应答。
                */
        
            
            {
                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");
                }
            }
            
        }
      • 异步:
        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: 以下代码演示如何对来自物联网平台的异步调用服务进行应答, 您可取消注释查看演示效果。
                */
        
            /*
            {
                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");
                }
            }
            */
        }
    • 透传/自定义格式
      • 二进制异步服务调用的处理逻辑,与二进制设置属性的接口一致。具体操作,请参见自定义Topic数据解析
      • 二进制同步服务调用的处理逻辑,示例代码如下:
        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: 以下代码演示如何对来自物联网平台的同步调用服务进行应答, 您可取消注释查看演示效果 */
            /*
            {
                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);
            }
            */
        }

步骤七:断开连接

说明

MQTT接入常应用于长连接的设备,程序通常不会运行至此。

例程的主线程任务为配置参数并成功建立连接。连接建立后,主线程可进入休眠。

调用aiot_mqtt_disconnect,向物联网平台发送断开连接的报文,然后断开网络连接。

    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;
    }

步骤八:退出程序

调用aiot_dm_deinit,销毁data-model客户端实例,释放资源。

    res = aiot_dm_deinit(&dm_handle);
    if (res < STATE_SUCCESS) {
        printf("aiot_dm_deinit failed: -0x%04X\n", -res);
        return -1;
    }

后续步骤

  • 例程文件配置完成后,需进行编译,生成可执行文件./output/data-model-basic-demo

    更多信息,请参见编译与运行

  • 关于运行结果的详细说明,请参见运行日志