ESP32 IDF 连接阿里云物联网平台进行一个OTA升级
文章目录
1、阿里云平台进行一个–产品–>设备创建
1.1、选择控制台
1.2、搜索找到物联网平台
1.3、选择公共示例
1.4、创建一个产品–ESP32Test
1.4.1、
输入产品名称、自定义品类、直连设备、WiFi连接方式、其余保持默认别去动。
1.5、在对应产品下创建设备
1.5.1、
输入设备名、进行一个设备用途备注
1.5.2、
设备创建成功,目前设备显示未激活,我们前往代码部分
2、直接创建一个基础示例,从零开始
2.1、main.c部分
主要就是进行一个NVS初始化,然后进行一个连接WiFi的操作,
#include <stdio.h>
#include "wifi.h"
/*
函数内容:初始化NVS
函数参数:无
返回值: 无
*/
void Init_NVS(void)
{
esp_err_t ret = nvs_flash_init(); // 初始化NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{ // 如果NVS存储不包含空页或者NVS库存在新的版本
ESP_ERROR_CHECK(nvs_flash_erase()); // 擦除NVS
ret = nvs_flash_init(); // 重新初始化NVS
}
ESP_ERROR_CHECK(ret); // 再次检测返回值
}
void app_main(void)
{
//初始化NVS
Init_NVS();
//进行WiFi连接
wifi_init_sta("Xiaomi_F617", (uint8_t)strlen("Xiaomi_F617"), "chenlong", (uint8_t)strlen("chenlong"));
while (1)
{
vTaskDelay(pdMS_TO_TICKS(100));
}
}
2.2、wifi.c
主要就是进行一个WiFi连接,WiFi连接成功之后进行一个阿里云连接,依据ESP32WiFiSTA示例进行改动,注释都已经打全,不理解或者注释有误的欢迎评论区讨论
#include "wifi.h"
#include "ALY.h"
static EventGroupHandle_t s_wifi_event_group; // wifi事件组句柄
static const char *WIFI_TAG = "wifi station"; // wifi部分日志标记
static int s_retry_num = 0; // 记录wifi重连的次数
/**
* 函数内容:STA模式回调函数,处理连接WIFI的相关事件
* 函数参数:
* void *arg-----------------------空指针,具体作用不清楚
* esp_event_base_t event_base-----WIFI事件类型
* int32_t event_id---------------WIFI事件ID
* void *event_data---------------事件数据
*/
static void STA_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
// 事件为WIFI事件且事件ID为WIFI事件开始
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
esp_wifi_connect(); // 连接WIFI
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
// 如果事件为wifi事件且事件ID为wifi事件连接失败
if (s_retry_num < STA_EXAMPLE_ESP_MAXIMUM_RETRY)
{ // 如果重连次数少与规定次数
esp_wifi_connect(); // 重新连接
s_retry_num++; // 重连次数自增
ESP_LOGI(WIFI_TAG, "retry to connect to the AP"); // 打印日志--重新连接wifi
}
else
{
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); // 如果大于规定次数,事件组标志设置为wifi连接失败
ESP_LOGI(WIFI_TAG, "connect to the AP fail"); // 打印日志,连接失败
}
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{ // 如果事件是IP事件,且得到了IP
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(WIFI_TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); // 打印日志--打印得到的IP号
s_retry_num = 0; // 清除重新连接次数
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); // 事件组标志位设置为wifi连接成功
}
}
/*
* 函数名称:wifi STA模式初始化
* 函数参数:
* const char *WifiName---WiFi名称
* uint8_t name_len-------WiFi名称长度
* const char *WifiKey----WiFi密码
* uint8_t key_len--------WiFi密码长度
* 返回值: 无
*/
void wifi_init_sta(const char *WifiName, uint8_t name_len, const char *WifiKey, uint8_t key_len)
{
esp_netif_t *sta_netif = NULL;
// 因为wifi的连接是需要建立时间的,所以需要创建一个事件标示组,通过事件标志组等待wifi连接。
s_wifi_event_group = xEventGroupCreate(); // 创建一个事件组
ESP_ERROR_CHECK(esp_netif_init()); // 创建一个 LwIP 核心任务,并初始化 LwIP 相关工作
ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建默认事件循环--不断的接收注册进事件循环内的事件
sta_netif = esp_netif_create_default_wifi_sta(); // 创建有 TCP/IP 堆栈的默认网络接口实例绑定 station
/* 使用WIFI_INIT_CONFIG_DEFAULT() 来获取一个默认的wifi配置参数结构体变量*/
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
/* 根据cfg参数初始化wifi连接所需要的资源 */
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id; // 初始化事件id
esp_event_handler_instance_t instance_got_ip;
/* 将事件处理程序注册到系统默认事件循环,分别是WiFi事件和IP地址事件 */
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, // wifi事件
ESP_EVENT_ANY_ID, // 注册回调对于任何的事件
&STA_event_handler, // 回调函数名
NULL, // 传入的参数
&instance_any_id)); // 事件id
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&STA_event_handler,
NULL,
&instance_got_ip));
/* 定义WiFi连接的ssid和password参数 */
wifi_config_t wifi_config = {
.sta = {
.ssid = STA_EXAMPLE_ESP_WIFI_SSID, // 要连接的wifi名称
.password = STA_EXAMPLE_ESP_WIFI_PASS, // 要连接的wifi密码
/* Setting a password implies station will connect to all security modes including WEP/WPA.
* However these modes are deprecated and not advisable to be used. Incase your Access point
* doesn't support WPA2, these mode can be enabled by commenting below line */
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // WiFi的安全配置
},
};
memset(wifi_config.sta.ssid,0,strlen((const char *)wifi_config.sta.ssid)); //清除原有WiFi名称
memset(wifi_config.sta.password,0,strlen((const char *)wifi_config.sta.password)); //清除原有WiFi密码
memcpy(wifi_config.sta.ssid, WifiName, name_len); //拷贝新WiFi名称
memcpy(wifi_config.sta.password, WifiKey, key_len); //拷贝新WiFi密码
/* 设置WiFi的工作模式为 STA */
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
/* 设置WiFi连接的参数,主要是ssid和password */
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
/* 启动WiFi连接 */
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(WIFI_TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
/* 定义一个事件位变量来接收事件标志组等待函数的返回值 */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, /* 需要等待的事件标志组的句柄 */
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, /* 需要等待的事件位 */
pdFALSE, /* 为pdFALSE时,当等待到事件满足任务唤醒事件时,系统不会清除指定的事件标志位*/
pdFALSE, /* 为pdFALSE时,设置的这些事件位任意一个置1就会返回,为pdTRUE则需全为1才返回 */
portMAX_DELAY); /* 设置为最长阻塞等待时间,单位为时钟节拍 */
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT)
{
/* WiFi连接成功事件 */
ESP_LOGI(WIFI_TAG, "connected to ap SSID:%s password:%s",
wifi_config.sta.ssid, wifi_config.sta.password);
//连接阿里云
user_mqtt_app_start();
}
else if (bits & WIFI_FAIL_BIT)
{
/* WiFi连接失败事件 */
ESP_LOGI(WIFI_TAG, "Failed to connect to SSID:%s, password:%s",
wifi_config.sta.ssid, wifi_config.sta.password);
}
else
{
ESP_LOGE(WIFI_TAG, "UNEXPECTED EVENT"); /* 没有等待到事件 */
}
}
2.2.1、wifi.h
对一些宏定义进行声明
#ifndef __WIFI_H
#define __WIFI_H
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "esp_attr.h"
#include "esp_sleep.h"
#include "string.h"
#define STA_EXAMPLE_ESP_WIFI_SSID "Redmi" // STA 默认wifi名称
#define STA_EXAMPLE_ESP_WIFI_PASS "1234567890" // STA 默认wifi密码
#define STA_EXAMPLE_ESP_MAXIMUM_RETRY 5 // STA 最大重连次数
#define WIFI_CONNECTED_BIT BIT0 // wifi连接成功位
#define WIFI_FAIL_BIT BIT1 // wifi连接失败位
void wifi_init_sta(const char * WifiName, uint8_t name_len, const char * WifiKey, uint8_t key_len);
#endif
2.3.1、aly.c
主要是进行一个阿里云的一个连接、连接成功之后在MQTT事件回调函数处对收到的阿里云数据进行一个分析处理,如果是OTA—topic则进行一个json数据解析,获取url,然后进行一个OTA升级,升级完成之后给阿里云部分进行一个进度上报。然后复位芯片,开始运行更新之后的程序
#include "ALY.h"
/*
函数内容:HTTP事件回调
函数参数:
esp_http_client_event_t *evt--事件指针
返回值:esp_err_t--错误代码
*/
static esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
switch (evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(MQTT_TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(MQTT_TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(MQTT_TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(MQTT_TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(MQTT_TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(MQTT_TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(MQTT_TAG, "HTTP_EVENT_DISCONNECTED");
break;
}
return ESP_OK;
}
/*
函数内容:MQTT事件回调
函数参数:
esp_mqtt_event_handle_t event--事件参数
返回值:esp_err_t--错误代码
*/
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
esp_mqtt_client_handle_t client = event->client; //事件句柄
int msg_id; //消息ID号
// 根据事件ID确定事件内容
switch (event->event_id)
{
case MQTT_EVENT_CONNECTED: // MQTT连接成功事件
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_CONNECTED");
// 使用QOS订阅指定主题
msg_id = esp_mqtt_client_subscribe(client, AliyunSubscribeTopic_user_get, 0); // 订阅阿里云自定义消息 topic
msg_id = esp_mqtt_client_subscribe(client, AliyunOTAupgrade, 0); // 订阅阿里云OTA topic
ESP_LOGI(MQTT_TAG, "sent subscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED: // MQTT断开连接
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED: // MQTT订阅成功事件
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
// 发布自定义topic 测试信息
msg_id = esp_mqtt_client_publish(client, AliyunPublishTopic_user_update, mqtt_publish_Testdata, strlen(mqtt_publish_Testdata), 0, 0);
// 上报阿里云OTA版本
msg_id = esp_mqtt_client_publish(client, AliyunOTAInform, mqtt_OTA_Data, strlen(mqtt_OTA_Data), 0, 0);
ESP_LOGI(MQTT_TAG, "sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED: // MQTT订阅失败事件
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED: // MQTT发布事件
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA: // MQTT接收到数据事件
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
// 根据阿里云topic进行数据解析
Get_ALY_Topic(event->topic, event->data);
break;
case MQTT_EVENT_ERROR: // MQTT错误事件
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_ERROR");
break;
default: // 其他事件
ESP_LOGI(MQTT_TAG, "Other event id:%d", event->event_id);
break;
}
return ESP_OK;
}
// MQTT事件回调
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
ESP_LOGD(MQTT_TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
mqtt_event_handler_cb(event_data);
}
/*
函数内容:进行阿里云连接
函数参数:无
返回值: 无
*/
void user_mqtt_app_start(void)
{
esp_mqtt_client_config_t mqtt_cfg = {
.host = Aliyun_host, //目标服务端域名
.port = Aliyun_port, //目标服务端端口
.client_id = Aliyun_client_id, //目标服务端id
.username = Aliyun_username, //目标服务端用户名
.password = Aliyun_password, //目标服务端密码
};
client = esp_mqtt_client_init(&mqtt_cfg); // 创建MQTT客户端句柄
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client); // 注册MQTT事件
esp_mqtt_client_start(client); // 启动MQTT客户端
}
/*
函数内容:解析阿里云的OTA升级数据--并下载更新内容
函数参数:const char *Data---阿里云下发的数据
返回值 无
*/
static esp_err_t Parse_Data(const char *Data)
{
char ALY_url[OTA_URL_SIZE] = {0};
// char *str;
cJSON *root = NULL;
root = cJSON_Parse(Data);
cJSON *code = cJSON_GetObjectItem(root, "code"); //状态码
ESP_LOGI(MQTT_TAG, "Code:%s", code->valuestring);
cJSON *data = cJSON_GetObjectItem(root, "data"); //数据字段名
cJSON *size = cJSON_GetObjectItem(data, "size"); //升级包文件大小,单位:字节。
ESP_LOGI(MQTT_TAG, "Size:%d", size->valueint);
cJSON *sign = cJSON_GetObjectItem(data, "sign"); //OTA升级包文件的签名。
ESP_LOGI(MQTT_TAG, "Sign:%s", sign->valuestring);
cJSON *version = cJSON_GetObjectItem(data, "version"); //设备升级包的版本信息。
ESP_LOGI(MQTT_TAG, "Version:%s", version->valuestring);
cJSON *signMethod = cJSON_GetObjectItem(data, "signMethod"); //签名方法。取值:SHA256,MD5
ESP_LOGI(MQTT_TAG, "SignMethod:%s", signMethod->valuestring);
cJSON *url = cJSON_GetObjectItem(data, "url"); //升级包在对象存储(OSS)上的存储地址
ESP_LOGI(MQTT_TAG, "URL:%s", url->valuestring);
memcpy(ALY_url, url->valuestring, strlen(url->valuestring)); // https升级时使用
/*str = strstr(url->valuestring, "//iot"); //HTTP升级时使用
if(str!=NULL)
{
ESP_LOGI(MQTT_TAG, "Test:%s", str);
sprintf(ALY_url, "http:%s", str);
ESP_LOGI(MQTT_TAG, "ALY_url:%s", ALY_url);
}*/
cJSON *md5 = cJSON_GetObjectItem(data, "md5"); //当签名方法为MD5时,除了会给sign赋值外还会给md5赋值。
ESP_LOGI(MQTT_TAG, "Md5:%s", md5->valuestring);
cJSON *id = cJSON_GetObjectItem(root, "id"); //消息ID号。每个消息ID在当前设备中具有唯一性。
ESP_LOGI(MQTT_TAG, "ID:%d", id->valueint);
cJSON *message = cJSON_GetObjectItem(root, "message"); //结果信息。
ESP_LOGI(MQTT_TAG, "Message:%s", message->valuestring);
cJSON_Delete(root); //释放json资源
root = NULL;
esp_http_client_config_t config = {
.url = ALY_url, //升级包地址
.cert_pem = (char *)server_cert_pem_start, //设备证书校验
.event_handler = _http_event_handler, //http回调
.keep_alive_enable = true, //保持活跃
};
esp_err_t ret = esp_https_ota(&config); //开始进行OTA升级
return ret;
}
/*
函数内容:通过阿里云下发的topic进行对应的数据解析
函数参数:
const char *topic---topic数据
const char *data----内容数据
返回值:无
*/
void Get_ALY_Topic(const char *topic, const char *data)
{
if (strstr(topic, "/ota/device/upgrade/a1pVSbsTyJb/test") != NULL) // 如果是阿里云的OTA升级数据
{
esp_err_t ret = 0; //错误代码变量
int msg_id=0; //消息ID号
ESP_LOGI(MQTT_TAG, "OTA Data");
ret = Parse_Data(data); // 进行OTA数据解析获取升级数据
if (ret == ESP_OK) //如果OTA升级成功
{
// 构造JSON格式数据,该数据用于反馈给阿里云物联网平台,作用是通知升级包接收进度
cJSON *Wroot = cJSON_CreateObject();
cJSON *Pitem = cJSON_CreateObject();
cJSON_AddItemToObject(Wroot, "id", cJSON_CreateString("002")); //消息ID
cJSON_AddItemToObject(Wroot, "params", Pitem); //二层字段名
cJSON_AddItemToObject(Pitem, "step", cJSON_CreateString("100")); //当前进度
cJSON_AddItemToObject(Pitem, "desc", cJSON_CreateString("OTA update successfully !")); //内容描述
char ota_inform_buf[256] = {0}; //发送消息的一个数组
int len = strlen(cJSON_Print(Wroot)); //获取消息的长度
memcpy(ota_inform_buf, cJSON_Print(Wroot), len); // 将JSON格式数据复制到数组中,将以数组的形式传递给发布函数
ESP_LOGI(MQTT_TAG, "ota_inform_buf:%s,len:%d", ota_inform_buf,len); //打印提示信息
msg_id = esp_mqtt_client_publish(client, AliyunOTAprogress, ota_inform_buf, strlen(ota_inform_buf), 0, 0); //MQTT发布消息
ESP_LOGI(MQTT_TAG, "sent publish ota inform successful, msg_id=%d", msg_id);//打印提示信息
cJSON_Delete(Wroot); //释放json资源
esp_restart(); // ESP32设备重启,重启后将执行刚才下载的程序
}
else
{
ESP_LOGE(MQTT_TAG, "OTA failed");
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
2.3.2、aly.h
主要是对阿里云的一些连接参数以及一些topic进行宏定义,需要按照自己的一个具体情况进行一个改动。
#ifndef __ALY_H
#define __ALY_H
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "os.h"
#include "cJSON.h"
//OTA
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "esp_wifi.h"
#include "mqtt_client.h"
#include "string.h"
#include "nvs.h"
#include "nvs_flash.h"
#include <sys/socket.h>
//阿里云连接三元组
#define Aliyun_host ""
#define Aliyun_port 1883
#define Aliyun_client_id ""
#define Aliyun_username ""
#define Aliyun_password ""
//自定义订阅与发布
#define AliyunSubscribeTopic_user_get ""
#define AliyunPublishTopic_user_update ""
//OTA相关
#define AliyunOTAInform ""
#define AliyunOTAupgrade ""
#define AliyunOTAprogress ""
#define AliyunOTAfirmware ""
extern const uint8_t server_cert_pem_start[] asm("_binary_root_crt_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_root_crt_end");
#define OTA_URL_SIZE 256
static const char *MQTT_TAG = "MQTT_EXAMPLE";
//MQTT测试发布数据
static char mqtt_publish_Testdata[] = "mqtt i am esp32";
static char mqtt_OTA_Data[]="{\"id\":\"001\",\"params\":{\"version\":\"1.0.1\"}}";
esp_mqtt_client_handle_t client; //MQTT客户端句柄
//进行阿里云连接
void user_mqtt_app_start(void);
//void Parse_Data(const char *Data);
//通过阿里云下发的topic进行对应的数据解析
void Get_ALY_Topic(const char *topic, const char *data);
#endif
2.3.3、
在OTA、自定义topic在产品中,通过点击查看自己的一个topic列表可以进行查看,需要注意topic中有一个${deviceName}是需要填入自己的一个设备名称的。
2.3.4、
阿里云连接三元组在设备中可以通过查看设备信息–MQTT连接参数进行一个查看,需注意,该连接参数不可随意让人知道,本教程为示例。
3、需注意
这边有一个server_certs的一个文件夹,里面存放的是阿里云的一个https根证书,我们需要下载根证书进行一个导入添加,不然就只能通过HTTP的方式进行一个下载文件升级。
最后,到这里我们的一个ESP32OTADemo就结束了
参考文章:来自while(1)博主的
完整代码:直接下载,不需要积分