From c272776d4733f36decb851bce0b34424373873f2 Mon Sep 17 00:00:00 2001 From: Rasmus Date: Sat, 12 Sep 2026 14:35:09 +0200 Subject: [PATCH] Gitea URL --- main/ota_manager.c | 30 ++++++++++-- main/ui_task.c | 64 +++++++++++++++++-------- main/web_server.c | 83 +++++++++----------------------- main/wifi_manager.c | 112 ++++++++++++++++++++++++++------------------ 4 files changed, 162 insertions(+), 127 deletions(-) diff --git a/main/ota_manager.c b/main/ota_manager.c index 14c6c60..37f7c73 100644 --- a/main/ota_manager.c +++ b/main/ota_manager.c @@ -384,7 +384,21 @@ static void ota_check_task(void *arg) if (aname && aname->valuestring && aurl && aurl->valuestring) { if (strstr(aname->valuestring, ".bin") != NULL) { const char *url_str = aurl->valuestring; - strncpy(s_release.download_url, url_str, sizeof(s_release.download_url) - 1); + const char *path = strstr(url_str, "/rasmus/MTGcompanion/releases/download/"); + if (path) { + snprintf(s_release.download_url, sizeof(s_release.download_url), + "https://git.rasmusbendtsen.dk%s", path); + } else if (strncmp(url_str, "http://", 7) == 0) { + const char *p = strchr(url_str + 7, '/'); + if (p) { + snprintf(s_release.download_url, sizeof(s_release.download_url), + "https://git.rasmusbendtsen.dk%s", p); + } else { + strncpy(s_release.download_url, url_str, sizeof(s_release.download_url) - 1); + } + } else { + strncpy(s_release.download_url, url_str, sizeof(s_release.download_url) - 1); + } if (asize && asize->valuedouble > 0) { s_release.binary_size = (size_t)asize->valuedouble; } @@ -581,9 +595,14 @@ esp_err_t ota_manager_check_update_async(void) if (s_status == OTA_STATUS_CHECKING || s_status == OTA_STATUS_DOWNLOADING) { return ESP_ERR_INVALID_STATE; } + set_state(OTA_STATUS_CHECKING, "Connecting to Gitea..."); BaseType_t ret = xTaskCreatePinnedToCore( ota_check_task, "ota_check", 8192, NULL, 5, &s_ota_task_handle, 0); - return (ret == pdPASS) ? ESP_OK : ESP_ERR_NO_MEM; + if (ret != pdPASS) { + set_state(OTA_STATUS_FAILED, "Out of memory"); + return ESP_ERR_NO_MEM; + } + return ESP_OK; } esp_err_t ota_manager_start_update_async(void) @@ -594,7 +613,12 @@ esp_err_t ota_manager_start_update_async(void) if (s_release.download_url[0] == '\0') { return ESP_ERR_NOT_FOUND; } + set_state(OTA_STATUS_DOWNLOADING, "Starting download..."); BaseType_t ret = xTaskCreatePinnedToCore( ota_download_task, "ota_update", 8192, NULL, 5, &s_ota_task_handle, 0); - return (ret == pdPASS) ? ESP_OK : ESP_ERR_NO_MEM; + if (ret != pdPASS) { + set_state(OTA_STATUS_FAILED, "Out of memory"); + return ESP_ERR_NO_MEM; + } + return ESP_OK; } diff --git a/main/ui_task.c b/main/ui_task.c index e56a00b..d80b203 100644 --- a/main/ui_task.c +++ b/main/ui_task.c @@ -114,6 +114,11 @@ static int s_storage_option_idx = 0; static uint32_t s_btn_held_ms = 0; static bool s_btn_long_fired = false; +/* OTA progress widgets */ +static lv_obj_t *s_ota_bar = NULL; +static lv_obj_t *s_ota_pct_lbl = NULL; +static lv_obj_t *s_ota_bytes_lbl = NULL; + /* Forward declarations */ static void view_menu(void); static void view_scanner(void); @@ -175,6 +180,8 @@ static bool load_art_crop(const char *uid) esp_jpeg_image_cfg_t cfg = { .indata = jpg_buf, .indata_size = (uint32_t)fsize, + .outbuf = NULL, + .outbuf_size = 0, .out_format = JPEG_IMAGE_FORMAT_RGB565, .out_scale = JPEG_IMAGE_SCALE_0, .flags = { @@ -241,6 +248,9 @@ static const char *get_json_str(cJSON *root, const char *key, const char *fallba static void ui_clean_screen(void) { free_art_buffer(); + s_ota_bar = NULL; + s_ota_pct_lbl = NULL; + s_ota_bytes_lbl = NULL; lv_obj_clean(lv_scr_act()); lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0); lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0); @@ -1075,6 +1085,17 @@ static void view_device_info(void) static ota_status_t s_last_ota_rendered_st = OTA_STATUS_IDLE; static int s_last_ota_percent = -1; +static void update_ota_progress(int percent, size_t rb, size_t tb) +{ + lvgl_port_lock(0); + if (s_ota_bar && s_ota_pct_lbl && s_ota_bytes_lbl) { + lv_bar_set_value(s_ota_bar, percent, LV_ANIM_OFF); + lv_label_set_text_fmt(s_ota_pct_lbl, "%d%%", percent); + lv_label_set_text_fmt(s_ota_bytes_lbl, "%d / %d KB", (int)(rb / 1024), (int)(tb / 1024)); + } + lvgl_port_unlock(); +} + static void view_ota(void) { lvgl_port_lock(0); @@ -1086,6 +1107,7 @@ static void view_ota(void) ui_draw_header("FIRMWARE UPDATE", "GITEA OTA"); switch (st) { + case OTA_STATUS_IDLE: case OTA_STATUS_CHECKING: { lv_obj_t *box = lv_obj_create(lv_scr_act()); lv_obj_set_size(box, DISP_W - 24, 180); @@ -1236,25 +1258,25 @@ static void view_ota(void) lv_obj_set_style_text_color(t, lv_color_hex(0x00E5FF), 0); lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 10); - lv_obj_t *p_lbl = lv_label_create(box); - lv_label_set_text_fmt(p_lbl, "%d%%", percent); - lv_obj_set_style_text_font(p_lbl, &lv_font_montserrat_48, 0); - lv_obj_set_style_text_color(p_lbl, lv_color_hex(0xFFCC00), 0); - lv_obj_align(p_lbl, LV_ALIGN_CENTER, 0, -15); + s_ota_pct_lbl = lv_label_create(box); + lv_label_set_text_fmt(s_ota_pct_lbl, "%d%%", percent); + lv_obj_set_style_text_font(s_ota_pct_lbl, &lv_font_montserrat_48, 0); + lv_obj_set_style_text_color(s_ota_pct_lbl, lv_color_hex(0xFFCC00), 0); + lv_obj_align(s_ota_pct_lbl, LV_ALIGN_CENTER, 0, -15); - lv_obj_t *bar = lv_bar_create(box); - lv_obj_set_size(bar, DISP_W - 60, 14); - lv_obj_align(bar, LV_ALIGN_CENTER, 0, 35); - lv_bar_set_range(bar, 0, 100); - lv_bar_set_value(bar, percent, LV_ANIM_OFF); - lv_obj_set_style_bg_color(bar, lv_color_hex(0x333333), 0); - lv_obj_set_style_bg_color(bar, lv_color_hex(0x00E676), LV_PART_INDICATOR); + s_ota_bar = lv_bar_create(box); + lv_obj_set_size(s_ota_bar, DISP_W - 60, 14); + lv_obj_align(s_ota_bar, LV_ALIGN_CENTER, 0, 35); + lv_bar_set_range(s_ota_bar, 0, 100); + lv_bar_set_value(s_ota_bar, percent, LV_ANIM_OFF); + lv_obj_set_style_bg_color(s_ota_bar, lv_color_hex(0x333333), 0); + lv_obj_set_style_bg_color(s_ota_bar, lv_color_hex(0x00E676), LV_PART_INDICATOR); - lv_obj_t *b_lbl = lv_label_create(box); - lv_label_set_text_fmt(b_lbl, "%d / %d KB", (int)(rb / 1024), (int)(tb / 1024)); - lv_obj_set_style_text_font(b_lbl, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(b_lbl, lv_color_hex(0xAAAAAA), 0); - lv_obj_align(b_lbl, LV_ALIGN_BOTTOM_MID, 0, -8); + s_ota_bytes_lbl = lv_label_create(box); + lv_label_set_text_fmt(s_ota_bytes_lbl, "%d / %d KB", (int)(rb / 1024), (int)(tb / 1024)); + lv_obj_set_style_text_font(s_ota_bytes_lbl, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color(s_ota_bytes_lbl, lv_color_hex(0xAAAAAA), 0); + lv_obj_align(s_ota_bytes_lbl, LV_ALIGN_BOTTOM_MID, 0, -8); lv_obj_t *warn = lv_label_create(lv_scr_act()); lv_label_set_text(warn, "DO NOT TURN OFF POWER"); @@ -2015,9 +2037,13 @@ static void ui_task(void *arg) if (app_get_view() == APP_STATE_OTA_UPDATE) { ota_status_t cur_ota_st = ota_manager_get_status(); int cur_percent = ota_manager_get_progress(); - if (cur_ota_st != s_last_ota_rendered_st || - (cur_ota_st == OTA_STATUS_DOWNLOADING && cur_percent != s_last_ota_percent)) { + if (cur_ota_st != s_last_ota_rendered_st) { view_ota(); + } else if (cur_ota_st == OTA_STATUS_DOWNLOADING && cur_percent != s_last_ota_percent) { + s_last_ota_percent = cur_percent; + size_t rb = 0, tb = 0; + ota_manager_get_bytes(&rb, &tb); + update_ota_progress(cur_percent, rb, tb); } } diff --git a/main/web_server.c b/main/web_server.c index f72ef1f..85a68cf 100644 --- a/main/web_server.c +++ b/main/web_server.c @@ -776,69 +776,32 @@ esp_err_t web_server_start(void) return ESP_OK; } - /* AP netif must exist before wifi start */ - esp_netif_t *ap = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); - if (ap == NULL) { - ap = esp_netif_create_default_wifi_ap(); + /* If Wi-Fi is not yet started, configure AP and start it */ + wifi_mode_t mode; + if (esp_wifi_get_mode(&mode) != ESP_OK) { + esp_netif_t *ap = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); if (ap == NULL) { - ESP_LOGE(TAG, "failed to create AP netif"); - return ESP_FAIL; + ap = esp_netif_create_default_wifi_ap(); + if (ap != NULL) { + esp_netif_set_hostname(ap, "mtg-companion"); + } } - esp_netif_set_hostname(ap, "mtg-companion"); + (void)esp_wifi_set_mode(WIFI_MODE_APSTA); + wifi_config_t wifi_cfg = { + .ap = { + .ssid_len = sizeof(AP_SSID) - 1, + .max_connection = 4, + .authmode = (AP_PASS[0] == '\0') ? WIFI_AUTH_OPEN : WIFI_AUTH_WPA2_PSK, + }, + }; + strncpy((char *)wifi_cfg.ap.ssid, AP_SSID, sizeof(wifi_cfg.ap.ssid) - 1); + if (AP_PASS[0] != '\0') { + strncpy((char *)wifi_cfg.ap.password, AP_PASS, sizeof(wifi_cfg.ap.password) - 1); + } + (void)esp_wifi_set_config(WIFI_IF_AP, &wifi_cfg); + (void)esp_wifi_start(); } - esp_err_t ret = esp_wifi_stop(); /* safe even if not started */ - if (ret != ESP_OK && ret != ESP_ERR_WIFI_NOT_STARTED) { - ESP_LOGE(TAG, "wifi stop failed: %s", esp_err_to_name(ret)); - return ret; - } - - ret = esp_wifi_set_mode(WIFI_MODE_APSTA); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "set mode APSTA failed: %s", esp_err_to_name(ret)); - return ret; - } - - wifi_config_t wifi_cfg = { - .ap = { - .ssid_len = sizeof(AP_SSID) - 1, - .max_connection = 4, - .authmode = (AP_PASS[0] == '\0') ? WIFI_AUTH_OPEN : WIFI_AUTH_WPA2_PSK, - }, - }; - strncpy((char *)wifi_cfg.ap.ssid, AP_SSID, sizeof(wifi_cfg.ap.ssid) - 1); - if (AP_PASS[0] != '\0') { - strncpy((char *)wifi_cfg.ap.password, AP_PASS, sizeof(wifi_cfg.ap.password) - 1); - } - ret = esp_wifi_set_config(WIFI_IF_AP, &wifi_cfg); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "set AP config failed: %s", esp_err_to_name(ret)); - return ret; - } - - esp_netif_ip_info_t ip_info = { - .ip = { .addr = ESP_IP4TOADDR(192, 168, 4, 1) }, - .netmask = { .addr = ESP_IP4TOADDR(255, 255, 255, 0) }, - .gw = { .addr = ESP_IP4TOADDR(192, 168, 4, 1) }, - }; - /* The AP netif has DHCP running by default; stop it before assigning a - * static IP, otherwise esp_netif_set_ip_info fails with - * ESP_ERR_ESP_NETIF_DHCP_NOT_STOPPED. */ - esp_netif_dhcps_stop(ap); - ret = esp_netif_set_ip_info(ap, &ip_info); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "set AP IP failed: %s", esp_err_to_name(ret)); - return ret; - } - - ret = esp_wifi_start(); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "wifi start (APSTA) failed: %s", esp_err_to_name(ret)); - return ret; - } - - (void)esp_netif_dhcps_start(ap); /* harmless if someone else started it */ - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.stack_size = 8 * 1024; config.max_uri_handlers = 24; @@ -846,7 +809,7 @@ esp_err_t web_server_start(void) config.keep_alive_enable = true; config.max_open_sockets = 4; - ret = httpd_start(&s_server, &config); + esp_err_t ret = httpd_start(&s_server, &config); if (ret != ESP_OK) { ESP_LOGE(TAG, "httpd_start failed: %s", esp_err_to_name(ret)); return ret; diff --git a/main/wifi_manager.c b/main/wifi_manager.c index 5b8f39e..411e189 100644 --- a/main/wifi_manager.c +++ b/main/wifi_manager.c @@ -29,7 +29,7 @@ static EventGroupHandle_t s_wifi_group = NULL; static int s_retry_count = 0; static char s_sta_ip[20] = "No Wi-Fi"; -static char s_active_ssid[WIFI_SSID_MAX_LEN + 1] = "Disconnected"; +static char s_active_ssid[WIFI_SSID_MAX_LEN + 1] = {0}; static SemaphoreHandle_t s_wifi_mux = NULL; static SemaphoreHandle_t s_net_lock = NULL; @@ -62,10 +62,8 @@ int wifi_manager_get_saved_profiles(wifi_profile_t *out_profiles, int max_count) size_t s_len = sizeof(out_profiles[loaded].ssid); size_t p_len = sizeof(out_profiles[loaded].password); - if (nvs_get_str(h, key_s, out_profiles[loaded].ssid, &s_len) == ESP_OK) { - if (nvs_get_str(h, key_p, out_profiles[loaded].password, &p_len) != ESP_OK) { - out_profiles[loaded].password[0] = '\0'; - } + if (nvs_get_str(h, key_s, out_profiles[loaded].ssid, &s_len) == ESP_OK && + nvs_get_str(h, key_p, out_profiles[loaded].password, &p_len) == ESP_OK) { loaded++; } } @@ -76,43 +74,40 @@ int wifi_manager_get_saved_profiles(wifi_profile_t *out_profiles, int max_count) esp_err_t wifi_manager_save_profile(const char *ssid, const char *password) { - if (ssid == NULL || strlen(ssid) == 0) { - return ESP_ERR_INVALID_ARG; - } + if (ssid == NULL || strlen(ssid) == 0) return ESP_ERR_INVALID_ARG; + if (password == NULL) password = ""; wifi_profile_t profiles[WIFI_MAX_PROFILES]; int count = wifi_manager_get_saved_profiles(profiles, WIFI_MAX_PROFILES); - /* Check if already exists - update password if so */ - int target_idx = -1; for (int i = 0; i < count; i++) { if (strcmp(profiles[i].ssid, ssid) == 0) { - target_idx = i; - break; + nvs_handle_t h; + esp_err_t err = nvs_open(NVS_WIFI_NS, NVS_READWRITE, &h); + if (err != ESP_OK) return err; + char key_p[16]; + snprintf(key_p, sizeof(key_p), "p_%d", i); + nvs_set_str(h, key_p, password); + err = nvs_commit(h); + nvs_close(h); + ESP_LOGI(TAG, "Updated password for saved profile '%s'", ssid); + return err; } } - if (target_idx >= 0) { - strncpy(profiles[target_idx].password, password ? password : "", sizeof(profiles[target_idx].password) - 1); - profiles[target_idx].password[sizeof(profiles[target_idx].password) - 1] = '\0'; - } else { - if (count < WIFI_MAX_PROFILES) { - target_idx = count; - count++; - } else { - /* Replace oldest (index 0), shift left */ - for (int i = 0; i < WIFI_MAX_PROFILES - 1; i++) { - profiles[i] = profiles[i + 1]; - } - target_idx = WIFI_MAX_PROFILES - 1; + if (count >= WIFI_MAX_PROFILES) { + for (int i = 0; i < WIFI_MAX_PROFILES - 1; i++) { + profiles[i] = profiles[i + 1]; } - strncpy(profiles[target_idx].ssid, ssid, sizeof(profiles[target_idx].ssid) - 1); - profiles[target_idx].ssid[sizeof(profiles[target_idx].ssid) - 1] = '\0'; - strncpy(profiles[target_idx].password, password ? password : "", sizeof(profiles[target_idx].password) - 1); - profiles[target_idx].password[sizeof(profiles[target_idx].password) - 1] = '\0'; + count = WIFI_MAX_PROFILES - 1; } - /* Write back to NVS */ + strncpy(profiles[count].ssid, ssid, sizeof(profiles[count].ssid) - 1); + profiles[count].ssid[sizeof(profiles[count].ssid) - 1] = '\0'; + strncpy(profiles[count].password, password, sizeof(profiles[count].password) - 1); + profiles[count].password[sizeof(profiles[count].password) - 1] = '\0'; + count++; + nvs_handle_t h; esp_err_t err = nvs_open(NVS_WIFI_NS, NVS_READWRITE, &h); if (err != ESP_OK) return err; @@ -128,7 +123,7 @@ esp_err_t wifi_manager_save_profile(const char *ssid, const char *password) err = nvs_commit(h); nvs_close(h); - ESP_LOGI(TAG, "Saved profile '%s' to NVS (total saved: %d)", ssid, count); + ESP_LOGI(TAG, "Saved profile '%s' to NVS (total: %d)", ssid, count); return err; } @@ -139,19 +134,17 @@ esp_err_t wifi_manager_delete_profile(const char *ssid) wifi_profile_t profiles[WIFI_MAX_PROFILES]; int count = wifi_manager_get_saved_profiles(profiles, WIFI_MAX_PROFILES); - int found = -1; + int del_idx = -1; for (int i = 0; i < count; i++) { if (strcmp(profiles[i].ssid, ssid) == 0) { - found = i; + del_idx = i; break; } } - if (found < 0) { - return ESP_ERR_NOT_FOUND; - } - /* Shift remaining */ - for (int i = found; i < count - 1; i++) { + if (del_idx == -1) return ESP_ERR_NOT_FOUND; + + for (int i = del_idx; i < count - 1; i++) { profiles[i] = profiles[i + 1]; } count--; @@ -185,14 +178,13 @@ static void wifi_event_handler(void *arg, esp_event_base_t base, int32_t id, voi (void)arg; if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) { - ESP_LOGI(TAG, "station started, connecting to '%s'...", s_active_ssid); - esp_wifi_connect(); + ESP_LOGI(TAG, "Wi-Fi station interface started"); } else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) { strncpy(s_sta_ip, "No Wi-Fi", sizeof(s_sta_ip)); if (s_wifi_group) { xEventGroupClearBits(s_wifi_group, WIFI_CONNECTED_BIT); } - if (s_retry_count < WIFI_RETRY_MAX) { + if (s_active_ssid[0] != '\0' && s_retry_count < WIFI_RETRY_MAX) { ESP_LOGW(TAG, "disconnected from '%s' (attempt %d), reconnecting...", s_active_ssid, s_retry_count); esp_wifi_connect(); s_retry_count++; @@ -221,7 +213,7 @@ const char *wifi_manager_get_ip(void) const char *wifi_manager_get_connected_ssid(void) { - if (!wifi_manager_connected()) { + if (!wifi_manager_connected() || s_active_ssid[0] == '\0') { return "Disconnected"; } return s_active_ssid; @@ -380,6 +372,19 @@ esp_err_t wifi_manager_init(void) esp_netif_set_hostname(sta_netif, "mtg-companion"); } + esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap(); + if (ap_netif != NULL) { + esp_netif_set_hostname(ap_netif, "mtg-companion"); + esp_netif_ip_info_t ip_info = { + .ip = { .addr = ESP_IP4TOADDR(192, 168, 4, 1) }, + .netmask = { .addr = ESP_IP4TOADDR(255, 255, 255, 0) }, + .gw = { .addr = ESP_IP4TOADDR(192, 168, 4, 1) }, + }; + esp_netif_dhcps_stop(ap_netif); + esp_netif_set_ip_info(ap_netif, &ip_info); + esp_netif_dhcps_start(ap_netif); + } + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); @@ -400,6 +405,17 @@ esp_err_t wifi_manager_start(void) const char *target_pass = NULL; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + + wifi_config_t ap_cfg = { + .ap = { + .ssid_len = sizeof("MTG-Companion") - 1, + .max_connection = 4, + .authmode = WIFI_AUTH_OPEN, + }, + }; + strncpy((char *)ap_cfg.ap.ssid, "MTG-Companion", sizeof(ap_cfg.ap.ssid) - 1); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_cfg)); + ESP_ERROR_CHECK(esp_wifi_start()); if (profile_count > 0) { @@ -465,10 +481,16 @@ esp_err_t wifi_manager_start(void) strncpy(s_active_ssid, target_ssid, sizeof(s_active_ssid) - 1); s_active_ssid[sizeof(s_active_ssid) - 1] = '\0'; + s_retry_count = 0; - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg)); - ESP_LOGI(TAG, "Connecting to target '%s'...", target_ssid); - esp_wifi_connect(); + esp_wifi_disconnect(); + esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_set_config failed: %s", esp_err_to_name(err)); + } else { + ESP_LOGI(TAG, "Connecting to target '%s'...", target_ssid); + esp_wifi_connect(); + } } else { ESP_LOGI(TAG, "No Wi-Fi networks configured yet. Device running in SoftAP setup mode."); }