本文为您介绍了如何在设备端完成物模型的开发,从而可以使设备端上报属性和事件,云端可以向设备端发送设置属性和调用服务的指令。

背景说明

阿里云物联网平台定义了一套设备功能建模的方法, 使用属性/事件/服务三个维度来管理设备:

概念 解释
属性 表示设备支持的某个可读、或者可读可写的参数,例如灯的亮度。
服务 表示一个设备支持的功能,例如让一个设备重启。
事件 是设备主动上报的某种情况发生的信息,例如有人在撬锁时智能锁可以上报一个暴力撬锁的事件到云端进行报警。
说明
物模型开发

如上图流程说明, 设备从MQTT长连接通道可与云平台进行属性/服务的双向通信, 以及单向的事件上报, 从而让阿里云平台理解和控制设备的运行。

限制说明

设备调用aiot_dm_send向云端上报消息太过频繁时, 会被云端限流, 云端限流发生时, 设备端不会收到任何来自网络的通知, 只能人工在控制台页面查看。

说明 云端对设备上报的限流阈值, 请实时参见云平台产品限制页面。

数据结构

SDK中的data-model功能将属性/服务/事件对应的消息内容都以结构体描述。

  • 所有上行消息的传递, 都以用户调用aiot_dm_send()接口, 从设备发往云平台。
  • 所有上行消息的内容, 都以 aiot_dm_msg_t 结构体存放, 它是aiot_dm_send()接口的入参。
  • 所有下行消息的传递, 都以用户调用aiot_mqtt_recv()接口, 进而触发用户注册的 aiot_dm_recv_handler_t 回调, 到达用户侧。
  • 所有下行消息的内容, 都以 aiot_dm_recv_t 结构体存放, 它是上述回调函数的入参。

SDK中的物模型模块不维护网络会话, 网络连接和数据收发由它所依赖MQTT模块提供。

因此, 用户在创建物模型实例后, 应正确地为它配置MQTT实例句柄, 在进行物模型数据收发前, 也应确保MQTT已经建连成功。

API列表

以下是API列表及简要说明 (详见components/data-model/aiot_dm_api.h)。

接口名 说明
aiot_dm_init 初始化data-model会话实例。
aiot_dm_setopt 设置data-model会话参数, 点击物模型详细设置参数了解所有参数。
aiot_dm_send 发送一条data-model消息到物联网平台, 消息类型和消息内容由msg参数决定。
aiot_dm_deinit 销毁data-model实例, 释放资源。

API使用概述

请按照以下流程使用API。

  1. 在使用物模型模块前, 用户应首先创建好一个MQTT实例.
  2. 调用aiot_dm_init创建一个物模型实例, 保存实例句柄。
  3. 调用aiot_dm_setopt配置AIOT_DMOPT_MQTT_HANDLE选项以设置MQTT句柄, 此选项为强制配置选项。
  4. 调用aiot_dm_setopt配置AIOT_DMOPT_PRODUCT_KEYAIOT_DMOPT_DEVICE_NAME选项以设置默认productKey和deviceName。
  5. 调用aiot_dm_setopt配置AIOT_DMOPT_RECV_HANDLERAIOT_DMOPT_USER_DATA选项以注册数据接受回调函数和用户上下文数据指针。
  6. 在使用aiot_dm_send发送消息前, 应先完成MQTT实例的建连。
  7. 调用aiot_dm_send发送不同类型的消息到云平台, 在注册的回调函数中处理各种类型的云平台下行消息。

data-model模块通过调用aiot_dm_send发送消息到云平台, 云平台下发给设备的消息通过调用aiot_dm_setopt注册的回调函数接受。

在讲解如何使用API进行数据收发前, 我们先介绍Alink JSON数据格式, 它分为请求和应答两种格式, 定义如下:

Alink请求:

{
    "id": "123", /* msgid, 一般为一串数字的字符串 */
    "version": "1.0",
    "params": /* 用户数据, JSON对象 */,
    "method": "请求方法字符串"
}

Alink应答:

{
    "id": "123", /* msgid, 一般为一串数字的字符串 */
    "code": 200,
    "data": /* 用户数据, JSON对象 */
}

其中paramsdata的value均为JSON对象, 这两个JSON对象都是需要用户进行组装或解析的, 用户可以使用cJSON等第三方库完成JSON对象的组装和解析。

以下列举了上报不同数据类型的属性时params的定义。

/* 整型数据 */
char *params = "{\"Brightness\":50}";

