upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-18 19:58:12 +0530
committerYour Name <you@example.com>2026-05-18 19:58:12 +0530
commit7820837d79ebc8e00221b5206bdd8e3ca0ae4c15 (patch)
tree98070e83e4e8bfa0b46009d202e7b81410c960f6
parent7dd532cee475d486552e886dd7173c8853f80b60 (diff)
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
-rw-r--r--DISPLAY_UI_PLAN.md171
-rw-r--r--main/display.c23
-rw-r--r--main/display.h1
-rw-r--r--main/tollgate_api.c3
-rw-r--r--main/tollgate_main.c13
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 @@
18| Orange | `0xFD20` | Accent (Bitcoin orange) | 18| Orange | `0xFD20` | Accent (Bitcoin orange) |
19| Red | `0xF800` | Errors, alerts | 19| Red | `0xF800` | Errors, alerts |
20| Dim gray | `0x8410` | Secondary info | 20| Dim gray | `0x8410` | Secondary info |
21| Dark bg | `0x2104` | Card backgrounds |
22 21
23## Screen States 22## Screen States
24 23
@@ -26,111 +25,141 @@
26Shown during startup until WiFi connects and services start. 25Shown during startup until WiFi connects and services start.
27 26
28``` 27```
29┌──────────────────────────┐ 0 28┌──────────────────────────┐
30│ │ 29│ TollGate │ cyan, scale 2
31│ TollGate │ y=180, cyan, scale 2 30│ connecting... │ yellow, scale 1
32│ connecting... │ y=205, yellow, scale 1 31│ WiFi: trying... │ dim, scale 1
33│ │ 32└──────────────────────────┘
34│ WiFi: trying... │ y=260, dim, scale 1
35│ │
36└──────────────────────────┘ 479
37``` 33```
38 34
39WiFi status line shows: "trying...", "connected!", "failed (retry)" 35### 2. READY — QR Cycling
40
41### 2. READY — QR Cycling (primary screen)
42Cycles every 5 seconds between WiFi QR and Portal QR. 36Cycles every 5 seconds between WiFi QR and Portal QR.
43 37
44**View A — WiFi QR:** 38**View A — WiFi QR:**
45``` 39```
46┌──────────────────────────┐ 0 40┌──────────────────────────┐
47│ ┌──────┐ │ 41│ ┌──────┐ │
48│ │ QR │ │ QR: WIFI:S:<ssid>;T:nopass;; 42│ │ QR │ │ WIFI:S:<ssid>;T:nopass;;
49│ │ │ │ Centered in top 2/3 of screen
50│ └──────┘ │ 43│ └──────┘ │
51│ │ ~y=320 44│ Scan to connect │ cyan
52│ Scan to connect │ cyan, scale 1 45│ SSID: TollGate-XXXX │ white
53│ SSID: TollGate-XXXX │ white, scale 1 46│ 21 sats/min │ orange
54│ 21 sats/min │ orange, scale 1 47│ Wallet: 420 sats │ green/yellow/red
55│ Wallet: 420 sats │ green, scale 1 48└──────────────────────────┘
56└──────────────────────────┘ 479
57``` 49```
58 50
59**View B — Portal QR:** 51**View B — Portal QR:**
60``` 52```
61┌──────────────────────────┐ 0 53┌──────────────────────────┐
62│ ┌──────┐ │ 54│ ┌──────┐ │
63│ │ QR │ │ QR: http://10.x.x.x/ 55│ │ QR │ │ http://10.x.x.x/
64│ │ │ │
65│ └──────┘ │ 56│ └──────┘ │
66│ │ ~y=320 57│ Portal URL │ cyan
67│ Portal URL │ cyan, scale 1 58│ Mint: testnut... │ orange
68│ testnut.cashu.space │ orange, scale 1 (mint domain) 59│ 21 sats/min │ orange
69│ 21 sats/min │ yellow, scale 1 60│ Clients: 3 │ green
70│ Clients: 3 │ green, scale 1 61└──────────────────────────┘
71└──────────────────────────┘ 479
72``` 62```
73 63
74### 3. PAYMENT_RECEIVED 64### 3. PAYMENT_RECEIVED
75Shows for 3 seconds after payment, then returns to READY. 65Shows for 3 seconds after payment, then returns to READY.
76 66
77``` 67```
78┌──────────────────────────┐ 0 68┌──────────────────────────┐
79│ │ 69│ ████████████████████ │ green bar
80│ ████████████████████ │ green filled bar, y=190..230
81│ ACCESS GRANTED │ white on green, scale 2 70│ ACCESS GRANTED │ white on green, scale 2
82│ ████████████████████ │ 71│ ████████████████████ │
83│ │ 72│ Paid: 42 sats │ white
84│ Paid: 21 sats │ white, scale 1 73│ Time: 2 min │ white
85│ Time: 1 min │ white, scale 1 74│ Wallet: 462 sats │ green
86│ │ 75└──────────────────────────┘
87│ Wallet: 441 sats │ green, scale 1
88└──────────────────────────┘ 479
89``` 76```
90 77
91### 4. ERROR 78### 4. ERROR
92Shown when upstream WiFi is disconnected. 79Shown when upstream WiFi is disconnected.
93 80
94``` 81```
95┌──────────────────────────┐ 0 82┌──────────────────────────┐
96│ ████████████████████ │ red filled bar, y=190..230 83│ ████████████████████ │ red bar
97│ NO UPSTREAM │ white on red, scale 2 84│ NO UPSTREAM │ white on red, scale 2
98│ ████████████████████ │ 85│ ████████████████████ │
99│ │ 86│ Internet unavailable │ white
100│ Internet unavailable │ white, scale 1 87│ Check WiFi config │ yellow
101│ Check WiFi config │ yellow, scale 1 88│ AP still active │ green
102│ │ 89│ SSID: TollGate-XXXX │ dim
103│ AP still active │ green, scale 1 90└──────────────────────────┘
104│ SSID: TollGate-XXXX │ dim, scale 1
105└──────────────────────────┘ 479
106``` 91```
107 92
108## Data Flow 93## State Synchronization
109 94
110### display_update() receives: 95### Data sources and update triggers
111```c
112void display_update(const char *ap_ssid, int active_clients,
113 uint64_t wallet_balance, const char *portal_url);
114```
115 96
116### Enhanced to also receive: 97| Display data | Source function | Update trigger |
117```c 98|-------------|----------------|----------------|
118void display_update(const char *ap_ssid, int active_clients, 99| SSID | `config.ap_ssid` | Once at `start_services()` |
119 uint64_t wallet_balance, const char *portal_url, 100| Portal URL | `config.ap_ip_str` | Once at `start_services()` |
120 const char *mint_url, int price_per_step, 101| Mint URL | `config.mint_url` | Once at `start_services()` |
121 const char *wifi_status); 102| Price | `config.price_per_step` | Once at `start_services()` |
103| **Wallet balance** | `nucula_wallet_balance()` | **Every 5s in main loop** |
104| **Client count** | `session_active_count()` | **Every 5s in main loop + AP events** |
105| **Last payment** | `display_notify_payment()` | **On each payment in API handler** |
106| WiFi status | Event handler | On STA connect/disconnect |
107
108### State transitions
109
110```
111app_main()
112 └─ display_set_state(BOOT)
113 └─ display_update(price, mint, ssid) ← config data available after config_init
114
115wifi_event_handler(STA_DISCONNECTED)
116 └─ display_set_state(ERROR)
117 └─ display_update(wifi_status="retrying...")
118
119ip_event_handler(STA_GOT_IP)
120 └─ start_services()
121 └─ display_set_state(READY)
122 └─ display_update(ssid, portal_url, mint, price)
123
124tollgate_api (POST / payment)
125 └─ session_create()
126 └─ nucula_wallet_receive()
127 └─ display_notify_payment(amount_sats, allotment) ← NEW
128 └─ display_set_state(PAYMENT_RECEIVED)
129
130display_task (3s timeout)
131 └─ auto-return PAYMENT_RECEIVED → READY
132
133app_main() main loop (every 5s)
134 └─ display_update(wallet_balance, client_count) ← NEW periodic refresh
122``` 135```
123 136
124### display_set_state() triggers: 137### New API: `display_notify_payment()`
125- `DISPLAY_BOOT` → at startup
126- `DISPLAY_READY` → when services start (WiFi connected)
127- `DISPLAY_PAYMENT_RECEIVED` → on successful payment (auto-returns to READY)
128- `DISPLAY_ERROR` → when upstream WiFi disconnects
129 138
130## Implementation Notes 139```c
140void display_notify_payment(int amount_sats, int64_t allotment_ms);
141```
131 142
132- Render every 2 seconds (reduces SPI bus load vs 1 second) 143Stores the last payment amount and time purchased for the PAYMENT screen to display.
133- QR codes: auto-size based on string length, centered in top portion 144
134- Mint URL: show only domain part (truncate at first `/`) 145## Implementation Checklist
135- Wallet balance: color-coded (green > 100, yellow > 0, red = 0) 146
136- Client count: "Clients: N" or empty string if 0 147### Done
148- [x] QSPI driver working with correct colors (DMA byte-swap + RAMWR)
149- [x] BOOT screen with title and WiFi status
150- [x] READY screen with QR cycling, price, mint, balance, clients
151- [x] PAYMENT screen layout (green banner, amount, time, wallet)
152- [x] ERROR screen layout (red banner, guidance, AP status)
153- [x] WiFi disconnect → ERROR state transition
154- [x] WiFi connect → READY state transition
155
156### TODO — State Sync
157- [ ] Periodic display data refresh in main loop (wallet balance, client count every 5s)
158- [ ] `display_notify_payment()` API to pass payment amount and allotment
159- [ ] Call `display_set_state(PAYMENT_RECEIVED)` from tollgate_api.c after payment
160- [ ] Pass config data (price, mint) to display during boot phase
161- [ ] Update client count on AP station connect/disconnect events
162
163### TODO — Polish
164- [ ] Run `make test-unit` to check for regressions
165- [ ] 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 @@
2#include "axs15231b.h" 2#include "axs15231b.h"
3#include "qrcoded.h" 3#include "qrcoded.h"
4#include "font.h" 4#include "font.h"
5#include "nucula_wallet.h"
5#include "esp_log.h" 6#include "esp_log.h"
6#include "freertos/FreeRTOS.h" 7#include "freertos/FreeRTOS.h"
7#include "freertos/task.h" 8#include "freertos/task.h"
@@ -34,6 +35,8 @@ static int s_price_per_step = 0;
34static bool s_initialized = false; 35static bool s_initialized = false;
35static int64_t s_last_qr_switch = 0; 36static int64_t s_last_qr_switch = 0;
36static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI; 37static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI;
38static int s_last_payment_sats = 0;
39static int64_t s_last_allotment_ms = 0;
37 40
38static int qr_version_from_strlen(int len) { 41static int qr_version_from_strlen(int len) {
39 if (len <= 17) return 1; 42 if (len <= 17) return 1;
@@ -251,13 +254,18 @@ static void render_payment_screen(void) {
251 254
252 char line[48]; 255 char line[48];
253 256
254 snprintf(line, sizeof(line), "Paid: %d sats", s_price_per_step); 257 snprintf(line, sizeof(line), "Paid: %d sats", s_last_payment_sats);
255 int lw = strlen(line) * 8; 258 int lw = strlen(line) * 8;
256 display_render_text((screen_w - lw) / 2, 270, line, COLOR_WHITE, COLOR_BG, 1); 259 display_render_text((screen_w - lw) / 2, 270, line, COLOR_WHITE, COLOR_BG, 1);
257 260
258 const char *time_msg = "Time: 1 min"; 261 int64_t secs = s_last_allotment_ms / 1000;
259 int tw = strlen(time_msg) * 8; 262 if (secs >= 60) {
260 display_render_text((screen_w - tw) / 2, 290, time_msg, COLOR_WHITE, COLOR_BG, 1); 263 snprintf(line, sizeof(line), "Time: %lld min", (long long)(secs / 60));
264 } else {
265 snprintf(line, sizeof(line), "Time: %lld sec", (long long)secs);
266 }
267 lw = strlen(line) * 8;
268 display_render_text((screen_w - lw) / 2, 290, line, COLOR_WHITE, COLOR_BG, 1);
261 269
262 snprintf(line, sizeof(line), "Wallet: %llu sats", (unsigned long long)s_wallet_balance); 270 snprintf(line, sizeof(line), "Wallet: %llu sats", (unsigned long long)s_wallet_balance);
263 lw = strlen(line) * 8; 271 lw = strlen(line) * 8;
@@ -378,3 +386,10 @@ void display_update(const char *ap_ssid, int active_clients,
378 s_active_clients = active_clients; 386 s_active_clients = active_clients;
379 s_wallet_balance = wallet_balance; 387 s_wallet_balance = wallet_balance;
380} 388}
389
390void display_notify_payment(int amount_sats, int64_t allotment_ms) {
391 s_last_payment_sats = amount_sats;
392 s_last_allotment_ms = allotment_ms;
393 s_wallet_balance = nucula_wallet_balance();
394 display_set_state(DISPLAY_PAYMENT_RECEIVED);
395}
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,
23 uint64_t wallet_balance, const char *portal_url, 23 uint64_t wallet_balance, const char *portal_url,
24 const char *mint_url, int price_per_step, 24 const char *mint_url, int price_per_step,
25 const char *wifi_status); 25 const char *wifi_status);
26void display_notify_payment(int amount_sats, int64_t allotment_ms);
26void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale); 27void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale);
27void display_render_qr(const char *text); 28void display_render_qr(const char *text);
28 29
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 @@
4#include "session.h" 4#include "session.h"
5#include "firewall.h" 5#include "firewall.h"
6#include "nucula_wallet.h" 6#include "nucula_wallet.h"
7#include "display.h"
7#include "esp_log.h" 8#include "esp_log.h"
8#include "cJSON.h" 9#include "cJSON.h"
9#include "lwip/sockets.h" 10#include "lwip/sockets.h"
@@ -313,6 +314,8 @@ static esp_err_t api_post_payment(httpd_req_t *req)
313 314
314 nucula_wallet_receive(body_copy); 315 nucula_wallet_receive(body_copy);
315 316
317 display_notify_payment(token->total_amount, allotment);
318
316 free(states); 319 free(states);
317 free(token); 320 free(token);
318 return ESP_OK; 321 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,
77 ESP_LOGI(TAG, "Station connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x", 77 ESP_LOGI(TAG, "Station connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x",
78 event->mac[0], event->mac[1], event->mac[2], 78 event->mac[0], event->mac[1], event->mac[2],
79 event->mac[3], event->mac[4], event->mac[5]); 79 event->mac[3], event->mac[4], event->mac[5]);
80 display_update(NULL, session_active_count(), 0, NULL, NULL, 0, NULL);
80 } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { 81 } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
81 wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; 82 wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
82 ESP_LOGI(TAG, "Station disconnected: MAC=%02x:%02x:%02x:%02x:%02x:%02x", 83 ESP_LOGI(TAG, "Station disconnected: MAC=%02x:%02x:%02x:%02x:%02x:%02x",
83 event->mac[0], event->mac[1], event->mac[2], 84 event->mac[0], event->mac[1], event->mac[2],
84 event->mac[3], event->mac[4], event->mac[5]); 85 event->mac[3], event->mac[4], event->mac[5]);
86 display_update(NULL, session_active_count(), 0, NULL, NULL, 0, NULL);
85 } 87 }
86} 88}
87 89
@@ -314,6 +316,9 @@ void app_main(void)
314 316
315 ESP_LOGI(TAG, "WiFi AP+STA started, waiting for connection..."); 317 ESP_LOGI(TAG, "WiFi AP+STA started, waiting for connection...");
316 318
319 display_update(tcfg->ap_ssid, 0, 0, NULL,
320 tcfg->mint_url, tcfg->price_per_step, "connecting...");
321
317 if (tollgate_config_get_wifi(&(wifi_config_t){0}) != ESP_OK) { 322 if (tollgate_config_get_wifi(&(wifi_config_t){0}) != ESP_OK) {
318 ESP_LOGI(TAG, "No STA network configured, starting services immediately"); 323 ESP_LOGI(TAG, "No STA network configured, starting services immediately");
319 xTaskCreate(services_start_task, "svc_start", 32768, NULL, 5, NULL); 324 xTaskCreate(services_start_task, "svc_start", 32768, NULL, 5, NULL);
@@ -324,5 +329,13 @@ void app_main(void)
324 session_tick(); 329 session_tick();
325 tollgate_client_tick(); 330 tollgate_client_tick();
326 lightning_payout_tick(); 331 lightning_payout_tick();
332
333 static int display_tick = 0;
334 if (++display_tick >= 5) {
335 display_tick = 0;
336 display_update(NULL, session_active_count(),
337 nucula_wallet_balance(), NULL,
338 NULL, 0, NULL);
339 }
327 } 340 }
328} 341}