ESP32使用mqtt协议连接onenet,实现OTA升级(带图文说明)
此内容是根据宸芯IOTh和超子说物联网两位大佬的视频照猫画虎来的。mqtt是看EMQ映云科技感谢几位的的视频。
代码下载地址:https://gitee.com/xiao-liu-harbors-dreams/onenet
网盘地址:https://pan.baidu.com/s/1yZjyiSnkPaLT2WLXfbqTbw?pwd=liu6 提取码: liu6
智能门锁项目已完成,使用到了onenet,可做参考。
一. onenet平台设置
1. 登录onenet平台根据提示注册账号
2. 点击控制台

3. 点击设备管理服务

4.创建产品
4.1 选择创建产品

4.2 选择其他行业,自定义功能

4.3 可选择设备接入(不能使用中移和物app控制,需自己开发UI,灵活性高),产品智能化(可以使用中移和物app控制)

4.4 这里产品名称,所属地市随便填写,开发方案选自定义方案,自己定义需要的功能,我是使用esp32s3通过wifi连接的所以这里选直连设备wifi,网关是帮助子设备进行通信的中间桥梁,子设备是没有联网功能,不能直接进行网络通信如ZigBee,蓝牙,LoRa得通过网关进行通信,可根据自己的连接方式自行选择。

4.5 选择产品开发

4.6 选择物模型。这里是官方对物模型的描述物模型,总的来说就是物联网设备的说明书,告诉云平台,这个设备是什么,能干什么,怎么控制它,根据这些将物模型分为三类基础功能:属性,服务,事件。举一个ws2812全彩灯的例子,属性就是RGB红绿蓝颜色(0-255)值,服务就是设置灯的颜色,事件就是如果设备设置颜色失败时触发。


4.7 添加自定义功能,这里还是以上面的ws2812设置属性为例,设置了两个功能分别为RBG亮度和开关,
-
功能选择属性设置,功能名称开关,标识符要记住一会要用,数据类型这里选择了布尔类型,读写类型为可读可写。功能选择属性设置。
-
功能名称RGB灯,标识符要记住一会要用,数据类型这里选择了结构体,选择结构体是因为结构体获取到数据解析时和其他数据有所不同。





4.8 选择设备开发基于模组开发,然后随便选一个即可,然后就可以扫描下载中移和物app了。注意如果前面选择设备接入就不会有这个界面,直接跳到 5 添加设备



4.9 选择交互配置,选择开启中移和物app控制,下面是设置app的配置,可根据官方文档交互配置自行配置。

5. 添加设备

所属产品选择刚刚创建的产品,名称随便起

onenet平台设置完了,下面直接干代码。
二.连接云平台
1. 环境介绍
我是根据B站宸芯IOT配置的开发环境,使用的esp32s3最小系统板。代码仅提供基本mqtt通信和ota升级功能,很多功能还未完善,只是测试连接云平台mqtt通信和OTA升级。
2. wifi连接
代码是在根据esp-idf例程修改而来,使用的smartconfig配网
2.1 下载配网工具
ESP-TOUCH安卓需要在github上下载,需要开加速器。这是网盘下载链接,就2.43M,通过网盘分享的文件:ESP_Touch
链接: https://pan.baidu.com/s/1bQ-d3PoHhlhOH6zXc0k5oA?pwd=liu6 提取码: liu6

下载后点击EspTouch

然后将单片机烧录好程序后,设置wifi密码,点击确认就可以了,等待一会就可以连接成功。

2.2 bsp_wifi.c
#include "bsp_wifi.h"
#define TAG "bsp_wificonfig"
/* wifi连接事件回调函数 */
EventGroupHandle_t g_xEventWifiConnect;
/* smartconfig任务函数 */
static void smartconfigInit_Task(void * parm);
/* wifi事件回调函数 */
void wifi_event_handler(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
BaseType_t Worken;
if(event_base == WIFI_EVENT) /* wifi 事件 */
{
switch (event_id)
{
case WIFI_EVENT_STA_START: /* 连接 WIIF */
/* 创建 smartconfig 任务获取 ssid 和密码 */
xTaskCreatePinnedToCore(smartconfigInit_Task,"smartconfigInit_Task",4096,NULL,1,NULL,0);
break;
case WIFI_EVENT_STA_CONNECTED: /* 连接成功*/
ESP_LOGI(TAG,"WIIF CONNECT");
break;
case WIFI_EVENT_STA_DISCONNECTED: /* 断开连接*/
esp_wifi_connect();
ESP_LOGI(TAG,"WIIF DISCONNECT");
break;
default:
break;
}
}
else if(event_base == IP_EVENT) /* ip 事件 */
{
switch (event_id)
{
case IP_EVENT_STA_GOT_IP: /* 获取到了IP */
ESP_LOGI(TAG,"ESP32 get ip address");
xEventGroupSetBitsFromISR(g_xEventWifiConnect,WIFI_CONNECT_BIT,&Worken);
break;
default:
break;
}
}
else if(event_base == SC_EVENT) /* smartconfig 事件 */
{
switch (event_id)
{
case SC_EVENT_SCAN_DONE: /* 扫描完成 */
ESP_LOGI(TAG,"ESP32 scan done");
break;
case SC_EVENT_GOT_SSID_PSWD: /* 获取到了 SSID 和密码 */
ESP_LOGI(TAG, "Got SSID and password");
smartconfig_event_got_ssid_pswd_t *sc_cfg = (smartconfig_event_got_ssid_pswd_t*)event_data; /* */
wifi_config_t Wifi_ConifgStruct = /* 配置WIFI认证方式为WPA2 */
{
.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.sta.pmf_cfg.capable = true,
.sta.pmf_cfg.required = false,
};
/* 设置ssid和password ssid*/
memset(&Wifi_ConifgStruct.sta.ssid,0,sizeof(Wifi_ConifgStruct.sta.ssid));
memset(&Wifi_ConifgStruct.sta.password,0,sizeof(Wifi_ConifgStruct.sta.password));
memcpy(Wifi_ConifgStruct.sta.ssid,sc_cfg->ssid,sizeof(Wifi_ConifgStruct.sta.ssid));
memcpy(Wifi_ConifgStruct.sta.password,sc_cfg->password,sizeof(Wifi_ConifgStruct.sta.password));
/* 根据获取到的数据选择是否设置 bssid (MAC地址)*/
Wifi_ConifgStruct.sta.bssid_set = sc_cfg->bssid_set;
if(Wifi_ConifgStruct.sta.bssid_set)
memcpy(Wifi_ConifgStruct.sta.bssid, sc_cfg->bssid, 6);
ESP_LOGI(TAG, "SSID:%s", Wifi_ConifgStruct.sta.ssid);
ESP_LOGI(TAG, "PASSWORD:%s", Wifi_ConifgStruct.sta.password);
ESP_ERROR_CHECK(esp_wifi_disconnect()); /* 断开连接 */
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA,&Wifi_ConifgStruct)); /* 配置WIFI */
ESP_ERROR_CHECK(esp_wifi_connect()); /* 连接WIFI */
break;
case SC_EVENT_SEND_ACK_DONE: /* 连接完成 */
xEventGroupSetBitsFromISR(g_xEventWifiConnect, ESPTOUCH_DONE_BIT,&Worken);
break;
default:
break;
}
}
}
static void smartconfigInit_Task(void * parm)
{
EventBits_t uxBits;
esp_event_handler_register(SC_EVENT,ESP_EVENT_ANY_ID,wifi_event_handler,NULL); /* 注册SmartConfig回调函数*/
ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH)); /* 设置smartconfig 模式*/
smartconfig_start_config_t SC_config = SMARTCONFIG_START_CONFIG_DEFAULT();
esp_smartconfig_start(&SC_config); /* 开启smartconfig */
while(1)
{
uxBits = xEventGroupWaitBits(g_xEventWifiConnect,WIFI_CONNECT_BIT | ESPTOUCH_DONE_BIT,pdTRUE,pdFALSE,portMAX_DELAY);
if(uxBits & WIFI_CONNECT_BIT)
{
ESP_LOGI(TAG, "WiFi Connected to ap");
}
else if(uxBits & ESPTOUCH_DONE_BIT)
{
ESP_LOGI(TAG, "smartconfig over");
esp_smartconfig_stop(); //关闭 smartconfig
}
vTaskDelay(1000);
}
}
void WIFI_init(void)
{
/* 创建wifi连接事件组 */
g_xEventWifiConnect = xEventGroupCreate();
ESP_ERROR_CHECK(nvs_flash_init()); /* 初始化NVS */
ESP_ERROR_CHECK(esp_netif_init()); /* 初始化LwIP */
ESP_ERROR_CHECK(esp_event_loop_create_default()); /* 创建事件系统 */
esp_netif_create_default_wifi_sta(); /* 创建STA工作栈 */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); /* 默认WIFI配置 */
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); /* 初始化WIFI默认配置 */
esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID ,wifi_event_handler,NULL); /* 注册WIFI事件回调函数 */
esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,wifi_event_handler,NULL); /* 注册IP事件回调函数 */
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); /* 设置WIFI模式为STA */
ESP_ERROR_CHECK(esp_wifi_start()); /* 开启WIFI */
}
void WIFI_Task(void* prams)
{
WIFI_init();
while(1)
{
vTaskDelay(1000);
}
}
2.3 bsp_wifi.h
#ifndef __BSP_WIFI_H__
#define __BSP_WIFI_H__
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "esp_private/esp_clk.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_smartconfig.h"
#include "esp_err.h"
#include "esp_log.h"
#include "string.h"
/* wifi 连接事件设置位 */
#define WIFI_CONNECT_BIT BIT0
#define ESPTOUCH_DONE_BIT BIT1
void WIFI_init(void);
void WIFI_Task(void* prams);
#endif
3. 属性上报和属性设置流程
3.1 MQTTX 下载
根据电脑系统自行选择下载即可,安装只需要修改安装地址,官网可能会下载慢,换成流量,或通过网盘分享的文件:MQTTX链接: https://pan.baidu.com/s/1loQ6nNPP0G1x182M7q8uhw?pwd=liu6 提取码: liu6 ,这是windows的。



