一、华为云IoTDA创建产品
创建如下服务,并添加对应的属性和命令。
二、小熊派接入
根据小熊派官方示例代码D6完成了小熊派接入华为云并实现属性上传命令下发。源码:小熊派开源社区/BearPi-HM_Nano
1. MQTT连接代码分析
这部分代码在oc_mqtt.c和oc_mqtt.h中
c
/*该结构体在oc_mqtt.h中
*用来存储与MQTT设备相关的认证和标识信息。
*/
struct bp_oc_info
{
char client_id[OC_CLIENT_ID_LEN];
char username[OC_USERNAME_LEN];
char password[OC_PASSWORD_LEN];
char user_device_id_flg;
};
typedef struct bp_oc_info *bp_oc_info_t;
/*该函数在oc_mqtt.c中
*在调用mqtt连接前,通过该函数对连接参数进行赋值
*/
void device_info_init(char *client_id, char * username, char *password)
{
oc_info.user_device_id_flg = 1;
strncpy(oc_info.client_id, client_id, strlen(client_id));
strncpy(oc_info.username, username, strlen(username));
strncpy(oc_info.password, password, strlen(password));
}
/*该函数在oc_mqtt.c中
*调用该函数可以完成mqtt的连接
*其中oc_mqtt_entry函数将根据云端地址进行连接,然后连接mqtt
*/
int oc_mqtt_init(void)
{
int result = 0;
if (init_ok)
{
//LOG_D("oc mqtt already init!");
return 0;
}
if (oc_mqtt_entry() < 0)
{
result = -2;
goto __exit;
}
__exit:
if (!result)
{
//LOG_I("oc package(V%s) initialize success.", oc_SW_VERSION);
init_ok = 1;//官网这里为0,根据逻辑这里应该为连接成功,连接成功后应该置1避免重复连接。
}
else
{
//LOG_E("oc package(V%s) initialize failed(%d).", oc_SW_VERSION, result);
}
return result;
}
2. 属性上报
华为云IoTDA中,属性上报格式如下:
c
{
"services": [{
"service_id": "xxxxx",//服务ID为产品创建后添加的服务
"properties": {
"temp": 23//属性和对应的值
}
}]
}
在小熊派源码中通过结构体封装了属性上报的函数,调用方便代码分析如下
c
typedef struct
{
void *nxt;
char *service_id; ///< the service id in the profile, which could not be NULL
char *event_time; ///< eventtime, which could be NULL means use the platform time
oc_mqtt_profile_kv_t *service_property; ///< the property in the profile, which could not be NULL
}oc_mqtt_profile_service_t;
该结构体位于oc_mqtt.h
中,用于表示接入云端的一个服务内容,具体分析如下:
void *nxt;
是一个指向下一个oc_mqtt_profile_service_t
结构体的指针,用于实现服务的链表结构。通过这个字段,可以将多个服务链接在一起。char *service_id;
:是一个指向字符的指针,表示服务的ID。在配置文件中,服务的ID是必需的,不能为空(NULL)。char *event_time;
:是一个指向字符的指针,表示事件的时间。这个字段可以是NULL,表示使用平台的时间。oc_mqtt_profile_kv_t *service_property;
:是一个指向oc_mqtt_profile_kv_t
结构体的指针,表示服务的属性。需要上报的属性。
c
typedef struct
{
void *nxt; ///< ponit to the next key
char *key;
en_oc_profile_data_t type;
void *value;
}oc_mqtt_profile_kv_t;
typedef enum
{
EN_OC_MQTT_PROFILE_VALUE_INT = 0,
EN_OC_MQTT_PROFILE_VALUE_LONG,
EN_OC_MQTT_PROFILE_VALUE_FLOAT,
EN_OC_MQTT_PROFILE_VALUE_STRING, ///< must be ended with '\0'
EN_OC_MQTT_PROFILE_VALUE_LAST,
}en_oc_profile_data_t;
该结构体位于oc_mqtt.h
中,用于存储服务的属性它包含以下字段:
void *nxt;
:是一个指向下一个oc_mqtt_profile_kv_t
结构体的指针,用于实现键值对的链表结构。通过这个字段,可以将多个键值对链接在一起。char *key;
:这是一个指向字符的指针,表示键的名称。en_oc_profile_data_t type;
:这是一个枚举类型,表示值的类型。枚举en_oc_profile_data_t
定义了多种数据类型,用于指定与键相关联的值的类型。void *value;
:这是一个指向任意类型数据的指针,表示与键相关联的值。由于value
的类型是void*
,它可以是任何类型的数据,具体类型由type
字段指定。
通过这两个结构体构建上报属性的消息更加方便,能够动态添加属性。属性上报代码如下:
c
/*该函数位于iot_cloud_oc_sample.c中,将需要上报的属性进行初始化*/
static void deal_report_msg(report_t *report)
{
oc_mqtt_profile_service_t service;
oc_mqtt_profile_kv_t fish_temp;
oc_mqtt_profile_kv_t fish_light;
oc_mqtt_profile_kv_t fish_pump;
oc_mqtt_profile_kv_t fish_heat;
service.event_time = NULL;
service.service_id = "HomeBox";
service.service_property = &fish_temp;
service.nxt = NULL;
fish_temp.key = "FishTemp";
fish_temp.value = &report->temp;
fish_temp.type = EN_OC_MQTT_PROFILE_VALUE_INT;
fish_temp.nxt = &fish_light;
fish_light.key = "FishLight";
fish_light.value = g_app_cb.light? "ON" : "OFF";
fish_light.type = EN_OC_MQTT_PROFILE_VALUE_STRING;
fish_light.nxt = &fish_pump;
fish_pump.key = "FishPump";
fish_pump.value = g_app_cb.pump ? "ON" : "OFF";
fish_pump.type = EN_OC_MQTT_PROFILE_VALUE_STRING;
fish_pump.nxt = &fish_heat;
fish_heat.key = "FishHeat";
fish_heat.value = g_app_cb.heat ? "ON" : "OFF";
fish_heat.type = EN_OC_MQTT_PROFILE_VALUE_STRING;
fish_heat.nxt = NULL;
oc_mqtt_profile_propertyreport(USERNAME, &service);
return;
}
其中oc_mqtt_profile_propertyreport
函数位于oc_mqtt.h
中,该函数是一个用于构建和发布 MQTT 消息,上报设备服务属性。该函数中间接调用了oc_mqtt_profile_package.c
文件中的oc_mqtt_profile_propertyrepor
、make_services
、make_service
、make_kvs
、profile_fmtvalue
函数,这些函数协同工作,以 JSON 格式创建服务属性,并通过 MQTT 发布。
c
int oc_mqtt_profile_propertyreport(char *deviceid,oc_mqtt_profile_service_t *payload)
{
int ret = (int)en_oc_mqtt_err_parafmt;
char *topic;
char *msg;
if(NULL == deviceid)
{
if(NULL == s_oc_mqtt_profile_cb.device_id)
{
return ret;
}
else
{
deviceid = s_oc_mqtt_profile_cb.device_id;
}
}
if((NULL== payload) || (NULL== payload->service_id) || (NULL == payload->service_property))
{
return ret;
}
topic = topic_make(CN_OC_MQTT_PROFILE_PROPERTYREPORT_TOPICFMT, deviceid,NULL);
msg = oc_mqtt_profile_package_propertyreport(payload);
printf("msg:%s \r\n",msg);
if((NULL != topic) && (NULL != msg))
{
ret = oc_mqtt_publish(topic,(uint8_t *)msg,strlen(msg),(int)en_mqtt_al_qos_1);
}
else
{
ret = (int)en_oc_mqtt_err_sysmem;
}
free(topic);
free(msg);
return ret;
}
oc_mqtt_profile_propertyreport
-->oc_mqtt_profile_package_propertyreport
(创建上报信息的json对象)-->make_services
(创建json对象数组)-->make_service
(创建属性json对象)-->make_kvs
(创建属性json数组)-->profile_fmtvalue
(创建各个属性内容的json对象)
oc_mqtt_profile_propertyreport
函数- 这个函数负责构建并发布一个 MQTT 消息,该消息包含设备的服务属性报告。
- 它首先检查
deviceid
和payload
是否为NULL。如果是,则根据全局回调函数中的设备 ID 或返回错误。 - 使用
topic_make
函数构建 MQTT 主题。 - 使用
oc_mqtt_profile_package_propertyreport
函数打包服务属性报告为消息。 - 使用
oc_mqtt_publish
函数(同样未在代码中定义)发布 MQTT 消息。 - 最后,释放分配的内存并返回结果。
make_services
函数- 这个函数创建一个 JSON 数组,该数组包含多个服务对象的 JSON 表示。
- 它遍历传入的
service_info
链表,为每个服务调用make_service
函数,并将结果添加到 JSON 数组中。 - 如果在内存分配过程中发生错误,它会跳转到EXIT_MEM标签,释放已分配的资源,并返回NULL
make_service
函数 :- 这个函数创建一个 JSON 对象,该对象表示单个服务。
- 它添加service_id、properties(使用make_kvs函数生成)和可选的event_time到 JSON 对象中。
- 如果在内存分配过程中发生错误,它会跳转到EXIT_MEM`标签,释放已分配的资源,并返回NULL。
make_kvs
函数:- 这个函数创建一个 JSON 对象,该对象包含键值对的列表,这些键值对表示服务的属性。
- 它遍历传入的kvlst链表,为每个键值对调用profile_fmtvalue函数,并将结果添加到 JSON 对象中。
- 如果在内存分配过程中发生错误,它会跳转到EXIT_MEM标签,释放已分配的资源,并返回NULL。
profile_fmtvalue
函数:- 这个函数根据键值对的类型(整数、长整数、浮点数或字符串)创建一个相应的 JSON 值。
- 它返回创建的 JSON 值,该值可以是数字或字符串。
3. 消息接收
在mqtt连接后,oc_mqtt.c
文件中oc_mqtt_entry
函数中设置了mqtt的回调函数mq_client.defaultMessageHandler = mqtt_callback;
,在函数mqtt_callback中将接收到的值存入结构体oc_mqtt.cmd_rsp_cb
中后续进行处理。
c
/*主函数*/
oc_set_cmd_rsp_cb(oc_cmd_rsp_cb);
void oc_cmd_rsp_cb(uint8_t *recv_data, size_t recv_size, uint8_t **resp_data, size_t *resp_size)
{
app_msg_t *app_msg;
int ret = 0;
app_msg = malloc(sizeof(app_msg_t));
app_msg->msg_type = en_msg_cmd;
app_msg->msg.cmd.payload = (char *)recv_data;
printf("recv data is %.*s\n", recv_size, recv_data);
ret = osMessageQueuePut(mid_MsgQueue, &app_msg, 0U, 0U);
if (ret != 0)
{
free(recv_data);
}
*resp_data = NULL;
*resp_size = 0;
}
/*oc_mqtt.c*/
void oc_set_cmd_rsp_cb(void (*cmd_rsp_cb)(uint8_t *recv_data, uint32_t recv_size, uint8_t **resp_data, uint32_t *resp_size))
{
oc_mqtt.cmd_rsp_cb = cmd_rsp_cb;
}
- 函数
oc_set_cmd_rsp_cb</font>
这个函数用于设置命令响应的回调函数。它接收一个参数:
void (*cmd_rsp_cb)(uint8_t *recv_data, uint32_t recv_size, uint8_t **resp_data, uint32_t *resp_size)
:这是一个函数指针,指向命令响应的回调函数。
函数内部逻辑如下:
- 将传入的回调函数
cmd_rsp_cb
赋值给oc_mqtt.cmd_rsp_cb
。oc_mqtt
是一个结构体,用于存储MQTT相关的配置和状态,其中 cmd_rsp_cb成员用于存储命令响应的回调函数。
- 回调函数
oc_cmd_rsp_cb
这个函数是命令响应的回调函数,当接收到命令时,这个函数会被调用。它接收四个参数:uint8_t *recv_data
:指向接收到的数据的指针。size_t recv_size
:接收到的数据的大小。uint8_t **resp_data
:指向响应数据的指针的地址,用于返回响应数据。size_t *resp_size>
:指向响应数据大小的指针,用于返回响应数据的大小。
接收到的消息存入消息队列进行处理