forked from xiaozhi/xiaozhi-esp32
Add activation version 2
This commit is contained in:
@@ -65,20 +65,31 @@ Application::~Application() {
|
|||||||
void Application::CheckNewVersion() {
|
void Application::CheckNewVersion() {
|
||||||
const int MAX_RETRY = 10;
|
const int MAX_RETRY = 10;
|
||||||
int retry_count = 0;
|
int retry_count = 0;
|
||||||
|
int retry_delay = 10; // 初始重试延迟为10秒
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
SetDeviceState(kDeviceStateActivating);
|
||||||
auto display = Board::GetInstance().GetDisplay();
|
auto display = Board::GetInstance().GetDisplay();
|
||||||
|
display->SetStatus(Lang::Strings::CHECKING_NEW_VERSION);
|
||||||
|
|
||||||
if (!ota_.CheckVersion()) {
|
if (!ota_.CheckVersion()) {
|
||||||
retry_count++;
|
retry_count++;
|
||||||
if (retry_count >= MAX_RETRY) {
|
if (retry_count >= MAX_RETRY) {
|
||||||
ESP_LOGE(TAG, "Too many retries, exit version check");
|
ESP_LOGE(TAG, "Too many retries, exit version check");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", 60, retry_count, MAX_RETRY);
|
ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", retry_delay, retry_count, MAX_RETRY);
|
||||||
vTaskDelay(pdMS_TO_TICKS(60000));
|
for (int i = 0; i < retry_delay; i++) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
if (device_state_ == kDeviceStateIdle) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retry_delay *= 2; // 每次重试后延迟时间翻倍
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
retry_count = 0;
|
retry_count = 0;
|
||||||
|
retry_delay = 10; // 重置重试延迟时间
|
||||||
|
|
||||||
if (ota_.HasNewVersion()) {
|
if (ota_.HasNewVersion()) {
|
||||||
Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "happy", Lang::Sounds::P3_UPGRADE);
|
Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "happy", Lang::Sounds::P3_UPGRADE);
|
||||||
@@ -125,24 +136,34 @@ void Application::CheckNewVersion() {
|
|||||||
|
|
||||||
// No new version, mark the current version as valid
|
// No new version, mark the current version as valid
|
||||||
ota_.MarkCurrentVersionValid();
|
ota_.MarkCurrentVersionValid();
|
||||||
if (ota_.HasActivationCode()) {
|
if (!ota_.HasActivationCode() && !ota_.HasActivationChallenge()) {
|
||||||
// Activation code is valid
|
xEventGroupSetBits(event_group_, CHECK_NEW_VERSION_DONE_EVENT);
|
||||||
SetDeviceState(kDeviceStateActivating);
|
// Exit the loop if done checking new version
|
||||||
ShowActivationCode();
|
break;
|
||||||
|
|
||||||
// Check again in 60 seconds or until the device is idle
|
|
||||||
for (int i = 0; i < 60; ++i) {
|
|
||||||
if (device_state_ == kDeviceStateIdle) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xEventGroupSetBits(event_group_, CHECK_NEW_VERSION_DONE_EVENT);
|
display->SetStatus(Lang::Strings::ACTIVATION);
|
||||||
// Exit the loop if done checking new version
|
// Activation code is shown to the user and waiting for the user to input
|
||||||
break;
|
if (ota_.HasActivationCode()) {
|
||||||
|
ShowActivationCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will block the loop until the activation is done or timeout
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
ESP_LOGI(TAG, "Activating... %d/%d", i + 1, 10);
|
||||||
|
esp_err_t err = ota_.Activate();
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
xEventGroupSetBits(event_group_, CHECK_NEW_VERSION_DONE_EVENT);
|
||||||
|
break;
|
||||||
|
} else if (err == ESP_ERR_TIMEOUT) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||||
|
} else {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||||
|
}
|
||||||
|
if (device_state_ == kDeviceStateIdle) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +368,6 @@ void Application::Start() {
|
|||||||
board.StartNetwork();
|
board.StartNetwork();
|
||||||
|
|
||||||
// Check for new firmware version or get the MQTT broker address
|
// Check for new firmware version or get the MQTT broker address
|
||||||
display->SetStatus(Lang::Strings::CHECKING_NEW_VERSION);
|
|
||||||
CheckNewVersion();
|
CheckNewVersion();
|
||||||
|
|
||||||
// Initialize the protocol
|
// Initialize the protocol
|
||||||
@@ -648,17 +668,23 @@ void Application::OnAudioInput() {
|
|||||||
#if CONFIG_USE_WAKE_WORD_DETECT
|
#if CONFIG_USE_WAKE_WORD_DETECT
|
||||||
if (wake_word_detect_.IsDetectionRunning()) {
|
if (wake_word_detect_.IsDetectionRunning()) {
|
||||||
std::vector<int16_t> data;
|
std::vector<int16_t> data;
|
||||||
ReadAudio(data, 16000, wake_word_detect_.GetFeedSize());
|
int samples = wake_word_detect_.GetFeedSize();
|
||||||
wake_word_detect_.Feed(data);
|
if (samples > 0) {
|
||||||
return;
|
ReadAudio(data, 16000, samples);
|
||||||
|
wake_word_detect_.Feed(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if CONFIG_USE_AUDIO_PROCESSOR
|
#if CONFIG_USE_AUDIO_PROCESSOR
|
||||||
if (audio_processor_.IsRunning()) {
|
if (audio_processor_.IsRunning()) {
|
||||||
std::vector<int16_t> data;
|
std::vector<int16_t> data;
|
||||||
ReadAudio(data, 16000, audio_processor_.GetFeedSize());
|
int samples = audio_processor_.GetFeedSize();
|
||||||
audio_processor_.Feed(data);
|
if (samples > 0) {
|
||||||
return;
|
ReadAudio(data, 16000, samples);
|
||||||
|
audio_processor_.Feed(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (device_state_ == kDeviceStateListening) {
|
if (device_state_ == kDeviceStateListening) {
|
||||||
|
|||||||
@@ -65,10 +65,16 @@ AudioProcessor::~AudioProcessor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioProcessor::GetFeedSize() {
|
size_t AudioProcessor::GetFeedSize() {
|
||||||
|
if (afe_data_ == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
|
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioProcessor::Feed(const std::vector<int16_t>& data) {
|
void AudioProcessor::Feed(const std::vector<int16_t>& data) {
|
||||||
|
if (afe_data_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
afe_iface_->feed(afe_data_, data.data());
|
afe_iface_->feed(afe_data_, data.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,10 +93,16 @@ bool WakeWordDetect::IsDetectionRunning() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WakeWordDetect::Feed(const std::vector<int16_t>& data) {
|
void WakeWordDetect::Feed(const std::vector<int16_t>& data) {
|
||||||
|
if (afe_data_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
afe_iface_->feed(afe_data_, data.data());
|
afe_iface_->feed(afe_data_, data.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WakeWordDetect::GetFeedSize() {
|
size_t WakeWordDetect::GetFeedSize() {
|
||||||
|
if (afe_data_ == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
|
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ void Display::Update() {
|
|||||||
kDeviceStateStarting,
|
kDeviceStateStarting,
|
||||||
kDeviceStateWifiConfiguring,
|
kDeviceStateWifiConfiguring,
|
||||||
kDeviceStateListening,
|
kDeviceStateListening,
|
||||||
|
kDeviceStateActivating,
|
||||||
};
|
};
|
||||||
if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) {
|
if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) {
|
||||||
icon = board.GetNetworkStateIcon();
|
icon = board.GetNetworkStateIcon();
|
||||||
|
|||||||
131
main/ota.cc
131
main/ota.cc
@@ -1,6 +1,5 @@
|
|||||||
#include "ota.h"
|
#include "ota.h"
|
||||||
#include "system_info.h"
|
#include "system_info.h"
|
||||||
#include "board.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "assets/lang_config.h"
|
#include "assets/lang_config.h"
|
||||||
|
|
||||||
@@ -9,6 +8,9 @@
|
|||||||
#include <esp_partition.h>
|
#include <esp_partition.h>
|
||||||
#include <esp_ota_ops.h>
|
#include <esp_ota_ops.h>
|
||||||
#include <esp_app_format.h>
|
#include <esp_app_format.h>
|
||||||
|
#include <esp_efuse.h>
|
||||||
|
#include <esp_efuse_table.h>
|
||||||
|
#include <esp_hmac.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -20,6 +22,17 @@
|
|||||||
|
|
||||||
Ota::Ota() {
|
Ota::Ota() {
|
||||||
SetCheckVersionUrl(CONFIG_OTA_VERSION_URL);
|
SetCheckVersionUrl(CONFIG_OTA_VERSION_URL);
|
||||||
|
|
||||||
|
// Read Serial Number from efuse user_data
|
||||||
|
uint8_t serial_number[33] = {0};
|
||||||
|
if (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA, serial_number, 32 * 8) == ESP_OK) {
|
||||||
|
if (serial_number[0] == 0) {
|
||||||
|
has_serial_number_ = false;
|
||||||
|
} else {
|
||||||
|
serial_number_ = std::string(reinterpret_cast<char*>(serial_number), 32);
|
||||||
|
has_serial_number_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ota::~Ota() {
|
Ota::~Ota() {
|
||||||
@@ -33,6 +46,25 @@ void Ota::SetHeader(const std::string& key, const std::string& value) {
|
|||||||
headers_[key] = value;
|
headers_[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Http* Ota::SetupHttp() {
|
||||||
|
auto& board = Board::GetInstance();
|
||||||
|
auto app_desc = esp_app_get_description();
|
||||||
|
|
||||||
|
auto http = board.CreateHttp();
|
||||||
|
for (const auto& header : headers_) {
|
||||||
|
http->SetHeader(header.first, header.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
http->SetHeader("Activation-Version", has_serial_number_ ? "2" : "1");
|
||||||
|
http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
|
||||||
|
http->SetHeader("Client-Id", board.GetUuid());
|
||||||
|
http->SetHeader("User-Agent", std::string(BOARD_NAME "/") + app_desc->version);
|
||||||
|
http->SetHeader("Accept-Language", Lang::CODE);
|
||||||
|
http->SetHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
return http;
|
||||||
|
}
|
||||||
|
|
||||||
bool Ota::CheckVersion() {
|
bool Ota::CheckVersion() {
|
||||||
auto& board = Board::GetInstance();
|
auto& board = Board::GetInstance();
|
||||||
auto app_desc = esp_app_get_description();
|
auto app_desc = esp_app_get_description();
|
||||||
@@ -46,17 +78,7 @@ bool Ota::CheckVersion() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto http = board.CreateHttp();
|
auto http = SetupHttp();
|
||||||
for (const auto& header : headers_) {
|
|
||||||
http->SetHeader(header.first, header.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
http->SetHeader("Ota-Version", "2");
|
|
||||||
http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
|
|
||||||
http->SetHeader("Client-Id", board.GetUuid());
|
|
||||||
http->SetHeader("User-Agent", std::string(BOARD_NAME "/") + app_desc->version);
|
|
||||||
http->SetHeader("Accept-Language", Lang::CODE);
|
|
||||||
http->SetHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
std::string data = board.GetJson();
|
std::string data = board.GetJson();
|
||||||
std::string method = data.length() > 0 ? "POST" : "GET";
|
std::string method = data.length() > 0 ? "POST" : "GET";
|
||||||
@@ -81,6 +103,7 @@ bool Ota::CheckVersion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
has_activation_code_ = false;
|
has_activation_code_ = false;
|
||||||
|
has_activation_challenge_ = false;
|
||||||
cJSON *activation = cJSON_GetObjectItem(root, "activation");
|
cJSON *activation = cJSON_GetObjectItem(root, "activation");
|
||||||
if (activation != NULL) {
|
if (activation != NULL) {
|
||||||
cJSON* message = cJSON_GetObjectItem(activation, "message");
|
cJSON* message = cJSON_GetObjectItem(activation, "message");
|
||||||
@@ -90,8 +113,17 @@ bool Ota::CheckVersion() {
|
|||||||
cJSON* code = cJSON_GetObjectItem(activation, "code");
|
cJSON* code = cJSON_GetObjectItem(activation, "code");
|
||||||
if (code != NULL) {
|
if (code != NULL) {
|
||||||
activation_code_ = code->valuestring;
|
activation_code_ = code->valuestring;
|
||||||
|
has_activation_code_ = true;
|
||||||
|
}
|
||||||
|
cJSON* challenge = cJSON_GetObjectItem(activation, "challenge");
|
||||||
|
if (challenge != NULL) {
|
||||||
|
activation_challenge_ = challenge->valuestring;
|
||||||
|
has_activation_challenge_ = true;
|
||||||
|
}
|
||||||
|
cJSON* timeout_ms = cJSON_GetObjectItem(activation, "timeout_ms");
|
||||||
|
if (timeout_ms != NULL) {
|
||||||
|
activation_timeout_ms_ = timeout_ms->valueint;
|
||||||
}
|
}
|
||||||
has_activation_code_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
has_mqtt_config_ = false;
|
has_mqtt_config_ = false;
|
||||||
@@ -327,3 +359,76 @@ bool Ota::IsNewVersionAvailable(const std::string& currentVersion, const std::st
|
|||||||
|
|
||||||
return newer.size() > current.size();
|
return newer.size() > current.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Ota::GetActivationPayload() {
|
||||||
|
if (!has_serial_number_) {
|
||||||
|
ESP_LOGI(TAG, "No serial number found");
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t hmac_result[32]; // SHA-256 输出为32字节
|
||||||
|
|
||||||
|
// 使用Key0计算HMAC
|
||||||
|
esp_err_t ret = esp_hmac_calculate(HMAC_KEY0, (uint8_t*)activation_challenge_.data(), activation_challenge_.size(), hmac_result);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "HMAC calculation failed: %s", esp_err_to_name(ret));
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string hmac_hex;
|
||||||
|
for (size_t i = 0; i < sizeof(hmac_result); i++) {
|
||||||
|
char buffer[3];
|
||||||
|
sprintf(buffer, "%02x", hmac_result[i]);
|
||||||
|
hmac_hex += buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *payload = cJSON_CreateObject();
|
||||||
|
cJSON_AddStringToObject(payload, "algorithm", "hmac-sha256");
|
||||||
|
cJSON_AddStringToObject(payload, "serial_number", serial_number_.c_str());
|
||||||
|
cJSON_AddStringToObject(payload, "challenge", activation_challenge_.c_str());
|
||||||
|
cJSON_AddStringToObject(payload, "hmac", hmac_hex.c_str());
|
||||||
|
std::string json = cJSON_Print(payload);
|
||||||
|
cJSON_Delete(payload);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Activation payload: %s", json.c_str());
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t Ota::Activate() {
|
||||||
|
if (!has_activation_challenge_) {
|
||||||
|
ESP_LOGW(TAG, "No activation challenge found");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string url = check_version_url_;
|
||||||
|
if (url.back() != '/') {
|
||||||
|
url += "/activate";
|
||||||
|
} else {
|
||||||
|
url += "activate";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto http = SetupHttp();
|
||||||
|
|
||||||
|
std::string data = GetActivationPayload();
|
||||||
|
if (!http->Open("POST", url, data)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to open HTTP connection");
|
||||||
|
delete http;
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto status_code = http->GetStatusCode();
|
||||||
|
data = http->GetBody();
|
||||||
|
http->Close();
|
||||||
|
delete http;
|
||||||
|
|
||||||
|
if (status_code == 202) {
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
if (status_code != 200) {
|
||||||
|
ESP_LOGE(TAG, "Failed to activate, code: %d, body: %s", status_code, data.c_str());
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Activation successful");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|||||||
12
main/ota.h
12
main/ota.h
@@ -5,6 +5,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
class Ota {
|
class Ota {
|
||||||
public:
|
public:
|
||||||
Ota();
|
Ota();
|
||||||
@@ -13,6 +16,8 @@ public:
|
|||||||
void SetCheckVersionUrl(std::string check_version_url);
|
void SetCheckVersionUrl(std::string check_version_url);
|
||||||
void SetHeader(const std::string& key, const std::string& value);
|
void SetHeader(const std::string& key, const std::string& value);
|
||||||
bool CheckVersion();
|
bool CheckVersion();
|
||||||
|
esp_err_t Activate();
|
||||||
|
bool HasActivationChallenge() { return has_activation_challenge_; }
|
||||||
bool HasNewVersion() { return has_new_version_; }
|
bool HasNewVersion() { return has_new_version_; }
|
||||||
bool HasMqttConfig() { return has_mqtt_config_; }
|
bool HasMqttConfig() { return has_mqtt_config_; }
|
||||||
bool HasActivationCode() { return has_activation_code_; }
|
bool HasActivationCode() { return has_activation_code_; }
|
||||||
@@ -33,15 +38,22 @@ private:
|
|||||||
bool has_mqtt_config_ = false;
|
bool has_mqtt_config_ = false;
|
||||||
bool has_server_time_ = false;
|
bool has_server_time_ = false;
|
||||||
bool has_activation_code_ = false;
|
bool has_activation_code_ = false;
|
||||||
|
bool has_serial_number_ = false;
|
||||||
|
bool has_activation_challenge_ = false;
|
||||||
std::string current_version_;
|
std::string current_version_;
|
||||||
std::string firmware_version_;
|
std::string firmware_version_;
|
||||||
std::string firmware_url_;
|
std::string firmware_url_;
|
||||||
|
std::string activation_challenge_;
|
||||||
|
std::string serial_number_;
|
||||||
|
int activation_timeout_ms_ = 30000;
|
||||||
std::map<std::string, std::string> headers_;
|
std::map<std::string, std::string> headers_;
|
||||||
|
|
||||||
void Upgrade(const std::string& firmware_url);
|
void Upgrade(const std::string& firmware_url);
|
||||||
std::function<void(int progress, size_t speed)> upgrade_callback_;
|
std::function<void(int progress, size_t speed)> upgrade_callback_;
|
||||||
std::vector<int> ParseVersion(const std::string& version);
|
std::vector<int> ParseVersion(const std::string& version);
|
||||||
bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion);
|
bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion);
|
||||||
|
std::string GetActivationPayload();
|
||||||
|
Http* SetupHttp();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _OTA_H
|
#endif // _OTA_H
|
||||||
|
|||||||
Reference in New Issue
Block a user