3.2 连接onenet的3大参数
3.2.1 username
- username:产品ID


3.2.2 clientid
- clientid:设备名称

3.2.3 连接onenet时密码(Token)
3.2.3.1 前期准备
-
设备密钥


3.2.3.2 Token的组成
-
version:参数组版本号,固定为2018-10-31
-
res:可根据规则自行选择,我是选择的一机一密

-
et:访问过期时间的时间戳,参数中的et要大于当前的时间,如1785642483为2026-08-02 11:48:03
-
method:加密算法支持hmac-md5,hmac-sha1,hmac-sha256
3.2.3.3 获取Token
分为两种官方工具生成和代码生成,官方工具生成,可以快速获取Token,操作简单,但生成的Token是固定的,代码实现灵活性更高,而且可根据系统时间设置过期时间时间戳,后期更好维护。我后面选择的代码生成。
-
通过官方工具直接生成

点击安装即可,这是个绿色软件,不用安装,可以直接使用。

打开后按照前面所讲的参数填入即可,这个
key如果前面res选择一型一密就是产品密钥,如果选择一机一密就是设备密钥,最后点击Generate就可直接生成。这就是mqtt连接时使用的密码-
使用代码生成
这就得加入一个新的参数和新的算法
-
参数:StringForSignature = et + '\n' + method + '\n' + res+ '\n' + version
如:1784985295\nsha256\nproducts/k2o67E1NP7/devices/ESP32_RGB\n2018-10-31
-
sign算法:
base64(hmac_<method>(base64decode(key), utf-8(StringForSignature)))base64decode(key):对产品密钥或设备密钥进行base64解码,还原为原来的二进制数据。
StringForSignature:就是前面构造的字符串
hmac_<method>(params1,params2):将解码后的密钥作为密钥对StringForSignature进行加密
base64(params):将加密好的字符串进行base64编码。得到最终的sign
得到sign后还需两部即得到Toke
-
将每个参数均采用key=value的形式表示,并用'&'作为分隔符
如version=2018-10-31&res=products/dafdfadfafdaf/devices/che1&et=1537255523&method=sha1&sign=ZjA1NzZlMmMxYzIOTg3MjBzNjYTI2MjA4Yw=
-
最后对token中key=value的形式的value部分进行URL编码将特殊字符转为相应编码
序号 符号 编码 1 + %2B 2 空格 %20 3 / %2F 4 ? %3F 5 % %25 6 # %23 7 & %26 8 = %3D -
得到最终Token:version=2018-10-31&res=products%2F123123&et=1537255523&method=sha1&sign=ZjA1NzZlMmMxYzIOTg3MjBzNjYTI2MjA4Yw%3D
后面会演示怎么使用代码得到Token
-
-
3.3 MQTTX连接onenet
为什么要用MQTTX连接onenet,不直接使用代码。
因为这样可以方便后面排除问题是消息的格式错了,还是连接有问题。知道上报消息的结构,方便写JSON代码。当然也可以直接看官方的例程写代码,这是链接文档中心。


- 名称:随便
- 服务器地址:mqtt://mqtts.heclouds.com,地址在文档中心

-
端口:1883
-
Client ID:设备名称
-
用户名:产品ID
-
密码:Token
-
MQTT版本:3.1.1,onenet目前只支持3.1.1

连接后刷新网页显示设备在线
3.4 onenet的mqtt通信主题
这是几个代码里用到的,其他主题和这些大差不差。
属性相关主题
| 功能 | 主题 | 操作权限 |
|---|---|---|
| 设备属性上报请求 | $sys/{pid}/{device-name}/thing/property/post | 发布 |
| 设备属性上报响应 | $sys/{pid}/{device-name}/thing/property/post/reply | 订阅 |
| 设备属性设置请求 | $sys/{pid}/{device-name}/thing/property/set | 订阅 |
| 设备属性设置响应 | $sys/{pid}/{device-name}/thing/property/set_reply | 发布 |
OTA升级相关主题
| 功能 | 主题 | 操作权限 |
|---|---|---|
| 系统OTA升级通知 | $sys/{pid}/{device-name}/ota/inform | 订阅 |
| 设备回复系统OTA升级通知 | $sys/{pid}/{device-name}/ota/inform_reply | 发布 |
- pid:产品ID
- device-name:设备名称
3.5 设备属性上报
- 首先订阅“设备属性上报响应”的主题

-
Topic:就是上面设备属性上报响应的主题
-
QoS:QoS 0消息仅发送一次,无确认机制,可能丢失。
QoS 1消息可能发送多次,确保消息至少送达一次,可能重复。
QoS 2 onenet目前不支持QoS 2。
- 然后启动调试


-
上报属性

{
"id": "123",
"version": "1.0",
"params": {
"RGB_Light": {
"value": {
"red": 55,
"green": 66,
"blue": 77
}
},
"Switch": {
"value": true
}
}
}
-
发布的主题就是刚刚”设备属性上报请求“的主题
-
上报内容:
id:消息号id,发送是使用什么接收时就使用什么,
version:物模型版本号,可以不填,默认为1.0
params:请求参数就是产品属性,可以上报一个或多个,RGB_Ligth和Switch就是上面创建产品时设置 的标识符,RGB_Ligth因为设置的是结构体类型,所以上报里面要嵌套一个cJSON类型把属性全部包含了,Switch是单一的类型,就可以直接上报。 -
收到的内容:id:就是发布时的id
code:200表示成功
msg:上报成功返回success

上报成功
3.6 设备属性设置
- 添加订阅,将主题改为“设备属性设置请求“的主题

- 设置相关属性

- 就可以收到属性设置的消息了。

回复消息为,根据自己情况是否回复属性设置成功,失败code随便返回一个值。
{
"id": "15",
"code": 200,
"msg": "success"
}
id:就是发布时的id
code:200表示成功
msg:上报成功返回success
3.7 ota升级通知
ota升级和上面两个基本相同,这是订阅的主题,当有ota升级时会发布通知。
这是发送的消息
{
"id": "123",
"version": "1.0",
"params": [" xxxxxxx-OTA-C"]
}
这是要回复的消息。
{
"id":"123",
"code":200,
"msg":"xxxx"
"data":{
“Xxxx”
}
}
id,code,msg和上面讲的一样。
data是用户数据,可以随便填写。
因为这个通知有时候不会立马通知,可能会延迟很长时间才通知,所以后面升级是在每次开机时检查是否有ota任务,有就升级,ota升级是使用http连接服务器进行升级的各种操作,后面会详细讲解,这里只是通知是否有ota任务。
到这里就结束了,后面就可以根据onenet发送的消息写代码。
3.8 使用mqtts连接onenet
这一步也是为了后面排除连接时的问题
mqtts连接时的证书


和前面设置基本一样,只需要修改
-
服务器地址:mqtts://mqttstls.heclouds.com

