同人逼死官方系列!基于sddc 协议的SDK框架 sddc_sdk_lib 解析
阅读原文时间:2023年07月08日阅读:1

基于sddc 协议的SDK框架 sddc_sdk_lib 解析

之前在移植 libsddc 库的时候感觉官方 demo 太低效了( ̄. ̄),复制粘贴代码好累,而且写出一个BUG,其他复制的代码整个就裂开了,于是写了一个 sddc_sdk_lib 库, 让 sddc 接入设备更加快速有效,本文就主要讲解一下这个库的构造以及新设备通过该库快速的使用 sddc 接入 Spirit 1,从而变成一个智能设备。ヽ(・ω・´メ)

关于 SDDC 协议的介绍可以参考:统一了 WiFi 和 ZigBee 上层使用的跨厂商发现与控制 DDC协议介绍

目前 sddc_sdk_lib 支持三个平台分别是提出 sddc 协议的翼辉信息发布的 MS-RTOS、安信可基于 FreeRTOS 的ESP平台、还有就是最近比较火的 Arduino 平台(这东西是真好使。习惯 Arduino 之后完全不想用传统平台了),老样子东西都放在了灵感桌面的秘密宝库

SDDC_SDK_lib.h 中,SDDC 整体结构描述如下:

/*********************************************************************************************************
   SDDC整体结构描述
*********************************************************************************************************/
typedef struct {
    char             *token;
    DEV_INFO         *devinfo;
    IO_DEV_REGINFO   *io_dev_reg;
    int              io_dev_reg_num;
    NUM_DEV_REGINFO  *num_dev_reg;
    int              num_dev_reg_num;
    DEV_STATE_GET    *state_get_reg;
    int              state_get_reg_num;
    DIS_DEV_REGINFO  *dis_dev_reg;
    int              dis_dev_num;
} SDDC_CONFIG_INFO;

其中 DEV_INFO 中的内容是通过 SDDC 上报到 Spirit 1的信息,例如设备名称、设备类型、描述等:

/*********************************************************************************************************
   SDDC里面信息上报涉及到的设备信息描述
*********************************************************************************************************/
typedef struct {
    char      *name;
    char      *type;
    sddc_bool_t excl;
    char      *desc;
    char      *model;
    char      *vendor;
} DEV_INFO;

IO_DEV_REGINFONUM_DEV_REGINFODIS_DEV_REGINFO 分别是 IO 类型、数字类型、显示类型设备的描述,其中包含对象名称以及对应的 Set 函数;

/*********************************************************************************************************
   SDDC里面IO类设备的注册结构体
*********************************************************************************************************/
typedef struct {
    char    *objname;
    Io_Set  IO_Fun;
} IO_DEV_REGINFO;

/*********************************************************************************************************
   SDDC里面数字类设备的注册结构体
*********************************************************************************************************/
typedef struct {
    char     *objname;
    Num_Set  Num_Fun;
} NUM_DEV_REGINFO;

/*********************************************************************************************************
   SDDC里面显示类设备的注册结构体
*********************************************************************************************************/
typedef struct {
    char     *objname;
    Dis_Set  Dis_Fun;
} DIS_DEV_REGINFO;

DEV_STATE_GET 中是所有设备对象的 Get 函数集合;

/*********************************************************************************************************
   SDDC结果查询类函数注册结构体
*********************************************************************************************************/
#define DEV_IO_TYPE       0
#define DEV_NUM_TYPE      1
#define DEV_DISPLAY_TYPE  2

typedef struct {
    char       *objname;
    int        type;
    STATE_GET  state_fun;
} DEV_STATE_GET;

统一了 WiFi 和 ZigBee 上层使用的跨厂商发现与控制 DDC协议介绍 这个文章中介绍了具体的协议流程,仿照着官方的 libsddc demo ,在 SDDC_SDK_lib.c 中是关于 SDDC 通信协议的实现函数,其中连接相关的函数可以不用怎么关注,主要的就是 sddc_on_message_lib 函数,这个函数当中是和 Spirit 1业务通信的入口,根据报文中的 method 字段来匹配是 get 或者 set ,再调用对应的处理函数,结合 SDDC 的整体结构,可以看出,所有类型设备共用同一个 get 接口,对于 set 接口根据设备类型不同,有着不同的处理函数,实际上就是把官方的 demo 中的 sddc_on_message 函数抽象了一下,把可能收到的报文都抽象出来了,这样就免去了写解析报文的步骤。

超级牛逼,超级方便是不是ヾ(゚∀゚ゞ),接下来就给大家接受一下这个 SDK 的扩展结构和使用:

/*********************************************************************************************************
** 函数名称: sddc_on_message_lib
** 功能描述: SDDC协议收到消息后的处理回调函数
** 输 入  : sddc          SDDC结构体
**           uid           发送消息的ID
**           message       接收到的报文消息
**           len           报文长度
** 输 出  : 0: 处理失败, 1: 处理成功.
** 全局变量:
** 调用模块: 无
*********************************************************************************************************/
static sddc_bool_t sddc_on_message_lib(sddc_t *sddc, const uint8_t *uid, const char *message, uint32_t len)
{
    cJSON   *root    = cJSON_Parse(message);
    cJSON   *Json_method;
    uint8_t uimethod =DDC_METHOD_VALIDE;

    Json_method = cJSON_GetObjectItem(root, "method");
    if (NULL == Json_method) {
        return SDDC_FALSE;
    }

    if (cJSON_IsString(Json_method)) {
        if (strcmp(Json_method->valuestring,"set") == 0) {
            uimethod = DDC_METHOD_SET;
        } else if (strcmp(Json_method->valuestring,"get") == 0) {
            uimethod = DDC_METHOD_GET;
        } else {
            return SDDC_FALSE;
        }
    } else {
        return SDDC_FALSE;
    }

    if (uimethod == DDC_METHOD_VALIDE) {
        return SDDC_FALSE;
    }

    if (uimethod == DDC_METHOD_SET) {
        int i;

        /*
         *  数字量、显示量先查询设置,防止开关量是设备的使能
         */
        for (i=0; i<G_config->num_dev_reg_num; i++) {
            object_Number_Set(root, G_config->num_dev_reg->objname, G_config->num_dev_reg->Num_Fun);
        }

        for (i=0; i<G_config->dis_dev_num; i++) {
            object_Display_Set(root, G_config->dis_dev_reg->objname, G_config->dis_dev_reg->Dis_Fun);
        }

        for (i=0; i<G_config->io_dev_reg_num; i++) {
            object_IO_Set(root, G_config->io_dev_reg->objname, G_config->io_dev_reg->IO_Fun);
        }
    } else if (uimethod == DDC_METHOD_GET) {
        object_get(sddc, uid, root, G_config->state_get_reg, G_config->state_get_reg_num);
    } else {
        return SDDC_FALSE;
    }

    return SDDC_TRUE;
}

库中还提供了一个主动上报消息的接口,具体如下,注意输入参数的格式要符合规范(重点!敲黑板!

):

/*********************************************************************************************************
** 函数名称: object_report
** 功能描述: 封装给外部用户用的上报接口
** 输 入  : reportlist   上报的内容列表,格式为{obj:"LED1","LED2","TMP"}
** 输 出  : 0: 处理失败, 1: 处理成功.
** 全局变量:
** 调用模块: 无
*********************************************************************************************************/
int object_report(cJSON *reportlist)
{
    if (NULL == G_sddc) {
        return SDDC_FALSE;
    }

    return object_get(G_sddc, NULL, reportlist, G_config->state_get_reg, G_config->state_get_reg_num);
}

可以使用这个函数快速实现一个符合标准的主动上报消息接口的输入参数:

/*
 *  主动数据上报函数
 */
static void report_sensor()
{
    int sensorValue = 0;
    cJSON *value;
    cJSON *root;
    // char  *msg;

    value =  cJSON_CreateArray();
    root = cJSON_CreateObject();
    sddc_return_if_fail(value);
    sddc_return_if_fail(root);

    // 按格式生成需要的参数
    cJSON_AddItemToArray(value, cJSON_CreateString("上报数据 1 "));    // 这里的字符串要和系统对象状态获取注册结构体里的对应
    // cJSON_AddItemToArray(value, cJSON_CreateString("上报数据 2 ")); // 需要上报几个就添加几个
    cJSON_AddItemToObject(root, "obj", value);

    // 发送数据给 EdgerOS
    // msg = cJSON_Print(root);
    // printf("触发上报: %s\n",msg);
    object_report(root);

    cJSON_Delete(value);
    cJSON_free(msg);
}

接下来就教大家如何快速使用这个 SDK 开发接入设备 (✪ω✪)。

首先要定义好设备完成 dev_info 的内容,再根据设备定义所需要的不同类型的对象,可以是一个设备只有一个对象,也可以是复杂的一个设备对应多个对象,根据这些来完成各个类型对象的构建以及 set、get 函数注册。

