Add Gitea OTA firmware update, dual-slot partition layout, and Gitea Actions release workflow

This commit is contained in:
2026-09-12 10:49:39 +02:00
parent 8ac465229c
commit 1d954287a2
82 changed files with 7611 additions and 395 deletions

View File

@@ -18,9 +18,9 @@
#include "wifi_manager.h"
#include "app_state.h"
#include "encoder.h"
#include "led_manager.h"
#include "buzzer.h"
#include "jpeg_decoder.h"
#include "ota_manager.h"
#include "ui_task.h"
static const char *TAG = "ui";
@@ -28,7 +28,6 @@ static const char *TAG = "ui";
#define UI_TASK_STACK (8 * 1024)
#define UI_TASK_PRIO (5)
#define UI_CORE (1)
#define LED_HOLD_MS (2500)
#define COLOR_AMBER (0xFFB300)
#define COLOR_GOLD (0xFFCC00)
@@ -36,13 +35,12 @@ static TaskHandle_t s_ui_task = NULL;
static char s_uid[RFID_UID_STR_MAX_LEN] = {0};
static char s_card_name[128] = {0};
static bool s_waiting_fetch = false;
static uint32_t s_led_off_tick = 0;
static lv_img_dsc_t s_art_img_dsc;
static uint16_t *s_art_pixels = NULL;
/* Menu items */
#define MENU_ITEM_COUNT 7
#define MENU_ITEM_COUNT 8
static int s_menu_idx = 0;
static const char *s_menu_labels[MENU_ITEM_COUNT] = {
"1. CARD SCANNER",
@@ -51,7 +49,8 @@ static const char *s_menu_labels[MENU_ITEM_COUNT] = {
"4. COMMANDER TAX",
"5. TOKEN & COUNTERS",
"6. DECK STORAGE (LFS)",
"7. DEVICE & WIFI INFO"
"7. DEVICE & WIFI INFO",
"8. CHECK FIRMWARE OTA"
};
/* Life & Counters */
@@ -127,6 +126,7 @@ static void view_storage(void);
static void view_device_info(void);
static void view_unmapped(const char *uid);
static void view_fetching(const char *name);
static void view_ota(void);
static void free_art_buffer(void)
{
@@ -280,13 +280,13 @@ static void view_menu(void)
ui_clean_screen();
ui_draw_header("MTG COMPANION", "- MAIN MENU -");
/* 7 Menu Items */
int start_y = 52;
/* 8 Menu Items */
int start_y = 44;
for (int i = 0; i < MENU_ITEM_COUNT; i++) {
bool selected = (i == s_menu_idx);
lv_obj_t *item_box = lv_obj_create(lv_scr_act());
lv_obj_set_size(item_box, DISP_W - 24, 31);
lv_obj_align(item_box, LV_ALIGN_TOP_MID, 0, start_y + i * 34);
lv_obj_set_size(item_box, DISP_W - 24, 28);
lv_obj_align(item_box, LV_ALIGN_TOP_MID, 0, start_y + i * 31);
lv_obj_set_style_radius(item_box, 6, 0);
lv_obj_set_style_pad_all(item_box, 2, 0);
lv_obj_set_scrollbar_mode(item_box, LV_SCROLLBAR_MODE_OFF);
@@ -446,8 +446,6 @@ static void view_life(void)
if (!s_was_lethal) {
s_was_lethal = true;
buzzer_play_sound(BUZZER_SOUND_LOSS);
led_manager_set_color(255, 0, 0);
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
}
lv_obj_t *dead_lbl = lv_label_create(lv_scr_act());
lv_label_set_text(dead_lbl, "*** LETHAL / DEFEAT ***");
@@ -595,31 +593,21 @@ static void roll_current_dice(void)
s_dice_result = (int)(esp_random() % 20) + 1;
if (s_dice_result == 20) {
/* Natural 20 - Victory Fanfare! */
led_manager_set_color(0, 255, 0);
buzzer_play_sound(BUZZER_SOUND_VICTORY);
} else if (s_dice_result == 1) {
/* Critical Fail */
led_manager_set_color(255, 0, 0);
buzzer_chirp(220, 150);
} else {
led_manager_set_color(0, 180, 255);
buzzer_chirp(880, 50);
}
} else if (s_dice_mode == DICE_MODE_D6) {
s_dice_result = (int)(esp_random() % 6) + 1;
led_manager_set_color(0, 180, 255);
buzzer_chirp(880, 50);
} else {
/* Coin */
s_dice_result = (int)(esp_random() % 2); // 0 = Tails, 1 = Heads
if (s_dice_result == 1) {
led_manager_set_color(0, 255, 60); // Green for Heads
} else {
led_manager_set_color(255, 140, 0); // Amber for Tails
}
buzzer_chirp(988, 60);
}
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(2000);
s_dice_rolled = true;
view_dice();
}
@@ -1005,8 +993,9 @@ static void view_device_info(void)
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,
"WI-FI STA:\n"
"WEB PAIR:\n"
"VERSION:\n"
"WI-FI IP:\n"
"HOTSPOT:\n"
"STORAGE:\n"
"CARDS:\n"
"FREE RAM:\n"
@@ -1021,6 +1010,7 @@ static void view_device_info(void)
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"
"192.168.4.1\n"
"%d / %d KB\n"
@@ -1029,6 +1019,7 @@ static void view_device_info(void)
"%d steps\n"
"ESP32+ST7789\n"
"MFRC522",
ota_manager_get_current_version(),
wifi_manager_get_ip(),
(int)(used_b / 1024), (int)(total_b / 1024),
json_c,
@@ -1045,6 +1036,237 @@ static void view_device_info(void)
lvgl_port_unlock();
}
/* 8. Firmware OTA Update Screen */
static ota_status_t s_last_ota_rendered_st = OTA_STATUS_IDLE;
static int s_last_ota_percent = -1;
static void view_ota(void)
{
lvgl_port_lock(0);
ui_clean_screen();
ota_status_t st = ota_manager_get_status();
s_last_ota_rendered_st = st;
ui_draw_header("FIRMWARE UPDATE", "GITEA OTA");
switch (st) {
case OTA_STATUS_CHECKING: {
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 180);
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
lv_obj_set_style_bg_color(box, lv_color_hex(0x1C1C1C), 0);
lv_obj_set_style_border_color(box, lv_color_hex(0x00E5FF), 0);
lv_obj_set_style_border_width(box, 2, 0);
lv_obj_set_style_radius(box, 8, 0);
lv_obj_t *lbl = lv_label_create(box);
lv_label_set_text(lbl, "CHECKING GITEA...\n\nConnecting to\nreleases API");
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(lbl, lv_color_hex(0x00E5FF), 0);
lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(lbl, LV_ALIGN_CENTER, 0, 0);
lv_obj_t *hint = lv_label_create(lv_scr_act());
lv_label_set_text(hint, "HOLD BUTTON: MAIN MENU");
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
break;
}
case OTA_STATUS_UPDATE_AVAILABLE: {
const ota_release_info_t *rel = ota_manager_get_release_info();
const char *cur_ver = ota_manager_get_current_version();
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 200);
lv_obj_align(box, LV_ALIGN_CENTER, 0, -15);
lv_obj_set_style_bg_color(box, lv_color_hex(0x102818), 0);
lv_obj_set_style_border_color(box, lv_color_hex(0x00E676), 0);
lv_obj_set_style_border_width(box, 2, 0);
lv_obj_set_style_radius(box, 8, 0);
lv_obj_t *t = lv_label_create(box);
lv_label_set_text(t, "NEW VERSION AVAILABLE!");
lv_obj_set_style_text_font(t, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(t, lv_color_hex(0x00E676), 0);
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 8);
lv_obj_t *v = lv_label_create(box);
lv_label_set_text_fmt(v, "TARGET: %s", rel->tag_name);
lv_obj_set_style_text_font(v, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(v, lv_color_hex(0xFFCC00), 0);
lv_obj_align(v, LV_ALIGN_TOP_MID, 0, 38);
lv_obj_t *c = lv_label_create(box);
lv_label_set_text_fmt(c, "Current: %s", cur_ver);
lv_obj_set_style_text_font(c, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(c, lv_color_white(), 0);
lv_obj_align(c, LV_ALIGN_TOP_MID, 0, 70);
lv_obj_t *s = lv_label_create(box);
if (rel->binary_size > 0) {
lv_label_set_text_fmt(s, "Size: %d KB", (int)(rel->binary_size / 1024));
} else {
lv_label_set_text(s, "Ready to stream");
}
lv_obj_set_style_text_font(s, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(s, lv_color_hex(0x888888), 0);
lv_obj_align(s, LV_ALIGN_TOP_MID, 0, 95);
lv_obj_t *act = lv_label_create(box);
lv_label_set_text(act, "> CLICK: INSTALL NOW <");
lv_obj_set_style_text_font(act, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(act, lv_color_hex(0x00E676), 0);
lv_obj_align(act, LV_ALIGN_BOTTOM_MID, 0, -8);
lv_obj_t *hint = lv_label_create(lv_scr_act());
lv_label_set_text(hint, "ROTATE / HOLD: SKIP FOR NOW");
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
break;
}
case OTA_STATUS_UP_TO_DATE: {
const char *cur_ver = ota_manager_get_current_version();
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 180);
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
lv_obj_set_style_bg_color(box, lv_color_hex(0x1C1C1C), 0);
lv_obj_set_style_border_color(box, lv_color_hex(0x333333), 0);
lv_obj_set_style_border_width(box, 1, 0);
lv_obj_set_style_radius(box, 8, 0);
lv_obj_t *t = lv_label_create(box);
lv_label_set_text(t, "UP TO DATE");
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(t, lv_color_hex(0x00E676), 0);
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 15);
lv_obj_t *v = lv_label_create(box);
lv_label_set_text_fmt(v, "Version: %s\n\nNo newer releases\nfound on Gitea.", cur_ver);
lv_obj_set_style_text_font(v, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(v, lv_color_white(), 0);
lv_obj_set_style_text_align(v, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(v, LV_ALIGN_CENTER, 0, 15);
lv_obj_t *hint = lv_label_create(lv_scr_act());
lv_label_set_text(hint, "CLICK: MAIN MENU");
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
break;
}
case OTA_STATUS_DOWNLOADING: {
int percent = ota_manager_get_progress();
s_last_ota_percent = percent;
size_t rb = 0, tb = 0;
ota_manager_get_bytes(&rb, &tb);
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 200);
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
lv_obj_set_style_bg_color(box, lv_color_hex(0x181828), 0);
lv_obj_set_style_border_color(box, lv_color_hex(0x00E5FF), 0);
lv_obj_set_style_border_width(box, 2, 0);
lv_obj_set_style_radius(box, 8, 0);
lv_obj_t *t = lv_label_create(box);
lv_label_set_text(t, "FLASHING FIRMWARE...");
lv_obj_set_style_text_font(t, &lv_font_montserrat_14, 0);
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);
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);
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);
lv_obj_t *warn = lv_label_create(lv_scr_act());
lv_label_set_text(warn, "DO NOT TURN OFF POWER");
lv_obj_set_style_text_font(warn, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(warn, lv_color_hex(0xFF1744), 0);
lv_obj_align(warn, LV_ALIGN_BOTTOM_MID, 0, -8);
break;
}
case OTA_STATUS_SUCCESS_REBOOTING: {
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 180);
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
lv_obj_set_style_bg_color(box, lv_color_hex(0x102818), 0);
lv_obj_set_style_border_color(box, lv_color_hex(0x00E676), 0);
lv_obj_set_style_border_width(box, 2, 0);
lv_obj_set_style_radius(box, 8, 0);
lv_obj_t *t = lv_label_create(box);
lv_label_set_text(t, "UPDATE SUCCESSFUL!");
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(t, lv_color_hex(0x00E676), 0);
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 20);
lv_obj_t *m = lv_label_create(box);
lv_label_set_text(m, "New firmware verified.\n\nRebooting device...");
lv_obj_set_style_text_font(m, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(m, lv_color_white(), 0);
lv_obj_set_style_text_align(m, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(m, LV_ALIGN_CENTER, 0, 25);
break;
}
case OTA_STATUS_FAILED:
default: {
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 180);
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
lv_obj_set_style_bg_color(box, lv_color_hex(0x281010), 0);
lv_obj_set_style_border_color(box, lv_color_hex(0xFF1744), 0);
lv_obj_set_style_border_width(box, 2, 0);
lv_obj_set_style_radius(box, 8, 0);
lv_obj_t *t = lv_label_create(box);
lv_label_set_text(t, "UPDATE FAILED");
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(t, lv_color_hex(0xFF1744), 0);
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 15);
lv_obj_t *msg = lv_label_create(box);
lv_label_set_text_fmt(msg, "%s\n\nExisting firmware intact.", ota_manager_get_message());
lv_obj_set_style_text_font(msg, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(msg, lv_color_white(), 0);
lv_obj_set_style_text_align(msg, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(msg, LV_ALIGN_CENTER, 0, 20);
lv_obj_t *hint = lv_label_create(lv_scr_act());
lv_label_set_text(hint, "CLICK: MAIN MENU");
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
break;
}
}
lvgl_port_unlock();
}
/* 8. Card View Screen */
static void view_card(void)
{
@@ -1225,22 +1447,39 @@ static void view_unmapped(const char *uid)
lv_obj_align(u_lbl, LV_ALIGN_TOP_MID, 0, 70);
lv_obj_t *sub = lv_label_create(lv_scr_act());
lv_label_set_text(sub, "PAIR IT ON THE WEB:");
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(sub, lv_color_white(), 0);
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 130);
lv_obj_t *ssid = lv_label_create(lv_scr_act());
lv_label_set_text(ssid, "SSID: MTG-Companion");
lv_obj_set_style_text_font(ssid, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(ssid, lv_color_hex(0xFFCC00), 0);
lv_obj_align(ssid, LV_ALIGN_TOP_MID, 0, 160);
lv_obj_t *ip = lv_label_create(lv_scr_act());
lv_label_set_text(ip, "http://192.168.4.1");
lv_obj_set_style_text_font(ip, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(ip, lv_color_hex(0x00E676), 0);
lv_obj_align(ip, LV_ALIGN_TOP_MID, 0, 190);
if (wifi_manager_connected()) {
lv_label_set_text(sub, "PAIR VIA WI-FI:");
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 105);
lv_label_set_text_fmt(ip, "http://%s", wifi_manager_get_ip());
lv_obj_align(ip, LV_ALIGN_TOP_MID, 0, 135);
lv_obj_t *alt = lv_label_create(lv_scr_act());
lv_label_set_text(alt, "or Hotspot: MTG-Companion\n(http://192.168.4.1)");
lv_obj_set_style_text_font(alt, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(alt, lv_color_hex(0x888888), 0);
lv_obj_set_style_text_align(alt, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(alt, LV_ALIGN_TOP_MID, 0, 175);
} else {
lv_label_set_text(sub, "PAIR ON HOTSPOT:");
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 105);
lv_obj_t *ssid = lv_label_create(lv_scr_act());
lv_label_set_text(ssid, "SSID: MTG-Companion");
lv_obj_set_style_text_font(ssid, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(ssid, lv_color_hex(0xFFCC00), 0);
lv_obj_align(ssid, LV_ALIGN_TOP_MID, 0, 135);
lv_label_set_text(ip, "http://192.168.4.1");
lv_obj_align(ip, LV_ALIGN_TOP_MID, 0, 165);
}
lv_obj_t *hint = lv_label_create(lv_scr_act());
lv_label_set_text(hint, "HOLD BUTTON: MAIN MENU");
@@ -1281,40 +1520,6 @@ static void view_fetching(const char *name)
lvgl_port_unlock();
}
/* ------------------------------------------------------------------ */
/* LED helpers */
/* ------------------------------------------------------------------ */
static void led_for_card(const char *mana)
{
uint8_t r = 255, g = 200, b = 0;
if (mana != NULL && mana[0] != '\0') {
if (strstr(mana, "{W}")) { r = 255; g = 255; b = 200; }
else if (strstr(mana, "{U}")) { r = 40; g = 100; b = 255; }
else if (strstr(mana, "{B}")) { r = 90; g = 20; b = 150; }
else if (strstr(mana, "{R}")) { r = 255; g = 60; b = 20; }
else if (strstr(mana, "{G}")) { r = 20; g = 200; b = 60; }
}
led_manager_set_color(r, g, b);
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
}
static void read_cached_color(const char *uid)
{
char json_path[72];
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
char *buf = NULL;
size_t len = 0;
if (storage_read_text(json_path, &buf, &len) == ESP_OK && buf != NULL) {
cJSON *root = cJSON_Parse(buf);
if (root != NULL) {
led_for_card(get_json_str(root, "mana_cost", ""));
cJSON_Delete(root);
}
free(buf);
}
}
static void handle_new_card(const char *uid)
{
snprintf(s_uid, sizeof(s_uid), "%s", uid);
@@ -1325,8 +1530,6 @@ static void handle_new_card(const char *uid)
if (ret != ESP_OK || name == NULL) {
app_set_view(APP_STATE_REGISTRATION);
view_unmapped(uid);
led_manager_set_color(60, 60, 200);
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
buzzer_chirp(392, 80);
free(name);
return;
@@ -1336,7 +1539,6 @@ static void handle_new_card(const char *uid)
app_set_view(APP_STATE_CARD_VIEW);
buzzer_chirp(880, 60);
read_cached_color(uid);
char json_path[72];
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
@@ -1375,12 +1577,9 @@ static void handle_fetch_ready(void)
scryfall_consume_result();
s_waiting_fetch = false;
if (success) {
read_cached_color(s_uid);
view_card();
} else {
buzzer_chirp(220, 150);
led_manager_set_color(255, 0, 0);
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
lvgl_port_lock(0);
ui_clean_screen();
@@ -1465,11 +1664,31 @@ static void handle_encoder_click(void)
app_set_view(APP_STATE_DEVICE_INFO);
view_device_info();
break;
case 7:
app_set_view(APP_STATE_OTA_UPDATE);
buzzer_chirp(988, 40);
ota_manager_check_update_async();
view_ota();
break;
default:
break;
}
break;
case APP_STATE_OTA_UPDATE: {
ota_status_t st = ota_manager_get_status();
if (st == OTA_STATUS_UPDATE_AVAILABLE) {
buzzer_chirp(1046, 100);
ota_manager_start_update_async();
view_ota();
} else if (st == OTA_STATUS_UP_TO_DATE || st == OTA_STATUS_FAILED) {
ota_manager_reset();
app_set_view(APP_STATE_MENU);
view_menu();
}
break;
}
case APP_STATE_LIFE_COUNTER:
s_life_mode = (s_life_mode + 1) % 3;
buzzer_chirp(880, 30);
@@ -1506,8 +1725,6 @@ static void handle_encoder_click(void)
if (s_storage_option_idx == 0) {
storage_manager_clear_all_cards(true);
buzzer_chirp(1046, 100);
led_manager_set_color(0, 255, 60);
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(1500);
s_storage_confirm_dialog = false;
view_storage();
} else {
@@ -1654,6 +1871,16 @@ static void handle_encoder_rotation(int32_t delta)
view_storage();
break;
case APP_STATE_OTA_UPDATE: {
ota_status_t st = ota_manager_get_status();
if (st == OTA_STATUS_UPDATE_AVAILABLE) {
ota_manager_reset();
app_set_view(APP_STATE_MENU);
view_menu();
}
break;
}
default:
break;
}
@@ -1669,13 +1896,17 @@ static void ui_task(void *arg)
ESP_LOGI(TAG, "UI task on core %d", xPortGetCoreID());
encoder_init();
led_manager_init();
buzzer_init();
/* Start on Main Menu at boot */
app_set_view(APP_STATE_MENU);
view_menu();
#if CONFIG_MTG_OTA_AUTO_CHECK
static bool s_boot_ota_checked = false;
static uint32_t s_wifi_up_ms = 0;
#endif
while (1) {
/* 1. New RFID card - scans immediately anywhere */
if (rfid_manager_has_pending_card()) {
@@ -1697,8 +1928,6 @@ static void ui_task(void *arg)
buzzer_chirp(1046, 50);
vTaskDelay(pdMS_TO_TICKS(40));
buzzer_chirp(1318, 70);
led_manager_set_color(180, 0, 255);
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(800);
app_set_view(APP_STATE_MENU);
view_menu();
}
@@ -1716,12 +1945,34 @@ static void ui_task(void *arg)
handle_encoder_rotation(delta);
}
/* 5. Auto-dim LED */
if (s_led_off_tick != 0 && xTaskGetTickCount() >= s_led_off_tick) {
s_led_off_tick = 0;
led_manager_off();
/* 5. OTA screen live refresh / status change */
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)) {
view_ota();
}
}
#if CONFIG_MTG_OTA_AUTO_CHECK
/* 6. Automatic background OTA check on startup after Wi-Fi connects */
if (!s_boot_ota_checked && wifi_manager_connected()) {
s_wifi_up_ms += 25;
if (s_wifi_up_ms >= 4000) {
s_boot_ota_checked = true;
ESP_LOGI(TAG, "Triggering startup Gitea OTA check...");
ota_manager_check_update_async();
}
}
if (s_boot_ota_checked && ota_manager_get_status() == OTA_STATUS_UPDATE_AVAILABLE &&
app_get_view() == APP_STATE_MENU) {
app_set_view(APP_STATE_OTA_UPDATE);
buzzer_chirp(1046, 120);
view_ota();
}
#endif
vTaskDelay(pdMS_TO_TICKS(25));
}
}