-
端口:8883
-
把SSL关掉否则连接不成功
-
CA文件:就是证书下载的位置
4. 设置发送的cJSON数据格式
这里都是用的一些创建以及添加cJSON对象一些函数
4.1onenet_dm.c
#include "OneNet_dm.h"
static int s_onenet_id = 0;
/* 生成物模型描述结构体 */
ONENET_DM_DES *onenet_malloc_des(ONENET_DM_TYPE type)
{
char id[13];
/* 创建物模型描述结构体 */
ONENET_DM_DES *dm = (ONENET_DM_DES *)malloc(sizeof(ONENET_DM_DES));
if(dm)
{
memset(dm,0,sizeof(ONENET_DM_DES)); /* dm结构体里的内容清空 */
dm->dm_js = cJSON_CreateObject(); /* 创建 cJSON 对象 */
switch (type)
{
case ONENET_DM_POST: /* 设置属性上报固定格式 */
snprintf(id,sizeof(id),"%d",s_onenet_id);
cJSON_AddStringToObject(dm->dm_js,"id",id); /* 往cJSON对象添加一个字符串键值对,表示消息ID */
cJSON_AddStringToObject(dm->dm_js,"version","1.0"); /* 固定为这个参数 */
cJSON_AddObjectToObject(dm->dm_js,"params"); /* 添加一个空的cJSON对象 存放参数*/
break;
case ONENET_DM_SET_ACK:
break;
case ONENET_DM_OTA_ACK:
break;
default:
break;
}
/* 报文的packer ID */
s_onenet_id++;
if(s_onenet_id >= 8192)
s_onenet_id = 0;
return dm;
}
return NULL;
}
/* 往物模型描述结构体添加属性值 */
void onenet_set_dm_int(ONENET_DM_DES* dm,const char* parsms_name,int params_value)
{
if(dm)
{
cJSON* params = cJSON_GetObjectItem(dm->dm_js,"params");
if(params)
{
cJSON* params_name = cJSON_AddObjectToObject(params,parsms_name);
cJSON_AddNumberToObject(params_name,"value",params_value);
}
}
}
/* 上报属性回复*/
void onenet_set_dm_property_ack(ONENET_DM_DES* dm,char *ack_id,int code,const char* msg)
{
if(dm)
{
cJSON* property = dm->dm_js;
cJSON_AddStringToObject(property,"id",ack_id);
cJSON_AddNumberToObject(property,"code",code);
cJSON_AddStringToObject(property,"msg",msg);
}
}
/* OTA升级回复*/
void onenet_dm_ota_ack(ONENET_DM_DES* dm,char *ack_id,int code,const char* msg)
{
if(dm)
{
cJSON* property = dm->dm_js;
cJSON_AddStringToObject(property,"id",ack_id);
cJSON_AddNumberToObject(property,"code",code);
cJSON_AddStringToObject(property,"msg",msg);
/* 回复自定义数据 */
cJSON* ota_data_js = cJSON_CreateObject();
cJSON_AddStringToObject(ota_data_js,"version","v1.0.5");
cJSON_AddItemToObject(property,"data",ota_data_js);
}
}
/* 生成cJSON字符串,保存在dm_js_str中 */
void onenet_dm_serialize(ONENET_DM_DES* dm)
{
if(dm)
{
if(dm->dm_js_str)
{
cJSON_free(dm->dm_js_str);
dm->dm_js_str = NULL;
}
dm->dm_js_str = cJSON_PrintUnformatted(dm->dm_js);
}
}
/* 释放描述物模型结构体 */
void onenet_dm_free(ONENET_DM_DES* dm)
{
if(dm)
{
if(dm->dm_js)
{
cJSON_Delete(dm->dm_js);
dm->dm_js = NULL;
}
if(dm->dm_js_str)
{
cJSON_free(dm->dm_js_str);
dm->dm_js_str = NULL;
}
free(dm);
dm = NULL;
}
}
4.2onenet_dm.h
#ifndef __ONENET_DM_H__
#define __OONNET_DM_H__
#include "cJSON.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
typedef enum
{
ONENET_DM_POST,
ONENET_DM_SET_ACK,
ONENET_DM_OTA_ACK,
}ONENET_DM_TYPE;
typedef struct
{
cJSON* dm_js;
char * dm_js_str;
}ONENET_DM_DES;
/* 生成物模型描述结构体 */
ONENET_DM_DES *onenet_malloc_des(ONENET_DM_TYPE type);
/* 往物模型描述结构体添加属性值 */
void onenet_set_dm_int(ONENET_DM_DES* dm,const char* params_name,int params_value);
/* 上报属性回复*/
void onenet_set_dm_property_ack(ONENET_DM_DES* dm,char *property_ack_id,int code,const char* msg);
/* OTA升级回复*/
void onenet_dm_ota_ack(ONENET_DM_DES* dm,char *ack_id,int code,const char* msg);
/* 生成cJSON字符串,保存在dm_js_str中 */
void onenet_dm_serialize(ONENET_DM_DES* dm);
/* 释放描述物模型结构体 */
void onenet_dm_free(ONENET_DM_DES* dm);
#endif
5. mqtt连接
5.5 bsp_mqtt.c
#include "bsp_mqtt.h"
#define TAG "OneNet_Message"
/* mqtt连接控制句柄 */
static esp_mqtt_client_handle_t mqtt_client_handle = NULL;
/* wifi 连接事件组 */
extern EventGroupHandle_t g_xEventWifiConnect;
/* URL编码索引和数据 */
char onenet_URL_index[8] = { '+', ' ', '/', '?', '%', '#', '&', '=' };
char onenet_URL_data[8][3] = { "%2B","%20","%2F","%3F","%25","%23","%26","%3D"};
/* onenet证书 */
extern char * g_OneNet_ca;
/* mqtt连接标志 */
static char is_mqtt_connect = 0;
/* onenet属性设置回调函数 */
static property_callback_t onenet_property_callback = NULL;
/* MQTT 事件回调函数 */
void MQTT_Event_handler(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
esp_mqtt_event_t* Data = (esp_mqtt_event_t*)event_data;
switch (event_id)
{
case MQTT_EVENT_CONNECTED: /* mqtt 连接成功事件 */
ESP_LOGI(TAG,"MQTT CONNECT");
char property[128];
/* 订阅设备属性上报响应,设备属性设置请求,系统OTA升级主题 */
sprintf(property,"$sys/%s/%s/thing/property/post/reply",MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME);
esp_mqtt_client_subscribe_single(mqtt_client_handle,property,0);
sprintf(property,"$sys/%s/%s/thing/property/set",MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME);
esp_mqtt_client_subscribe_single(mqtt_client_handle,property,0);
sprintf(property,"$sys/%s/%s/ota/inform",MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME);
esp_mqtt_client_subscribe_single(mqtt_client_handle,property,0);
/* 上报版本号并检查是否有升级任务 */
xTaskCreatePinnedToCore(onenet_ota_task,"onenet_ota_task",8192,NULL,2,NULL,1);
is_mqtt_connect = 1;
break;
case MQTT_EVENT_DISCONNECTED: /* mqtt 连接断开事件 */
is_mqtt_connect = 0;
ESP_LOGI(TAG,"MQTT DISCONNECT");
break;
case MQTT_EVENT_PUBLISHED: /* mqtt发布事件 */
ESP_LOGI(TAG,"MQTT PUBLISH");
break;
case MQTT_EVENT_SUBSCRIBED: /* mqtt订阅事件 */
ESP_LOGI(TAG,"MQTT SUBSCRIBE");
break;
case MQTT_EVENT_DATA: /* 获取到数据 */
ESP_LOGI(TAG,"Topic:%.*s",Data->topic_len,Data->topic);
ESP_LOGI(TAG,"data:%.*s",Data->data_len,Data->data);
/* 收到平台的属性设置 */
if(strstr(Data->topic,"/property/set"))
{
char property_ack_id[13];
cJSON* data_js = cJSON_Parse(Data->data);
/* 保存消息ID回复时使用 */ sscanf(cJSON_GetStringValue(cJSON_GetObjectItem(data_js,"id")),"%13s",property_ack_id);
cJSON* params_js = cJSON_GetObjectItem(data_js,"params");
/* 参数存在并且注册了属性设置回调函数 */
if(params_js && onenet_property_callback)
{
cJSON* property_name_js = params_js->child;
while(property_name_js)
{
onenet_property_callback(property_name_js,property_name_js->string,property_ack_id);
property_name_js = property_name_js->next;
}
}
cJSON_Delete(data_js);
}
else if(strstr(Data->topic,"/ota/inform")) /* 收到OTA升级消息 */
{
char ota_ack_id[13];
cJSON* data_js = cJSON_Parse(Data->data);
/* 保存消息ID回复时使用 */ sscanf(cJSON_GetStringValue(cJSON_GetObjectItem(data_js,"id")),"%13s",ota_ack_id);
onenet_set_dm_property(ota_ack_id,200,"success");
/* 创建OTA升级任务 */
xTaskCreatePinnedToCore(onenet_ota_task,"onenet_ota_task",8192,NULL,2,NULL,1);
}
break;
default:
break;
}
}
/**
*@brief base64解码
*@param input: 要解码的字符串
*@param input_len: 解码字符串的长度
*@param base64_decoded: 解码后的字符串
*@param base64_decoded_len: 解码后的字符串长度
*@retval 无
*/
static void onenet_device_key_base64_decoded(char* input,uint8_t input_len,unsigned char* base64_decoded,uint8_t *base64_decoded_len)
{
/* 编码长度 */
size_t dec_len;
/* 调用时返回错误正常,直接读取解码长度就行*/
int ret = mbedtls_base64_decode(
NULL, // 输出缓冲区
0, // 缓冲区大小
&dec_len, // 实际解码长度
(const unsigned char *)input, // 输入字符串
input_len // 输入长度
);
unsigned char* onenet_base64_decoded = malloc(dec_len + 1); // 输出缓冲区
// Base64 解码
ret = mbedtls_base64_decode(
onenet_base64_decoded, // 输出缓冲区
dec_len, // 缓冲区大小
&dec_len, // 实际解码长度
(const unsigned char *)input, // 输入字符串
input_len // 输入长度
);
if (ret != 0) {
ESP_LOGI("Base64"," Decoded: %d\n", ret);
return;
}
// 确保解码后数据以 \0 结尾(字符串形式)
onenet_base64_decoded[dec_len] = '\0';
memcpy(base64_decoded,onenet_base64_decoded,dec_len + 1);
*base64_decoded_len = dec_len + 1;
free(onenet_base64_decoded);
}
/**
*@brief hamc-sha256加密
*@param input: 要加密的字符串
*@param input_len: 加密字符串的长度
*@param base64_decoded: 加密的密钥
*@param base64_decoded_len: 加密的密钥长度
*@param output: 加密后的字符串
*@retval 无
*/
static void calc_hmac(const unsigned char* input,uint8_t input_len,const unsigned char* key,uint8_t key_len,uint8_t* output)
{
mbedtls_md_context_t ctx;
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx,md_info,1); /* 1表示使用hmac */
mbedtls_md_hmac_starts(&ctx,key,key_len); /* 长度要加上'\0' !!!!!!!!!!!!!*/
mbedtls_md_hmac_update(&ctx,input,input_len);
mbedtls_md_hmac_finish(&ctx,output);
mbedtls_md_free(&ctx);
}
/* 检查sha256加密后的数据 */
static void print_hex(const uint8_t* data, size_t len) {
char hex_buf[65] = {0}; // 32字节 * 2 + 1
for (int i = 0; i < len; i++) {
snprintf(&hex_buf[i*2], 3, "%02x", data[i]); // 小写十六进制
}
ESP_LOGI("HMAC", "%s",hex_buf); // 输出到日志
}
/**
*@brief URL编码
*@param input: 要编码的字符串
*@param input_len: 编码字符串的长度
*@param output: 编码后的字符串
*@retval 无
*/
static void onenet_url_encode(char* input,uint8_t input_len,char *output)
{
uint8_t i,j,k = 0;
for(i = 0;i < input_len;i++)
{
for(j = 0;j < 8;j++)
{
if(input[i] == onenet_URL_index[j])
{
memcpy(&output[k],onenet_URL_data[j],3);
k += 3;
break;
}
}
if(j == 8)
{
output[k++] = input[i];
}
}
}
/**
*@brief 获取连接Token
*@param Token: 连接所用的Token
*@retval 无
*/
void onenet_get_connect_Token(char* Token)
{
/* 初始化Token结构体 */
Tokenstructuretypedef token;
memset(&token,0,sizeof(token));
/* 1. 对设备密钥进行base64解码*/
onenet_device_key_base64_decoded(MQTT_ONENET_DEVICE_KEY,strlen(MQTT_ONENET_DEVICE_KEY),token.base64_decoded,&token.base64_decoded_len);
/* 2. 拼接 StringForSignature 字符串 */
snprintf(token.res,sizeof(token.res),"products/%s/devices/%s",MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME);
sprintf((char*)token.StringForSignature,"%s\n%s\n%.*s\n%s",MQTT_ONENET_ET,MQTT_ONENET_METHOD,strlen(token.res),token.res,MQTT_ONENET_CONNECT_VERSION);
/* 3. 使用解码后的设备密钥作为密钥加密 StringForSignature信息 */
calc_hmac(token.StringForSignature,strlen((char*)token.StringForSignature),token.base64_decoded,token.base64_decoded_len,token.hmac_result);
//print_hex(hmac_result,32);
/* 4. base64编码 */
/* 预计算base64编码后的长度 */
mbedtls_base64_encode(NULL, 0, &token.sign_len, token.hmac_result, sizeof(token.hmac_result));
int ret = mbedtls_base64_encode(token.sign,token.sign_len + 1,&token.sign_len, (const unsigned char*)token.hmac_result, sizeof(token.hmac_result));
if (ret == 0)
{
token.sign[token.sign_len] = '\0';
} else {
printf("Error: %d\n", ret);
}
/* 5. 对Token进行URL编码*/
onenet_url_encode((char*)token.sign,token.sign_len + 1,token.sign_url);
onenet_url_encode(token.res,sizeof(token.res),token.res_url);
sprintf(Token,"version=%s&res=%s&et=%s&method=%s&sign=%s",MQTT_ONENET_CONNECT_VERSION,token.res_url,MQTT_ONENET_ET,MQTT_ONENET_METHOD,token.sign_url);
// ESP_LOGI("Token:","%s",Token);
}
/* 获取OTA Token */
void onenet_get_OTA_Token(char* Token)
{
/* 初始化Token结构体 */
Tokenstructuretypedef token;
memset(&token,0,sizeof(token));
/* 1. 对设备密钥进行base64解码*/
onenet_device_key_base64_decoded(MQTT_ONENET_ACCESSKEY,strlen(MQTT_ONENET_ACCESSKEY),token.base64_decoded,&token.base64_decoded_len);
/* 2. 拼接 StringForSignature 字符串 */
snprintf(token.res,sizeof(token.res),"userid/%s",MQTT_ONENET_USERID);
sprintf((char*)token.StringForSignature,"%s\n%s\n%.*s\n%s",MQTT_ONENET_ET,MQTT_ONENET_METHOD,strlen(token.res),token.res,MQTT_ONENET_OTA_VERSION);
/* 3. 使用解码后的设备密钥作为密钥加密 StringForSignature信息 */
calc_hmac(token.StringForSignature,strlen((char*)token.StringForSignature),token.base64_decoded,token.base64_decoded_len,token.hmac_result);
// print_hex(token.hmac_result,32);
/* 4. base64编码 */
/* 预计算base64编码后的长度 */
mbedtls_base64_encode(NULL, 0, &token.sign_len, token.hmac_result, sizeof(token.hmac_result));
int ret = mbedtls_base64_encode(token.sign,token.sign_len + 1,&token.sign_len, (const unsigned char*)token.hmac_result, sizeof(token.hmac_result));
if (ret == 0)
{
token.sign[token.sign_len] = '\0';
// printf("Base64: %s\n", sign); // 打印编码结果
} else {
printf("Error: %d\n", ret);
}
/* 5. 对Token进行URL编码*/
onenet_url_encode((char*)token.sign,token.sign_len + 1,token.sign_url);
onenet_url_encode(token.res,sizeof(token.res),token.res_url);
sprintf(Token,"version=%s&res=%s&et=%s&method=%s&sign=%s",MQTT_ONENET_OTA_VERSION,token.res_url,MQTT_ONENET_ET,MQTT_ONENET_METHOD,token.sign_url);
// ESP_LOGI("Token:","%s",Token);
}
/* 注册属性设置回调函数 */
void onenet_set_property_cb(property_callback_t f)
{
onenet_property_callback = f;
}
/* 属性设置回调函数 */
void onenet_property_ack_callback(cJSON* property_name_js,const char* name,char* property_ack_id)
{
/* 这里"BrightValue" 和 "RGB_Light" 是刚刚设置产品功能时的标识符 */
if(strstr(name,"BrightValue"))
{
int Bright_value = (int)cJSON_GetNumberValue(property_name_js);
ESP_LOGI(TAG,"bright_value: %d",Bright_value);
/* 添加设置属性功能 设置失败直接返回*/
}
if(strstr(name,"RGB_Light")) /* 这里因为RGB数据是结构体类型,所以需要多解析一层 */
{
cJSON* RGB_Light = property_name_js->child;
int Red_value = cJSON_GetNumberValue(RGB_Light);
RGB_Light = RGB_Light->next;
int Green_value = cJSON_GetNumberValue(RGB_Light);
RGB_Light = RGB_Light->next;
int Blue_value = cJSON_GetNumberValue(RGB_Light);
ESP_LOGI(TAG,"R:%d G:%d B:%d",Red_value,Green_value,Blue_value);
/* 添加设置属性功能 设置失败直接返回*/
}
/* 设置成功返回 code:200 msg:success*/
onenet_set_dm_property(property_ack_id,200,"success");
}
/* 初始化MQTT */
void MQTT_Init(void)
{
char Token[256];
onenet_get_connect_Token(Token);
esp_mqtt_client_config_t mqtt_client_config =
{
.broker.address.uri = MQTT_ONENET_URL, /* mqtt服务器URL */
.broker.address.port = MQTT_ONENET_PORT, /* mqtt连接服务器端口 */
.credentials.client_id = MQTT_ONENET_CLIENT_ID, /* mqtt客户端标识符 */
.credentials.username = MQTT_ONENET_USERNAME, /* mqtt用户名 */
.credentials.authentication.password = Token, /* mqtt连接时的密码 */
.broker.verification.certificate = (const char *)g_OneNet_ca, /* mqtt服务器证书 */
.broker.verification.skip_cert_common_name_check = true,/* 跳过域名验证 onenet证书没有域名 */
.network.timeout_ms = 15000, /* 断线重连时间 */
.session.keepalive = 120, /* 心跳间隔 */
};
/* 初始化mqtt连接 */
mqtt_client_handle = esp_mqtt_client_init(&mqtt_client_config);
if (mqtt_client_handle == NULL) {
ESP_LOGE(TAG, "MQTT客户端初始化失败");
return;
}
/* 注册mqtt客户端事件回调函数 */
esp_mqtt_client_register_event(mqtt_client_handle,MQTT_EVENT_ANY,MQTT_Event_handler,NULL);
/* 等待 wifi 连接成功再开始 mqtt 连接 */
xEventGroupWaitBits(g_xEventWifiConnect,WIFI_CONNECT_BIT | ESPTOUCH_DONE_BIT,true,false,portMAX_DELAY);
esp_err_t err = esp_mqtt_client_start(mqtt_client_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "MQTT客户端启动失败,错误码: %d", err);
return;
}
ESP_LOGI(TAG, "MQTT客户端初始化完成");
/* 注册属性设置回复回调函数 */
onenet_set_property_cb(onenet_property_ack_callback);
}
/* mqtt任务 */
void mqtt_task(void *params)
{
MQTT_Init();
uint8_t i = 12;
while(1)
{
static uint32_t reconnect_delay = 1000; // 初始1秒
if (!is_mqtt_connect) /* 连接失败 */
{
ESP_LOGI(TAG, "尝试重连,等待%lums", reconnect_delay);
vTaskDelay(pdMS_TO_TICKS(reconnect_delay));
esp_mqtt_client_reconnect(mqtt_client_handle);
reconnect_delay *= 2; // 退避加倍
if(reconnect_delay > 60000)
reconnect_delay = 60000;
}
else /* 连接成功 */
{
reconnect_delay = 1000;
i++;
if(is_mqtt_connected())
{
/* 这里可根据自己的情况设置上报的属性 */
/* "BrightValue" 是刚刚设置产品功能时的标识符 */
// onenet_post_property_int("BrightValue",i);
vTaskDelay(1000);
}
}
}
}
/* 检查mqtt是否连接 */
char is_mqtt_connected(void)
{
return is_mqtt_connect;
}
/**
*@brief 上报int型属性
*@param name: 键
*@param value: 值
*@retval 无
*/
void onenet_post_property_int(const char* name,int value)
{
/* 生成物模型描述结构体 */
ONENET_DM_DES * dm= onenet_malloc_des(ONENET_DM_POST);
/* 往物模型描述结构体添加属性值 */
onenet_set_dm_int(dm,name,value);
/* 生成cJSON字符串,保存在dm_js_str中 */
onenet_dm_serialize(dm);
/* 发布消息 */
char property_set_Topic[128];
sprintf(property_set_Topic,"$sys/%s/%s/thing/property/post",MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME);
esp_mqtt_client_publish(mqtt_client_handle,property_set_Topic,dm->dm_js_str,strlen(dm->dm_js_str),1,0);
ESP_LOGI(TAG,"%s",dm->dm_js_str);
// ESP_LOGI(TAG,"%s",property);
/* 释放描述物模型结构体 */
onenet_dm_free(dm);
}
/**
*@brief 回复属性设置消息
*@param property_ack_id: 回复的消息ID
*@param code: 设置成功与否的代码,200表示成功
*@param msg: 是否设置成功,success表示设置成功
*@retval 无
*/
void onenet_set_dm_property(char* property_ack_id,int code,const char* msg)
{
ONENET_DM_DES * dm = onenet_malloc_des(ONENET_DM_SET_ACK);
onenet_set_dm_property_ack(dm,property_ack_id,code,msg);
onenet_dm_serialize(dm);
char property_set_ack_Topic[128];
sprintf(property_set_ack_Topic,"$sys/%s/%s/thing/property/set_reply",MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME);
esp_mqtt_client_publish(mqtt_client_handle,property_set_ack_Topic,dm->dm_js_str,strlen(dm->dm_js_str),1,0);
ESP_LOGI(TAG,"%s",dm->dm_js_str);
onenet_dm_free(dm);
}
/* mqtts连接时的证书 */
char *g_OneNet_ca =
"-----BEGIN CERTIFICATE-----\n"
"MIIDOzCCAiOgAwIBAgIJAPCCNfxANtVEMA0GCSqGSIb3DQEBCwUAMDQxCzAJBgNV\n"
"BAYTAkNOMQ4wDAYDVQQKDAVDTUlPVDEVMBMGA1UEAwwMT25lTkVUIE1RVFRTMB4X\n"
"DTE5MDUyOTAxMDkyOFoXDTQ5MDUyMTAxMDkyOFowNDELMAkGA1UEBhMCQ04xDjAM\n"
"BgNVBAoMBUNNSU9UMRUwEwYDVQQDDAxPbmVORVQgTVFUVFMwggEiMA0GCSqGSIb3\n"
"DQEBAQUAA4IBDwAwggEKAoIBAQC/VvJ6lGWfy9PKdXKBdzY83OERB35AJhu+9jkx\n"
"5d4SOtZScTe93Xw9TSVRKrFwu5muGgPusyAlbQnFlZoTJBZY/745MG6aeli6plpR\n"
"r93G6qVN5VLoXAkvqKslLZlj6wXy70/e0GC0oMFzqSP0AY74icANk8dUFB2Q8usS\n"
"UseRafNBcYfqACzF/Wa+Fu/upBGwtl7wDLYZdCm3KNjZZZstvVB5DWGnqNX9HkTl\n"
"U9NBMS/7yph3XYU3mJqUZxryb8pHLVHazarNRppx1aoNroi+5/t3Fx/gEa6a5PoP\n"
"ouH35DbykmzvVE67GUGpAfZZtEFE1e0E/6IB84PE00llvy3pAgMBAAGjUDBOMB0G\n"
"A1UdDgQWBBTTi/q1F2iabqlS7yEoX1rbOsz5GDAfBgNVHSMEGDAWgBTTi/q1F2ia\n"
"bqlS7yEoX1rbOsz5GDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAL\n"
"aqJ2FgcKLBBHJ8VeNSuGV2cxVYH1JIaHnzL6SlE5q7MYVg+Ofbs2PRlTiWGMazC7\n"
"q5RKVj9zj0z/8i3ScWrWXFmyp85ZHfuo/DeK6HcbEXJEOfPDvyMPuhVBTzuBIRJb\n"
"41M27NdIVCdxP6562n6Vp0gbE8kN10q+ksw8YBoLFP0D1da7D5WnSV+nwEIP+F4a\n"
"3ZX80bNt6tRj9XY0gM68mI60WXrF/qYL+NUz+D3Lw9bgDSXxpSN8JGYBR85BxBvR\n"
"NNAhsJJ3yoAvbPUQ4m8J/CoVKKgcWymS1pvEHmF47pgzbbjm5bdthlIx+swdiGFa\n"
"WzdhzTYwVkxBaU+xf/2w\n"
"-----END CERTIFICATE-----";
5.6 bsp_mqtt.h
#ifndef __BSP_MQTT_H__
#define __BSP_MQTT_H__
#include "mqtt_client.h"
#include "bsp_wifi.h"
#include "esp_sntp.h"
#include "OneNet_dm.h"
#include "base64.h"
#include "mbedtls/sha256.h"
#include "mbedtls/md.h"
#include "esp_partition.h"
#include "esp_ota_ops.h"
#include "onenet_ota.h"
#define MQTT_ONENET_PRODUCT_ID "k2o67E1NP7" /* 产品ID */
#define MQTT_ONENET_DRVICE_NAME "ESP32_RGB" /* 设备名称 */
#define MQTT_ONENET_USERID "459616" /* 用户ID 后面会说到*/
#define MQTT_ONENET_ACCESSKEY "fsdfla1465fs1dsf" /* 用户访问密钥 */
#define MQTT_ONENET_OTA_VERSION "2022-05-01" /* 使用用户ID加用户密钥使用这个日期 */
#define MQTT_ONENET_ET "1784985295" /* 访问时间的时间戳 */
#define MQTT_ONENET_METHOD "sha256" /* Token加密方法 */
#define MQTT_ONENET_CONNECT_VERSION "2018-10-31" /* 连接版本目前只能是它 */
#define MQTT_ONENET_DEVICE_KEY "cjU9nfVGOQU6pxQ055RT42UmxBNBFmb5V3R9F3UUc=" /* 设备密钥 */
#define MQTT_ONENET_URL "mqtts://mqttstls.heclouds.com" /* mqtt连接url */
#define MQTT_ONENET_PORT 8883 /* mqtts连接端口 */
#define MQTT_ONENET_USERNAME "k2o67E1NP7" /* 产品ID */
#define MQTT_ONENET_CLIENT_ID "ESP32_RGB" /* 设备名称 */
#define PROPERTY_SET_ACK BIT0 /* 属性设置回应 */
typedef struct
{
unsigned char base64_decoded[128]; /* base64 解码后的密钥 */
uint8_t base64_decoded_len; /* 解码后的密钥长度 */
unsigned char StringForSignature[128]; /* StringForSignature 字符串 */
char res[128]; /* res */
uint8_t hmac_result[32]; /* sha256 加密后的字符串 */
uint8_t sign[128]; /* sign */
size_t sign_len; /* sign 长度 */
char sign_url[128]; /* url编码后的sign */
char res_url[128]; /* url编码后的res */
}Tokenstructuretypedef;
/* 获取连接Token */
void onenet_get_connect_Token(char* Token);
/* 获取OTA Token */
void onenet_get_OTA_Token(char* Token);
/* 获取当前版本号 */
void onenet_get_current_version(char *version);
/* 属性回复回调函数 */
typedef void (*property_callback_t)(cJSON* property_name_js,const char* property_name,char* property_ack_id);
/* 初始化MQTT */
void MQTT_Init(void);
/* MQTT任务*/
void mqtt_task(void *params);
/* 检查MQTT是否连接 */
char is_mqtt_connected(void);
/* 上报int型属性 */
void onenet_post_property_int(const char* name,int value);
/* 回复属性设置消息 */
void onenet_set_dm_property(char* property_ack_id,int code,const char* msg);
/* OTA回复消息 */
void onenet_ota_dm_ack(char* ack_id,int code,const char* msg);
/* 注册属性回复回调函数 */
void onenet_set_property_cb(property_callback_t f);
#endif
5.7 功能展示
- 属性设置