/* 浮点型数据上报 */
char *params = "{\"Temperature\":11.11}";

/* 枚举型数据上报 */
char *params = "{\"WorkMode\":2}";

/* 布尔型数据上报, 在物模型定义中, 布尔型为整型, 取值为0或1, 与JSON格式的整型不同 */
char *params = "{\"LightSwitch\":1}";

/* 字符串数据上报 */
char *params = "{\"Description\":\"Amazing Example\"}";

/* 时间型数据上报, 在物模型定义中, 时间型为整数字符串 */
char *params = "{\"Timestamp\":\"1252512000\"}";

/* 复合类型属性上报, 在物模型定义中, 符合类型属性为JSON对象 */
char *params = "{\"RGBColor:{\"Red\":11,\"Green\":22,\"Blue\":33}\"}";

/* 多属性上报, 如果需要上报以上各种数据类型的所有属性, 将它们放在一个JSON对象中即可 */
char *params = "{\"Brightness\":50,\"Temperature\":11.11,"
               "\"WorkMode\":2,\"LightSwitch\":1,\"Description\":\"Amazing Example\","
               "\"Timestamp\":\"1252512000\","
               "\"RGBColor:{\"Red\":11,\"Green\":22,\"Blue\":33}\"}";
说明 如何在属性上报和时间上报的params参数中带上时间戳,请参见线上文档设备属性/事件/服务

