upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-18 19:25:32 +0530
committerYour Name <you@example.com>2026-05-18 19:25:32 +0530
commite1da6ffc0221abf4d0fb3a8c3e6be0b90d69397a (patch)
tree3a0055b5af2bb09492c8c38fdf2712922ecc7123 /main
parentf0f8cca10196b265c3b453f71322239d2ecaf4ae (diff)
Implement full TollGate display UI
- BOOT screen: centered title + WiFi status line - READY screen: QR cycling (WiFi/Portal), price, mint domain, wallet balance (color-coded), client count - PAYMENT screen: green banner 'ACCESS GRANTED', paid amount, time - ERROR screen: red banner 'NO UPSTREAM', guidance, AP reassurance - Enhanced display_update() API with mint_url, price, wifi_status - Extract domain helper for clean mint URL display - Render interval: 2s (reduced SPI load) - Color palette: cyan, yellow, orange, green, red, dim gray - WiFi events update display status and trigger ERROR state - Board C now on /dev/ttyACM3
Diffstat (limited to 'main')
-rw-r--r--main/display.c168
-rw-r--r--main/display.h4
-rw-r--r--main/tollgate_main.c9
3 files changed, 149 insertions, 32 deletions
diff --git a/main/display.c b/main/display.c
index 0b641ea..add97db 100644
--- a/main/display.c
+++ b/main/display.c
@@ -12,18 +12,28 @@
12static const char *TAG = "display"; 12static const char *TAG = "display";
13 13
14#define QR_CYCLE_MS 5000 14#define QR_CYCLE_MS 5000
15#define RENDER_INTERVAL_MS 2000
16
17#define COLOR_BG 0x0000
18#define COLOR_WHITE 0xFFFF
19#define COLOR_CYAN 0x07FF
20#define COLOR_YELLOW 0xFFE0
21#define COLOR_GREEN 0x07E0
22#define COLOR_ORANGE 0xFD20
23#define COLOR_RED 0xF800
24#define COLOR_DIM 0x8410
15 25
16static volatile display_state_t s_state = DISPLAY_BOOT; 26static volatile display_state_t s_state = DISPLAY_BOOT;
17static char s_ap_ssid[32] = ""; 27static char s_ap_ssid[32] = "";
18static char s_portal_url[256] = ""; 28static char s_portal_url[256] = "";
29static char s_mint_url[256] = "";
30static char s_wifi_status[32] = "starting...";
19static int s_active_clients = 0; 31static int s_active_clients = 0;
20static uint64_t s_wallet_balance = 0; 32static uint64_t s_wallet_balance = 0;
33static int s_price_per_step = 0;
21static bool s_initialized = false; 34static bool s_initialized = false;
22static int64_t s_last_qr_switch = 0; 35static int64_t s_last_qr_switch = 0;
23static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI; 36static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI;
24static display_state_t s_rendered_state = DISPLAY_BOOT;
25static display_qr_mode_t s_rendered_qr_mode = DISPLAY_QR_WIFI;
26static bool s_force_render = true;
27 37
28static int qr_version_from_strlen(int len) { 38static int qr_version_from_strlen(int len) {
29 if (len <= 17) return 1; 39 if (len <= 17) return 1;
@@ -68,6 +78,16 @@ static void build_wifi_qr_string(char *out, int out_size) {
68 snprintf(out, out_size, "WIFI:S:%s;T:nopass;;", escaped_ssid); 78 snprintf(out, out_size, "WIFI:S:%s;T:nopass;;", escaped_ssid);
69} 79}
70 80
81static void extract_domain(const char *url, char *domain, int domain_size) {
82 const char *start = url;
83 if (strncmp(url, "https://", 8) == 0) start = url + 8;
84 else if (strncmp(url, "http://", 7) == 0) start = url + 7;
85 strncpy(domain, start, domain_size - 1);
86 domain[domain_size - 1] = '\0';
87 char *slash = strchr(domain, '/');
88 if (slash) *slash = '\0';
89}
90
71void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale) { 91void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale) {
72 int cx = x; 92 int cx = x;
73 int cy = y; 93 int cy = y;
@@ -146,54 +166,134 @@ void display_render_qr(const char *text) {
146 axs15231b_flush(); 166 axs15231b_flush();
147} 167}
148 168
169static uint16_t wallet_color(void) {
170 if (s_wallet_balance == 0) return COLOR_RED;
171 if (s_wallet_balance < 100) return COLOR_YELLOW;
172 return COLOR_GREEN;
173}
174
149static void render_boot_screen(void) { 175static void render_boot_screen(void) {
150 axs15231b_fill_screen(0x0000); 176 int screen_w = axs15231b_get_width();
151 display_render_text(96, 220, "TollGate", 0x07FF, 0x0000, 2); 177 axs15231b_fill_screen(COLOR_BG);
152 display_render_text(116, 245, "starting...", 0xFFE0, 0x0000, 1); 178
179 const char *title = "TollGate";
180 int title_w = strlen(title) * 8 * 2;
181 display_render_text((screen_w - title_w) / 2, 200, title, COLOR_CYAN, COLOR_BG, 2);
182
183 int status_w = strlen(s_wifi_status) * 8;
184 display_render_text((screen_w - status_w) / 2, 228, s_wifi_status, COLOR_YELLOW, COLOR_BG, 1);
185
153 axs15231b_flush(); 186 axs15231b_flush();
154} 187}
155 188
156static void render_ready_screen(void) { 189static void render_ready_screen(void) {
157 axs15231b_fill_screen(0x0000);
158
159 int screen_w = axs15231b_get_width(); 190 int screen_w = axs15231b_get_width();
160 int screen_h = axs15231b_get_height(); 191 int text_area_y = 330;
161 int text_area_y = screen_h - 55; 192 axs15231b_fill_screen(COLOR_BG);
162 193
163 char qr_text[320]; 194 char qr_text[320];
164 const char *label;
165
166 if (s_qr_mode == DISPLAY_QR_WIFI) { 195 if (s_qr_mode == DISPLAY_QR_WIFI) {
167 build_wifi_qr_string(qr_text, sizeof(qr_text)); 196 build_wifi_qr_string(qr_text, sizeof(qr_text));
168 label = "Scan to connect";
169 } else { 197 } else {
170 strncpy(qr_text, s_portal_url, sizeof(qr_text) - 1); 198 strncpy(qr_text, s_portal_url, sizeof(qr_text) - 1);
171 qr_text[sizeof(qr_text) - 1] = '\0'; 199 qr_text[sizeof(qr_text) - 1] = '\0';
172 label = "Portal URL";
173 } 200 }
174 201
175 render_qr_at(qr_text, 0, 0, screen_w, text_area_y - 5); 202 render_qr_at(qr_text, 0, 5, screen_w, text_area_y - 10);
176 203
177 display_render_text(10, text_area_y, label, 0xB5B6, 0x0000, 2); 204 int y = text_area_y;
205 char line[48];
178 206
179 char line[64]; 207 if (s_qr_mode == DISPLAY_QR_WIFI) {
180 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid); 208 snprintf(line, sizeof(line), "Scan to connect");
181 display_render_text(10, text_area_y + 20, line, 0xB5B6, 0x0000, 2); 209 display_render_text(10, y, line, COLOR_CYAN, COLOR_BG, 1);
210 y += 16;
211
212 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid);
213 display_render_text(10, y, line, COLOR_WHITE, COLOR_BG, 1);
214 y += 16;
215 } else {
216 snprintf(line, sizeof(line), "Portal URL");
217 display_render_text(10, y, line, COLOR_CYAN, COLOR_BG, 1);
218 y += 16;
219
220 char domain[48];
221 extract_domain(s_mint_url, domain, sizeof(domain));
222 snprintf(line, sizeof(line), "Mint: %.30s", domain);
223 display_render_text(10, y, line, COLOR_ORANGE, COLOR_BG, 1);
224 y += 16;
225 }
226
227 snprintf(line, sizeof(line), "%d sats/min", s_price_per_step);
228 display_render_text(10, y, line, COLOR_ORANGE, COLOR_BG, 1);
229 y += 16;
230
231 snprintf(line, sizeof(line), "Wallet: %llu sats", (unsigned long long)s_wallet_balance);
232 display_render_text(10, y, line, wallet_color(), COLOR_BG, 1);
233 y += 16;
234
235 if (s_active_clients > 0) {
236 snprintf(line, sizeof(line), "Clients: %d", s_active_clients);
237 display_render_text(10, y, line, COLOR_GREEN, COLOR_BG, 1);
238 }
182 239
183 axs15231b_flush(); 240 axs15231b_flush();
184} 241}
185 242
186static void render_payment_screen(void) { 243static void render_payment_screen(void) {
187 axs15231b_fill_screen(0x07E0); 244 int screen_w = axs15231b_get_width();
188 display_render_text(128, 225, "Paid!", 0x0000, 0x07E0, 2); 245 axs15231b_fill_screen(COLOR_BG);
189 display_render_text(104, 245, "Access granted", 0x0000, 0x07E0, 1); 246
247 axs15231b_fill_rect(0, 190, screen_w, 50, COLOR_GREEN);
248 const char *msg = "ACCESS GRANTED";
249 int msg_w = strlen(msg) * 8 * 2;
250 display_render_text((screen_w - msg_w) / 2, 202, msg, COLOR_WHITE, COLOR_GREEN, 2);
251
252 char line[48];
253
254 snprintf(line, sizeof(line), "Paid: %d sats", s_price_per_step);
255 int lw = strlen(line) * 8;
256 display_render_text((screen_w - lw) / 2, 270, line, COLOR_WHITE, COLOR_BG, 1);
257
258 const char *time_msg = "Time: 1 min";
259 int tw = strlen(time_msg) * 8;
260 display_render_text((screen_w - tw) / 2, 290, time_msg, COLOR_WHITE, COLOR_BG, 1);
261
262 snprintf(line, sizeof(line), "Wallet: %llu sats", (unsigned long long)s_wallet_balance);
263 lw = strlen(line) * 8;
264 display_render_text((screen_w - lw) / 2, 320, line, wallet_color(), COLOR_BG, 1);
265
190 axs15231b_flush(); 266 axs15231b_flush();
191} 267}
192 268
193static void render_error_screen(void) { 269static void render_error_screen(void) {
194 axs15231b_fill_screen(0xF800); 270 int screen_w = axs15231b_get_width();
195 display_render_text(104, 225, "No upstream", 0xFFFF, 0xF800, 2); 271 axs15231b_fill_screen(COLOR_BG);
196 display_render_text(120, 245, "Check config", 0xFFFF, 0xF800, 1); 272
273 axs15231b_fill_rect(0, 190, screen_w, 50, COLOR_RED);
274 const char *msg = "NO UPSTREAM";
275 int msg_w = strlen(msg) * 8 * 2;
276 display_render_text((screen_w - msg_w) / 2, 202, msg, COLOR_WHITE, COLOR_RED, 2);
277
278 char line[48];
279 int lw;
280
281 const char *l1 = "Internet unavailable";
282 lw = strlen(l1) * 8;
283 display_render_text((screen_w - lw) / 2, 270, l1, COLOR_WHITE, COLOR_BG, 1);
284
285 const char *l2 = "Check WiFi config";
286 lw = strlen(l2) * 8;
287 display_render_text((screen_w - lw) / 2, 290, l2, COLOR_YELLOW, COLOR_BG, 1);
288
289 const char *l3 = "AP still active";
290 lw = strlen(l3) * 8;
291 display_render_text((screen_w - lw) / 2, 320, l3, COLOR_GREEN, COLOR_BG, 1);
292
293 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid);
294 lw = strlen(line) * 8;
295 display_render_text((screen_w - lw) / 2, 340, line, COLOR_DIM, COLOR_BG, 1);
296
197 axs15231b_flush(); 297 axs15231b_flush();
198} 298}
199 299
@@ -220,7 +320,7 @@ static void display_task(void *pvParameters) {
220 break; 320 break;
221 case DISPLAY_PAYMENT_RECEIVED: 321 case DISPLAY_PAYMENT_RECEIVED:
222 render_payment_screen(); 322 render_payment_screen();
223 vTaskDelay(pdMS_TO_TICKS(2000)); 323 vTaskDelay(pdMS_TO_TICKS(3000));
224 s_state = DISPLAY_READY; 324 s_state = DISPLAY_READY;
225 break; 325 break;
226 case DISPLAY_ERROR: 326 case DISPLAY_ERROR:
@@ -228,7 +328,7 @@ static void display_task(void *pvParameters) {
228 break; 328 break;
229 } 329 }
230 330
231 vTaskDelay(pdMS_TO_TICKS(1000)); 331 vTaskDelay(pdMS_TO_TICKS(RENDER_INTERVAL_MS));
232 } 332 }
233} 333}
234 334
@@ -252,11 +352,12 @@ esp_err_t display_init(void) {
252 352
253void display_set_state(display_state_t state) { 353void display_set_state(display_state_t state) {
254 s_state = state; 354 s_state = state;
255 s_force_render = true;
256} 355}
257 356
258void display_update(const char *ap_ssid, int active_clients, 357void display_update(const char *ap_ssid, int active_clients,
259 uint64_t wallet_balance, const char *portal_url) { 358 uint64_t wallet_balance, const char *portal_url,
359 const char *mint_url, int price_per_step,
360 const char *wifi_status) {
260 if (ap_ssid) { 361 if (ap_ssid) {
261 strncpy(s_ap_ssid, ap_ssid, sizeof(s_ap_ssid) - 1); 362 strncpy(s_ap_ssid, ap_ssid, sizeof(s_ap_ssid) - 1);
262 s_ap_ssid[sizeof(s_ap_ssid) - 1] = '\0'; 363 s_ap_ssid[sizeof(s_ap_ssid) - 1] = '\0';
@@ -265,6 +366,15 @@ void display_update(const char *ap_ssid, int active_clients,
265 strncpy(s_portal_url, portal_url, sizeof(s_portal_url) - 1); 366 strncpy(s_portal_url, portal_url, sizeof(s_portal_url) - 1);
266 s_portal_url[sizeof(s_portal_url) - 1] = '\0'; 367 s_portal_url[sizeof(s_portal_url) - 1] = '\0';
267 } 368 }
369 if (mint_url) {
370 strncpy(s_mint_url, mint_url, sizeof(s_mint_url) - 1);
371 s_mint_url[sizeof(s_mint_url) - 1] = '\0';
372 }
373 if (wifi_status) {
374 strncpy(s_wifi_status, wifi_status, sizeof(s_wifi_status) - 1);
375 s_wifi_status[sizeof(s_wifi_status) - 1] = '\0';
376 }
377 if (price_per_step > 0) s_price_per_step = price_per_step;
268 s_active_clients = active_clients; 378 s_active_clients = active_clients;
269 s_wallet_balance = wallet_balance; 379 s_wallet_balance = wallet_balance;
270} 380}
diff --git a/main/display.h b/main/display.h
index 407521b..1530e57 100644
--- a/main/display.h
+++ b/main/display.h
@@ -20,7 +20,9 @@ typedef enum {
20esp_err_t display_init(void); 20esp_err_t display_init(void);
21void display_set_state(display_state_t state); 21void display_set_state(display_state_t state);
22void display_update(const char *ap_ssid, int active_clients, 22void 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,
25 const char *wifi_status);
24void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale); 26void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale);
25void display_render_qr(const char *text); 27void display_render_qr(const char *text);
26 28
diff --git a/main/tollgate_main.c b/main/tollgate_main.c
index c0ff65f..7fd50ad 100644
--- a/main/tollgate_main.c
+++ b/main/tollgate_main.c
@@ -54,7 +54,11 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base,
54 s_retry_count++; 54 s_retry_count++;
55 ESP_LOGW(TAG, "WiFi disconnected, retry %d/%d", s_retry_count, MAX_STA_RETRY); 55 ESP_LOGW(TAG, "WiFi disconnected, retry %d/%d", s_retry_count, MAX_STA_RETRY);
56 tollgate_client_on_sta_disconnected(); 56 tollgate_client_on_sta_disconnected();
57 if (s_services_running) stop_services(); 57 if (s_services_running) {
58 stop_services();
59 display_set_state(DISPLAY_ERROR);
60 }
61 display_update(NULL, 0, 0, NULL, NULL, 0, "WiFi retry...");
58 if (s_retry_count < MAX_STA_RETRY) { 62 if (s_retry_count < MAX_STA_RETRY) {
59 esp_wifi_connect(); 63 esp_wifi_connect();
60 } else { 64 } else {
@@ -173,7 +177,8 @@ static void start_services(void)
173 display_set_state(DISPLAY_READY); 177 display_set_state(DISPLAY_READY);
174 char portal_url[128]; 178 char portal_url[128];
175 snprintf(portal_url, sizeof(portal_url), "http://%s/", cfg->ap_ip_str); 179 snprintf(portal_url, sizeof(portal_url), "http://%s/", cfg->ap_ip_str);
176 display_update(cfg->ap_ssid, 0, 0, portal_url); 180 display_update(cfg->ap_ssid, 0, 0, portal_url,
181 cfg->mint_url, cfg->price_per_step, NULL);
177} 182}
178 183
179static void stop_services(void) 184static void stop_services(void)