-
属性上报


三. OTA升级
1. OTA升级Token(http报文头信息)
1.1 Token参数
-
res:多了一种选择,形式为userid/{userid},
-
version:签名算法版本号,如果前面选择userid/{userid},就改为2022-05-01
-
密钥:如果前面选择userid/{userid}就为用户访问密钥
ps:如果嫌麻烦可以不改,刚开始我还以为必须改为userid/{userid},后面试了一下原来的Token也能使用。直接跳到设置升级包。


1.2 获取Token
-
官方工具获取

修改key和res,version后点击生成即可
-
代码获得,其他编码,加密,解码代码并无修改只修改了key和res,version的值
#define MQTT_ONENET_USERID "459616"
#define MQTT_ONENET_ACCESSKEY "asdfjlaksdfj"
#define MQTT_ONENET_OTA_VERSION "2022-05-01"
/**
*@brief 获取OTA的Token
*@param Token: OTA所用的Token
*@retval 无
*/
void onenet_get_OTA_Token(char* Token)
{
/* 初始化Token结构体 */
Tokenstructuretypedef token;
memset(&token,0,sizeof(token));
/* 1. 对设备密钥进行base64解码*/
onenet_device_key_base64_decoded(MQTT_ONENET_ACCESSKEY,strlen(MQTT_ONENET_ACCESSKEY),token.base64_decoded,&token.base64_decoded_len);
/* 2. 拼接 StringForSignature 字符串 */
snprintf(token.res,sizeof(token.res),"userid/%s",MQTT_ONENET_USERID);
sprintf((char*)token.StringForSignature,"%s\n%s\n%.*s\n%s",MQTT_ONENET_ET,MQTT_ONENET_METHOD,strlen(token.res),token.res,MQTT_ONENET_OTA_VERSION);
/* 3. 使用解码后的设备密钥作为密钥加密 StringForSignature信息 */
calc_hmac(token.StringForSignature,strlen((char*)token.StringForSignature),token.base64_decoded,token.base64_decoded_len,token.hmac_result);
// print_hex(token.hmac_result,32);
/* 4. base64编码 */
/* 预计算base64编码后的长度 */
mbedtls_base64_encode(NULL, 0, &token.sign_len, token.hmac_result, sizeof(token.hmac_result));
int ret = mbedtls_base64_encode(token.sign,token.sign_len + 1,&token.sign_len, (const unsigned char*)token.hmac_result, sizeof(token.hmac_result));
if (ret == 0)
{
token.sign[token.sign_len] = '\0';
// printf("Base64: %s\n", sign); // 打印编码结果
} else {
printf("Error: %d\n", ret);
}
/* 5. 对Token进行URL编码*/
onenet_url_encode((char*)token.sign,token.sign_len + 1,token.sign_url);
onenet_url_encode(token.res,sizeof(token.res),token.res_url);
sprintf(Token,"version=%s&res=%s&et=%s&method=%s&sign=%s",MQTT_ONENET_OTA_VERSION,token.res_url,MQTT_ONENET_ET,MQTT_ONENET_METHOD,token.sign_url);
// ESP_LOGI("Token:","%s",Token);
}
2. 设置升级包


