/* ota_display.c — OTA notification/progress UI on GC9A01 (bd-1yr8) * * Renders OTA state overlaid on the 240×240 round HUD display: * - BADGE: small dot on top-right when update available (idle state) * - UPDATE SCREEN: version compare, Update Balance / Update IO / Update All * - PROGRESS: arc around display perimeter + % + status text * - ERROR: red banner + "RETRY" prompt * * The display_draw_* primitives must be provided by the GC9A01 driver. * Actual SPI driver implementation is in a separate driver bead. */ #include "ota_display.h" #include "gitea_ota.h" #include "version.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include #include static const char *TAG = "ota_disp"; /* Display centre and radius for the 240×240 GC9A01 */ #define CX 120 #define CY 120 #define RAD 110 /* ── Availability badge: 8×8 dot at top-right of display ── */ static void draw_badge(bool balance_avail, bool io_avail) { uint16_t col = (balance_avail || io_avail) ? COL_ORANGE : COL_BG; display_fill_rect(200, 15, 12, 12, col); } /* ── Progress arc: sweeps 0→360° proportional to progress% ── */ static void draw_progress_arc(uint8_t pct, uint16_t color) { int end_deg = (int)(360 * pct / 100); display_draw_arc(CX, CY, RAD, 0, end_deg, 6, color); } /* ── Status banner: 2 lines of text centred on display ── */ static void draw_status(const char *line1, const char *line2, uint16_t fg, uint16_t bg) { display_fill_rect(20, 90, 200, 60, bg); if (line1 && line1[0]) display_draw_string(CX - (int)(strlen(line1) * 6 / 2), 96, line1, fg, bg); if (line2 && line2[0]) display_draw_string(CX - (int)(strlen(line2) * 6 / 2), 116, line2, fg, bg); } /* ── Main render logic ── */ void ota_display_update(void) { /* Determine dominant OTA state */ ota_self_state_t self = g_ota_self_state; uart_ota_send_state_t io_s = g_uart_ota_state; switch (self) { case OTA_SELF_DOWNLOADING: case OTA_SELF_VERIFYING: case OTA_SELF_APPLYING: { /* Balance self-update in progress */ char pct_str[16]; snprintf(pct_str, sizeof(pct_str), "%d%%", g_ota_self_progress); const char *phase = (self == OTA_SELF_VERIFYING) ? "Verifying..." : (self == OTA_SELF_APPLYING) ? "Applying..." : "Downloading..."; draw_progress_arc(g_ota_self_progress, COL_BLUE); draw_status("Updating Balance", pct_str, COL_WHITE, COL_BG); ESP_LOGD(TAG, "balance OTA %s %d%%", phase, g_ota_self_progress); return; } case OTA_SELF_REBOOTING: draw_status("Update complete", "Rebooting...", COL_GREEN, COL_BG); return; case OTA_SELF_FAILED: draw_progress_arc(0, COL_RED); draw_status("Balance update", "FAILED RETRY?", COL_RED, COL_BG); return; default: break; } switch (io_s) { case UART_OTA_S_DOWNLOADING: draw_progress_arc(g_uart_ota_progress, COL_YELLOW); draw_status("Downloading IO", "firmware...", COL_WHITE, COL_BG); return; case UART_OTA_S_SENDING: { char pct_str[16]; snprintf(pct_str, sizeof(pct_str), "%d%%", g_uart_ota_progress); draw_progress_arc(g_uart_ota_progress, COL_YELLOW); draw_status("Updating IO", pct_str, COL_WHITE, COL_BG); return; } case UART_OTA_S_DONE: draw_status("IO update done", "", COL_GREEN, COL_BG); return; case UART_OTA_S_FAILED: draw_progress_arc(0, COL_RED); draw_status("IO update", "FAILED RETRY?", COL_RED, COL_BG); return; default: break; } /* Idle — show badge if update available */ bool bal_avail = g_balance_update.available; bool io_avail = g_io_update.available; draw_badge(bal_avail, io_avail); if (bal_avail || io_avail) { /* Show available versions on display when idle */ char verline[32]; if (bal_avail) { snprintf(verline, sizeof(verline), "Bal v%s rdy", g_balance_update.version); draw_status(verline, io_avail ? "IO update rdy" : "", COL_ORANGE, COL_BG); } else if (io_avail) { snprintf(verline, sizeof(verline), "IO v%s rdy", g_io_update.version); draw_status(verline, "", COL_ORANGE, COL_BG); } } else { /* Clear OTA overlay area */ display_fill_rect(20, 90, 200, 60, COL_BG); draw_badge(false, false); } } /* ── Background display task (5 Hz) ── */ static void ota_display_task(void *arg) { for (;;) { vTaskDelay(pdMS_TO_TICKS(200)); ota_display_update(); } } void ota_display_init(void) { xTaskCreate(ota_display_task, "ota_disp", 2048, NULL, 3, NULL); ESP_LOGI(TAG, "OTA display task started"); }