diff --git a/esp32s3/balance/main/ota_display.c b/esp32s3/balance/main/ota_display.c new file mode 100644 index 0000000..bcf3f71 --- /dev/null +++ b/esp32s3/balance/main/ota_display.c @@ -0,0 +1,150 @@ +/* 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"); +} diff --git a/esp32s3/balance/main/ota_display.h b/esp32s3/balance/main/ota_display.h new file mode 100644 index 0000000..bfd2ee8 --- /dev/null +++ b/esp32s3/balance/main/ota_display.h @@ -0,0 +1,33 @@ +#pragma once +/* ota_display.h — OTA notification UI on GC9A01 round LCD (bd-1yr8) + * + * GC9A01 240×240 round display via SPI (IO12 CS, IO11 DC, IO10 RST, IO9 BL). + * Calls into display_draw_* primitives (provided by display driver layer). + * This module owns the "OTA notification overlay" rendered over the HUD. + */ + +#include +#include +#include "ota_self.h" +#include "uart_ota.h" + +/* ── Display primitives API (must be provided by display driver) ── */ +void display_fill_rect(int x, int y, int w, int h, uint16_t rgb565); +void display_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg); +void display_draw_arc(int cx, int cy, int r, int start_deg, int end_deg, + int thickness, uint16_t color); + +/* ── Colour palette (RGB565) ── */ +#define COL_BG 0x0000u /* black */ +#define COL_WHITE 0xFFFFu +#define COL_GREEN 0x07E0u +#define COL_YELLOW 0xFFE0u +#define COL_RED 0xF800u +#define COL_BLUE 0x001Fu +#define COL_ORANGE 0xFD20u + +/* ── OTA display task: runs at 5 Hz, overlays OTA state on HUD ── */ +void ota_display_init(void); + +/* Called from main loop or display task to render the OTA overlay */ +void ota_display_update(void);