- 升级包类型:选择完整包
- 升级包名称:随便
- 所属产品:选择刚刚创建的产品
- 升级模块:自行选择,这个就是后续查询升级包的参数
- 目标版本:随便
- 上传文件:升级包的文件
- 最后点击添加即可
3. onenet的ota升级流程
使用NetAssist软件演示,,这也是和上面MQTTX一样,测试连接参数,和发送接收数据格式,方便后面写代码。

左上角设置和我设置相同就行。后续的代码将自己的信息填入后完整复制到发送消息对话框点击发送就行。
ps:消息最后要加两个换行(shift+enter)
3.1 上报版本号
发送
POST /fuse-ota/{pro_id}/{dev_name}/version HTTP/1.1
Content-Type: application/json
Authorization:填自己的Token
host:iot-api.heclouds.com
Content-Length:41
{"s_version":"V2.2", "f_version": "V2.2"}
-
pro_id:产品ID
-
dev_name:设备名称
-
Authorization:Token
-
s_version:mcu软件版本号
-
f_verison:模块版本号
可以上报两个版本号。
回应
HTTP/1.1 200 OK
Date: Sat, 02 Aug 2025 09:50:51 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 71
Connection: keep-alive
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Server: nginx
Pragma: no-cache
{"code":0,"msg":"succ","request_id":"e9de51c3ab0c460c93305489c29ae8db"}
-
code:错误码,0表示请求成功
-
mag:错误描述信息
-
request_id:API请求链路id
上报完版本号就可以验证升级了


