同人逼死官方系列 从 DDC 嗅探器到 sddc_sdk_lib 的数据解析

从 DDC 嗅探器到 sddc_sdk_lib 的数据解析

之前的 DDC 协议介绍 主要讲了设备加入、退出以及维持设备状态,而 SDK框架 sddc_sdk_lib 解析 主要讲了 SDK 库的结构和如何使用这个库,DDC 协议嗅探器 则介绍了一个前端构筑报文,对于具体的消息报文没有详细的说明介绍。

这篇文章就着重讲一下消息报文的传输和处理流程。从之前写的 DDC 协议嗅探器 构建报文开始到嵌入式设备中 sddc_sdk_lib 对消息处理解析,全面的讲解下 message 消息在整个 Spirit 1 网络中的传递路径以及数据在 sddc_sdk_lib 中是如何被一步一步解析的。

在这里插入图片描述
感觉自己真是太厉害了!(叉腰)

DDC 协议嗅探器中的消息构建以及传输

嗅探器工具和 SDDC 的代码,老样子都放在了灵感桌面的秘密宝库 中(不会使用 Git 的朋友请看简单无脑,上手即用 - 手把手教你使用 智能红外温度传感器代码以及依赖的 gitee 库!)。

前端的主页面代码在 ui/src/views/DeviceMessage.tsx 中,使用的 vue 以及 vant 库来开发,前端构建好 json 报文调用 sendMessage() 方法即可,其中调用 this.$api.sendMessage(this.clientMessage) 来向后端发送 http 请求;

  private async sendMessage() {
    const res = this.checkClientMessage();
    if (!res) return;
    let len = 0;
    if (this.clientMessageType === 'array') {
      len = (this.clientMessage as IValueType[]).length;
    } else {
      len = Object.keys(this.clientMessage).length;
    }
    if (!len) {
      return edger.notify.error('发送数据不可为空!');
    }
    try {
      const res = await this.$api.sendMessage(this.clientMessage);
    } catch (error) {
      edger.notify.error('发送数据失败!')
    }
  }

后端在 eap/main.js 中会处理前端 http 请求传递的数据并调用 device 模块中的 device.send 方法将消息通过 EdgerOS 向硬件设备发送消息;

app.post('/send-message', (req, res) => {
  if (Object.keys(req.body).length > 0) {
    device.send(
      req.body,
      (error) => {
        if (error) {
          console.error('send message error!' + error);
          io.emit('error', 'device.send error,try again!');
        } else {
          console.info('setSensorParams success!' + JSON.stringify(req.body));
        }
      },
      3
    );
    res.send('send success!');
  } else {
    res.sendStatus(400, 'not Data!');
  }
});

eap/main.js 中 通过下方代码来监听硬件设备返回或者上报的数据;

      device.on('message', function (msg) {
        io.emit('message', msg);
      });

sddc_sdk_lib 中对消息的解析处理

sddc_sdk_lib 库基于不同平台我也适配了不同版本,具体代码还是到我的 灵感桌面的秘密宝库中拿,具体规定的报文格式如下:

// 应用向硬件设备主动查询 attr1 和 attr2 两个属性的状态
{
	"method": "get"
	"obj" : ["attr1","attr2"]
}

// 应用向硬件设备主动设置或控制 attr1 和 attr2 两个属性的状态
{
	"method": "set"
	"attr1" : "ON"
	"attr2" : 0.12
}

// 硬件设备回复应用的查询或主动上报 attr1 和 attr2 两个属性的状态
{
	"method": "report"
	"data" : {
		"attr1" : "ON"
		"attr2" : 0.12
    }
}

“attr1”,"attr2"就是要和写在对应的注册函数的字符串对应!(重点!敲黑板!)

SDDC_SDK_lib.c 文件中 sddc_on_message_lib() 函数是注册到 SDDC 协议中的消息入口,函数中主要的功能就是识别 method 字段是 get 方式还是 set 方式,再根据不同的方式调用不同的实现函数,其中 set 方式会遍历匹配属性名(也就是“attr1”,“attr2”)是否一致后再调用对应的函数实现;