目前data-model模块支持的消息类型主要有以下几种:

  • 属性上报请求和云平台的应答

    以下代码演示了如何用aiot_dm_send发送属性上报请求, 其中params参数为包含属性键值对的JSON对象字符串, 如例程中的"{\"LightSwitch\": 0}"

    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;
    
        /* 发送成功将返回消息的msg_id */
        return aiot_dm_send(dm_handle, &msg);
    }
    
    /* 演示上报灯开关属性 */
    demo_send_property_post(dm_handle, "{\"LightSwitch\": 0}");

    在成功上报属性后, 设备将收到云端下发的应答消息, 消息类型为AIOT_DMRECV_GENERIC_REPLY, 应答消息的msg_id与对应请求的msg_id一致。

    void aiot_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *user_data)
    {
        switch (recv->type) {
            /* 属性上报或事件上报应答 */
            case AIOT_DMRECV_GENERIC_REPLY: {
                printf("msg_id = %d, code = %d\r\n",
                        recv->data.generic_reply.msg_id,
                        recv->data.generic_reply.code);
            } break;
            ...
        }
    }
  • 事件上报请求和云平台的应答

    以下代码演示了如何使用aiot_dm_send发送事件上报请求, 其中params参数为包含属性键值对的JSON对象字符串, 如例程中的"{\"ErrorCode\": 0}"

    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);
    }
    
    /* 演示上报event_id为"Error"的错误事件上报 */
    demo_send_event_post(dm_handle, "Error", "{\"ErrorCode\": 0}");

    在成功上报事件后, 设备将收到云端下发的应答消息, 消息类型为AIOT_DMRECV_GENERIC_REPLY, 应答消息的msg_id与对应请求的msg_id一致。

    void aiot_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *user_data)
    {
        switch (recv->type) {
            /* 属性上报或事件上报应答 */
            case AIOT_DMRECV_GENERIC_REPLY: {
                printf("msg_id = %d, code = %d\r\n",
                        recv->data.generic_reply.msg_id,
                        recv->data.generic_reply.code);
            } break;
            ... ...
        }
    }
  • 属性设置请求和设备发送应答

    来自云平台的属性设置请求将通过AIOT_DMRECV_PROPERTY_SET类型消息通知给用户, 其中params参数为包含属性键值对的JSON对象字符串。

    用户可使用第三方JSON库对其进行解析。

    用户可调用aiot_dm_send发送AIOT_DMMSG_PROPERTY_SET_REPLY类型消息进行应答。

    /* 用户数据接收处理回调函数 */
    void aiot_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *user_data)
    {
        printf("aiot_dm_recv_handler, type = %d\r\n", recv->type);
    
        switch (recv->type) {
            /* 属性设置 */
            case AIOT_DMRECV_PROPERTY_SET: {
                printf("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");
                    }
                }
                */
            }
            break;
  • 异步服务请求和应答

    来自云平台的异步服务调用请求将通过AIOT_DMRECV_ASYNC_SERVICE_INVOKE类型消息通知给用户, 其中params参数为包含服务输入数据键值对的JSON对象字符串。

    用户可使用第三方JSON库对其进行解析。

    用户可调用aiot_dm_send搭配AIOT_DMMSG_ASYNC_SERVICE_REPLY类型, 发送应答消息给云端。

    /* 异步服务调用 */
            case AIOT_DMRECV_ASYNC_SERVICE_INVOKE: {
                printf("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: 以下代码演示如何对来自云平台的异步服务调用进行应答, 用户可取消注释查看演示效果
                 *
                 * 注意: 如果用户在回调函数外进行应答, 需要自行保存msg_id, 因为回调函数入参在退出回调函数后将被SDK销毁, 不可以再访问到
                 */
    
                /*
                {
                    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");
                    }
                }
                */
            }
            break;
    注意 如果用户不在回调函数中立即应答, 而是在回调函数外完成业务处理后再发送应答, 则需自行保存服务msg_id等信息, 以保证应答数据在云端能与请求相匹配。
  • 同步服务请求和应答

    来自云平台的同步服务调用请求将通过AIOT_DMRECV_SYNC_SERVICE_INVOKE类型消息通知给用户, 其中params参数为包含服务输入数据键值对的JSON对象字符串。

    用户可使用第三方JSON库对其进行解析。

    用户可调用aiot_dm_send搭配AIOT_DMMSG_SYNC_SERVICE_REPLY类型, 发送应答消息给云端。

    /* 同步服务调用 */
            case AIOT_DMRECV_SYNC_SERVICE_INVOKE: {
                printf("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: 以下代码演示如何对来自云平台的同步服务调用进行应答, 用户可取消注释查看演示效果
                 *
                 * 注意: 如果用户在回调函数外进行应答, 需要自行保存msg_id和rrpc_id字符串, 因为回调函数入参在退出回调函数后将被SDK销毁, 不可以再访问到
                 */
    
                /*
                {
                    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");
                    }
                }
                */
            }
            break;

    说明:

    • 同步服务调用, 在云端的超时时间为7秒, 如果设备不在超时时间内完成应答, 云端API接口调用将返回超时失败。
    • 如果用户不在回调函数中立即应答, 而是在回调函数外完成业务处理后再应答, 则
      • 需要自行保存服务rrpc_id(用字符串拷贝), msg_id等信息。
      • 需要保证应答中含有与请求相同的rrpc_idmsg_id, 使云端能匹配应答与请求。
  • 二进制数据上下行

    来自云平台的下行二进制数据将通过AIOT_DMRECV_RAW_DATA类型消息通知给用户。 用户可调用aiot_dm_send发送AIOT_DMMSG_RAW_DATA类型消息进行应答。

    在使用二进制数据进行通信前, 用户应确保设备所属产品使用的数据格式为透传/自定义格式, 并已经正确的在云端部署了数据解析脚本

    /* 下行二进制数据 */
            case AIOT_DMRECV_RAW_DATA: {
                printf("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);
                }
                */
            }
            break;
  • 二进制同步服务上下行

    来自云平台的二进制格式的同步服务调用将通过AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE类型消息通知给用户。

    用户可调用aiot_dm_send搭配AIOT_DMMSG_RAW_SERVICE_REPLY类型, 发送应答消息给云端。

    在使用二进制数据进行通信前, 用户应确保设备所属产品使用的数据格式为透传/自定义格式, 并已经正确部署了数据解析脚本

    /* 二进制格式的同步服务调用, 比单纯的二进制数据消息多了个rrpc_id */
            case AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE: {
                printf("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);
            }
            break;
    注意 二进制同步服务调用, 比普通的AIOT_DMRECV_RAW_DATA类型数据多了rrpc_id字符串, 用户的应答响应数据必要带上相同的rrpc_id字符串。

例程讲解

现对照demos/data_model_basic_demo.c例程, 分步骤讲解如何使用API,细节信息请参见下图:

开发流程图

这个例程适用于Linux这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程。

说明
  • 一个线程用于保活长连接。
  • 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调。

接着演示了在MQTT连接上进行属性上报, 事件上报, 以及处理收到的属性设置, 服务调用, 取消这些代码段落的注释即可观察运行效果。

需要用户关注或修改的部分, 已经用 TODO 在注释中标明。

  1. 设置设备的设备证书

    例程使用的设备证书是公用的, 所以应用于实际业务时, 请替换如下的TODO部分, 传入用户自己真实的设备证书。

    /* TODO: 替换为自己设备的设备证书 */
        char *product_key       = "a18wPXXXXXX";
        char *device_name       = "data_model_basic_demo";
        char *device_secret     = "uwMTmVAMnGGHaAkqmeDY6cHxxBXXXXXX";
  2. 进入程序入口, 给SDK配置全局的底层依赖和日志回调

    底层依赖描述了硬件平台的资源使用方式, 比如怎样获取时钟, 分配内存等, 日志回调是用户的函数, SDK有log输出的时候会进入这个函数。

    int main(int argc, char *argv[])
    {
        ...
        ...
        /* 配置SDK的底层依赖 */
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        /* 配置SDK的日志输出 */
        aiot_state_set_logcb(demo_state_logcb);
  3. 给MQTT会话配置参数

    这些连接参数包括如何建立TLS连接, 服务器地址在哪, 连接后的数据及事件回调函数是什么等等, 然后用 aiot_mqtt_connect() 建立连接。

    /* 创建1个MQTT客户端实例并内部初始化默认参数 */
        mqtt_handle = aiot_mqtt_init();
        if (mqtt_handle == NULL) {
            printf("aiot_mqtt_init failed\n");
            return -1;
        }
    
        snprintf(host, 100, "%s.%s", product_key, url);
        /* 配置MQTT服务器地址 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)host);
        /* 配置MQTT服务器端口 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
        /* 配置设备productKey */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
        /* 配置设备deviceName */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
        /* 配置设备deviceSecret */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
        /* 配置网络连接的安全凭据, 上面已经创建好了 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
        /* 配置MQTT事件回调函数 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
  4. 创建物模型会话实例并建立MQTT连接

    物模型会话创建后, 可通过其会话句柄组装和解析标准的物模型报文, 这些报文依赖MQTT模块发送和接收, 因此要关联物模型的会话和MQTT的会话, 并建立MQTT连接。

    /* 创建DATA-MODEL实例 */
        void *dm_handle = aiot_dm_init();
        if (dm_handle == NULL) {
            printf("aiot_dm_init failed");
            return -1;
        }
        /* 配置MQTT实例句柄 */
        aiot_dm_setopt(dm_handle, AIOT_DMOPT_MQTT_HANDLE, mqtt_handle);
        /* 配置设备productKey */
        aiot_dm_setopt(dm_handle, AIOT_DMOPT_PRODUCT_KEY, (void *)product_key);
        /* 配置设备deviceName */
        aiot_dm_setopt(dm_handle, AIOT_DMOPT_DEVICE_NAME, (void *)device_name);
        /* 配置消息接收处理回调函数 */
        aiot_dm_setopt(dm_handle, AIOT_DMOPT_RECV_HANDLER, (void *)aiot_dm_recv_handler);
    
        /* 与服务器建立MQTT连接 */
        res = aiot_mqtt_connect(mqtt_handle);
  5. 数据收发

    请查看API使用概述部分。

  6. 其他说明

    例程使用的物模型如下所示, 用户可按照以下json定义产品物模型以完成demo的演示。

    {
      "properties": [
        {
          "identifier": "LightSwitch",
          "dataType": {
            "type": "bool"
          }
        }
      ],
      "events": [
        {
          "outputData": [
            {
              "identifier": "ErrorCode",
              "dataType": {
                "type": "enum"
              }
            }
          ],
          "identifier": "Error",
          "type": "info"
        }
      ],
      "services": [
        {
          "identifier": "SetLightSwitchTimer",
          "inputData": [
            {
              "identifier": "Timer",
              "dataType": {
                "type": "double"
              }
            },
            {
              "identifier": "LightSwitch",
              "dataType": {
                "type": "bool"
              }
            }
          ]
        },
        {
          "outputData": [
            {
              "identifier": "dataA",
              "dataType": {
                "type": "int"
              }
            }
          ],
          "identifier": "ToggleLightSwitch",
          "inputData": [
            {
              "identifier": "paramA",
              "dataType": {
                "type": "int"
              }
            }
          ]
        }
      ]
    }
  7. 观察日志

    以上各种和云端的交互代码都被注释掉, 用户取消注释后, 重新编译运行例程, 即可看到运行过程, 以属性上报和事件上报为例。

    [1581401572.800][LK-0313] MQTT user calls aiot_mqtt_connect api, connect
    [1581401572.800][LK-0317] data_model_basic_demo&a18wPXXXXXX
    [1581401572.800][LK-0318] DCDDD8568269B74AB67BDD3BC9B854E4F45FD94649750362134F744979C59684
    [1581401572.800][LK-0319] a18wPXXXXXX.data_model_basic_demo|timestamp=2524608000000,_ss=1,_v=sdk-c-4.0.0,securemode=2,signmethod=hmacsha256,ext=1,|
    establish mbedtls connection with server(host='a18wPXXXXXX.iot-as-mqtt.cn-shanghai.aliyuncs.com', port=[443])
    success to establish mbedtls connection, fd = 3(cost 44763 bytes in total, max used 47675 bytes)
    [1581401572.877][LK-0313] MQTT connect success in 75 ms
    AIOT_MQTTEVT_CONNECT
    [1581401572.877][LK-0309] pub: /sys/a18wPXXXXXX/data_model_basic_demo/thing/event/property/post
    
    [LK-030A] > 7B 22 69 64 22 3A 22 30  22 2C 22 76 65 72 73 69 | {"id":"0","versi
    [LK-030A] > 6F 6E 22 3A 22 31 2E 30  22 2C 22 70 61 72 61 6D | on":"1.0","param
    [LK-030A] > 73 22 3A 7B 22 4C 69 67  68 74 53 77 69 74 63 68 | s":{"LightSwitch
    [LK-030A] > 22 3A 20 30 7D 7D                                | ": 0}}
    
    [1581401572.877][LK-0309] pub: /sys/a18wPXXXXXX/data_model_basic_demo/thing/event/Error/post
    
    [LK-030A] > 7B 22 69 64 22 3A 22 31  22 2C 22 76 65 72 73 69 | {"id":"1","versi
    [LK-030A] > 6F 6E 22 3A 22 31 2E 30  22 2C 22 70 61 72 61 6D | on":"1.0","param
    [LK-030A] > 73 22 3A 7B 22 45 72 72  6F 72 43 6F 64 65 22 3A | s":{"ErrorCode":
    [LK-030A] > 20 30 7D 7D                                      |  0}}
    
    [1581401572.911][LK-0309] pub: /sys/a18wPXXXXXX/data_model_basic_demo/thing/event/Error/post_reply
    
    [LK-030A] < 7B 22 63 6F 64 65 22 3A  32 30 30 2C 22 64 61 74 | {"code":200,"dat
    [LK-030A] < 61 22 3A 7B 7D 2C 22 69  64 22 3A 22 31 22 2C 22 | a":{},"id":"1","
    [LK-030A] < 6D 65 73 73 61 67 65 22  3A 22 73 75 63 63 65 73 | message":"succes
    [LK-030A] < 73 22 2C 22 6D 65 74 68  6F 64 22 3A 22 74 68 69 | s","method":"thi
    [LK-030A] < 6E 67 2E 65 76 65 6E 74  2E 45 72 72 6F 72 2E 70 | ng.event.Error.p
    [LK-030A] < 6F 73 74 22 2C 22 76 65  72 73 69 6F 6E 22 3A 22 | ost","version":"
    [LK-030A] < 31 2E 30 22 7D                                   | 1.0"}
    
    [1581401572.911][LK-0A08] DM recv generic reply
    aiot_dm_recv_handler, type = 0
    msg_id = 1, code = 200
    [1581401572.911][LK-0309] pub: /sys/a18wPXXXXXX/data_model_basic_demo/thing/event/property/post_reply
    
    [LK-030A] < 7B 22 63 6F 64 65 22 3A  32 30 30 2C 22 64 61 74 | {"code":200,"dat
    [LK-030A] < 61 22 3A 7B 7D 2C 22 69  64 22 3A 22 30 22 2C 22 | a":{},"id":"0","
    [LK-030A] < 6D 65 73 73 61 67 65 22  3A 22 73 75 63 63 65 73 | message":"succes
    [LK-030A] < 73 22 2C 22 6D 65 74 68  6F 64 22 3A 22 74 68 69 | s","method":"thi
    [LK-030A] < 6E 67 2E 65 76 65 6E 74  2E 70 72 6F 70 65 72 74 | ng.event.propert
    [LK-030A] < 79 2E 70 6F 73 74 22 2C  22 76 65 72 73 69 6F 6E | y.post","version
    [LK-030A] < 22 3A 22 31 2E 30 22 7D                          | ":"1.0"}
    
    [1581401572.911][LK-0A08] DM recv generic reply
    aiot_dm_recv_handler, type = 0
    msg_id = 0, code = 200