3.2 检测升级任务
回应
GET /fuse-ota/{pro_id}/{dev_name}/check?type=1&version=1.2 HTTP/1.1
Content-Type: application/json
Authorization:填自己的Token
host:iot-api.heclouds.com
- type:1是模块版本号,2是mcu软件版本号,
- version:设备当前版本
接收
1.有升级任务并且是检查时type对应的模块
HTTP/1.1 200 OK
Date: Sat, 02 Aug 2025 09:59:49 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 184
Connection: keep-alive
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Server: nginx
Pragma: no-cache
{"code":0,"msg":"succ","data":{"target":"11","tid":1242714,"size":1030128,"md5":"d845ceee6ba5f776a04e7fe498ff0594","status":1,"type":1},"request_id":"dca25839411944bab9a92ed9a688c8fa"}
- target:升级任务的目标版本
- tid:任务ID,下载升级包时需要使用
- size:升级包大小
- md5:升级文件的md5
- status:升级状态,1 :待升级, 2 :下载中, 3 :升级中
- type:1是完整包,2是差分包
- 有升级任务但不是检查时type对应的模块
HTTP/1.1 200 OK
Date: Sat, 02 Aug 2025 10:11:06 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 86
Connection: keep-alive
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Server: nginx
Pragma: no-cache
{"code":12010,"msg":"task type error","request_id":"10c475f9b23e4cbbbc05678e6f569b5b"}
"msg":"task type error"
- 没有升级任务
HTTP/1.1 200 OK
Date: Sat, 02 Aug 2025 10:13:05 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 80
Connection: keep-alive
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Server: nginx
Pragma: no-cache
{"code":12012,"msg":"not exist","request_id":"71142a0a43c74e6b832aee2b71df8ab0"}
"msg":"not exist"
3.3 下载升级包
发送
GET /fuse-ota/{pro_id}/{dev_name}/{tid}/download HTTP/1.1
Content-Type: application/json
Authorization:填自己的Token
host:iot-api.heclouds.com
- tid:检测升级包成功获得的tid
接收
HTTP/1.1 200 OK
Date: Sat, 02 Aug 2025 10:23:53 GMT
Content-Type: application/octet-stream;charset=UTF-8
Content-Length: 8314
Connection: keep-alive
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Content-Disposition: attachment; filename="44"
Ota-Errno: 0
Server: nginx
Pragma: no-cache
升级包数据
3.4 再次上报版本号,升级成功