static sddc_bool_t sddc_on_message_lib(sddc_t *sddc, const uint8_t *uid, const char *message, size_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;
}

关于 set 方式,三种不同类型的属性有不同的实现,以下方 Num 类型的处理为例,如果数据类型不是 Num 就不会处理,相反就会调用注册的具体实现,注册相关可以参考这篇文章 基于sddc 协议的SDK框架 sddc_sdk_lib 解析

static sddc_bool_t object_Number_Set(cJSON   *input,  const char *object_name, Num_Set do_fun)
{
    cJSON   *Json_object;

    Json_object = cJSON_GetObjectItem(input, object_name);
    if (NULL == Json_object) {
        return SDDC_FALSE;
    }

    if (cJSON_IsNumber(Json_object)) {
        return do_fun((uint64_t)Json_object->valuedouble);
    } else {
        return SDDC_FALSE;
    }

    return SDDC_TRUE;
}

对于 get 方式,调用统一的 object_get() 函数即可,会依次遍历数组中的每个属性,调用注册的 get 函数实现,并构建 report 报文发送给应用;

static sddc_bool_t object_get(sddc_t *sddc, const uint8_t *uid, cJSON   *input,
                            DEV_STATE_GET *state_get_list, int list_len)
{
    cJSON   *Json_object;
    cJSON   *root;
    cJSON   *json_data;
    char    *str;
    int     i,j;

    Json_object = cJSON_GetObjectItem(input, "obj");
    if (NULL == Json_object) {
        return SDDC_FALSE;
    }

    if (!cJSON_IsArray(Json_object)) {
        return SDDC_FALSE;
    }

    root = cJSON_CreateObject();
    cJSON_AddStringToObject(root, "method", "report");
    json_data = cJSON_CreateObject();
	
    for (i=0; i<cJSON_GetArraySize(Json_object); i++) {
        for (j=0; j<list_len; j++) {
            if (strcmp(cJSON_GetArrayItem(Json_object, i)->valuestring, state_get_list[j].objname) == 0) {
                if (state_get_list[j].type == DEV_IO_TYPE) {
                    if (state_get_list[j].state_fun(cstate_buf, sizeof(cstate_buf)) == SDDC_TRUE) {
                        cJSON_AddStringToObject(json_data, state_get_list[j].objname, cstate_buf);
                    }
                } else if (state_get_list[j].type == DEV_NUM_TYPE ){
                    if (state_get_list[j].state_fun(cstate_buf, sizeof(cstate_buf)) == SDDC_TRUE) {
                        cJSON_AddNumberToObject(json_data, state_get_list[j].objname, atof(cstate_buf));
                        printf("atof(cstate_buf) = %lf\n", atof(cstate_buf));
                    }
                } else if (state_get_list[j].type == DEV_DISPLAY_TYPE) {
                    if (state_get_list[j].state_fun(cstate_buf, sizeof(cstate_buf)) == SDDC_TRUE) {
                        cJSON   *display_obj;
                        display_obj = cJSON_AddObjectToObject(json_data, state_get_list[j].objname);
                        cJSON_AddStringToObject(display_obj, state_get_list[j].objname, cstate_buf);
                    }
                }
            }
        }
    }
    cJSON_AddItemToObject(root, "data", json_data);
    str = cJSON_Print(root);
    printf("object_get str = %s\n", str);
    if (!uid) {
        sddc_broadcast_message(sddc, str, strlen(str), 3, SDDC_FALSE, NULL);
    } else {
        sddc_send_message(sddc, uid, str, strlen(str), 3, SDDC_FALSE, NULL);
    }

    cJSON_free(str);

    cJSON_Delete(root);

    return SDDC_TRUE;
}

总结

这就是基于 spirit 1 构建智能设备从应用到硬件的完整数据链路,其实只需要根据规定好的 json 格式构建处理对应的 json 数据包就可以了,对于前端应用和嵌入式开发都很友好,只需要关注各自的领域,中间的就交个这个神奇的 spirit 1吧ヾ(◍°∇°◍)ノ゙。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值