diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 9920a7a..a2da56e 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "ESP-IDF", - "compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20251107/riscv32-esp-elf/bin/riscv32-esp-elf-gcc", + "compilerPath": "/home/marc/.espressif/tools/riscv32-esp-elf/esp-14.2.0_20251107/riscv32-esp-elf/bin/riscv32-esp-elf-gcc", "compileCommands": "${config:idf.buildPath}/compile_commands.json", "includePath": [ "${config:idf.espIdfPath}/components/**", diff --git a/.vscode/launch.json b/.vscode/launch.json index 234b247..5882626 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,7 +37,7 @@ "type": "cppdbg", "request": "launch", "preLaunchTask": "Build - Build IHM", - "program": "${workspaceFolder}/components/domotic_display/test_host/build/nvs_host_test.elf", + "program": "${workspaceFolder}/components/domotic_display/test_host/build/host_test.elf", "args": [], "cwd": "${workspaceFolder}", "stopAtEntry": false, diff --git a/CMakeLists.txt b/CMakeLists.txt index e0067a9..bfe084e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,54 @@ +# -------------------------------------------------- +# 1. Version de base (version.txt) +# -------------------------------------------------- +file(READ "${CMAKE_SOURCE_DIR}/version.txt" FW_VERSION) +string(STRIP "${FW_VERSION}" FW_VERSION) + +# -------------------------------------------------- +# 2. Détection git + branche +# -------------------------------------------------- +find_package(Git) + +set(GIT_BRANCH "") +set(IS_GIT_REPO OFF) + +if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") + set(IS_GIT_REPO ON) + + execute_process( + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) +endif() + +# -------------------------------------------------- +# 3. Mode dev ou release +# -------------------------------------------------- +# Convention simple : +# - branche main / master => release +# - le reste => dev +set(IS_DEV_BUILD OFF) + +if(IS_GIT_REPO AND NOT GIT_BRANCH STREQUAL "main" AND NOT GIT_BRANCH STREQUAL "master") + set(IS_DEV_BUILD ON) +endif() + +# -------------------------------------------------- +# 4. Nettoyage nom de branche +# -------------------------------------------------- +if(IS_DEV_BUILD) + # remplace / par - + string(REPLACE "/" "-" GIT_BRANCH_CLEAN "${GIT_BRANCH}") + set(FW_VERSION "${FW_VERSION}-${GIT_BRANCH_CLEAN}") +endif() + +message(STATUS "Firmware version: ${FW_VERSION}") + +# -------------------------------------------------- + cmake_minimum_required(VERSION 3.16) option(SIMULATION_QEMU "Build for QEMU simulation" OFF) message(STATUS "ROOT:: SIMULATION_QEMU = ${SIMULATION_QEMU}") @@ -33,7 +84,7 @@ endif() #list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer") include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(rgb_lcd) +project(domotic) # ------------------------------------------------- @@ -61,3 +112,32 @@ else() add_link_options(-fsanitize=address) endif() + +# --- Paramètres OTA --- +set(OTA_DIR "${CMAKE_SOURCE_DIR}/../ota/fw") +set(DEVICE "esp32p4") + + +# --- Nom final du binaire --- +set(OTA_BIN_NAME "${PROJECT_NAME}-v${FW_VERSION}.bin") +set(BUILD_BIN "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.bin") + +message(STATUS "Firmware version: ${FW_VERSION}") + +# --- Target OTA --- +add_custom_target(ota_push ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${OTA_DIR} + COMMAND ${CMAKE_COMMAND} -E copy + ${BUILD_BIN} + ${OTA_DIR}/${OTA_BIN_NAME} + COMMENT "📦 Copy firmware to OTA server directory" + DEPENDS app +) + +add_custom_target(ota_latest + COMMAND ${CMAKE_COMMAND} -E echo + "{ \"version\":\"${FW_VERSION}\", \ + \"bin\":\"${OTA_BIN_NAME}\" }" + > ${OTA_DIR}/latest.json + DEPENDS ota_push +) \ No newline at end of file diff --git a/components/domotic_display/ihm.c b/components/domotic_display/ihm.c index 5933bee..90c9df2 100644 --- a/components/domotic_display/ihm.c +++ b/components/domotic_display/ihm.c @@ -191,6 +191,11 @@ static void create_ui(void*) #include // usleep #include "platform_detect.h" +bool suspended; +void suspendIHM(){ + suspended=true; + lvgl_port_stop(); +} void drawIhm(void *param) { // Init display + LVGL init_display_ihm(); @@ -205,10 +210,14 @@ void drawIhm(void *param) { // Loop unifiée while (1) { - ihm_gateway_process_queue(); - #if CONFIG_IDF_TARGET_LINUX - uint32_t idle_time = lv_timer_handler(); - #endif + if(!suspended){ + ihm_gateway_process_queue(); + #if CONFIG_IDF_TARGET_LINUX + uint32_t idle_time = lv_timer_handler(); + #endif + }else{ + vTaskDelay(500 / portTICK_PERIOD_MS); + } // ESP32 : task FreeRTOS //vTaskDelay(500 / portTICK_PERIOD_MS); diff --git a/components/domotic_display/include/ihm.h b/components/domotic_display/include/ihm.h index a5544d4..31156cd 100644 --- a/components/domotic_display/include/ihm.h +++ b/components/domotic_display/include/ihm.h @@ -45,4 +45,6 @@ void draw_tabCuve(lv_obj_t * parent); void draw_tabHome(lv_obj_t * parent); void draw_tabSettings(lv_obj_t * parent); +void suspendIHM(); + void drawIhm(void *pvParameter); diff --git a/components/domotic_display/test_host/dependencies.lock b/components/domotic_display/test_host/dependencies.lock index 5dfd139..98ab3f9 100644 --- a/components/domotic_display/test_host/dependencies.lock +++ b/components/domotic_display/test_host/dependencies.lock @@ -2,7 +2,7 @@ dependencies: idf: source: type: idf - version: 5.5.2 + version: 5.5.0 lvgl/lvgl: component_hash: 17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f dependencies: [] diff --git a/components/eventsManager/obtain_time.c b/components/eventsManager/obtain_time.c index bbbd102..73b92fd 100644 --- a/components/eventsManager/obtain_time.c +++ b/components/eventsManager/obtain_time.c @@ -73,8 +73,6 @@ void updateTime(void *pvParameter) while (1) { time(&now); - setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); - tzset(); struct tm timeinfo = {0}; localtime_r(&now, &timeinfo); sprintf(strftime_buf, "%s %d %s %02d:%02d (%lli mn depuis reboot)", days[timeinfo.tm_wday], timeinfo.tm_mday, months[timeinfo.tm_mon], timeinfo.tm_hour, timeinfo.tm_min, (esp_timer_get_time() / 1000 / 1000 / 60)); diff --git a/dependencies.lock b/dependencies.lock index dabc4b7..340f646 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -129,7 +129,7 @@ dependencies: type: service version: 1.0.4 espressif/esp_hosted: - component_hash: 5e1f8af8b51b47714eafb30c59bac952d6abc03125c01cb6a970f368d1e1cb83 + component_hash: 9eea9ce83444a412b4a02311f787f3639a5d29fe67e6df83863010dd68ab250b dependencies: - name: idf require: private @@ -137,7 +137,7 @@ dependencies: source: registry_url: https://components.espressif.com type: service - version: 2.11.7 + version: 2.12.0 espressif/esp_ipa: component_hash: 31003e536f0d26158e10d7fcf2448b6377ce1148518287d0f34ed3b6c942c6d8 dependencies: @@ -217,7 +217,7 @@ dependencies: type: service version: 1.2.0~1 espressif/esp_lvgl_port: - component_hash: 88bbda87376ae20fabfbb5d794ed78d23b3b4a186ecfafecf2edbf06223fd6c9 + component_hash: b6360960f47b6776462e7092861b3ea66477ffb762a01baa0aecbb3d74cd50f4 dependencies: - name: idf require: private @@ -229,7 +229,7 @@ dependencies: source: registry_url: https://components.espressif.com/ type: service - version: 2.7.1 + version: 2.7.2 espressif/esp_sccb_intf: component_hash: c071b189e49f40940722aea01a5489f873385cc39cd5b14012341135b85d1a9d dependencies: @@ -351,12 +351,12 @@ dependencies: type: service version: 1.20.4 lvgl/lvgl: - component_hash: 17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f + component_hash: 184e532558c1c45fefed631f3e235423d22582aafb4630f3e8885c35281a49ae dependencies: [] source: registry_url: https://components.espressif.com type: service - version: 9.4.0 + version: 9.5.0 suda-morris/am2302_rmt: component_hash: 890df8ebfec652eb9f8e1d612959f00a951dbe9241335e5e335fc7fb1468ea32 dependencies: diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 585e2ef..c2bd679 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -9,7 +9,7 @@ if(${IDF_TARGET} STREQUAL "esp32p4") idf_component_register(SRCS main.c communication.c INCLUDE_DIRS "./include" REQUIRES ${comps} - EMBED_TXTFILES ${PROJECT_DIR}/main/ca_cert.pem + EMBED_TXTFILES ${PROJECT_DIR}/main/ca_cert.pem ${PROJECT_DIR}/../ota/certs/server.crt EMBED_FILES "index.html") littlefs_create_partition_image(littlefs medias FLASH_IN_PROJECT) elseif(${IDF_TARGET} STREQUAL "linux") diff --git a/main/communication.c b/main/communication.c index e656ceb..98d0bdb 100644 --- a/main/communication.c +++ b/main/communication.c @@ -238,6 +238,10 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ msg_id = esp_mqtt_client_subscribe(client, topicConsoElec, 0); ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + msg_id = esp_mqtt_client_subscribe(client, "devices/esp32p4_01/ota/update", 0); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + + //msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1); //ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); diff --git a/main/main.c b/main/main.c index f29e39d..e14e879 100644 --- a/main/main.c +++ b/main/main.c @@ -20,10 +20,10 @@ #include "ihm_gateway.h" // OTA -/*#include "esp_ota_ops.h" -#include "esp_http_client.h" +#include "esp_ota_ops.h" #include "esp_https_ota.h" -*/ +#include "esp_http_client.h" + // Includes personnels //#include "wifi_logger.h" #include "obtain_time.h" @@ -39,6 +39,7 @@ #if CONFIG_IDF_TARGET_ESP32P4 #include "am2302_rmt.h" +#include "esp_wifi.h" #endif // GPIO assignment #define AM2302_GPIO 4 @@ -106,8 +107,128 @@ void bh1750_init(void) bh1750 = bh1750_create(I2C_MASTER_NUM, BH1750_I2C_ADDRESS_DEFAULT, bus_handle); */ } +TaskHandle_t ihm_task; +typedef struct { + char version[32]; + char url[256]; + char sha256[65]; + bool force; +} ota_msg_t; +static QueueHandle_t ota_queue; +static const char *OTA_TOPIC = "devices/esp32p4_01/ota/update"; +#include +bool parse_ota_json(const char *json, ota_msg_t *msg) +{ + ESP_LOGE("OTA","On demande à parser : %s", json); + cJSON *root = cJSON_Parse(json); + if (!root) return false; + snprintf(msg->version, sizeof(msg->version), + "%s", cJSON_GetStringValue(cJSON_GetObjectItem(root, "version"))); + + snprintf(msg->url, sizeof(msg->url), + "%s", cJSON_GetStringValue(cJSON_GetObjectItem(root, "url"))); + + snprintf(msg->sha256, sizeof(msg->sha256), + "%s", cJSON_GetStringValue(cJSON_GetObjectItem(root, "sha256"))); + + msg->force = cJSON_IsTrue(cJSON_GetObjectItem(root, "force")); + + cJSON_Delete(root); + return true; +} +extern const uint8_t server_cert_pem_start[] asm("_binary_server_crt_start"); +extern const uint8_t server_cert_pem_end[] asm("_binary_server_crt_end"); +void ota_task(void *arg) +{ + ota_msg_t msg; + + while (1) { + if (xQueueReceive(ota_queue, &msg, portMAX_DELAY)) { + + ESP_LOGI("OTA", "Starting OTA to %s", msg.version); + + esp_http_client_config_t http_cfg = { + .url = msg.url, + .cert_pem = (const char *)server_cert_pem_start, + .timeout_ms = 15000, + .skip_cert_common_name_check = true, + }; + + esp_https_ota_config_t ota_cfg = { + .http_config = &http_cfg, + }; + lv_obj_t *ota = lv_msgbox_create(lv_scr_act()); + lv_msgbox_add_text(ota,"Mise à jour OTA en cours"); + suspendIHM(); + + esp_err_t ret = esp_https_ota(&ota_cfg); + + if (ret == ESP_OK) { + ESP_LOGI("OTA", "OTA successful, rebooting"); + esp_restart(); + } else { + ESP_LOGE("OTA", "OTA failed (%s)", esp_err_to_name(ret)); + } + } + } +} +static bool parse_semver(const char *ver, int *maj, int *min, int *pat) +{ + return sscanf(ver, "%d.%d.%d", maj, min, pat) == 3; +} +static bool is_dev_build(void) +{ + const esp_app_desc_t *desc = esp_app_get_description(); + return strchr(desc->version, '-') != NULL; +} +static const char *get_suffix(const char *ver) +{ + const char *dash = strchr(ver, '-'); + return dash ? dash + 1 : NULL; +} +bool is_new_version(const char *incoming) +{ + int cur_maj, cur_min, cur_pat; + int new_maj, new_min, new_pat; + + const esp_app_desc_t *desc = esp_app_get_description(); + const char *current = desc->version; + + if (!parse_semver(current, &cur_maj, &cur_min, &cur_pat) || + !parse_semver(incoming, &new_maj, &new_min, &new_pat)) { + ESP_LOGW("OTA", "Invalid version format"); + return true; // fail-open + } + + // 1️⃣ Upgrade semver classique + if (new_maj != cur_maj) return new_maj > cur_maj; + if (new_min != cur_min) return new_min > cur_min; + if (new_pat != cur_pat) return new_pat > cur_pat; + + // 2️⃣ Même X.Y.Z → comportement dépend du mode + if (is_dev_build()) { + const char *cur_suffix = get_suffix(current); + const char *new_suffix = get_suffix(incoming); + + // si une des deux n'a pas de suffixe → contexte différent + if (!cur_suffix || !new_suffix) { + ESP_LOGI("OTA", "Dev mode: context change (suffix missing)"); + return true; + } + + // suffix différent → OTA + if (strcmp(cur_suffix, new_suffix) != 0) { + ESP_LOGI("OTA", "Dev mode: branch change (%s → %s)", + cur_suffix, new_suffix); + return true; + } + } + + ESP_LOGI("OTA", "No OTA needed"); + return false; +} void mqtt_cb(mqtt_evt evt, esp_mqtt_event_handle_t event){ switch (evt) { @@ -128,11 +249,31 @@ void mqtt_cb(mqtt_evt evt, esp_mqtt_event_handle_t event){ break; case MQTT_DATA_RECEIVED: //lv_subject_set_int(&mqttStatus,2); - ESP_LOGD(TAG, "\nMQTT_EVENT_DATA"); - ESP_LOGD(TAG, "TOPIC=%.*s\n", event->topic_len, event->topic); - ESP_LOGD(TAG, "DATA=%.*s\n", event->data_len, event->data); + ESP_LOGE(TAG, "MQTT_EVENT_DATA"); + ESP_LOGE(TAG, "TOPIC=%.*s\n", event->topic_len, event->topic); + ESP_LOGE(TAG, "DATA=%.*s\n", event->data_len, event->data); char *topic = strndup(event->topic, event->topic_len); - if (strcmp(topic, topicTempExt) == 0) + if (strncmp(topic, OTA_TOPIC, event->topic_len ) == 0) { + ota_msg_t msg = {0}; + + // ⚠️ copie safe (topic/data pas null-terminated) + char payload[512] = {0}; + memcpy(payload, event->data, event->data_len); + + if (!parse_ota_json(payload, &msg)) { + ESP_LOGE("OTA", "Invalid OTA JSON"); + return; + } + + if (!is_new_version(msg.version) && !msg.force) { + ESP_LOGI("OTA", "Version already installed"); + return; + } + + ESP_LOGI("OTA", "OTA requested: %s", msg.version); + + xQueueSend(ota_queue, &msg, 0); } + else if (strcmp(topic, topicTempExt) == 0) { //if(lvgl_port_lock(50)){ float temp = strtof(event->data, NULL); @@ -297,7 +438,7 @@ extern char *days[7]; extern char *months[12]; -/* esp_err_t _ota_http_event_handler(esp_http_client_event_t *evt) +esp_err_t _ota_http_event_handler(esp_http_client_event_t *evt) { switch (evt->event_id) { case HTTP_EVENT_ERROR: @@ -327,11 +468,10 @@ extern char *months[12]; } return ESP_OK; } - */ extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start"); extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); -/* static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) +static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) { if (new_app_info == NULL) { return ESP_ERR_INVALID_ARG; @@ -352,12 +492,11 @@ extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); return ESP_OK; } - */ void simple_ota_example_task(void *pvParameter) { -/* ESP_LOGE(TAG,"En attente connexion wifi"); + ESP_LOGE(TAG,"En attente connexion wifi"); // Waiting until either the connection is established (WIFI_CONNECTED_BIT). EventBits_t bits = xEventGroupWaitBits(domotic_event_group, BIT0, @@ -381,8 +520,8 @@ void simple_ota_example_task(void *pvParameter) esp_http_client_config_t config = { .url = "https://192.168.0.28:8070/rgb_lcd.bin", .timeout_ms = 30000, - .buffer_size = 20000, - .buffer_size_tx = 20000, //TX Buffer, Main Buffer + .buffer_size = 6144, + .buffer_size_tx = 6144, //TX Buffer, Main Buffer .event_handler = _ota_http_event_handler, .keep_alive_enable = true, .cert_pem = (char *)server_cert_pem_start, @@ -483,7 +622,7 @@ ota_end: ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed"); vTaskDelete(NULL); } - */} + } #if CONFIG_IDF_TARGET_ESP32P4 am2302_handle_t sensor = NULL; @@ -872,9 +1011,14 @@ void lightSensorTask(void *pvParameter){ */ } +//#include "audio.h" void app_main(void) { + setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); + tzset(); + + ESP_ERROR_CHECK(esp_event_loop_create_default()); ihm_gateway_init(); @@ -888,14 +1032,24 @@ void app_main(void) #else littlefs_mount(); - xTaskCreate(&drawIhm,"ihm_task",10000,getIHMQueueHandle(),10,NULL); + xTaskCreate(&drawIhm,"ihm_task",10000,getIHMQueueHandle(),3,&ihm_task); //et sinon on se connecte ESP_LOGI(TAG, "ESP_WIFI_MODE_STA"); wifi_init_sta(wifi_cb); //start_wifi_logger(); + /* Ensure to disable any WiFi power save mode, this allows best throughput + * and hence timings for overall OTA operation. + */ + esp_wifi_set_ps(WIFI_PS_NONE); + //xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL); #endif + boucleMeteo(); + + ota_queue = xQueueCreate(1, sizeof(ota_msg_t)); + xTaskCreate(ota_task,"ota_task",8192,NULL,5,NULL); + mqtt_app_start(mqtt_cb, domotic_event_group); TaskHandle_t xHandle = NULL; @@ -921,6 +1075,8 @@ void app_main(void) xTaskCreate(&readTempHumid, "read_temp_task", 8192, NULL, 5, NULL); #endif + esp_ota_mark_app_valid_cancel_rollback(); + } diff --git a/version.txt b/version.txt index be58634..bcab45a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3 +0.0.3