From 7820837d79ebc8e00221b5206bdd8e3ca0ae4c15 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 18 May 2026 19:58:12 +0530 Subject: Sync display with live TollGate state - Periodic refresh: wallet balance + client count every 5s from main loop - display_notify_payment() API: passes payment amount and time allotment - tollgate_api.c: triggers PAYMENT_RECEIVED state after wallet receive - AP station events update client count in real-time - Config data (price, mint) passed to display during boot phase - Payment screen shows actual sats paid and time purchased - Updated DISPLAY_UI_PLAN.md with state-sync audit and checklist --- DISPLAY_UI_PLAN.md | 171 ++++++++++++++++++++++++++++++--------------------- main/display.c | 23 +++++-- main/display.h | 1 + main/tollgate_api.c | 3 + main/tollgate_main.c | 13 ++++ 5 files changed, 136 insertions(+), 75 deletions(-) diff --git a/DISPLAY_UI_PLAN.md b/DISPLAY_UI_PLAN.md index 3050b8a..e78db44 100644 --- a/DISPLAY_UI_PLAN.md +++ b/DISPLAY_UI_PLAN.md @@ -18,7 +18,6 @@ | Orange | `0xFD20` | Accent (Bitcoin orange) | | Red | `0xF800` | Errors, alerts | | Dim gray | `0x8410` | Secondary info | -| Dark bg | `0x2104` | Card backgrounds | ## Screen States @@ -26,111 +25,141 @@ Shown during startup until WiFi connects and services start. ``` -┌──────────────────────────┐ 0 -│ │ -│ TollGate │ y=180, cyan, scale 2 -│ connecting... │ y=205, yellow, scale 1 -│ │ -│ WiFi: trying... │ y=260, dim, scale 1 -│ │ -└──────────────────────────┘ 479 +┌──────────────────────────┐ +│ TollGate │ cyan, scale 2 +│ connecting... │ yellow, scale 1 +│ WiFi: trying... │ dim, scale 1 +└──────────────────────────┘ ``` -WiFi status line shows: "trying...", "connected!", "failed (retry)" - -### 2. READY — QR Cycling (primary screen) +### 2. READY — QR Cycling Cycles every 5 seconds between WiFi QR and Portal QR. **View A — WiFi QR:** ``` -┌──────────────────────────┐ 0 +┌──────────────────────────┐ │ ┌──────┐ │ -│ │ QR │ │ QR: WIFI:S:;T:nopass;; -│ │ │ │ Centered in top 2/3 of screen +│ │ QR │ │ WIFI:S:;T:nopass;; │ └──────┘ │ -│ │ ~y=320 -│ Scan to connect │ cyan, scale 1 -│ SSID: TollGate-XXXX │ white, scale 1 -│ 21 sats/min │ orange, scale 1 -│ Wallet: 420 sats │ green, scale 1 -└──────────────────────────┘ 479 +│ Scan to connect │ cyan +│ SSID: TollGate-XXXX │ white +│ 21 sats/min │ orange +│ Wallet: 420 sats │ green/yellow/red +└──────────────────────────┘ ``` **View B — Portal QR:** ``` -┌──────────────────────────┐ 0 +┌──────────────────────────┐ │ ┌──────┐ │ -│ │ QR │ │ QR: http://10.x.x.x/ -│ │ │ │ +│ │ QR │ │ http://10.x.x.x/ │ └──────┘ │ -│ │ ~y=320 -│ Portal URL │ cyan, scale 1 -│ testnut.cashu.space │ orange, scale 1 (mint domain) -│ 21 sats/min │ yellow, scale 1 -│ Clients: 3 │ green, scale 1 -└──────────────────────────┘ 479 +│ Portal URL │ cyan +│ Mint: testnut... │ orange +│ 21 sats/min │ orange +│ Clients: 3 │ green +└──────────────────────────┘ ``` ### 3. PAYMENT_RECEIVED Shows for 3 seconds after payment, then returns to READY. ``` -┌──────────────────────────┐ 0 -│ │ -│ ████████████████████ │ green filled bar, y=190..230 +┌──────────────────────────┐ +│ ████████████████████ │ green bar │ ACCESS GRANTED │ white on green, scale 2 │ ████████████████████ │ -│ │ -│ Paid: 21 sats │ white, scale 1 -│ Time: 1 min │ white, scale 1 -│ │ -│ Wallet: 441 sats │ green, scale 1 -└──────────────────────────┘ 479 +│ Paid: 42 sats │ white +│ Time: 2 min │ white +│ Wallet: 462 sats │ green +└──────────────────────────┘ ``` ### 4. ERROR Shown when upstream WiFi is disconnected. ``` -┌──────────────────────────┐ 0 -│ ████████████████████ │ red filled bar, y=190..230 +┌──────────────────────────┐ +│ ████████████████████ │ red bar │ NO UPSTREAM │ white on red, scale 2 │ ████████████████████ │ -│ │ -│ Internet unavailable │ white, scale 1 -│ Check WiFi config │ yellow, scale 1 -│ │ -│ AP still active │ green, scale 1 -│ SSID: TollGate-XXXX │ dim, scale 1 -└──────────────────────────┘ 479 +│ Internet unavailable │ white +│ Check WiFi config │ yellow +│ AP still active │ green +│ SSID: TollGate-XXXX │ dim +└──────────────────────────┘ ``` -## Data Flow +## State Synchronization -### display_update() receives: -```c -void display_update(const char *ap_ssid, int active_clients, - uint64_t wallet_balance, const char *portal_url); -``` +### Data sources and update triggers -### Enhanced to also receive: -```c -void display_update(const char *ap_ssid, int active_clients, - uint64_t wallet_balance, const char *portal_url, - const char *mint_url, int price_per_step, - const char *wifi_status); +| Display data | Source function | Update trigger | +|-------------|----------------|----------------| +| SSID | `config.ap_ssid` | Once at `start_services()` | +| Portal URL | `config.ap_ip_str` | Once at `start_services()` | +| Mint URL | `config.mint_url` | Once at `start_services()` | +| Price | `config.price_per_step` | Once at `start_services()` | +| **Wallet balance** | `nucula_wallet_balance()` | **Every 5s in main loop** | +| **Client count** | `session_active_count()` | **Every 5s in main loop + AP events** | +| **Last payment** | `display_notify_payment()` | **On each payment in API handler** | +| WiFi status | Event handler | On STA connect/disconnect | + +### State transitions + +``` +app_main() + └─ display_set_state(BOOT) + └─ display_update(price, mint, ssid) ← config data available after config_init + +wifi_event_handler(STA_DISCONNECTED) + └─ display_set_state(ERROR) + └─ display_update(wifi_status="retrying...") + +ip_event_handler(STA_GOT_IP) + └─ start_services() + └─ display_set_state(READY) + └─ display_update(ssid, portal_url, mint, price) + +tollgate_api (POST / payment) + └─ session_create() + └─ nucula_wallet_receive() + └─ display_notify_payment(amount_sats, allotment) ← NEW + └─ display_set_state(PAYMENT_RECEIVED) + +display_task (3s timeout) + └─ auto-return PAYMENT_RECEIVED → READY + +app_main() main loop (every 5s) + └─ display_update(wallet_balance, client_count) ← NEW periodic refresh ``` -### display_set_state() triggers: -- `DISPLAY_BOOT` → at startup -- `DISPLAY_READY` → when services start (WiFi connected) -- `DISPLAY_PAYMENT_RECEIVED` → on successful payment (auto-returns to READY) -- `DISPLAY_ERROR` → when upstream WiFi disconnects +### New API: `display_notify_payment()` -## Implementation Notes +```c +void display_notify_payment(int amount_sats, int64_t allotment_ms); +``` -- Render every 2 seconds (reduces SPI bus load vs 1 second) -- QR codes: auto-size based on string length, centered in top portion -- Mint URL: show only domain part (truncate at first `/`) -- Wallet balance: color-coded (green > 100, yellow > 0, red = 0) -- Client count: "Clients: N" or empty string if 0 +Stores the last payment amount and time purchased for the PAYMENT screen to display. + +## Implementation Checklist + +### Done +- [x] QSPI driver working with correct colors (DMA byte-swap + RAMWR) +- [x] BOOT screen with title and WiFi status +- [x] READY screen with QR cycling, price, mint, balance, clients +- [x] PAYMENT screen layout (green banner, amount, time, wallet) +- [x] ERROR screen layout (red banner, guidance, AP status) +- [x] WiFi disconnect → ERROR state transition +- [x] WiFi connect → READY state transition + +### TODO — State Sync +- [ ] Periodic display data refresh in main loop (wallet balance, client count every 5s) +- [ ] `display_notify_payment()` API to pass payment amount and allotment +- [ ] Call `display_set_state(PAYMENT_RECEIVED)` from tollgate_api.c after payment +- [ ] Pass config data (price, mint) to display during boot phase +- [ ] Update client count on AP station connect/disconnect events + +### TODO — Polish +- [ ] Run `make test-unit` to check for regressions +- [ ] Commit, push, prepare for merge diff --git a/main/display.c b/main/display.c index add97db..0935743 100644 --- a/main/display.c +++ b/main/display.c @@ -2,6 +2,7 @@ #include "axs15231b.h" #include "qrcoded.h" #include "font.h" +#include "nucula_wallet.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -34,6 +35,8 @@ static int s_price_per_step = 0; static bool s_initialized = false; static int64_t s_last_qr_switch = 0; static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI; +static int s_last_payment_sats = 0; +static int64_t s_last_allotment_ms = 0; static int qr_version_from_strlen(int len) { if (len <= 17) return 1; @@ -251,13 +254,18 @@ static void render_payment_screen(void) { char line[48]; - snprintf(line, sizeof(line), "Paid: %d sats", s_price_per_step); + snprintf(line, sizeof(line), "Paid: %d sats", s_last_payment_sats); int lw = strlen(line) * 8; display_render_text((screen_w - lw) / 2, 270, line, COLOR_WHITE, COLOR_BG, 1); - const char *time_msg = "Time: 1 min"; - int tw = strlen(time_msg) * 8; - display_render_text((screen_w - tw) / 2, 290, time_msg, COLOR_WHITE, COLOR_BG, 1); + int64_t secs = s_last_allotment_ms / 1000; + if (secs >= 60) { + snprintf(line, sizeof(line), "Time: %lld min", (long long)(secs / 60)); + } else { + snprintf(line, sizeof(line), "Time: %lld sec", (long long)secs); + } + lw = strlen(line) * 8; + display_render_text((screen_w - lw) / 2, 290, line, COLOR_WHITE, COLOR_BG, 1); snprintf(line, sizeof(line), "Wallet: %llu sats", (unsigned long long)s_wallet_balance); lw = strlen(line) * 8; @@ -378,3 +386,10 @@ void display_update(const char *ap_ssid, int active_clients, s_active_clients = active_clients; s_wallet_balance = wallet_balance; } + +void display_notify_payment(int amount_sats, int64_t allotment_ms) { + s_last_payment_sats = amount_sats; + s_last_allotment_ms = allotment_ms; + s_wallet_balance = nucula_wallet_balance(); + display_set_state(DISPLAY_PAYMENT_RECEIVED); +} diff --git a/main/display.h b/main/display.h index 1530e57..49b3ffd 100644 --- a/main/display.h +++ b/main/display.h @@ -23,6 +23,7 @@ void display_update(const char *ap_ssid, int active_clients, uint64_t wallet_balance, const char *portal_url, const char *mint_url, int price_per_step, const char *wifi_status); +void display_notify_payment(int amount_sats, int64_t allotment_ms); void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale); void display_render_qr(const char *text); diff --git a/main/tollgate_api.c b/main/tollgate_api.c index 650b0f3..7ddfb9e 100644 --- a/main/tollgate_api.c +++ b/main/tollgate_api.c @@ -4,6 +4,7 @@ #include "session.h" #include "firewall.h" #include "nucula_wallet.h" +#include "display.h" #include "esp_log.h" #include "cJSON.h" #include "lwip/sockets.h" @@ -313,6 +314,8 @@ static esp_err_t api_post_payment(httpd_req_t *req) nucula_wallet_receive(body_copy); + display_notify_payment(token->total_amount, allotment); + free(states); free(token); return ESP_OK; diff --git a/main/tollgate_main.c b/main/tollgate_main.c index 7fd50ad..345295a 100644 --- a/main/tollgate_main.c +++ b/main/tollgate_main.c @@ -77,11 +77,13 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, ESP_LOGI(TAG, "Station connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x", event->mac[0], event->mac[1], event->mac[2], event->mac[3], event->mac[4], event->mac[5]); + display_update(NULL, session_active_count(), 0, NULL, NULL, 0, NULL); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; ESP_LOGI(TAG, "Station disconnected: MAC=%02x:%02x:%02x:%02x:%02x:%02x", event->mac[0], event->mac[1], event->mac[2], event->mac[3], event->mac[4], event->mac[5]); + display_update(NULL, session_active_count(), 0, NULL, NULL, 0, NULL); } } @@ -314,6 +316,9 @@ void app_main(void) ESP_LOGI(TAG, "WiFi AP+STA started, waiting for connection..."); + display_update(tcfg->ap_ssid, 0, 0, NULL, + tcfg->mint_url, tcfg->price_per_step, "connecting..."); + if (tollgate_config_get_wifi(&(wifi_config_t){0}) != ESP_OK) { ESP_LOGI(TAG, "No STA network configured, starting services immediately"); xTaskCreate(services_start_task, "svc_start", 32768, NULL, 5, NULL); @@ -324,5 +329,13 @@ void app_main(void) session_tick(); tollgate_client_tick(); lightning_payout_tick(); + + static int display_tick = 0; + if (++display_tick >= 5) { + display_tick = 0; + display_update(NULL, session_active_count(), + nucula_wallet_balance(), NULL, + NULL, 0, NULL); + } } } -- cgit v1.2.3