forked from xiaozhi/xiaozhi-esp32
add OTA support
This commit is contained in:
@@ -4,5 +4,7 @@
|
|||||||
# CMakeLists in this exact order for cmake to work correctly
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
set(PROJECT_VER "0.1.0")
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
project(xiaozhi)
|
project(xiaozhi)
|
||||||
|
|||||||
@@ -98,6 +98,18 @@ void Application::Start() {
|
|||||||
builtin_led_.SetBlue();
|
builtin_led_.SetBlue();
|
||||||
builtin_led_.BlinkOnce();
|
builtin_led_.BlinkOnce();
|
||||||
wifi_station_.Start();
|
wifi_station_.Start();
|
||||||
|
|
||||||
|
// Check if there is a new firmware version available
|
||||||
|
firmware_upgrade_.CheckVersion();
|
||||||
|
if (firmware_upgrade_.HasNewVersion()) {
|
||||||
|
builtin_led_.TurnOn();
|
||||||
|
firmware_upgrade_.StartUpgrade();
|
||||||
|
// If upgrade success, the device will reboot and never reach here
|
||||||
|
ESP_LOGI(TAG, "Firmware upgrade failed...");
|
||||||
|
builtin_led_.TurnOff();
|
||||||
|
} else {
|
||||||
|
firmware_upgrade_.MarkValid();
|
||||||
|
}
|
||||||
|
|
||||||
StartCommunication();
|
StartCommunication();
|
||||||
StartDetection();
|
StartDetection();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "OpusEncoder.h"
|
#include "OpusEncoder.h"
|
||||||
#include "WebSocketClient.h"
|
#include "WebSocketClient.h"
|
||||||
#include "BuiltinLed.h"
|
#include "BuiltinLed.h"
|
||||||
|
#include "FirmwareUpgrade.h"
|
||||||
|
|
||||||
#include "opus.h"
|
#include "opus.h"
|
||||||
#include "resampler_structs.h"
|
#include "resampler_structs.h"
|
||||||
@@ -40,6 +41,7 @@ private:
|
|||||||
WifiStation wifi_station_;
|
WifiStation wifi_station_;
|
||||||
AudioDevice audio_device_;
|
AudioDevice audio_device_;
|
||||||
BuiltinLed builtin_led_;
|
BuiltinLed builtin_led_;
|
||||||
|
FirmwareUpgrade firmware_upgrade_;
|
||||||
|
|
||||||
std::recursive_mutex mutex_;
|
std::recursive_mutex mutex_;
|
||||||
WebSocketClient* ws_client_ = nullptr;
|
WebSocketClient* ws_client_ = nullptr;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ set(SOURCES "AudioDevice.cc"
|
|||||||
"WifiConfigurationAp.cc"
|
"WifiConfigurationAp.cc"
|
||||||
"main.cc"
|
"main.cc"
|
||||||
"WifiStation.cc"
|
"WifiStation.cc"
|
||||||
|
"FirmwareUpgrade.cc"
|
||||||
)
|
)
|
||||||
|
|
||||||
idf_component_register(SRCS ${SOURCES}
|
idf_component_register(SRCS ${SOURCES}
|
||||||
|
|||||||
169
main/FirmwareUpgrade.cc
Normal file
169
main/FirmwareUpgrade.cc
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#include "FirmwareUpgrade.h"
|
||||||
|
#include "SystemInfo.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_https_ota.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_http_client.h"
|
||||||
|
#include "esp_crt_bundle.h"
|
||||||
|
#include "cJSON.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define TAG "FirmwareUpgrade"
|
||||||
|
|
||||||
|
|
||||||
|
FirmwareUpgrade::FirmwareUpgrade() {
|
||||||
|
}
|
||||||
|
|
||||||
|
FirmwareUpgrade::~FirmwareUpgrade() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirmwareUpgrade::CheckVersion() {
|
||||||
|
std::string current_version = esp_app_get_description()->version;
|
||||||
|
ESP_LOGI(TAG, "Current version: %s", current_version.c_str());
|
||||||
|
|
||||||
|
// Get device info and request the latest firmware from the server
|
||||||
|
std::string device_info = SystemInfo::GetJsonString();
|
||||||
|
|
||||||
|
esp_http_client_config_t config = {};
|
||||||
|
config.url = CONFIG_OTA_UPGRADE_URL;
|
||||||
|
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||||
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||||
|
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||||
|
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||||
|
esp_http_client_set_header(client, "Device-Id", SystemInfo::GetMacAddress().c_str());
|
||||||
|
esp_http_client_set_post_field(client, device_info.c_str(), device_info.length());
|
||||||
|
esp_err_t err = esp_http_client_open(client, 0);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to perform HTTP request: %s", esp_err_to_name(err));
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int content_length = esp_http_client_fetch_headers(client);
|
||||||
|
if (content_length <= 0) {
|
||||||
|
ESP_LOGE(TAG, "Failed to fetch headers");
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string response;
|
||||||
|
response.resize(content_length);
|
||||||
|
int ret = esp_http_client_read_response(client, (char*)response.data(), content_length);
|
||||||
|
if (ret <= 0) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read response content_length=%d", content_length);
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_http_client_close(client);
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
|
||||||
|
// Response: { "firmware": { "version": "1.0.0", "url": "http://" } }
|
||||||
|
// Parse the JSON response and check if the version is newer
|
||||||
|
// If it is, set has_new_version_ to true and store the new version and URL
|
||||||
|
|
||||||
|
cJSON *root = cJSON_Parse(response.c_str());
|
||||||
|
if (root == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Failed to parse JSON response");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cJSON *firmware = cJSON_GetObjectItem(root, "firmware");
|
||||||
|
if (firmware == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Failed to get firmware object");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cJSON *version = cJSON_GetObjectItem(firmware, "version");
|
||||||
|
if (version == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Failed to get version object");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cJSON *url = cJSON_GetObjectItem(firmware, "url");
|
||||||
|
if (url == NULL) {
|
||||||
|
ESP_LOGE(TAG, "Failed to get url object");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_version_ = version->valuestring;
|
||||||
|
new_version_url_ = url->valuestring;
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
has_new_version_ = new_version_ != current_version;
|
||||||
|
if (has_new_version_) {
|
||||||
|
ESP_LOGI(TAG, "New version available: %s", new_version_.c_str());
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "No new version available");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirmwareUpgrade::MarkValid() {
|
||||||
|
auto partition = esp_ota_get_running_partition();
|
||||||
|
if (strcmp(partition->label, "factory") == 0) {
|
||||||
|
ESP_LOGI(TAG, "Running from factory partition, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Running partition: %s", partition->label);
|
||||||
|
esp_ota_img_states_t state;
|
||||||
|
if (esp_ota_get_state_partition(partition, &state) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to get state of partition");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||||
|
ESP_LOGI(TAG, "Marking firmware as valid");
|
||||||
|
esp_ota_mark_app_valid_cancel_rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirmwareUpgrade::Upgrade(std::string firmware_url) {
|
||||||
|
ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str());
|
||||||
|
|
||||||
|
esp_http_client_config_t config = {};
|
||||||
|
config.url = firmware_url.c_str();
|
||||||
|
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||||
|
config.event_handler = [](esp_http_client_event_t *evt) {
|
||||||
|
switch (evt->event_id) {
|
||||||
|
case HTTP_EVENT_ERROR:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_CONNECTED:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_HEADER_SENT:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_HEADER:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_DATA:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_ON_FINISH:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_DISCONNECTED:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
|
||||||
|
break;
|
||||||
|
case HTTP_EVENT_REDIRECT:
|
||||||
|
ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
};
|
||||||
|
config.keep_alive_enable = true;
|
||||||
|
|
||||||
|
esp_https_ota_config_t ota_config = {};
|
||||||
|
ota_config.http_config = &config;
|
||||||
|
esp_err_t err = esp_https_ota(&ota_config);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to upgrade firmware: %s", esp_err_to_name(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Firmware upgrade successful, rebooting in 3 seconds...");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
24
main/FirmwareUpgrade.h
Normal file
24
main/FirmwareUpgrade.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef _FIRMWARE_UPGRADE_H
|
||||||
|
#define _FIRMWARE_UPGRADE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class FirmwareUpgrade {
|
||||||
|
public:
|
||||||
|
FirmwareUpgrade();
|
||||||
|
~FirmwareUpgrade();
|
||||||
|
|
||||||
|
void CheckVersion();
|
||||||
|
bool HasNewVersion() { return has_new_version_; }
|
||||||
|
void StartUpgrade() { Upgrade(new_version_url_); }
|
||||||
|
void MarkValid();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool has_new_version_ = false;
|
||||||
|
std::string new_version_;
|
||||||
|
std::string new_version_url_;
|
||||||
|
|
||||||
|
void Upgrade(std::string firmware_url);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _FIRMWARE_UPGRADE_H
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
menu "Xiaozhi Assistant"
|
menu "Xiaozhi Assistant"
|
||||||
|
|
||||||
|
config OTA_UPGRADE_URL
|
||||||
|
string "OTA Upgrade URL"
|
||||||
|
default "https://"
|
||||||
|
help
|
||||||
|
The application will access this URL to check for updates when it starts and every 24 hours.
|
||||||
|
|
||||||
config WEBSOCKET_URL
|
config WEBSOCKET_URL
|
||||||
string "Websocket URL"
|
string "Websocket URL"
|
||||||
default "ws://"
|
default "wss://"
|
||||||
help
|
help
|
||||||
Communication with the server through websocket after wake up.
|
Communication with the server through websocket after wake up.
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "esp_partition.h"
|
#include "esp_partition.h"
|
||||||
#include "esp_app_desc.h"
|
#include "esp_app_desc.h"
|
||||||
#include "esp_psram.h"
|
#include "esp_psram.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
|
||||||
|
|
||||||
#define TAG "SystemInfo"
|
#define TAG "SystemInfo"
|
||||||
@@ -62,14 +64,22 @@ std::string SystemInfo::GetJsonString() {
|
|||||||
"idf_version": "4.2-dev"
|
"idf_version": "4.2-dev"
|
||||||
"elf_sha256": ""
|
"elf_sha256": ""
|
||||||
},
|
},
|
||||||
"partition_table": {
|
"partition_table": [
|
||||||
"app": {
|
"app": {
|
||||||
"label": "app",
|
"label": "app",
|
||||||
"type": 1,
|
"type": 1,
|
||||||
"subtype": 2,
|
"subtype": 2,
|
||||||
"address": 0x10000,
|
"address": 0x10000,
|
||||||
"size": 0x100000
|
"size": 0x100000
|
||||||
},
|
}
|
||||||
|
],
|
||||||
|
"ota": {
|
||||||
|
"label": "ota_0"
|
||||||
|
},
|
||||||
|
"wifi": {
|
||||||
|
"ssid": "my-wifi",
|
||||||
|
"rss": -50,
|
||||||
|
"channel": 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
@@ -117,7 +127,20 @@ std::string SystemInfo::GetJsonString() {
|
|||||||
it = esp_partition_next(it);
|
it = esp_partition_next(it);
|
||||||
}
|
}
|
||||||
json.pop_back(); // Remove the last comma
|
json.pop_back(); // Remove the last comma
|
||||||
json += "]";
|
json += "],";
|
||||||
|
|
||||||
|
json += "\"ota\":{";
|
||||||
|
auto ota_partition = esp_ota_get_running_partition();
|
||||||
|
json += "\"label\":\"" + std::string(ota_partition->label) + "\"";
|
||||||
|
json += "},";
|
||||||
|
|
||||||
|
wifi_ap_record_t ap_info;
|
||||||
|
esp_wifi_sta_get_ap_info(&ap_info);
|
||||||
|
json += "\"wifi\":{";
|
||||||
|
json += "\"ssid\":\"" + std::string(reinterpret_cast<char*>(ap_info.ssid)) + "\",";
|
||||||
|
json += "\"rssi\":" + std::to_string(ap_info.rssi) + ",";
|
||||||
|
json += "\"channel\":" + std::to_string(ap_info.primary);
|
||||||
|
json += "}";
|
||||||
|
|
||||||
// Close the JSON object
|
// Close the JSON object
|
||||||
json += "}";
|
json += "}";
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ void WifiStation::Start() {
|
|||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "WifiStation started");
|
ESP_LOGI(TAG, "WifiStation started");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get station info
|
||||||
|
wifi_ap_record_t ap_info;
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap_info));
|
||||||
|
ESP_LOGI(TAG, "Connected to %s rssi=%d channel=%d", ap_info.ssid, ap_info.rssi, ap_info.primary);
|
||||||
|
rssi_ = ap_info.rssi;
|
||||||
|
channel_ = ap_info.primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WifiStation::IsConnected() {
|
bool WifiStation::IsConnected() {
|
||||||
|
|||||||
@@ -11,12 +11,16 @@ public:
|
|||||||
bool IsConnected();
|
bool IsConnected();
|
||||||
std::string ssid() { return ssid_; }
|
std::string ssid() { return ssid_; }
|
||||||
std::string ip_address() { return ip_address_; }
|
std::string ip_address() { return ip_address_; }
|
||||||
|
int8_t rssi() { return rssi_; }
|
||||||
|
uint8_t channel() { return channel_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EventGroupHandle_t event_group_;
|
EventGroupHandle_t event_group_;
|
||||||
std::string ssid_;
|
std::string ssid_;
|
||||||
std::string password_;
|
std::string password_;
|
||||||
std::string ip_address_;
|
std::string ip_address_;
|
||||||
|
uint8_t rssi_ = 0;
|
||||||
|
uint8_t channel_ = 0;
|
||||||
int reconnect_count_ = 0;
|
int reconnect_count_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,3 +18,5 @@ dependencies:
|
|||||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
||||||
# # All dependencies of `main` are public by default.
|
# # All dependencies of `main` are public by default.
|
||||||
# public: true
|
# public: true
|
||||||
|
description: "An AI voice assistant for ESP32"
|
||||||
|
url: "https://github.com/78/xiaozhi-esp32"
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ extern "C" void app_main(void)
|
|||||||
auto app = new Application();
|
auto app = new Application();
|
||||||
app->Start();
|
app->Start();
|
||||||
|
|
||||||
// Dump CPU usage every 1 second
|
// Dump CPU usage every 10 second
|
||||||
while (true) {
|
while (true) {
|
||||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
vTaskDelay(10000 / portTICK_PERIOD_MS);
|
||||||
// SystemInfo::PrintRealTimeStats(STATS_TICKS);
|
// SystemInfo::PrintRealTimeStats(STATS_TICKS);
|
||||||
int free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
|
int free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
|
||||||
ESP_LOGI(TAG, "Free heap size: %u minimal internal: %u", SystemInfo::GetFreeHeapSize(), free_sram);
|
ESP_LOGI(TAG, "Free heap size: %u minimal internal: %u", SystemInfo::GetFreeHeapSize(), free_sram);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y
|
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y
|
||||||
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
|
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
|
||||||
CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y
|
CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y
|
||||||
|
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
||||||
|
|
||||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
|
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
|
||||||
|
|||||||
Reference in New Issue
Block a user