This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "cJSON.h"
|
||||
#include "wifi_manager.h"
|
||||
#include "ota_manager.h"
|
||||
|
||||
static const char *TAG = "ota";
|
||||
@@ -30,7 +31,42 @@ static const char *TAG = "ota";
|
||||
#define GITEA_RELEASE_API_URL "https://git.rasmusbendtsen.dk/api/v1/repos/rasmus/MTGcompanion/releases/latest"
|
||||
#define GITEA_FIRMWARE_FALLBACK_URL "https://git.rasmusbendtsen.dk/rasmus/MTGcompanion/releases/download/latest/mtg-rfid-companion.bin"
|
||||
|
||||
#define GITEA_API_BUF_SIZE (8192)
|
||||
#define GITEA_API_BUF_SIZE (5120)
|
||||
|
||||
/* Let's Encrypt ISRG Root X1 CA Certificate (valid through 2035) */
|
||||
static const char s_isrg_root_x1_pem[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
|
||||
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
|
||||
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
|
||||
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
|
||||
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
|
||||
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
|
||||
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
|
||||
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
|
||||
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
|
||||
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB\n"
|
||||
"5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
|
||||
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
|
||||
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
|
||||
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
|
||||
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
|
||||
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
|
||||
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
|
||||
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
|
||||
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
|
||||
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
|
||||
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
|
||||
"NFtY2PwByVS5uCbMiogzihDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORA\n"
|
||||
"zI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7UrTkX\n"
|
||||
"WStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdCjNP\n"
|
||||
"ElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVcoyi\n"
|
||||
"3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4Rg\n"
|
||||
"qsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRG\n"
|
||||
"unUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demy\n"
|
||||
"PxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
|
||||
static SemaphoreHandle_t s_ota_mux = NULL;
|
||||
static ota_status_t s_status = OTA_STATUS_IDLE;
|
||||
@@ -215,6 +251,14 @@ static void ota_check_task(void *arg)
|
||||
|
||||
ESP_LOGI(TAG, "Querying Gitea release API: %s", GITEA_RELEASE_API_URL);
|
||||
|
||||
if (wifi_manager_acquire_net_lock(pdMS_TO_TICKS(15000)) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Network lock busy, cannot start OTA check");
|
||||
set_state(OTA_STATUS_FAILED, "Network busy");
|
||||
s_ota_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
http_rx_buf_t rx = {
|
||||
.buf = malloc(GITEA_API_BUF_SIZE),
|
||||
.len = 0,
|
||||
@@ -222,6 +266,7 @@ static void ota_check_task(void *arg)
|
||||
};
|
||||
|
||||
if (!rx.buf) {
|
||||
wifi_manager_release_net_lock();
|
||||
set_state(OTA_STATUS_FAILED, "Out of memory");
|
||||
s_ota_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
@@ -232,15 +277,16 @@ static void ota_check_task(void *arg)
|
||||
esp_http_client_config_t cfg = {
|
||||
.url = GITEA_RELEASE_API_URL,
|
||||
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||
.timeout_ms = 10000,
|
||||
.timeout_ms = 15000,
|
||||
.event_handler = http_rx_event_handler,
|
||||
.user_data = &rx,
|
||||
.buffer_size = 2048,
|
||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||
.buffer_size = 1024,
|
||||
.cert_pem = s_isrg_root_x1_pem,
|
||||
};
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
||||
if (!client) {
|
||||
wifi_manager_release_net_lock();
|
||||
free(rx.buf);
|
||||
set_state(OTA_STATUS_FAILED, "HTTP init failed");
|
||||
s_ota_task_handle = NULL;
|
||||
@@ -255,6 +301,8 @@ static void ota_check_task(void *arg)
|
||||
esp_err_t ret = esp_http_client_perform(client);
|
||||
int status_code = esp_http_client_get_status_code(client);
|
||||
esp_http_client_cleanup(client);
|
||||
wifi_manager_release_net_lock();
|
||||
|
||||
|
||||
if (ret != ESP_OK || status_code != 200) {
|
||||
char err_msg[64];
|
||||
@@ -415,6 +463,14 @@ static void ota_download_task(void *arg)
|
||||
s_bytes_read = 0;
|
||||
s_total_bytes = s_release.binary_size;
|
||||
|
||||
if (wifi_manager_acquire_net_lock(pdMS_TO_TICKS(15000)) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Network lock busy, cannot start OTA download");
|
||||
set_state(OTA_STATUS_FAILED, "Network busy");
|
||||
s_ota_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
set_state(OTA_STATUS_DOWNLOADING, "Starting download...");
|
||||
ESP_LOGI(TAG, "Starting HTTPS OTA from: %s", s_release.download_url);
|
||||
|
||||
@@ -424,7 +480,7 @@ static void ota_download_task(void *arg)
|
||||
.timeout_ms = 30000,
|
||||
.keep_alive_enable = true,
|
||||
.buffer_size = 2048,
|
||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||
.cert_pem = s_isrg_root_x1_pem,
|
||||
};
|
||||
|
||||
esp_https_ota_config_t ota_cfg = {
|
||||
@@ -434,6 +490,7 @@ static void ota_download_task(void *arg)
|
||||
esp_https_ota_handle_t ota_handle = NULL;
|
||||
esp_err_t err = esp_https_ota_begin(&ota_cfg, &ota_handle);
|
||||
if (err != ESP_OK || ota_handle == NULL) {
|
||||
wifi_manager_release_net_lock();
|
||||
ESP_LOGE(TAG, "esp_https_ota_begin failed: %s", esp_err_to_name(err));
|
||||
set_state(OTA_STATUS_FAILED, "Failed to connect to image");
|
||||
s_ota_task_handle = NULL;
|
||||
@@ -448,6 +505,7 @@ static void ota_download_task(void *arg)
|
||||
if (memcmp(new_app_info.app_elf_sha256, running->app_elf_sha256, sizeof(new_app_info.app_elf_sha256)) == 0) {
|
||||
ESP_LOGW(TAG, "Incoming image has identical ELF SHA-256 as running app - aborting flash write");
|
||||
esp_https_ota_abort(ota_handle);
|
||||
wifi_manager_release_net_lock();
|
||||
set_state(OTA_STATUS_UP_TO_DATE, "Firmware is already up to date");
|
||||
s_ota_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
@@ -459,6 +517,7 @@ static void ota_download_task(void *arg)
|
||||
if (s_cancel_requested) {
|
||||
ESP_LOGW(TAG, "OTA canceled by user");
|
||||
esp_https_ota_abort(ota_handle);
|
||||
wifi_manager_release_net_lock();
|
||||
set_state(OTA_STATUS_IDLE, "Update canceled");
|
||||
s_ota_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
@@ -481,6 +540,7 @@ static void ota_download_task(void *arg)
|
||||
if (err == ESP_OK) {
|
||||
if (esp_https_ota_is_complete_data_received(ota_handle)) {
|
||||
esp_err_t finish_err = esp_https_ota_finish(ota_handle);
|
||||
wifi_manager_release_net_lock();
|
||||
if (finish_err == ESP_OK) {
|
||||
/* Store installed release hash in NVS */
|
||||
nvs_handle_t nvs_h;
|
||||
@@ -502,11 +562,13 @@ static void ota_download_task(void *arg)
|
||||
}
|
||||
} else {
|
||||
esp_https_ota_abort(ota_handle);
|
||||
wifi_manager_release_net_lock();
|
||||
set_state(OTA_STATUS_FAILED, "Incomplete download");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "esp_https_ota_perform failed: %s", esp_err_to_name(err));
|
||||
esp_https_ota_abort(ota_handle);
|
||||
wifi_manager_release_net_lock();
|
||||
set_state(OTA_STATUS_FAILED, "Flash write failed");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ static const char *TAG = "rfid";
|
||||
* Keep this <= 15 chars and in sync with the rc522 library's
|
||||
* xTaskCreate("rc522_poll", ...). */
|
||||
#define RC522_TASK_NAME "rc522_poll"
|
||||
#define RC522_POLL_INTERVAL_MS (100)
|
||||
#define RC522_POLL_INTERVAL_MS (125)
|
||||
#define RC522_TASK_STACK_SIZE (6 * 1024)
|
||||
#define RC522_TASK_PRIORITY (1)
|
||||
|
||||
|
||||
@@ -148,6 +148,12 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
|
||||
static esp_err_t http_request(const char *url, http_sink_t *sink)
|
||||
{
|
||||
if (wifi_manager_acquire_net_lock(pdMS_TO_TICKS(15000)) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Network lock busy (e.g. OTA update in progress), deferring fetch %s", url);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
esp_err_t overall_ret = ESP_FAIL;
|
||||
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
||||
if (sink->file != NULL) {
|
||||
rewind(sink->file);
|
||||
@@ -168,7 +174,8 @@ static esp_err_t http_request(const char *url, http_sink_t *sink)
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
overall_ret = ESP_ERR_NO_MEM;
|
||||
break;
|
||||
}
|
||||
|
||||
esp_http_client_set_method(client, HTTP_METHOD_GET);
|
||||
@@ -181,17 +188,21 @@ static esp_err_t http_request(const char *url, http_sink_t *sink)
|
||||
|
||||
ESP_LOGD(TAG, "GET %s -> %s (status %d)", url, esp_err_to_name(ret), status);
|
||||
if (ret == ESP_OK && status >= 200 && status < 300) {
|
||||
return ESP_OK;
|
||||
overall_ret = ESP_OK;
|
||||
break;
|
||||
}
|
||||
if (ret == ESP_OK && (status < 200 || status >= 300)) {
|
||||
ESP_LOGW(TAG, "HTTP GET returned status %d for %s", status, url);
|
||||
}
|
||||
if (sink->err != ESP_OK) {
|
||||
return sink->err;
|
||||
overall_ret = sink->err;
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
return ESP_FAIL;
|
||||
|
||||
wifi_manager_release_net_lock();
|
||||
return overall_ret;
|
||||
}
|
||||
|
||||
static esp_err_t http_get_json(const char *url, char **out_buf)
|
||||
|
||||
@@ -17,20 +17,6 @@ static const char *TAG = "storage";
|
||||
|
||||
#define STORAGE_PARTITION_LABEL "storage"
|
||||
|
||||
static const char SAMPLE_MAPPINGS[] =
|
||||
"{\n"
|
||||
" \"04A2B3C4\": \"Sol Ring\",\n"
|
||||
" \"04E1F2A3\": \"Atraxa, Praetors' Voice\"\n"
|
||||
"}\n";
|
||||
|
||||
static const char SAMPLE_CARD[] =
|
||||
"{\n"
|
||||
" \"name\": \"Sol Ring\",\n"
|
||||
" \"mana_cost\": \"{1}\",\n"
|
||||
" \"type\": \"Artifact\",\n"
|
||||
" \"oracle\": \"Tap: Add CC.\",\n"
|
||||
" \"price_usd\": \"1.25\"\n"
|
||||
"}\n";
|
||||
|
||||
bool storage_file_exists(const char *path)
|
||||
{
|
||||
@@ -222,22 +208,12 @@ esp_err_t storage_manager_lookup_name(const char *uid, char **out)
|
||||
|
||||
static esp_err_t seed_defaults(void)
|
||||
{
|
||||
/* Create /spiffs/cards directory */
|
||||
/* Ensure /spiffs/cards directory exists */
|
||||
mkdir(STORAGE_CARDS_DIR, 0755);
|
||||
|
||||
if (!storage_file_exists(STORAGE_MAPPINGS)) {
|
||||
ESP_LOGI(TAG, "seeding %s", STORAGE_MAPPINGS);
|
||||
esp_err_t ret = storage_write_text(STORAGE_MAPPINGS, SAMPLE_MAPPINGS);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
char sample_path[64];
|
||||
snprintf(sample_path, sizeof(sample_path), STORAGE_CARDS_DIR "/sample.json");
|
||||
if (!storage_file_exists(sample_path)) {
|
||||
ESP_LOGI(TAG, "seeding %s", sample_path);
|
||||
esp_err_t ret = storage_write_text(sample_path, SAMPLE_CARD);
|
||||
ESP_LOGI(TAG, "Creating empty %s", STORAGE_MAPPINGS);
|
||||
esp_err_t ret = storage_write_text(STORAGE_MAPPINGS, "{\n}\n");
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
167
main/ui_task.c
167
main/ui_task.c
@@ -887,34 +887,45 @@ static void view_storage(void)
|
||||
lv_obj_set_style_pad_all(stat_box, 8, 0);
|
||||
lv_obj_set_scrollbar_mode(stat_box, LV_SCROLLBAR_MODE_OFF);
|
||||
|
||||
lv_obj_t *key_lbl = lv_label_create(stat_box);
|
||||
lv_obj_set_width(key_lbl, 125);
|
||||
lv_obj_set_style_text_font(key_lbl, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(key_lbl, lv_color_hex(0x00E5FF), 0);
|
||||
lv_label_set_text(key_lbl,
|
||||
"CACHED CARDS:\n"
|
||||
"ART CROPS:\n\n"
|
||||
"STORAGE USED:\n"
|
||||
"PERCENT USED:\n"
|
||||
"DECK CAPACITY:");
|
||||
lv_obj_align(key_lbl, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||
struct {
|
||||
const char *key;
|
||||
char val[32];
|
||||
} stat_rows[5];
|
||||
|
||||
snprintf(stat_rows[0].val, sizeof(stat_rows[0].val), "%d", json_c);
|
||||
stat_rows[0].key = "CACHED CARDS:";
|
||||
|
||||
snprintf(stat_rows[1].val, sizeof(stat_rows[1].val), "%d", jpg_c);
|
||||
stat_rows[1].key = "ART CROPS:";
|
||||
|
||||
snprintf(stat_rows[2].val, sizeof(stat_rows[2].val), "%d / %d KB", (int)(used_b / 1024), (int)(total_b / 1024));
|
||||
stat_rows[2].key = "STORAGE USED:";
|
||||
|
||||
lv_obj_t *val_lbl = lv_label_create(stat_box);
|
||||
lv_obj_set_width(val_lbl, 70);
|
||||
lv_obj_set_style_text_font(val_lbl, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(val_lbl, lv_color_hex(COLOR_AMBER), 0);
|
||||
lv_obj_set_style_text_align(val_lbl, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
int percent = total_b > 0 ? (int)((used_b * 100) / total_b) : 0;
|
||||
lv_label_set_text_fmt(val_lbl,
|
||||
"%d\n"
|
||||
"%d\n\n"
|
||||
"%d KB\n"
|
||||
"%d%%\n"
|
||||
"~120-140",
|
||||
json_c, jpg_c,
|
||||
(int)(used_b / 1024),
|
||||
percent);
|
||||
lv_obj_align(val_lbl, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
snprintf(stat_rows[3].val, sizeof(stat_rows[3].val), "%d%%", percent);
|
||||
stat_rows[3].key = "PERCENT USED:";
|
||||
|
||||
snprintf(stat_rows[4].val, sizeof(stat_rows[4].val), "~100 (EDH)");
|
||||
stat_rows[4].key = "DECK CAPACITY:";
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int y_pos = (i < 2) ? (i * 20) : (i * 20 + 8);
|
||||
|
||||
lv_obj_t *k = lv_label_create(stat_box);
|
||||
lv_label_set_text(k, stat_rows[i].key);
|
||||
lv_obj_set_style_text_font(k, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(k, lv_color_hex(0x00E5FF), 0);
|
||||
lv_obj_align(k, LV_ALIGN_TOP_LEFT, 0, y_pos);
|
||||
|
||||
lv_obj_t *v = lv_label_create(stat_box);
|
||||
lv_label_set_text(v, stat_rows[i].val);
|
||||
lv_obj_set_width(v, 90);
|
||||
lv_label_set_long_mode(v, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_style_text_font(v, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(v, lv_color_hex(COLOR_AMBER), 0);
|
||||
lv_obj_set_style_text_align(v, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
lv_obj_align(v, LV_ALIGN_TOP_RIGHT, 0, y_pos);
|
||||
}
|
||||
|
||||
/* Action 0: Clear Cards Button */
|
||||
lv_obj_t *btn0 = lv_obj_create(lv_scr_act());
|
||||
@@ -990,54 +1001,66 @@ static void view_device_info(void)
|
||||
|
||||
const char *ssid = wifi_manager_get_connected_ssid();
|
||||
int rssi = wifi_manager_get_rssi();
|
||||
char wifi_line[32];
|
||||
char wifi_line[64];
|
||||
if (wifi_manager_connected() && ssid && strlen(ssid) > 0) {
|
||||
snprintf(wifi_line, sizeof(wifi_line), "%s (%ddB)", ssid, rssi);
|
||||
} else {
|
||||
snprintf(wifi_line, sizeof(wifi_line), "Disconnected");
|
||||
}
|
||||
|
||||
lv_obj_t *lbl_keys = lv_label_create(box);
|
||||
lv_obj_set_width(lbl_keys, 75);
|
||||
lv_obj_set_style_text_font(lbl_keys, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_keys, lv_color_hex(0x00E5FF), 0);
|
||||
lv_label_set_text(lbl_keys,
|
||||
"VERSION:\n"
|
||||
"WI-FI:\n"
|
||||
"IP ADDR:\n"
|
||||
"HOTSPOT:\n"
|
||||
"STORAGE:\n"
|
||||
"CARDS:\n"
|
||||
"FREE RAM:\n"
|
||||
"ENCODER:\n"
|
||||
"HARDWARE:\n"
|
||||
"RFID:");
|
||||
lv_obj_align(lbl_keys, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||
struct {
|
||||
const char *key;
|
||||
char val[64];
|
||||
} rows[10];
|
||||
|
||||
lv_obj_t *lbl_vals = lv_label_create(box);
|
||||
lv_obj_set_width(lbl_vals, 117);
|
||||
lv_obj_set_style_text_font(lbl_vals, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(lbl_vals, lv_color_hex(COLOR_AMBER), 0);
|
||||
lv_obj_set_style_text_align(lbl_vals, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
lv_label_set_text_fmt(lbl_vals,
|
||||
"%s\n"
|
||||
"%s\n"
|
||||
"%s\n"
|
||||
"192.168.4.1\n"
|
||||
"%d / %d KB\n"
|
||||
"%d cached\n"
|
||||
"%d KB\n"
|
||||
"%d steps\n"
|
||||
"ESP32+ST7789\n"
|
||||
"MFRC522",
|
||||
ota_manager_get_current_version(),
|
||||
wifi_line,
|
||||
wifi_manager_get_ip(),
|
||||
(int)(used_b / 1024), (int)(total_b / 1024),
|
||||
json_c,
|
||||
(int)(free_heap / 1024),
|
||||
(int)encoder_get_steps_per_detent());
|
||||
lv_obj_align(lbl_vals, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
snprintf(rows[0].val, sizeof(rows[0].val), "%s", ota_manager_get_current_version());
|
||||
rows[0].key = "VERSION:";
|
||||
|
||||
snprintf(rows[1].val, sizeof(rows[1].val), "%s", wifi_line);
|
||||
rows[1].key = "WI-FI:";
|
||||
|
||||
snprintf(rows[2].val, sizeof(rows[2].val), "%s", wifi_manager_get_ip());
|
||||
rows[2].key = "IP ADDR:";
|
||||
|
||||
snprintf(rows[3].val, sizeof(rows[3].val), "192.168.4.1");
|
||||
rows[3].key = "HOTSPOT:";
|
||||
|
||||
snprintf(rows[4].val, sizeof(rows[4].val), "%d / %d KB", (int)(used_b / 1024), (int)(total_b / 1024));
|
||||
rows[4].key = "STORAGE:";
|
||||
|
||||
snprintf(rows[5].val, sizeof(rows[5].val), "%d cached", json_c);
|
||||
rows[5].key = "CARDS:";
|
||||
|
||||
snprintf(rows[6].val, sizeof(rows[6].val), "%d KB", (int)(free_heap / 1024));
|
||||
rows[6].key = "FREE RAM:";
|
||||
|
||||
snprintf(rows[7].val, sizeof(rows[7].val), "%d steps", (int)encoder_get_steps_per_detent());
|
||||
rows[7].key = "ENCODER:";
|
||||
|
||||
snprintf(rows[8].val, sizeof(rows[8].val), "ESP32+ST7789");
|
||||
rows[8].key = "HARDWARE:";
|
||||
|
||||
snprintf(rows[9].val, sizeof(rows[9].val), "MFRC522");
|
||||
rows[9].key = "RFID:";
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int y_pos = i * 20;
|
||||
|
||||
lv_obj_t *k = lv_label_create(box);
|
||||
lv_label_set_text(k, rows[i].key);
|
||||
lv_obj_set_style_text_font(k, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(k, lv_color_hex(0x00E5FF), 0);
|
||||
lv_obj_align(k, LV_ALIGN_TOP_LEFT, 0, y_pos);
|
||||
|
||||
lv_obj_t *v = lv_label_create(box);
|
||||
lv_label_set_text(v, rows[i].val);
|
||||
lv_obj_set_width(v, 120);
|
||||
lv_label_set_long_mode(v, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_style_text_font(v, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(v, lv_color_hex(COLOR_AMBER), 0);
|
||||
lv_obj_set_style_text_align(v, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
lv_obj_align(v, LV_ALIGN_TOP_RIGHT, 0, y_pos);
|
||||
}
|
||||
|
||||
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(hint, "CLICK: MENU");
|
||||
@@ -1561,6 +1584,7 @@ static void handle_new_card(const char *uid)
|
||||
char *name = NULL;
|
||||
esp_err_t ret = storage_manager_lookup_name(uid, &name);
|
||||
if (ret != ESP_OK || name == NULL) {
|
||||
wifi_manager_set_power_save(false);
|
||||
app_set_view(APP_STATE_REGISTRATION);
|
||||
view_unmapped(uid);
|
||||
buzzer_chirp(392, 80);
|
||||
@@ -1757,6 +1781,9 @@ static void handle_encoder_click(void)
|
||||
} else {
|
||||
if (s_storage_option_idx == 0) {
|
||||
storage_manager_clear_all_cards(true);
|
||||
s_card_name[0] = '\0';
|
||||
s_uid[0] = '\0';
|
||||
s_waiting_fetch = false;
|
||||
buzzer_chirp(1046, 100);
|
||||
s_storage_confirm_dialog = false;
|
||||
view_storage();
|
||||
@@ -1778,6 +1805,9 @@ static void handle_encoder_click(void)
|
||||
case APP_STATE_SCANNER:
|
||||
case APP_STATE_CARD_VIEW:
|
||||
case APP_STATE_REGISTRATION:
|
||||
if (cur == APP_STATE_REGISTRATION) {
|
||||
wifi_manager_set_power_save(true);
|
||||
}
|
||||
if (cur == APP_STATE_CARD_VIEW && s_card_name[0] != '\0' && !s_waiting_fetch) {
|
||||
char jp[72];
|
||||
snprintf(jp, sizeof(jp), STORAGE_CARDS_DIR "/%s.json", s_uid);
|
||||
@@ -1961,6 +1991,9 @@ static void ui_task(void *arg)
|
||||
buzzer_chirp(1046, 50);
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
buzzer_chirp(1318, 70);
|
||||
if (app_get_view() == APP_STATE_REGISTRATION) {
|
||||
wifi_manager_set_power_save(true);
|
||||
}
|
||||
app_set_view(APP_STATE_MENU);
|
||||
view_menu();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ 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 SemaphoreHandle_t s_wifi_mux = NULL;
|
||||
static SemaphoreHandle_t s_net_lock = NULL;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* NVS Storage Helpers */
|
||||
@@ -203,6 +205,9 @@ static void wifi_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
|
||||
if (s_wifi_group) {
|
||||
xEventGroupSetBits(s_wifi_group, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
/* Enable modem power saving when idle on station network */
|
||||
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
|
||||
ESP_LOGI(TAG, "Wi-Fi modem sleep enabled (WIFI_PS_MIN_MODEM: ~30-40 mA saved)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,11 +366,13 @@ esp_err_t wifi_manager_init(void)
|
||||
}
|
||||
|
||||
s_wifi_mux = xSemaphoreCreateMutex();
|
||||
s_net_lock = xSemaphoreCreateMutex();
|
||||
s_wifi_group = xEventGroupCreate();
|
||||
if (s_wifi_group == NULL) {
|
||||
if (s_wifi_group == NULL || s_net_lock == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||
@@ -467,4 +474,45 @@ esp_err_t wifi_manager_start(void)
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static bool s_power_save_enabled = true;
|
||||
|
||||
esp_err_t wifi_manager_acquire_net_lock(TickType_t timeout_ticks)
|
||||
{
|
||||
if (!s_net_lock) {
|
||||
s_net_lock = xSemaphoreCreateMutex();
|
||||
}
|
||||
if (!s_net_lock) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
BaseType_t taken = xSemaphoreTake(s_net_lock, timeout_ticks);
|
||||
if (taken == pdTRUE) {
|
||||
/* Full RF performance for active TLS data transfers / OTA */
|
||||
esp_wifi_set_ps(WIFI_PS_NONE);
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
void wifi_manager_release_net_lock(void)
|
||||
{
|
||||
if (s_net_lock) {
|
||||
/* Revert to low-power modem sleep when idle */
|
||||
if (s_power_save_enabled && wifi_manager_connected()) {
|
||||
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
|
||||
}
|
||||
xSemaphoreGive(s_net_lock);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t wifi_manager_set_power_save(bool enable)
|
||||
{
|
||||
s_power_save_enabled = enable;
|
||||
wifi_ps_type_t ps_type = enable ? WIFI_PS_MIN_MODEM : WIFI_PS_NONE;
|
||||
esp_err_t err = esp_wifi_set_ps(ps_type);
|
||||
ESP_LOGI(TAG, "Wi-Fi power save %s (%s)",
|
||||
enable ? "enabled" : "disabled",
|
||||
enable ? "WIFI_PS_MIN_MODEM" : "WIFI_PS_NONE");
|
||||
return err;
|
||||
}
|
||||
@@ -99,6 +99,23 @@ int wifi_manager_get_saved_profiles(wifi_profile_t *out_profiles, int max_count)
|
||||
*/
|
||||
esp_err_t wifi_manager_connect_new(const char *ssid, const char *password, bool save_to_nvs);
|
||||
|
||||
/**
|
||||
* @brief Acquire exclusive network lock for heavy TLS / HTTPS operations.
|
||||
* Prevents simultaneous TLS handshakes (OTA vs Scryfall) from exhausting SRAM.
|
||||
*/
|
||||
esp_err_t wifi_manager_acquire_net_lock(TickType_t timeout_ticks);
|
||||
|
||||
/**
|
||||
* @brief Release exclusive network lock.
|
||||
*/
|
||||
void wifi_manager_release_net_lock(void);
|
||||
|
||||
/**
|
||||
* @brief Set Wi-Fi modem power saving mode.
|
||||
* @param enable True for WIFI_PS_MIN_MODEM (saves ~30-40 mA), False for WIFI_PS_NONE.
|
||||
*/
|
||||
esp_err_t wifi_manager_set_power_save(bool enable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user