#include // SDK #include "cJSON.h" #include "esp_http_server.h" #include "esp_image_format.h" #include "esp_littlefs.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "esp_partition.h" #include "esp_vfs.h" #include // Project #include "appstate.hpp" #include "types.hpp" #include "utils.hpp" internal esp_err_t api_ota_status_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); cJSON *root = cJSON_CreateObject(); cJSON_AddNumberToObject(root, "active_slot", g_Active_WWW_Partition); cJSON *parts_arr = cJSON_AddArrayToObject(root, "partitions"); esp_partition_iterator_t it = esp_partition_find( ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); while (it != NULL) { const esp_partition_t *p = esp_partition_get(it); cJSON *p_obj = cJSON_CreateObject(); cJSON_AddStringToObject(p_obj, "label", p->label); cJSON_AddNumberToObject(p_obj, "type", p->type); cJSON_AddNumberToObject(p_obj, "subtype", p->subtype); cJSON_AddNumberToObject(p_obj, "address", p->address); cJSON_AddNumberToObject(p_obj, "size", p->size); // Try to get LittleFS info if it's a data partition if (p->type == ESP_PARTITION_TYPE_DATA) { size_t total = 0, used = 0; if (esp_littlefs_info(p->label, &total, &used) == ESP_OK) { cJSON_AddNumberToObject(p_obj, "used", used); cJSON_AddNumberToObject(p_obj, "free", total - used); } else { // For other data partitions (nvs, phy_init), just show total as used // for now cJSON_AddNumberToObject(p_obj, "used", p->size); cJSON_AddNumberToObject(p_obj, "free", 0); } } // For app partitions, try to find the binary size else if (p->type == ESP_PARTITION_TYPE_APP) { esp_app_desc_t app_desc; if (esp_ota_get_partition_description(p, &app_desc) == ESP_OK) { cJSON_AddStringToObject(p_obj, "app_version", app_desc.version); // Get the true binary size from image metadata esp_image_metadata_t data; const esp_partition_pos_t pos = {.offset = p->address, .size = p->size}; if (esp_image_get_metadata(&pos, &data) == ESP_OK) { cJSON_AddNumberToObject(p_obj, "used", data.image_len); cJSON_AddNumberToObject(p_obj, "free", p->size - data.image_len); } } } cJSON_AddItemToArray(parts_arr, p_obj); it = esp_partition_next(it); } cJSON_AddStringToObject(root, "active_partition", g_Active_WWW_Partition == 0 ? "www_0" : "www_1"); cJSON_AddStringToObject(root, "target_partition", g_Active_WWW_Partition == 0 ? "www_1" : "www_0"); const esp_partition_t *running = esp_ota_get_running_partition(); if (running) { cJSON_AddStringToObject(root, "running_firmware_label", running->label); if (running->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MIN && running->subtype <= ESP_PARTITION_SUBTYPE_APP_OTA_MAX) { cJSON_AddNumberToObject(root, "running_firmware_slot", running->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_MIN); } else { cJSON_AddNumberToObject(root, "running_firmware_slot", -1); // Factory or other } } const char *status_info = cJSON_Print(root); httpd_resp_sendstr(req, status_info); free((void *)status_info); cJSON_Delete(root); return ESP_OK; } internal const httpd_uri_t api_ota_status_uri = {.uri = "/api/ota/status", .method = HTTP_GET, .handler = api_ota_status_handler, .user_ctx = NULL};