upleb.uk

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

summaryrefslogtreecommitdiff
path: root/main/display.c
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-19 04:21:14 +0530
committerYour Name <you@example.com>2026-05-19 04:21:14 +0530
commitaa58b47996083f36e3587b8e10f9bbb681610491 (patch)
tree4c845da2ee4b217a2c77deea3b10dd8d288d1b09 /main/display.c
parent9f7dd94029c8dc12117494548f5f32221a729307 (diff)
feat: web-based WiFi setup via captive portal, portrait-only display
- Remove touchscreen WiFi setup (touch.c, keyboard.c, wifi_setup.c from build) - Remove offscreen buffer and landscape rotation from axs15231b driver - Add /setup HTML page with WiFi scan/connect via captive portal - Add /wifi/scan, /wifi/connect, /wifi/status HTTP endpoints - Display shows SETUP_PENDING (QR + SSID + setup URL) when unconfigured - Display shows ERROR with setup URL when upstream is down - All 101 unit tests pass, builds and flashes to Board C
Diffstat (limited to 'main/display.c')
-rw-r--r--main/display.c654
1 files changed, 104 insertions, 550 deletions
diff --git a/main/display.c b/main/display.c
index 77b911d..53cdd4d 100644
--- a/main/display.c
+++ b/main/display.c
@@ -3,9 +3,6 @@
3#include "qrcoded.h" 3#include "qrcoded.h"
4#include "font.h" 4#include "font.h"
5#include "nucula_wallet.h" 5#include "nucula_wallet.h"
6#include "touch.h"
7#include "keyboard.h"
8#include "wifi_setup.h"
9#include "config.h" 6#include "config.h"
10#include "esp_log.h" 7#include "esp_log.h"
11#include "esp_wifi.h" 8#include "esp_wifi.h"
@@ -43,74 +40,10 @@ static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI;
43static int s_last_payment_sats = 0; 40static int s_last_payment_sats = 0;
44static int64_t s_last_allotment_ms = 0; 41static int64_t s_last_allotment_ms = 0;
45 42
46#define COLOR_GRAY 0x8410 43static uint16_t wallet_color(void) {
47#define COLOR_DARKGRAY 0x4208 44 if (s_wallet_balance == 0) return COLOR_RED;
48#define COLOR_LIGHTBLUE 0xA5FF 45 if (s_wallet_balance < 100) return COLOR_YELLOW;
49#define COLOR_HIGHLIGHT 0x07E0 46 return COLOR_GREEN;
50
51static wifi_setup_t s_wifi_setup;
52static kb_state_t s_kb_state;
53static bool s_wifi_setup_active = false;
54static bool s_touch_initialized = false;
55static bool s_wifi_scan_pending = false;
56static int s_display_rotation = 0;
57
58#define SETUP_BTN_X 240
59#define SETUP_BTN_Y 440
60#define SETUP_BTN_W 72
61#define SETUP_BTN_H 30
62
63static void enter_wifi_setup_rotation(void) {
64 if (s_display_rotation != 1) {
65 s_display_rotation = 1;
66 axs15231b_set_rotation(1);
67 touch_set_rotation(1);
68 kb_layout_t landscape = {
69 .key_w = 38,
70 .key_h = 40,
71 .key_gap = 2,
72 .start_y = 170,
73 .screen_w = 480,
74 .row_count = 4,
75 };
76 kb_set_layout(&landscape);
77 }
78}
79
80static void exit_wifi_setup_rotation(void) {
81 if (s_display_rotation != 0) {
82 s_display_rotation = 0;
83 axs15231b_set_rotation(0);
84 touch_set_rotation(0);
85 kb_layout_t portrait = {
86 .key_w = 28,
87 .key_h = 36,
88 .key_gap = 2,
89 .start_y = 70,
90 .screen_w = 320,
91 .row_count = 4,
92 };
93 kb_set_layout(&portrait);
94 }
95}
96
97static void render_setup_button(int x, int y, int w, int h) {
98 axs15231b_fill_rect(x, y, w, h, COLOR_DARKGRAY);
99 const char *label = "Setup";
100 int lw = strlen(label) * 8;
101 display_render_text(x + (w - lw) / 2, y + (h - 8) / 2, label, COLOR_WHITE, COLOR_DARKGRAY, 1);
102}
103
104static bool touch_in_rect(uint16_t tx, uint16_t ty, int x, int y, int w, int h) {
105 return tx >= x && tx < x + w && ty >= y && ty < y + h;
106}
107
108static void highlight_rect(int x, int y, int w, int h) {
109 axs15231b_fill_rect(x, y, w, h, COLOR_HIGHLIGHT);
110 axs15231b_flush();
111 vTaskDelay(pdMS_TO_TICKS(80));
112 axs15231b_fill_rect(x, y, w, h, COLOR_DARKGRAY);
113 axs15231b_flush();
114} 47}
115 48
116static int qr_version_from_strlen(int len) { 49static int qr_version_from_strlen(int len) {
@@ -150,20 +83,60 @@ static int escape_wifi_field(const char *src, char *dst, int dst_size) {
150 return di; 83 return di;
151} 84}
152 85
86static void extract_domain(const char *url, char *out, int out_size) {
87 const char *start = url;
88 if (strncmp(url, "https://", 8) == 0) start = url + 8;
89 else if (strncmp(url, "http://", 7) == 0) start = url + 7;
90 strncpy(out, start, out_size - 1);
91 out[out_size - 1] = '\0';
92 char *slash = strchr(out, '/');
93 if (slash) *slash = '\0';
94}
95
153static void build_wifi_qr_string(char *out, int out_size) { 96static void build_wifi_qr_string(char *out, int out_size) {
154 char escaped_ssid[64]; 97 char escaped_ssid[64];
155 escape_wifi_field(s_ap_ssid, escaped_ssid, sizeof(escaped_ssid)); 98 escape_wifi_field(s_ap_ssid, escaped_ssid, sizeof(escaped_ssid));
156 snprintf(out, out_size, "WIFI:S:%s;T:nopass;;", escaped_ssid); 99 const tollgate_config_t *cfg = tollgate_config_get();
100 if (strlen(cfg->ap_password) > 0) {
101 char escaped_pass[128];
102 escape_wifi_field(cfg->ap_password, escaped_pass, sizeof(escaped_pass));
103 snprintf(out, out_size, "WIFI:S:%s;T:WPA;P:%s;;", escaped_ssid, escaped_pass);
104 } else {
105 snprintf(out, out_size, "WIFI:S:%s;T:nopass;;", escaped_ssid);
106 }
157} 107}
158 108
159static void extract_domain(const char *url, char *domain, int domain_size) { 109static void render_qr_at(const char *text, int x_off, int y_off, int max_w, int max_h) {
160 const char *start = url; 110 int len = strlen(text);
161 if (strncmp(url, "https://", 8) == 0) start = url + 8; 111 int version = qr_version_from_strlen(len);
162 else if (strncmp(url, "http://", 7) == 0) start = url + 7; 112 int px = qr_pixel_size(len);
163 strncpy(domain, start, domain_size - 1); 113
164 domain[domain_size - 1] = '\0'; 114 uint16_t buf_size = qrcode_getBufferSize(version);
165 char *slash = strchr(domain, '/'); 115 uint8_t *qr_buf = (uint8_t *)malloc(buf_size);
166 if (slash) *slash = '\0'; 116 if (!qr_buf) return;
117
118 QRCode qrcode;
119 if (qrcode_initText(&qrcode, qr_buf, version, ECC_LOW, text) != 0) {
120 free(qr_buf);
121 return;
122 }
123
124 int qr_px_w = qrcode.size * px;
125 int qr_px_h = qrcode.size * px;
126 int cx = x_off + (max_w - qr_px_w) / 2;
127 int cy = y_off + (max_h - qr_px_h) / 2;
128 if (cx < 0) cx = 0;
129 if (cy < 0) cy = 0;
130
131 for (int y = 0; y < qrcode.size; y++) {
132 for (int x = 0; x < qrcode.size; x++) {
133 bool mod = qrcode_getModule(&qrcode, x, y);
134 uint16_t color = mod ? COLOR_WHITE : COLOR_BG;
135 axs15231b_fill_rect(cx + x * px, cy + y * px, px, px, color);
136 }
137 }
138
139 free(qr_buf);
167} 140}
168 141
169void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale) { 142void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale) {
@@ -199,57 +172,14 @@ void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t b
199 } 172 }
200} 173}
201 174
202static void render_qr_at(const char *text, int x_off, int y_off, int max_w, int max_h) {
203 int len = strlen(text);
204 int version = qr_version_from_strlen(len);
205 int px = qr_pixel_size(len);
206
207 uint16_t buf_size = qrcode_getBufferSize(version);
208 uint8_t *qr_buf = (uint8_t *)malloc(buf_size);
209 if (!qr_buf) {
210 ESP_LOGE(TAG, "Failed to allocate QR buffer");
211 return;
212 }
213
214 QRCode qr;
215 if (qrcode_initText(&qr, qr_buf, version, ECC_LOW, text) != 0) {
216 ESP_LOGE(TAG, "QR generation failed");
217 free(qr_buf);
218 return;
219 }
220
221 int qr_px_w = qr.size * px;
222 int qr_px_h = qr.size * px;
223 int cx = x_off + (max_w - qr_px_w) / 2;
224 int cy = y_off + (max_h - qr_px_h) / 2;
225 if (cx < 0) cx = 0;
226 if (cy < 0) cy = 0;
227
228 for (int y = 0; y < qr.size; y++) {
229 for (int x = 0; x < qr.size; x++) {
230 bool mod = qrcode_getModule(&qr, x, y);
231 uint16_t color = mod ? 0xFFFF : 0x0000;
232 axs15231b_fill_rect(cx + x * px, cy + y * px, px, px, color);
233 }
234 }
235
236 free(qr_buf);
237}
238
239void display_render_qr(const char *text) { 175void display_render_qr(const char *text) {
240 int screen_w = axs15231b_get_width(); 176 int screen_w = axs15231b_get_width();
241 int screen_h = axs15231b_get_height(); 177 int screen_h = axs15231b_get_height();
242 axs15231b_fill_screen(0x0000); 178 axs15231b_fill_screen(COLOR_BG);
243 render_qr_at(text, 0, 0, screen_w, screen_h); 179 render_qr_at(text, 0, 0, screen_w, screen_h);
244 axs15231b_flush(); 180 axs15231b_flush();
245} 181}
246 182
247static uint16_t wallet_color(void) {
248 if (s_wallet_balance == 0) return COLOR_RED;
249 if (s_wallet_balance < 100) return COLOR_YELLOW;
250 return COLOR_GREEN;
251}
252
253static void render_boot_screen(void) { 183static void render_boot_screen(void) {
254 int screen_w = axs15231b_get_width(); 184 int screen_w = axs15231b_get_width();
255 axs15231b_fill_screen(COLOR_BG); 185 axs15231b_fill_screen(COLOR_BG);
@@ -315,7 +245,44 @@ static void render_ready_screen(void) {
315 display_render_text(10, y, line, COLOR_GREEN, COLOR_BG, 1); 245 display_render_text(10, y, line, COLOR_GREEN, COLOR_BG, 1);
316 } 246 }
317 247
318 render_setup_button(SETUP_BTN_X, SETUP_BTN_Y, SETUP_BTN_W, SETUP_BTN_H); 248 axs15231b_flush();
249}
250
251static void render_setup_pending_screen(void) {
252 int screen_w = axs15231b_get_width();
253 axs15231b_fill_screen(COLOR_BG);
254
255 char qr_text[320];
256 build_wifi_qr_string(qr_text, sizeof(qr_text));
257 render_qr_at(qr_text, 0, 5, screen_w, 280);
258
259 int y = 290;
260 char line[64];
261
262 const char *title = "WiFi Setup";
263 int tw = strlen(title) * 8;
264 display_render_text((screen_w - tw) / 2, y, title, COLOR_CYAN, COLOR_BG, 1);
265 y += 20;
266
267 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid);
268 display_render_text(10, y, line, COLOR_WHITE, COLOR_BG, 1);
269 y += 18;
270
271 const char *hint1 = "1. Connect to WiFi above";
272 display_render_text(10, y, hint1, COLOR_DIM, COLOR_BG, 1);
273 y += 16;
274
275 const char *hint2 = "2. Open browser, go to:";
276 display_render_text(10, y, hint2, COLOR_DIM, COLOR_BG, 1);
277 y += 18;
278
279 const tollgate_config_t *cfg = tollgate_config_get();
280 snprintf(line, sizeof(line), "http://%s/setup", cfg->ap_ip_str);
281 display_render_text(10, y, line, COLOR_YELLOW, COLOR_BG, 1);
282 y += 22;
283
284 const char *hint3 = "3. Configure upstream WiFi";
285 display_render_text(10, y, hint3, COLOR_DIM, COLOR_BG, 1);
319 286
320 axs15231b_flush(); 287 axs15231b_flush();
321} 288}
@@ -360,324 +327,29 @@ static void render_error_screen(void) {
360 int msg_w = strlen(msg) * 8 * 2; 327 int msg_w = strlen(msg) * 8 * 2;
361 display_render_text((screen_w - msg_w) / 2, 202, msg, COLOR_WHITE, COLOR_RED, 2); 328 display_render_text((screen_w - msg_w) / 2, 202, msg, COLOR_WHITE, COLOR_RED, 2);
362 329
363 char line[48]; 330 char line[64];
364 int lw; 331 int lw;
365 332
366 const char *l1 = "Internet unavailable"; 333 const char *l1 = "Internet unavailable";
367 lw = strlen(l1) * 8; 334 lw = strlen(l1) * 8;
368 display_render_text((screen_w - lw) / 2, 270, l1, COLOR_WHITE, COLOR_BG, 1); 335 display_render_text((screen_w - lw) / 2, 270, l1, COLOR_WHITE, COLOR_BG, 1);
369 336
370 const char *l2 = "Check WiFi config";
371 lw = strlen(l2) * 8;
372 display_render_text((screen_w - lw) / 2, 290, l2, COLOR_YELLOW, COLOR_BG, 1);
373
374 const char *l3 = "AP still active";
375 lw = strlen(l3) * 8;
376 display_render_text((screen_w - lw) / 2, 320, l3, COLOR_GREEN, COLOR_BG, 1);
377
378 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid); 337 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid);
379 lw = strlen(line) * 8; 338 lw = strlen(line) * 8;
380 display_render_text((screen_w - lw) / 2, 340, line, COLOR_DIM, COLOR_BG, 1); 339 display_render_text((screen_w - lw) / 2, 300, line, COLOR_DIM, COLOR_BG, 1);
381
382 render_setup_button(SETUP_BTN_X, SETUP_BTN_Y, SETUP_BTN_W, SETUP_BTN_H);
383
384 axs15231b_flush();
385}
386 340
387static void render_wifi_setup_scanning(void) { 341 const tollgate_config_t *cfg = tollgate_config_get();
388 int screen_w = axs15231b_get_width(); 342 snprintf(line, sizeof(line), "Setup: http://%s/setup", cfg->ap_ip_str);
389 axs15231b_fill_screen(COLOR_BG); 343 lw = strlen(line) * 8;
390 344 display_render_text((screen_w - lw) / 2, 330, line, COLOR_YELLOW, COLOR_BG, 1);
391 const char *title = "WiFi Setup";
392 int tw = strlen(title) * 8;
393 display_render_text((screen_w - tw) / 2, 180, title, COLOR_CYAN, COLOR_BG, 1);
394
395 const char *msg = "Scanning...";
396 int mw = strlen(msg) * 8;
397 display_render_text((screen_w - mw) / 2, 220, msg, COLOR_WHITE, COLOR_BG, 1);
398
399 axs15231b_flush();
400}
401
402static void render_wifi_setup_list(void) {
403 int screen_w = axs15231b_get_width();
404 int screen_h = axs15231b_get_height();
405 axs15231b_fill_screen(COLOR_BG);
406
407 const char *title = "Select Network";
408 int tw = strlen(title) * 8;
409 display_render_text((screen_w - tw) / 2, 5, title, COLOR_CYAN, COLOR_BG, 1);
410
411 int y = 25;
412 int visible = wifi_setup_visible_count(&s_wifi_setup);
413 int item_w = screen_w - 20;
414 int max_y = screen_h - 40;
415
416 for (int i = 0; i < visible && y < max_y; i++) {
417 const wifi_ap_info_t *ap = wifi_setup_get_visible(&s_wifi_setup, i);
418 if (!ap) break;
419
420 int rssi_bars = 0;
421 if (ap->rssi >= -30) rssi_bars = 4;
422 else if (ap->rssi >= -50) rssi_bars = 3;
423 else if (ap->rssi >= -70) rssi_bars = 2;
424 else rssi_bars = 1;
425
426 axs15231b_fill_rect(10, y, item_w, 26, COLOR_DARKGRAY);
427 display_render_text(15, y + 4, ap->ssid, COLOR_WHITE, COLOR_DARKGRAY, 1);
428
429 int bar_x = 10 + item_w - 50;
430 for (int b = 0; b < rssi_bars; b++) {
431 axs15231b_fill_rect(bar_x + b * 10, y + 16 - (b + 1) * 4, 8, (b + 1) * 4, COLOR_GREEN);
432 }
433
434 if (ap->secured) {
435 display_render_text(bar_x - 12, y + 4, "*", COLOR_YELLOW, COLOR_DARKGRAY, 1);
436 }
437
438 y += 30;
439 }
440
441 int btn_y = screen_h - 30;
442 axs15231b_fill_rect(10, btn_y, 50, 26, COLOR_DARKGRAY);
443 display_render_text(25, btn_y + 5, "X", COLOR_WHITE, COLOR_DARKGRAY, 1);
444
445 axs15231b_flush();
446}
447
448static void render_wifi_setup_password(void) {
449 int screen_w = axs15231b_get_width();
450 const kb_layout_t *kb = kb_get_layout();
451 axs15231b_fill_screen(COLOR_BG);
452
453 display_render_text(10, 5, s_wifi_setup.selected_ssid, COLOR_CYAN, COLOR_BG, 1);
454 display_render_text(10, 25, "Password:", COLOR_DIM, COLOR_BG, 1);
455
456 int field_w = screen_w - 80;
457 axs15231b_fill_rect(10, 40, field_w, 20, COLOR_DARKGRAY);
458
459 char masked[KB_INPUT_MAX + 1];
460 if (s_kb_state.reveal) {
461 strncpy(masked, s_kb_state.input, sizeof(masked) - 1);
462 masked[sizeof(masked) - 1] = '\0';
463 } else {
464 int len = s_kb_state.cursor;
465 int max_chars = (field_w - 8) / 8;
466 if (len > max_chars) len = max_chars;
467 for (int i = 0; i < len; i++) masked[i] = '*';
468 masked[len] = '\0';
469 }
470 display_render_text(14, 43, masked, COLOR_WHITE, COLOR_DARKGRAY, 1);
471
472 int eye_x = 10 + field_w + 5;
473 const char *eye_label = s_kb_state.reveal ? "H" : "S";
474 axs15231b_fill_rect(eye_x, 40, 24, 20, COLOR_GRAY);
475 display_render_text(eye_x + 8, 43, eye_label, COLOR_WHITE, COLOR_GRAY, 1);
476
477 int kb_y = kb->start_y;
478 for (int row = 0; row < kb->row_count; row++) {
479 const char *row_str;
480 int key_count = kb_get_row_keys(row, s_kb_state.layer, &row_str);
481 const char *tmp;
482 int total_keys = kb_get_row_keys(row, s_kb_state.layer, &tmp);
483 int x_off = (row == 0) ? (screen_w - total_keys * kb->key_w - (total_keys - 1) * kb->key_gap) / 2
484 : (row == 1) ? (screen_w - total_keys * kb->key_w - (total_keys - 1) * kb->key_gap) / 2 + kb->key_w / 2
485 : (row == 2) ? (screen_w - total_keys * kb->key_w - (total_keys - 1) * kb->key_gap) / 2 + kb->key_w
486 : (screen_w - total_keys * kb->key_w - (total_keys - 1) * kb->key_gap) / 2;
487
488 int cx = x_off;
489 for (int col = 0; col < key_count; col++) {
490 char c = row_str[col];
491 int kw = kb->key_w;
492 if (row == 3) {
493 int margin = (screen_w - total_keys * kb->key_w - (total_keys - 1) * kb->key_gap) / 2;
494 int available = screen_w - margin * 2;
495 int side_w = (available - kb->key_gap) / 4;
496 if (col == 0) kw = side_w;
497 else if (col == key_count - 1) kw = side_w;
498 else kw = available - side_w * 2 - kb->key_gap * 2;
499 }
500
501 uint16_t bg = COLOR_DARKGRAY;
502 uint16_t fg = COLOR_WHITE;
503 char label[2] = {0, 0};
504
505 if (c == '\001') {
506 label[0] = s_kb_state.layer == KB_ALPHA_UPPER ? 'A' : 'a';
507 bg = COLOR_GRAY;
508 } else if (c == '\002') {
509 label[0] = s_kb_state.layer == KB_NUMSYM ? 'a' : '#';
510 bg = COLOR_GRAY;
511 } else if (c == '\003') {
512 label[0] = '_';
513 } else if (c == '\004') {
514 label[0] = '>';
515 fg = COLOR_GREEN;
516 } else if (c == '\b') {
517 label[0] = '<';
518 bg = COLOR_GRAY;
519 } else {
520 label[0] = c;
521 }
522
523 axs15231b_fill_rect(cx, kb_y, kw, kb->key_h, bg);
524 int lw = 8;
525 display_render_text(cx + (kw - lw) / 2, kb_y + (kb->key_h - 8) / 2, label, fg, bg, 1);
526 cx += kw + kb->key_gap;
527 }
528 kb_y += kb->key_h + kb->key_gap;
529 }
530
531 axs15231b_flush();
532}
533
534static void render_wifi_setup_connecting(void) {
535 int screen_w = axs15231b_get_width();
536 axs15231b_fill_screen(COLOR_BG);
537
538 const char *msg1 = "Connecting to";
539 int w1 = strlen(msg1) * 8;
540 display_render_text((screen_w - w1) / 2, 200, msg1, COLOR_WHITE, COLOR_BG, 1);
541
542 int w2 = strlen(s_wifi_setup.selected_ssid) * 8;
543 display_render_text((screen_w - w2) / 2, 225, s_wifi_setup.selected_ssid, COLOR_CYAN, COLOR_BG, 1);
544
545 const char *dots = "...";
546 int dw = strlen(dots) * 8;
547 display_render_text((screen_w - dw) / 2, 255, dots, COLOR_DIM, COLOR_BG, 1);
548
549 axs15231b_flush();
550}
551
552static void render_wifi_setup_result(void) {
553 int screen_w = axs15231b_get_width();
554 int screen_h = axs15231b_get_height();
555 axs15231b_fill_screen(COLOR_BG);
556
557 if (s_wifi_setup.state == SETUP_SUCCESS) {
558 const char *msg = "Connected!";
559 int mw = strlen(msg) * 8 * 2;
560 display_render_text((screen_w - mw) / 2, screen_h / 2 - 40, msg, COLOR_WHITE, COLOR_GREEN, 2);
561 345
562 if (s_wifi_setup.connect_ip[0]) { 346 const char *l3 = "AP still active";
563 char ip_line[32]; 347 lw = strlen(l3) * 8;
564 snprintf(ip_line, sizeof(ip_line), "IP: %s", s_wifi_setup.connect_ip); 348 display_render_text((screen_w - lw) / 2, 360, l3, COLOR_GREEN, COLOR_BG, 1);
565 int iw = strlen(ip_line) * 8;
566 display_render_text((screen_w - iw) / 2, screen_h / 2, ip_line, COLOR_WHITE, COLOR_BG, 1);
567 }
568 } else {
569 const char *msg = "Connection failed";
570 int mw = strlen(msg) * 8 * 2;
571 display_render_text((screen_w - mw) / 2, screen_h / 2 - 40, msg, COLOR_WHITE, COLOR_RED, 2);
572
573 const char *hint = "Wrong password?";
574 int hw = strlen(hint) * 8;
575 display_render_text((screen_w - hw) / 2, screen_h / 2, hint, COLOR_YELLOW, COLOR_BG, 1);
576
577 int btn_y = screen_h / 2 + 30;
578 int btn_w = (screen_w - 40) / 2;
579 axs15231b_fill_rect(10, btn_y, btn_w, 30, COLOR_DARKGRAY);
580 display_render_text(10 + (btn_w - 40) / 2, btn_y + 8, "Retry", COLOR_WHITE, COLOR_DARKGRAY, 1);
581 axs15231b_fill_rect(20 + btn_w, btn_y, btn_w, 30, COLOR_DARKGRAY);
582 display_render_text(20 + btn_w + (btn_w - 50) / 2, btn_y + 8, "Change", COLOR_WHITE, COLOR_DARKGRAY, 1);
583 }
584 349
585 axs15231b_flush(); 350 axs15231b_flush();
586} 351}
587 352
588static void handle_wifi_setup_touch(uint16_t tx, uint16_t ty) {
589 int screen_w = axs15231b_get_width();
590 int screen_h = axs15231b_get_height();
591
592 switch (s_wifi_setup.state) {
593 case SETUP_SCAN:
594 break;
595
596 case SETUP_LIST: {
597 int btn_y = screen_h - 30;
598 if (touch_in_rect(tx, ty, 10, btn_y, 50, 26)) {
599 highlight_rect(10, btn_y, 50, 26);
600 wifi_setup_handle_cancel(&s_wifi_setup);
601 s_wifi_setup_active = false;
602 exit_wifi_setup_rotation();
603 s_state = DISPLAY_ERROR;
604 return;
605 }
606 int item_w = screen_w - 20;
607 int y = 25;
608 int max_y = screen_h - 40;
609 int visible = wifi_setup_visible_count(&s_wifi_setup);
610 for (int i = 0; i < visible && y < max_y; i++) {
611 if (touch_in_rect(tx, ty, 10, y, item_w, 26)) {
612 highlight_rect(10, y, item_w, 26);
613 wifi_setup_handle_select(&s_wifi_setup, i);
614 kb_state_init(&s_kb_state);
615 return;
616 }
617 y += 30;
618 }
619 break;
620 }
621
622 case SETUP_PASSWORD: {
623 int field_w = screen_w - 80;
624 int eye_x = 10 + field_w + 5;
625 if (touch_in_rect(tx, ty, eye_x, 40, 24, 20)) {
626 s_kb_state.reveal = !s_kb_state.reveal;
627 return;
628 }
629 kb_result_t r = kb_hit_test(tx, ty, s_kb_state.layer);
630 if (r.action != KB_ACTION_NONE) {
631 axs15231b_fill_rect(tx - 20, ty - 20, 40, 40, COLOR_HIGHLIGHT);
632 axs15231b_flush();
633 vTaskDelay(pdMS_TO_TICKS(60));
634 if (r.action == KB_ACTION_DONE && s_kb_state.cursor > 0) {
635 wifi_setup_handle_connect(&s_wifi_setup);
636 wifi_config_t wifi_cfg = {0};
637 strncpy((char *)wifi_cfg.sta.ssid, s_wifi_setup.selected_ssid,
638 sizeof(wifi_cfg.sta.ssid) - 1);
639 strncpy((char *)wifi_cfg.sta.password, s_kb_state.input,
640 sizeof(wifi_cfg.sta.password) - 1);
641 wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
642 esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
643 esp_wifi_connect();
644 return;
645 }
646 kb_apply(&s_kb_state, r);
647 }
648 break;
649 }
650
651 case SETUP_CONNECTING:
652 break;
653
654 case SETUP_SUCCESS: {
655 wifi_setup_handle_cancel(&s_wifi_setup);
656 s_wifi_setup_active = false;
657 exit_wifi_setup_rotation();
658 s_state = DISPLAY_READY;
659 return;
660 }
661
662 case SETUP_FAILED: {
663 int btn_y = screen_h / 2 + 30;
664 int btn_w = (screen_w - 40) / 2;
665 if (touch_in_rect(tx, ty, 10, btn_y, btn_w, 30)) {
666 highlight_rect(10, btn_y, btn_w, 30);
667 wifi_setup_handle_retry(&s_wifi_setup);
668 kb_state_init(&s_kb_state);
669 } else if (touch_in_rect(tx, ty, 20 + btn_w, btn_y, btn_w, 30)) {
670 highlight_rect(20 + btn_w, btn_y, btn_w, 30);
671 wifi_setup_handle_change_network(&s_wifi_setup);
672 }
673 break;
674 }
675
676 default:
677 break;
678 }
679}
680
681static void display_task(void *pvParameters) { 353static void display_task(void *pvParameters) {
682 ESP_LOGI(TAG, "Display task started"); 354 ESP_LOGI(TAG, "Display task started");
683 355
@@ -692,72 +364,6 @@ static void display_task(void *pvParameters) {
692 } 364 }
693 } 365 }
694 366
695 touch_point_t tp;
696 if (s_touch_initialized && touch_read(&tp) && tp.touched) {
697 if (state == DISPLAY_WIFI_SETUP) {
698 handle_wifi_setup_touch(tp.x, tp.y);
699 state = s_state;
700 } else if (state == DISPLAY_ERROR || state == DISPLAY_READY) {
701 if (touch_in_rect(tp.x, tp.y, SETUP_BTN_X, SETUP_BTN_Y, SETUP_BTN_W, SETUP_BTN_H)) {
702 enter_wifi_setup_rotation();
703 s_wifi_setup_active = true;
704 wifi_setup_init(&s_wifi_setup);
705 kb_state_init(&s_kb_state);
706 s_wifi_scan_pending = true;
707 s_state = DISPLAY_WIFI_SETUP;
708 state = DISPLAY_WIFI_SETUP;
709 }
710 }
711 }
712
713 if (s_wifi_scan_pending && s_wifi_setup.state == SETUP_SCAN) {
714 s_wifi_scan_pending = false;
715 esp_wifi_disconnect();
716 vTaskDelay(pdMS_TO_TICKS(500));
717 wifi_scan_config_t scan_cfg = {0};
718 scan_cfg.scan_type = WIFI_SCAN_TYPE_ACTIVE;
719 scan_cfg.scan_time.active.min = 100;
720 scan_cfg.scan_time.active.max = 300;
721 esp_err_t scan_ret = esp_wifi_scan_start(&scan_cfg, true);
722 if (scan_ret != ESP_OK) {
723 ESP_LOGE(TAG, "WiFi scan failed: %s", esp_err_to_name(scan_ret));
724 wifi_setup_set_aps(&s_wifi_setup, NULL, 0);
725 } else {
726 uint16_t ap_count = 0;
727 esp_wifi_scan_get_ap_num(&ap_count);
728 if (ap_count > WIFI_SETUP_MAX_APS) ap_count = WIFI_SETUP_MAX_APS;
729 wifi_ap_record_t ap_records[WIFI_SETUP_MAX_APS];
730 esp_wifi_scan_get_ap_records(&ap_count, ap_records);
731
732 wifi_ap_info_t aps[WIFI_SETUP_MAX_APS];
733 for (int i = 0; i < (int)ap_count; i++) {
734 strncpy(aps[i].ssid, (const char *)ap_records[i].ssid, WIFI_SETUP_SSID_LEN - 1);
735 aps[i].ssid[WIFI_SETUP_SSID_LEN - 1] = '\0';
736 aps[i].rssi = ap_records[i].rssi;
737 aps[i].secured = (ap_records[i].authmode != WIFI_AUTH_OPEN);
738 }
739
740 for (int i = 0; i < (int)ap_count - 1; i++) {
741 for (int j = i + 1; j < (int)ap_count; j++) {
742 if (aps[j].rssi > aps[i].rssi) {
743 wifi_ap_info_t tmp = aps[i];
744 aps[i] = aps[j];
745 aps[j] = tmp;
746 }
747 }
748 }
749
750 wifi_setup_set_aps(&s_wifi_setup, aps, (int)ap_count);
751 }
752 }
753
754 if (s_wifi_setup_active && s_wifi_setup.state == SETUP_CONNECTING) {
755 wifi_ap_record_t ap_info;
756 if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
757 wifi_setup_handle_connect_result(&s_wifi_setup, true, NULL);
758 }
759 }
760
761 switch (state) { 367 switch (state) {
762 case DISPLAY_BOOT: 368 case DISPLAY_BOOT:
763 render_boot_screen(); 369 render_boot_screen();
@@ -773,34 +379,8 @@ static void display_task(void *pvParameters) {
773 case DISPLAY_ERROR: 379 case DISPLAY_ERROR:
774 render_error_screen(); 380 render_error_screen();
775 break; 381 break;
776 case DISPLAY_WIFI_SETUP: 382 case DISPLAY_SETUP_PENDING:
777 switch (s_wifi_setup.state) { 383 render_setup_pending_screen();
778 case SETUP_SCAN:
779 render_wifi_setup_scanning();
780 break;
781 case SETUP_LIST:
782 render_wifi_setup_list();
783 break;
784 case SETUP_PASSWORD:
785 render_wifi_setup_password();
786 break;
787 case SETUP_CONNECTING:
788 render_wifi_setup_connecting();
789 break;
790 case SETUP_SUCCESS:
791 render_wifi_setup_result();
792 vTaskDelay(pdMS_TO_TICKS(3000));
793 wifi_setup_handle_cancel(&s_wifi_setup);
794 s_wifi_setup_active = false;
795 exit_wifi_setup_rotation();
796 s_state = DISPLAY_READY;
797 break;
798 case SETUP_FAILED:
799 render_wifi_setup_result();
800 break;
801 default:
802 break;
803 }
804 break; 384 break;
805 } 385 }
806 386
@@ -820,14 +400,6 @@ esp_err_t display_init(void) {
820 s_initialized = true; 400 s_initialized = true;
821 s_last_qr_switch = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; 401 s_last_qr_switch = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
822 402
823 esp_err_t touch_ret = touch_init();
824 if (touch_ret == ESP_OK) {
825 s_touch_initialized = true;
826 ESP_LOGI(TAG, "Touch controller initialized");
827 } else {
828 ESP_LOGW(TAG, "Touch init failed (non-fatal): %s", esp_err_to_name(touch_ret));
829 }
830
831 xTaskCreatePinnedToCore(display_task, "display", 24576, NULL, 2, NULL, 1); 403 xTaskCreatePinnedToCore(display_task, "display", 24576, NULL, 2, NULL, 1);
832 404
833 ESP_LOGI(TAG, "Display initialized"); 405 ESP_LOGI(TAG, "Display initialized");
@@ -871,26 +443,8 @@ void display_notify_payment(int amount_sats, int64_t allotment_ms) {
871} 443}
872 444
873void display_notify_wifi_connected(const char *ip) { 445void display_notify_wifi_connected(const char *ip) {
874 if (s_wifi_setup_active && s_wifi_setup.state == SETUP_CONNECTING) { 446 (void)ip;
875 wifi_setup_handle_connect_result(&s_wifi_setup, true, ip);
876 if (s_kb_state.cursor > 0) {
877 tollgate_config_add_wifi(s_wifi_setup.selected_ssid, s_kb_state.input);
878 }
879 }
880} 447}
881 448
882void display_notify_wifi_disconnected(void) { 449void display_notify_wifi_disconnected(void) {
883 if (s_wifi_setup_active && s_wifi_setup.state == SETUP_CONNECTING) {
884 wifi_setup_handle_connect_result(&s_wifi_setup, false, NULL);
885 }
886}
887
888void display_enter_wifi_setup(void) {
889 if (!s_initialized) return;
890 enter_wifi_setup_rotation();
891 s_wifi_setup_active = true;
892 wifi_setup_init(&s_wifi_setup);
893 kb_state_init(&s_kb_state);
894 s_wifi_scan_pending = true;
895 s_state = DISPLAY_WIFI_SETUP;
896} 450}