后续代码升级过程就按这个流程来。
4 获取系统版本号
在项目根目录下的CMakeLists文件添加set(PROJECT_VER "V1.0.0")

获取版本号代码
/* 获取当前版本号 */
void onenet_get_current_version(char *version)
{
static char Version[33] = {0};
if(Version[0] == 0)
{
const esp_partition_t* running = esp_ota_get_running_partition();
esp_app_desc_t running_desc;
esp_ota_get_partition_description(running,&running_desc);
snprintf(Version,sizeof(Version),"%s",running_desc.version);
}
sprintf(version,"%s",Version);
}
5 onenet_ota.c
#include "onenet_ota.h"
#define TAG "ONENET_HTTP"
/* 存放版本号 onenet可上报s_version mcu软件版本号 和 f_version 模块版本号这里只上报了模块版本号*/
static char version[33];
/* https连接时的服务器证书 */
extern char *g_OneNet_http_ca;
/* https 报文接收缓冲区 */
static char *http_receive_buffer = NULL;
/* https 报文接收缓冲区长度 */
static uint16_t http_receive_buffer_len = 0;
/* https 报文接收完成标志位 */
static uint8_t http_receive_finish = 0;
/* ota任务结构体 */
onenet_http_ota_task_structuretypedef ota_task_struct;
/* http客户端连接句柄 */
esp_http_client_handle_t onenet_http_client_handle;
/* ota升级标志位 */
static bool ota_flag = false;
/* HTTP 事件回调函数 */
esp_err_t onenet_http_event_callback(esp_http_client_event_t *evt)
{
esp_err_t http_err = ESP_OK;
switch (evt->event_id)
{
case HTTP_EVENT_ON_CONNECTED: /* http连接成功 */
ESP_LOGI(TAG,"HTTP CONNECT SUCCESS");
/* 设置HTTP 请求头*/
http_err = esp_http_client_set_header(evt->client,"Content-Type","application/json");
if(http_err != ESP_OK)
ESP_LOGI(TAG,"HTTP set header fail");
char Token[256];
/* 获取ota的Token 也可使用mqtt连接时的Token*/
onenet_get_OTA_Token(Token);
http_err = esp_http_client_set_header(evt->client,"Authorization",Token);
if(http_err != ESP_OK)
ESP_LOGI(TAG,"HTTP set header fail");
ESP_LOGI(TAG,"message head set success");
break;
case HTTP_EVENT_DISCONNECTED: /* http连接断开 */
ESP_LOGI(TAG,"HTTP DISCONNECT");
break;
case HTTP_EVENT_ON_DATA: /* 获取到数据 */
// ESP_LOGI(TAG,"HTTP RECEIVE DATA");
/* 连续读取数据 */
if(evt->data_len > 0)
{
char * new_data = realloc(http_receive_buffer,http_receive_buffer_len + evt->data_len + 1);
if(new_data)
{
http_receive_buffer = new_data;
memcpy(http_receive_buffer + http_receive_buffer_len,evt->data,evt->data_len);
http_receive_buffer_len += evt->data_len;
http_receive_buffer[http_receive_buffer_len] = '\0';
}
}
break;
case HTTP_EVENT_ON_FINISH: /* 数据接收完成 */
ESP_LOGI(TAG,"HTTP FINISH SESSION");
ESP_LOGI("TAG","http receive data: %.*s",http_receive_buffer_len,http_receive_buffer);
http_receive_finish = 1; /* 标志位置1 */
break;
default:
break;
}
return ESP_OK;
}
/* 发送http报文 */
void onenet_http_send_message(onenet_http_ota_task_structuretypedef *ota_task_struct)
{
esp_err_t http_err = ESP_OK;
char onenet_url[128];
switch (ota_task_struct->type)
{
case POST_OTA_VERSION: /* 上报版本号 */
sprintf(onenet_url,"%s/%s/%s/%s",ONENET_URL,MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME,"version");
/* 设置连接时的url */
esp_http_client_set_url(onenet_http_client_handle,onenet_url);
/* 设置http请求方式 */
esp_http_client_set_method(onenet_http_client_handle,HTTP_METHOD_POST);
/* 设置报文主体 */
onenet_get_current_version(version);
cJSON *onenet_post_version_data_js = cJSON_CreateObject();
cJSON_AddStringToObject(onenet_post_version_data_js,"s_version","V1.0");
cJSON_AddStringToObject(onenet_post_version_data_js,"f_version",version);
char *temp= cJSON_PrintUnformatted(onenet_post_version_data_js);
char onenet_post_version_data[64];
memcpy(onenet_post_version_data,temp,strlen(temp));
cJSON_free(temp);
cJSON_Delete(onenet_post_version_data_js);
/* 设置报文主体,可以上报 s_version mcu软件版本号 和 f_version 模块版本号 */
http_err = esp_http_client_set_post_field(onenet_http_client_handle,onenet_post_version_data,strlen(onenet_post_version_data));
// ESP_LOGI(TAG,"%s",onenet_post_version_data);
if(http_err != ESP_OK)
ESP_LOGI(TAG,"HTTP set post field fail");
/* 升级成功防回滚 */
if (esp_ota_mark_app_valid_cancel_rollback() == ESP_OK) {
ESP_LOGI(TAG, "App is valid, rollback cancelled successfully");
} else {
ESP_LOGE(TAG, "Failed to cancel rollback");
}
break;
case CHECK_OTA_TASK: /* 查看是否有OTA任务 */
/* 查询软件或模块是否有升级 并上报现在查询的版本 */
sprintf(onenet_url,"%s/%s/%s/check?type=1&version=%s",ONENET_URL,MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME,version);
/* 设置连接时的url和http请求方式 */
esp_http_client_set_url(onenet_http_client_handle,onenet_url);
esp_http_client_set_method(onenet_http_client_handle,HTTP_METHOD_GET);
break;
case DOENLOAD_OTA_PACKAGE:
// sprintf(onenet_url,"%s/%s/%s/%lu/download",ONENET_URL,MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME,ota_task_struct->ota_task_tid);
break;
case POST_OTA_STATUS:
break;
default:
break;
}
/* 发送报文 */
http_err = esp_http_client_perform(onenet_http_client_handle);
if(http_err != ESP_OK)
ESP_LOGI(TAG,"HTTP trans fail");
/* 等待接收完报文 */
while (!http_receive_finish);
/* 查看http响应 */
switch (ota_task_struct->type)
{
case POST_OTA_VERSION: /* 上报版本回应 */
if(strstr(http_receive_buffer,"succ"))
ESP_LOGI(TAG,"post version success");
else
ESP_LOGI(TAG,"post version fail");
break;
case CHECK_OTA_TASK: /* 查询升级任务回应 */
if(strstr(http_receive_buffer,"target") && strstr(http_receive_buffer,"tid") && strstr(http_receive_buffer,"size")) /* 有升级任务并且是检查时type对应的模块 */
{
cJSON* root = cJSON_Parse(http_receive_buffer);
if(root == NULL)
{
ESP_LOGI(TAG,"receive type error");
return;
}
/* 解析target,tid,size 数据*/
cJSON* data = cJSON_GetObjectItem(root,"data");;
cJSON* target = cJSON_GetObjectItem(data,"target");
cJSON* tid = cJSON_GetObjectItem(data,"tid");
cJSON* size = cJSON_GetObjectItem(data,"size");
if(target && cJSON_IsString(target))
sprintf(ota_task_struct->target,"%s",cJSON_GetStringValue(target));
else
ESP_LOGI(TAG,"not target field");
if(tid && cJSON_IsNumber(tid))
ota_task_struct->ota_task_tid = (uint32_t)cJSON_GetNumberValue(tid);
else
ESP_LOGI(TAG,"not tid field");
if(size && cJSON_IsNumber(size))
ota_task_struct->ota_task_size = (uint32_t)cJSON_GetNumberValue(size);
else
ESP_LOGI(TAG,"not size field");
cJSON_Delete(root);
ESP_LOGI(TAG,"target:%s,tid:%lu,size:%lu",ota_task_struct->target,ota_task_struct->ota_task_tid,ota_task_struct->ota_task_size);
ota_flag = true; /* 设置ota标志位 */
}
else if(strstr(http_receive_buffer,"task type error")) /*有升级任务但不是检查时type对应的模块 */
ESP_LOGI(TAG,"ota task type error");
else if(strstr(http_receive_buffer,"not exist")) /* 没有升级任务*/
ESP_LOGI(TAG,"not ota task");
else
ESP_LOGI(TAG,"check ota task fail");
break;
case DOENLOAD_OTA_PACKAGE:
break;
case POST_OTA_STATUS:
break;
default:
break;
}
http_receive_finish = 0;
/* 释放realloc分配的空间 */
free(http_receive_buffer);
http_receive_buffer = NULL;
http_receive_buffer_len = 0;
/* 获取状态码 */
int status_code = esp_http_client_get_status_code(onenet_http_client_handle);
ESP_LOGI(TAG,"status:%d",status_code);
}
/* OTA升级设置 */
void onenet_ota_task(void* params)
{
esp_err_t http_err = ESP_OK;
esp_http_client_config_t onenet_http_client_config =
{
.url = ONENET_URL, /* 连接时的url */
.port = 443, /* 通信端口https选择443 */
.method = HTTP_METHOD_POST, /* http请求方法 */
.cert_pem = (const char *)g_OneNet_http_ca, /* 服务器证书 */
.skip_cert_common_name_check = false, /* 需要域名验证 */
.event_handler = onenet_http_event_callback, /* 事件回调函数 */
.timeout_ms = 30*1000, /* 超时时间 */
.keep_alive_enable = true, /* 保持连接 */
};
/* HTTP 初始化 */
onenet_http_client_handle = esp_http_client_init(&onenet_http_client_config);
if(onenet_http_client_handle == NULL)
ESP_LOGI(TAG,"HTTP Init fail");
/* 上报版本号 */
ota_task_struct.type = POST_OTA_VERSION;
onenet_http_send_message(&ota_task_struct);
/* 检查是否有下载任务 */
ota_task_struct.type = CHECK_OTA_TASK;
onenet_http_send_message(&ota_task_struct);
/* 关闭http连接并释放资源*/
esp_http_client_cleanup(onenet_http_client_handle);
/* 如果有升级任务 */
if(ota_flag == true)
{
/* 设置url */
static char onenet_url[128];
sprintf(onenet_url,"%s/%s/%s/%lu/download",ONENET_URL,MQTT_ONENET_PRODUCT_ID,MQTT_ONENET_DRVICE_NAME,ota_task_struct.ota_task_tid);
char Token[256];
onenet_get_OTA_Token(Token);
esp_http_client_config_t onenet_http_client_ota_config =
{
.url = onenet_url, /* 连接时的url */
.port = 443, /* 通信端口https选择443 */
.method = HTTP_METHOD_GET, /* http请求方法 */
.cert_pem = (const char *)g_OneNet_http_ca, /* 服务CA证书 */
.skip_cert_common_name_check = false, /* 域名验证 */
.event_handler = onenet_http_event_callback, /* 事件回调函数 */
.timeout_ms = 60*1000, /* 超时时间 */
.keep_alive_enable = true, /* 保持连接 */
};
/* 设置ota结构体 */
esp_https_ota_config_t onenet_https_ota =
{
.http_config = &onenet_http_client_ota_config,
};
/* 自动进行ota升级的全部工作 */
http_err = esp_https_ota(&onenet_https_ota);
if(http_err == ESP_OK)
{
/* 升级完成等待两秒重新启动 */
ESP_LOGI(TAG,"2s reset");
vTaskDelay(pdMS_TO_TICKS(2000));
esp_restart();
}
else
{
ESP_LOGI(TAG,"OTA fail:%d",http_err);
}
ota_flag = false;
}
vTaskDelete(NULL);
}
/* onenet进行https连时的服务器证书 */
char *g_OneNet_http_ca =
"-----BEGIN CERTIFICATE-----\n"
"MIIEfjCCA2agAwIBAgIQD+Ayq4RNAzEGxQyOE8iwaDANBgkqhkiG9w0BAQsFADBh\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"
"QTAeFw0yNDAxMTgwMDAwMDBaFw0zMTExMDkyMzU5NTlaMGExCzAJBgNVBAYTAlVT\n"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo4IBMDCC\n"
"ASwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUTiJUIBiV5uNu5g/6+rkS7QYX\n"
"jzkwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQD\n"
"AgGGMHQGCCsGAQUFBwEBBGgwZjAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZGln\n"
"aWNlcnQuY24wPwYIKwYBBQUHMAKGM2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNu\n"
"L0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBABgNVHR8EOTA3MDWgM6Axhi9odHRw\n"
"Oi8vY3JsLmRpZ2ljZXJ0LmNuL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDARBgNV\n"
"HSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAHRBl3jN7+XHBUK0dZnu\n"
"hMdoNwD1nCROU3BTIh1TNzRI0bQ0m5+C/dCRzzlqoSAFHUlOi+OiDltWkXTzmQn6\n"
"Z8bH5PFBy5sYpc/8cNPoSzhyqcpvvEZvv/Ivc0Up+dzma7vBDJC9WrMRUUlSFSQp\n"
"kdXSmphDNkXJsgARmxzc18IN6LYMRiOWlY7RE2F900pPW60BvJHHNCX0bbSRj/Ql\n"
"bmVq8wuftBD++D+RS8K++ujpMjFBROyWfBX+woQDGsMazkmgulQdnZrdj476elOL\n"
"axRvrSgEorju1kJM7M65z2RUZrfzQYW/1rs8mRUXin6iEtad/Rv1ZI1WGYmWPyBm\n"
"pbo=\n"
"-----END CERTIFICATE-----";
6 onenet_ota.h
##ifndef __ONENET_OTA_H__
#define __ONENET_OTA_H__
#include "esp_err.h"
#include "esp_log.h"
#include "esp_https_ota.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_http_client.h"
#include "bsp_mqtt.h"
#include "esp_tls.h"
#include "string.h"
#include "stdio.h"
#define ONENET_URL "https://iot-api.heclouds.com/fuse-ota"
typedef enum
{
POST_OTA_VERSION,
CHECK_OTA_TASK,
DOENLOAD_OTA_PACKAGE,
POST_OTA_STATUS,
}onenet_http_request_type;
typedef struct
{
onenet_http_request_type type;
char target[32];
uint32_t ota_task_tid;
uint32_t ota_task_size;
}onenet_http_ota_task_structuretypedef;
/* onenet OTA 任务*/
void onenet_ota_task(void* params);
#endif
7 功能展示


升级前版本

升级后版本


浙公网安备 33010602011771号