/*********************************************************************************************************
   当前设备的信息定义
*********************************************************************************************************/
DEV_INFO    dev_info = {
            .name     = "InfraredSensor",                                     // 设备名,你添加设备搜索到的就是这个名字
            .type     = "device",                                             // 设备类型,应用那边需要通过这个来区分设备类型
            .excl     = SDDC_FALSE,
            .desc     = "Infrared sensor based on nodemcu",
            .model    = "1",
            .vendor   = "inspiration-desktop",                                // 属于你的名字
};

/*********************************************************************************************************
   IO设备对象设置函数与处理方法注册
*********************************************************************************************************/
IO_DEV_REGINFO io_dev[] = { // 注册 IO(string类型) 消息 set 处理函数
        {"attr1", NULL},
        {"attr2", NULL},
};

/*********************************************************************************************************
   数字量设备对象函数与处理方法注册
*********************************************************************************************************/
NUM_DEV_REGINFO num_dev[] = { // 注册 数字量(int,float之类的) 消息 set 处理函数
        {"attr1", NULL},
        {"attr2", NULL},
};

/*********************************************************************************************************
   显示设备对象函数与处理方法注册
*********************************************************************************************************/
DIS_DEV_REGINFO dis_dev[] = { // 注册显示设备(屏幕之类的) 消息 set 处理函数
        {"attr1", NULL},
        {"attr2", NULL},
};

/*********************************************************************************************************
   系统对象状态获取注册
*********************************************************************************************************/
DEV_STATE_GET  dev_state_get_reg[] = {
        {"attr1", DEV_IO_TYPE, gpio_dev_state_get},        // 注册 get 处理函数
        {"attr2", DEV_IO_TYPE, gpio_dev_state_get},        // 注册 get 处理函数
};

/*********************************************************************************************************
   系统注册对象汇聚
*********************************************************************************************************/
SDDC_CONFIG_INFO sys_cfg = {
        .token             = "12345678",
        .devinfo           = &dev_info,
        .io_dev_reg        = io_dev,
        .io_dev_reg_num    = MS_ARRAY_SIZE(io_dev),
        .num_dev_reg       = num_dev,
        .num_dev_reg_num   = MS_ARRAY_SIZE(num_dev),
        .state_get_reg     = dev_state_get_reg,
        .state_get_reg_num = MS_ARRAY_SIZE(dev_state_get_reg),
        .dis_dev_reg       = dis_dev,
        .dis_dev_num       = MS_ARRAY_SIZE(dis_dev),
};

至于相关的 set 、get 函数以及初始化操作就需要根据不同的设备来完成不同的实现了,之后在主函数中将 sys_cfg 作为参数执行 sddc_lib_main 就可以启动 SDDC 并完成相关配置,下方是基于 FreeRTOS 的安信可平台上的主函数代码:

int app_main (int argc, char **argv)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    ESP_ERROR_CHECK(example_connect());

    gpio_dev_init();

    while (1) {
        sddc_lib_main(&sys_cfg);
    }

    return  (0);
}

而对于 Arduino 平台,因为主函数结构不同分为 setup() 和 loop(),部分代码无法在库中调用,具体代码如下:

void setup() {
    byte mac[6];

    uart_dev_init();

    // 启动 WiFi 并且连接网络
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }

    // 获取并打印 IP 地址
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("'ip :");
    Serial.print(WiFi.localIP());
    Serial.println("' to connect"); 

    // 清除一下按键状态机的状态
    button.reset();

    // 创建按键扫描线程,长按 IO0 按键,松开后ESP32 将会进入 SmartConfig 模式
    sddc_printf("长按按键进入 Smartconfig...\n");
    button.attachLongPressStop(esp_io0_key_task);
    xTaskCreate(esp_tick_task, "button_tick", ESP_TASK_STACK_SIZE, NULL, ESP_TASK_PRIO, NULL);

    // sddc协议初始化
    sddc_lib_main(&sys_cfg);

    // 获取并打印网卡 mac 地址
    WiFi.macAddress(mac);
    sddc_printf("MAC addr: %02x:%02x:%02x:%02x:%02x:%02x\n",
              mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
    // 使用网卡 mac 地址设置设备唯一标识 UID
    sddc_set_uid(G_sddc, mac);
}

void loop() {

  // 运行 SDDC 协议循环
    while (1)
    {
        sddc_printf("SDDC running...\n");
        sddc_run(G_sddc);
        sddc_printf("SDDC quit!\n");
    }

    // 销毁 SDDC 协议
    sddc_destroy(G_sddc);
}

本文仅个人学习使用,如有错误,欢迎指正, ( ੭ ˙ᗜ˙ )੭谢谢老板!

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章