123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- #include "ota.h"
- #include "system_info.h"
- #include "settings.h"
- #include "assets/lang_config.h"
- #include <cJSON.h>
- #include <esp_log.h>
- #include <esp_partition.h>
- #include <esp_ota_ops.h>
- #include <esp_app_format.h>
- #include <esp_efuse.h>
- #include <esp_efuse_table.h>
- #ifdef SOC_HMAC_SUPPORTED
- #include <esp_hmac.h>
- #endif
- #include <cstring>
- #include <vector>
- #include <sstream>
- #include <algorithm>
- #define TAG "Ota"
- Ota::Ota() {
- #ifdef ESP_EFUSE_BLOCK_USR_DATA
- // 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;
- }
- }
- #endif
- }
- Ota::~Ota() {
- }
- std::string Ota::GetCheckVersionUrl() {
- Settings settings("wifi", false);
- std::string url = settings.GetString("ota_url");
- if (url.empty()) {
- url = CONFIG_OTA_URL;
- }
- return url;
- }
- std::unique_ptr<Http> Ota::SetupHttp() {
- auto& board = Board::GetInstance();
- auto app_desc = esp_app_get_description();
- auto network = board.GetNetwork();
- auto http = network->CreateHttp(0);
- http->SetHeader("Activation-Version", has_serial_number_ ? "2" : "1");
- http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
- http->SetHeader("Client-Id", board.GetUuid());
- if (has_serial_number_) {
- http->SetHeader("Serial-Number", serial_number_.c_str());
- }
- 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;
- }
- /*
- * Specification: https://ccnphfhqs21z.feishu.cn/wiki/FjW6wZmisimNBBkov6OcmfvknVd
- */
- bool Ota::CheckVersion() {
- auto& board = Board::GetInstance();
- auto app_desc = esp_app_get_description();
- // Check if there is a new firmware version available
- current_version_ = app_desc->version;
- ESP_LOGI(TAG, "Current version: %s", current_version_.c_str());
- std::string url = GetCheckVersionUrl();
- if (url.length() < 10) {
- ESP_LOGE(TAG, "Check version URL is not properly set");
- return false;
- }
- auto http = SetupHttp();
- std::string data = board.GetJson();
- std::string method = data.length() > 0 ? "POST" : "GET";
- http->SetContent(std::move(data));
- if (!http->Open(method, url)) {
- ESP_LOGE(TAG, "Failed to open HTTP connection");
- return false;
- }
- auto status_code = http->GetStatusCode();
- if (status_code != 200) {
- ESP_LOGE(TAG, "Failed to check version, status code: %d", status_code);
- return false;
- }
- data = http->ReadAll();
- http->Close();
- // 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(data.c_str());
- if (root == NULL) {
- ESP_LOGE(TAG, "Failed to parse JSON response");
- return false;
- }
- has_activation_code_ = false;
- has_activation_challenge_ = false;
- cJSON *activation = cJSON_GetObjectItem(root, "activation");
- if (cJSON_IsObject(activation)) {
- cJSON* message = cJSON_GetObjectItem(activation, "message");
- if (cJSON_IsString(message)) {
- activation_message_ = message->valuestring;
- }
- cJSON* code = cJSON_GetObjectItem(activation, "code");
- if (cJSON_IsString(code)) {
- activation_code_ = code->valuestring;
- has_activation_code_ = true;
- }
- cJSON* challenge = cJSON_GetObjectItem(activation, "challenge");
- if (cJSON_IsString(challenge)) {
- activation_challenge_ = challenge->valuestring;
- has_activation_challenge_ = true;
- }
- cJSON* timeout_ms = cJSON_GetObjectItem(activation, "timeout_ms");
- if (cJSON_IsNumber(timeout_ms)) {
- activation_timeout_ms_ = timeout_ms->valueint;
- }
- }
- has_mqtt_config_ = false;
- cJSON *mqtt = cJSON_GetObjectItem(root, "mqtt");
- if (cJSON_IsObject(mqtt)) {
- Settings settings("mqtt", true);
- cJSON *item = NULL;
- cJSON_ArrayForEach(item, mqtt) {
- if (cJSON_IsString(item)) {
- if (settings.GetString(item->string) != item->valuestring) {
- settings.SetString(item->string, item->valuestring);
- }
- } else if (cJSON_IsNumber(item)) {
- if (settings.GetInt(item->string) != item->valueint) {
- settings.SetInt(item->string, item->valueint);
- }
- }
- }
- has_mqtt_config_ = true;
- } else {
- ESP_LOGI(TAG, "No mqtt section found !");
- }
- has_websocket_config_ = false;
- cJSON *websocket = cJSON_GetObjectItem(root, "websocket");
- if (cJSON_IsObject(websocket)) {
- Settings settings("websocket", true);
- cJSON *item = NULL;
- cJSON_ArrayForEach(item, websocket) {
- if (cJSON_IsString(item)) {
- if (settings.GetString(item->string) != item->valuestring) {
- settings.SetString(item->string, item->valuestring);
- }
- } else if (cJSON_IsNumber(item)) {
- if (settings.GetInt(item->string) != item->valueint) {
- settings.SetInt(item->string, item->valueint);
- }
- }
- }
- has_websocket_config_ = true;
- } else {
- ESP_LOGI(TAG, "No websocket section found!");
- }
- has_server_time_ = false;
- cJSON *server_time = cJSON_GetObjectItem(root, "server_time");
- if (cJSON_IsObject(server_time)) {
- cJSON *timestamp = cJSON_GetObjectItem(server_time, "timestamp");
- cJSON *timezone_offset = cJSON_GetObjectItem(server_time, "timezone_offset");
-
- if (cJSON_IsNumber(timestamp)) {
- // 设置系统时间
- struct timeval tv;
- double ts = timestamp->valuedouble;
-
- // 如果有时区偏移,计算本地时间
- if (cJSON_IsNumber(timezone_offset)) {
- ts += (timezone_offset->valueint * 60 * 1000); // 转换分钟为毫秒
- }
-
- tv.tv_sec = (time_t)(ts / 1000); // 转换毫秒为秒
- tv.tv_usec = (suseconds_t)((long long)ts % 1000) * 1000; // 剩余的毫秒转换为微秒
- settimeofday(&tv, NULL);
- has_server_time_ = true;
- }
- } else {
- ESP_LOGW(TAG, "No server_time section found!");
- }
- has_new_version_ = false;
- cJSON *firmware = cJSON_GetObjectItem(root, "firmware");
- if (cJSON_IsObject(firmware)) {
- cJSON *version = cJSON_GetObjectItem(firmware, "version");
- if (cJSON_IsString(version)) {
- firmware_version_ = version->valuestring;
- }
- cJSON *url = cJSON_GetObjectItem(firmware, "url");
- if (cJSON_IsString(url)) {
- firmware_url_ = url->valuestring;
- }
- if (cJSON_IsString(version) && cJSON_IsString(url)) {
- // Check if the version is newer, for example, 0.1.0 is newer than 0.0.1
- has_new_version_ = IsNewVersionAvailable(current_version_, firmware_version_);
- if (has_new_version_) {
- ESP_LOGI(TAG, "New version available: %s", firmware_version_.c_str());
- } else {
- ESP_LOGI(TAG, "Current is the latest version");
- }
- // If the force flag is set to 1, the given version is forced to be installed
- cJSON *force = cJSON_GetObjectItem(firmware, "force");
- if (cJSON_IsNumber(force) && force->valueint == 1) {
- has_new_version_ = true;
- }
- }
- } else {
- ESP_LOGW(TAG, "No firmware section found!");
- }
- cJSON_Delete(root);
- return true;
- }
- void Ota::MarkCurrentVersionValid() {
- 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();
- }
- }
- bool Ota::Upgrade(const std::string& firmware_url) {
- ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str());
- esp_ota_handle_t update_handle = 0;
- auto update_partition = esp_ota_get_next_update_partition(NULL);
- if (update_partition == NULL) {
- ESP_LOGE(TAG, "Failed to get update partition");
- return false;
- }
- ESP_LOGI(TAG, "Writing to partition %s at offset 0x%lx", update_partition->label, update_partition->address);
- bool image_header_checked = false;
- std::string image_header;
- auto network = Board::GetInstance().GetNetwork();
- auto http = network->CreateHttp(0);
- if (!http->Open("GET", firmware_url)) {
- ESP_LOGE(TAG, "Failed to open HTTP connection");
- return false;
- }
- if (http->GetStatusCode() != 200) {
- ESP_LOGE(TAG, "Failed to get firmware, status code: %d", http->GetStatusCode());
- return false;
- }
- size_t content_length = http->GetBodyLength();
- if (content_length == 0) {
- ESP_LOGE(TAG, "Failed to get content length");
- return false;
- }
- char buffer[512];
- size_t total_read = 0, recent_read = 0;
- auto last_calc_time = esp_timer_get_time();
- while (true) {
- int ret = http->Read(buffer, sizeof(buffer));
- if (ret < 0) {
- ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
- return false;
- }
- // Calculate speed and progress every second
- recent_read += ret;
- total_read += ret;
- if (esp_timer_get_time() - last_calc_time >= 1000000 || ret == 0) {
- size_t progress = total_read * 100 / content_length;
- ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %uB/s", progress, total_read, content_length, recent_read);
- if (upgrade_callback_) {
- upgrade_callback_(progress, recent_read);
- }
- last_calc_time = esp_timer_get_time();
- recent_read = 0;
- }
- if (ret == 0) {
- break;
- }
- if (!image_header_checked) {
- image_header.append(buffer, ret);
- if (image_header.size() >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
- esp_app_desc_t new_app_info;
- memcpy(&new_app_info, image_header.data() + sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t), sizeof(esp_app_desc_t));
- ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
- auto current_version = esp_app_get_description()->version;
- if (memcmp(new_app_info.version, current_version, sizeof(new_app_info.version)) == 0) {
- ESP_LOGE(TAG, "Firmware version is the same, skipping upgrade");
- return false;
- }
- if (esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle)) {
- esp_ota_abort(update_handle);
- ESP_LOGE(TAG, "Failed to begin OTA");
- return false;
- }
- image_header_checked = true;
- std::string().swap(image_header);
- }
- }
- auto err = esp_ota_write(update_handle, buffer, ret);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
- esp_ota_abort(update_handle);
- return false;
- }
- }
- http->Close();
- esp_err_t err = esp_ota_end(update_handle);
- if (err != ESP_OK) {
- if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
- ESP_LOGE(TAG, "Image validation failed, image is corrupted");
- } else {
- ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
- }
- return false;
- }
- err = esp_ota_set_boot_partition(update_partition);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Failed to set boot partition: %s", esp_err_to_name(err));
- return false;
- }
- ESP_LOGI(TAG, "Firmware upgrade successful");
- return true;
- }
- bool Ota::StartUpgrade(std::function<void(int progress, size_t speed)> callback) {
- upgrade_callback_ = callback;
- return Upgrade(firmware_url_);
- }
- std::vector<int> Ota::ParseVersion(const std::string& version) {
- std::vector<int> versionNumbers;
- std::stringstream ss(version);
- std::string segment;
-
- while (std::getline(ss, segment, '.')) {
- versionNumbers.push_back(std::stoi(segment));
- }
-
- return versionNumbers;
- }
- bool Ota::IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion) {
- std::vector<int> current = ParseVersion(currentVersion);
- std::vector<int> newer = ParseVersion(newVersion);
-
- for (size_t i = 0; i < std::min(current.size(), newer.size()); ++i) {
- if (newer[i] > current[i]) {
- return true;
- } else if (newer[i] < current[i]) {
- return false;
- }
- }
-
- return newer.size() > current.size();
- }
- std::string Ota::GetActivationPayload() {
- if (!has_serial_number_) {
- return "{}";
- }
- std::string hmac_hex;
- #ifdef SOC_HMAC_SUPPORTED
- 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 "{}";
- }
- for (size_t i = 0; i < sizeof(hmac_result); i++) {
- char buffer[3];
- sprintf(buffer, "%02x", hmac_result[i]);
- hmac_hex += buffer;
- }
- #endif
- 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());
- auto json_str = cJSON_PrintUnformatted(payload);
- std::string json(json_str);
- cJSON_free(json_str);
- 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 = GetCheckVersionUrl();
- if (url.back() != '/') {
- url += "/activate";
- } else {
- url += "activate";
- }
- auto http = SetupHttp();
- std::string data = GetActivationPayload();
- http->SetContent(std::move(data));
- if (!http->Open("POST", url)) {
- ESP_LOGE(TAG, "Failed to open HTTP connection");
- return ESP_FAIL;
- }
-
- auto status_code = http->GetStatusCode();
- if (status_code == 202) {
- return ESP_ERR_TIMEOUT;
- }
- if (status_code != 200) {
- ESP_LOGE(TAG, "Failed to activate, code: %d, body: %s", status_code, http->ReadAll().c_str());
- return ESP_FAIL;
- }
- ESP_LOGI(TAG, "Activation successful");
- return ESP_OK;
- }
|