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 22:55:37 +0530
committerYour Name <you@example.com>2026-05-18 22:55:37 +0530
commit46e7dabaf80333ca045061ff480377a4186e5deb (patch)
tree06bd62bed43e15c432cc27faf20993a50d7c3cee
parenteb2ae72d91a4b2ec0333a2e6d18eeeeee6b60464 (diff)
feat: integrate touchscreen WiFi setup into display UI
- Add DISPLAY_WIFI_SETUP state with full rendering pipeline - Touch init on display_init(), touch polling in display task - WiFi scan triggered on setup entry, sorted AP list - On-screen keyboard for password entry - Connect result via WiFi event notifications - Setup button on READY and ERROR screens - Auto-enter WiFi setup on first boot with no credentials - Save new WiFi credentials to SPIFFS config.json
-rw-r--r--TOUCH_WIFI_SETUP_PLAN.md32
-rw-r--r--main/CMakeLists.txt5
-rw-r--r--main/display.c410
-rw-r--r--main/display.h6
-rw-r--r--main/tollgate_main.c8
5 files changed, 441 insertions, 20 deletions
diff --git a/TOUCH_WIFI_SETUP_PLAN.md b/TOUCH_WIFI_SETUP_PLAN.md
index afbc0b3..f441138 100644
--- a/TOUCH_WIFI_SETUP_PLAN.md
+++ b/TOUCH_WIFI_SETUP_PLAN.md
@@ -67,26 +67,26 @@ Add `DISPLAY_WIFI_SETUP` to `display_state_t`.
67## Implementation Checklist 67## Implementation Checklist
68 68
69### Phase 1: Touch Driver 69### Phase 1: Touch Driver
70- [ ] Create `main/touch.h` with API 70- [x] Create `main/touch.h` with API
71- [ ] Create `main/touch.c` with ESP-IDF I2C v5 implementation 71- [x] Create `main/touch.c` with ESP-IDF I2C v5 implementation
72- [ ] Create `tests/unit/test_touch.c` with coordinate parsing tests 72- [x] Create `tests/unit/test_touch.c` with coordinate parsing tests
73- [ ] Run `make test-unit`, verify all pass 73- [x] Run `make test-unit`, verify all pass
74 74
75### Phase 2: On-Screen Keyboard 75### Phase 2: On-Screen Keyboard
76- [ ] Create `main/keyboard.h` with API 76- [x] Create `main/keyboard.h` with API
77- [ ] Create `main/keyboard.c` with QWERTY layout + hit detection 77- [x] Create `main/keyboard.c` with QWERTY layout + hit detection
78- [ ] Create `tests/unit/test_keyboard.c` with layout + key lookup tests 78- [x] Create `tests/unit/test_keyboard.c` with layout + key lookup tests
79- [ ] Run `make test-unit`, verify all pass 79- [x] Run `make test-unit`, verify all pass
80 80
81### Phase 3: WiFi Setup Flow 81### Phase 3: WiFi Setup Flow
82- [ ] Create `main/wifi_setup.h` with API 82- [x] Create `main/wifi_setup.h` with API
83- [ ] Create `main/wifi_setup.c` with scan/list/connect state machine 83- [x] Create `main/wifi_setup.c` with scan/list/connect state machine
84- [ ] Add `tollgate_config_add_wifi()` to config.c + config.h 84- [x] Add `tollgate_config_add_wifi()` to config.c + config.h
85- [ ] Create `tests/unit/test_wifi_setup.c` with state machine tests 85- [x] Create `tests/unit/test_wifi_setup.c` with state machine tests
86- [ ] Run `make test-unit`, verify all pass 86- [x] Run `make test-unit`, verify all pass
87 87
88### Phase 4: Integration 88### Phase 4: Integration
89- [ ] Add `DISPLAY_WIFI_SETUP` to display.h 89- [x] Add `DISPLAY_WIFI_SETUP` to display.h
90- [ ] Update display.c with WiFi setup rendering + touch input 90- [x] Update display.c with WiFi setup rendering + touch input
91- [ ] Update `main/CMakeLists.txt` with new source files 91- [x] Update `main/CMakeLists.txt` with new source files
92- [ ] Build, flash to Board C, verify full flow 92- [ ] Build, flash to Board C, verify full flow
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 9b0fb1c..c87b2b8 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -18,8 +18,11 @@ idf_component_register(SRCS "tollgate_main.c"
18 "cvm_server.c" 18 "cvm_server.c"
19 "display.c" 19 "display.c"
20 "font.c" 20 "font.c"
21 "touch.c"
22 "keyboard.c"
23 "wifi_setup.c"
21 INCLUDE_DIRS "." 24 INCLUDE_DIRS "."
22 REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server 25 REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server
23 lwip json esp_http_client mbedtls esp-tls log spiffs 26 lwip json esp_http_client mbedtls esp-tls log spiffs
24 nucula_lib secp256k1 axs15231b qrcode 27 nucula_lib secp256k1 axs15231b qrcode driver
25 PRIV_REQUIRES esp-tls) 28 PRIV_REQUIRES esp-tls)
diff --git a/main/display.c b/main/display.c
index 0935743..bec795d 100644
--- a/main/display.c
+++ b/main/display.c
@@ -3,7 +3,12 @@
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 "esp_log.h" 10#include "esp_log.h"
11#include "esp_wifi.h"
7#include "freertos/FreeRTOS.h" 12#include "freertos/FreeRTOS.h"
8#include "freertos/task.h" 13#include "freertos/task.h"
9#include <string.h> 14#include <string.h>
@@ -38,6 +43,32 @@ static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI;
38static int s_last_payment_sats = 0; 43static int s_last_payment_sats = 0;
39static int64_t s_last_allotment_ms = 0; 44static int64_t s_last_allotment_ms = 0;
40 45
46#define COLOR_GRAY 0x8410
47#define COLOR_DARKGRAY 0x4208
48#define COLOR_LIGHTBLUE 0xA5FF
49
50static wifi_setup_t s_wifi_setup;
51static kb_state_t s_kb_state;
52static bool s_wifi_setup_active = false;
53static bool s_touch_initialized = false;
54static bool s_wifi_scan_pending = false;
55
56#define SETUP_BTN_X 240
57#define SETUP_BTN_Y 440
58#define SETUP_BTN_W 72
59#define SETUP_BTN_H 30
60
61static void render_setup_button(int x, int y, int w, int h) {
62 axs15231b_fill_rect(x, y, w, h, COLOR_DARKGRAY);
63 const char *label = "Setup";
64 int lw = strlen(label) * 8;
65 display_render_text(x + (w - lw) / 2, y + (h - 8) / 2, label, COLOR_WHITE, COLOR_DARKGRAY, 1);
66}
67
68static bool touch_in_rect(uint16_t tx, uint16_t ty, int x, int y, int w, int h) {
69 return tx >= x && tx < x + w && ty >= y && ty < y + h;
70}
71
41static int qr_version_from_strlen(int len) { 72static int qr_version_from_strlen(int len) {
42 if (len <= 17) return 1; 73 if (len <= 17) return 1;
43 if (len <= 32) return 2; 74 if (len <= 32) return 2;
@@ -240,6 +271,8 @@ static void render_ready_screen(void) {
240 display_render_text(10, y, line, COLOR_GREEN, COLOR_BG, 1); 271 display_render_text(10, y, line, COLOR_GREEN, COLOR_BG, 1);
241 } 272 }
242 273
274 render_setup_button(SETUP_BTN_X, SETUP_BTN_Y, SETUP_BTN_W, SETUP_BTN_H);
275
243 axs15231b_flush(); 276 axs15231b_flush();
244} 277}
245 278
@@ -302,9 +335,269 @@ static void render_error_screen(void) {
302 lw = strlen(line) * 8; 335 lw = strlen(line) * 8;
303 display_render_text((screen_w - lw) / 2, 340, line, COLOR_DIM, COLOR_BG, 1); 336 display_render_text((screen_w - lw) / 2, 340, line, COLOR_DIM, COLOR_BG, 1);
304 337
338 render_setup_button(SETUP_BTN_X, SETUP_BTN_Y, SETUP_BTN_W, SETUP_BTN_H);
339
340 axs15231b_flush();
341}
342
343static void render_wifi_setup_scanning(void) {
344 int screen_w = axs15231b_get_width();
345 axs15231b_fill_screen(COLOR_BG);
346
347 const char *title = "WiFi Setup";
348 int tw = strlen(title) * 8;
349 display_render_text((screen_w - tw) / 2, 180, title, COLOR_CYAN, COLOR_BG, 1);
350
351 const char *msg = "Scanning...";
352 int mw = strlen(msg) * 8;
353 display_render_text((screen_w - mw) / 2, 220, msg, COLOR_WHITE, COLOR_BG, 1);
354
355 axs15231b_flush();
356}
357
358static void render_wifi_setup_list(void) {
359 int screen_w = axs15231b_get_width();
360 axs15231b_fill_screen(COLOR_BG);
361
362 const char *title = "Select Network";
363 int tw = strlen(title) * 8;
364 display_render_text((screen_w - tw) / 2, 5, title, COLOR_CYAN, COLOR_BG, 1);
365
366 int y = 25;
367 int visible = wifi_setup_visible_count(&s_wifi_setup);
368
369 for (int i = 0; i < visible && y < 295; i++) {
370 const wifi_ap_info_t *ap = wifi_setup_get_visible(&s_wifi_setup, i);
371 if (!ap) break;
372
373 char line[40];
374 int rssi_bars = 0;
375 if (ap->rssi >= -30) rssi_bars = 4;
376 else if (ap->rssi >= -50) rssi_bars = 3;
377 else if (ap->rssi >= -70) rssi_bars = 2;
378 else rssi_bars = 1;
379
380 snprintf(line, sizeof(line), "%s", ap->ssid);
381 axs15231b_fill_rect(5, y, 260, 26, COLOR_DARKGRAY);
382 display_render_text(10, y + 4, line, COLOR_WHITE, COLOR_DARKGRAY, 1);
383
384 for (int b = 0; b < rssi_bars; b++) {
385 axs15231b_fill_rect(270 + b * 8, y + 16 - (b + 1) * 4, 6, (b + 1) * 4, COLOR_GREEN);
386 }
387
388 if (ap->secured) {
389 const char *lock = "*";
390 display_render_text(254, y + 4, lock, COLOR_YELLOW, COLOR_DARKGRAY, 1);
391 }
392
393 y += 30;
394 }
395
396 render_setup_button(5, 440, 50, 28);
397 display_render_text(10, 444, "X", COLOR_WHITE, COLOR_DARKGRAY, 1);
398
305 axs15231b_flush(); 399 axs15231b_flush();
306} 400}
307 401
402static void render_wifi_setup_password(void) {
403 axs15231b_fill_screen(COLOR_BG);
404
405 display_render_text(10, 5, s_wifi_setup.selected_ssid, COLOR_CYAN, COLOR_BG, 1);
406 display_render_text(10, 25, "Password:", COLOR_DIM, COLOR_BG, 1);
407
408 axs15231b_fill_rect(10, 40, 220, 20, COLOR_DARKGRAY);
409
410 char masked[KB_INPUT_MAX + 1];
411 if (s_kb_state.reveal) {
412 strncpy(masked, s_kb_state.input, sizeof(masked) - 1);
413 masked[sizeof(masked) - 1] = '\0';
414 } else {
415 int len = s_kb_state.cursor;
416 if (len > 27) len = 27;
417 for (int i = 0; i < len; i++) masked[i] = '*';
418 masked[len] = '\0';
419 }
420 display_render_text(14, 43, masked, COLOR_WHITE, COLOR_DARKGRAY, 1);
421
422 const char *eye_label = s_kb_state.reveal ? "H" : "S";
423 axs15231b_fill_rect(235, 40, 24, 20, COLOR_GRAY);
424 display_render_text(241, 43, eye_label, COLOR_WHITE, COLOR_GRAY, 1);
425
426 int kb_y = 70;
427 for (int row = 0; row < KB_ROW_COUNT; row++) {
428 const char *row_str;
429 int key_count = kb_get_row_keys(row, s_kb_state.layer, &row_str);
430 int x_off = (row == 0) ? 5 : (row == 1) ? 14 : (row == 2) ? 23 : 5;
431
432 int cx = x_off;
433 for (int col = 0; col < key_count; col++) {
434 char c = row_str[col];
435 int kw = 28;
436 if (row == 3) {
437 if (col == 0) kw = 42;
438 else if (col == key_count - 1) kw = 50;
439 else kw = 168;
440 }
441
442 uint16_t bg = COLOR_DARKGRAY;
443 uint16_t fg = COLOR_WHITE;
444 char label[2] = {0, 0};
445
446 if (c == '\001') {
447 label[0] = s_kb_state.layer == KB_ALPHA_UPPER ? 'A' : 'a';
448 bg = COLOR_GRAY;
449 } else if (c == '\002') {
450 label[0] = s_kb_state.layer == KB_NUMSYM ? 'a' : '#';
451 bg = COLOR_GRAY;
452 } else if (c == '\003') {
453 label[0] = '_';
454 } else if (c == '\004') {
455 label[0] = '>';
456 fg = COLOR_GREEN;
457 bg = COLOR_DARKGRAY;
458 } else if (c == '\b') {
459 label[0] = '<';
460 bg = COLOR_GRAY;
461 } else {
462 label[0] = c;
463 }
464
465 axs15231b_fill_rect(cx, kb_y, kw, 30, bg);
466 int lw = 8;
467 display_render_text(cx + (kw - lw) / 2, kb_y + 8, label, fg, bg, 1);
468 cx += kw + 2;
469 }
470 kb_y += 34;
471 }
472
473 axs15231b_flush();
474}
475
476static void render_wifi_setup_connecting(void) {
477 int screen_w = axs15231b_get_width();
478 axs15231b_fill_screen(COLOR_BG);
479
480 const char *msg1 = "Connecting to";
481 int w1 = strlen(msg1) * 8;
482 display_render_text((screen_w - w1) / 2, 200, msg1, COLOR_WHITE, COLOR_BG, 1);
483
484 int w2 = strlen(s_wifi_setup.selected_ssid) * 8;
485 display_render_text((screen_w - w2) / 2, 225, s_wifi_setup.selected_ssid, COLOR_CYAN, COLOR_BG, 1);
486
487 const char *dots = "...";
488 int dw = strlen(dots) * 8;
489 display_render_text((screen_w - dw) / 2, 255, dots, COLOR_DIM, COLOR_BG, 1);
490
491 axs15231b_flush();
492}
493
494static void render_wifi_setup_result(void) {
495 int screen_w = axs15231b_get_width();
496 axs15231b_fill_screen(COLOR_BG);
497
498 if (s_wifi_setup.state == SETUP_SUCCESS) {
499 const char *msg = "Connected!";
500 int mw = strlen(msg) * 8 * 2;
501 display_render_text((screen_w - mw) / 2, 180, msg, COLOR_WHITE, COLOR_GREEN, 2);
502
503 if (s_wifi_setup.connect_ip[0]) {
504 char ip_line[32];
505 snprintf(ip_line, sizeof(ip_line), "IP: %s", s_wifi_setup.connect_ip);
506 int iw = strlen(ip_line) * 8;
507 display_render_text((screen_w - iw) / 2, 240, ip_line, COLOR_WHITE, COLOR_BG, 1);
508 }
509 } else {
510 const char *msg = "Connection failed";
511 int mw = strlen(msg) * 8 * 2;
512 display_render_text((screen_w - mw) / 2, 180, msg, COLOR_WHITE, COLOR_RED, 2);
513
514 const char *hint = "Wrong password?";
515 int hw = strlen(hint) * 8;
516 display_render_text((screen_w - hw) / 2, 240, hint, COLOR_YELLOW, COLOR_BG, 1);
517
518 axs15231b_fill_rect(30, 280, 120, 30, COLOR_DARKGRAY);
519 display_render_text(50, 288, "Retry", COLOR_WHITE, COLOR_DARKGRAY, 1);
520 axs15231b_fill_rect(170, 280, 120, 30, COLOR_DARKGRAY);
521 display_render_text(180, 288, "Change", COLOR_WHITE, COLOR_DARKGRAY, 1);
522 }
523
524 axs15231b_flush();
525}
526
527static void handle_wifi_setup_touch(uint16_t tx, uint16_t ty) {
528 switch (s_wifi_setup.state) {
529 case SETUP_SCAN:
530 break;
531
532 case SETUP_LIST: {
533 if (touch_in_rect(tx, ty, 5, 440, 50, 28)) {
534 wifi_setup_handle_cancel(&s_wifi_setup);
535 s_wifi_setup_active = false;
536 s_state = DISPLAY_ERROR;
537 return;
538 }
539 int y = 25;
540 int visible = wifi_setup_visible_count(&s_wifi_setup);
541 for (int i = 0; i < visible; i++) {
542 if (touch_in_rect(tx, ty, 5, y, 300, 26)) {
543 wifi_setup_handle_select(&s_wifi_setup, i);
544 kb_state_init(&s_kb_state);
545 return;
546 }
547 y += 30;
548 }
549 break;
550 }
551
552 case SETUP_PASSWORD: {
553 if (touch_in_rect(tx, ty, 235, 40, 24, 20)) {
554 s_kb_state.reveal = !s_kb_state.reveal;
555 return;
556 }
557 kb_result_t r = kb_hit_test(tx, ty, s_kb_state.layer);
558 if (r.action != KB_ACTION_NONE) {
559 if (r.action == KB_ACTION_DONE && s_kb_state.cursor > 0) {
560 wifi_setup_handle_connect(&s_wifi_setup);
561 wifi_config_t wifi_cfg = {0};
562 strncpy((char *)wifi_cfg.sta.ssid, s_wifi_setup.selected_ssid,
563 sizeof(wifi_cfg.sta.ssid) - 1);
564 strncpy((char *)wifi_cfg.sta.password, s_kb_state.input,
565 sizeof(wifi_cfg.sta.password) - 1);
566 wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
567 esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
568 esp_wifi_connect();
569 return;
570 }
571 kb_apply(&s_kb_state, r);
572 }
573 break;
574 }
575
576 case SETUP_CONNECTING:
577 break;
578
579 case SETUP_SUCCESS: {
580 wifi_setup_handle_cancel(&s_wifi_setup);
581 s_wifi_setup_active = false;
582 s_state = DISPLAY_READY;
583 return;
584 }
585
586 case SETUP_FAILED: {
587 if (touch_in_rect(tx, ty, 30, 280, 120, 30)) {
588 wifi_setup_handle_retry(&s_wifi_setup);
589 kb_state_init(&s_kb_state);
590 } else if (touch_in_rect(tx, ty, 170, 280, 120, 30)) {
591 wifi_setup_handle_change_network(&s_wifi_setup);
592 }
593 break;
594 }
595
596 default:
597 break;
598 }
599}
600
308static void display_task(void *pvParameters) { 601static void display_task(void *pvParameters) {
309 ESP_LOGI(TAG, "Display task started"); 602 ESP_LOGI(TAG, "Display task started");
310 603
@@ -319,6 +612,61 @@ static void display_task(void *pvParameters) {
319 } 612 }
320 } 613 }
321 614
615 touch_point_t tp;
616 if (s_touch_initialized && touch_read(&tp) && tp.touched) {
617 if (state == DISPLAY_WIFI_SETUP) {
618 handle_wifi_setup_touch(tp.x, tp.y);
619 state = s_state;
620 } else if (state == DISPLAY_ERROR || state == DISPLAY_READY) {
621 if (touch_in_rect(tp.x, tp.y, SETUP_BTN_X, SETUP_BTN_Y, SETUP_BTN_W, SETUP_BTN_H)) {
622 s_wifi_setup_active = true;
623 wifi_setup_init(&s_wifi_setup);
624 kb_state_init(&s_kb_state);
625 s_wifi_scan_pending = true;
626 s_state = DISPLAY_WIFI_SETUP;
627 state = DISPLAY_WIFI_SETUP;
628 }
629 }
630 }
631
632 if (s_wifi_scan_pending && s_wifi_setup.state == SETUP_SCAN) {
633 s_wifi_scan_pending = false;
634 wifi_scan_config_t scan_cfg = {0};
635 esp_wifi_scan_start(&scan_cfg, true);
636 uint16_t ap_count = 0;
637 esp_wifi_scan_get_ap_num(&ap_count);
638 if (ap_count > WIFI_SETUP_MAX_APS) ap_count = WIFI_SETUP_MAX_APS;
639 wifi_ap_record_t ap_records[WIFI_SETUP_MAX_APS];
640 esp_wifi_scan_get_ap_records(&ap_count, ap_records);
641
642 wifi_ap_info_t aps[WIFI_SETUP_MAX_APS];
643 for (int i = 0; i < (int)ap_count; i++) {
644 strncpy(aps[i].ssid, (const char *)ap_records[i].ssid, WIFI_SETUP_SSID_LEN - 1);
645 aps[i].ssid[WIFI_SETUP_SSID_LEN - 1] = '\0';
646 aps[i].rssi = ap_records[i].rssi;
647 aps[i].secured = (ap_records[i].authmode != WIFI_AUTH_OPEN);
648 }
649
650 for (int i = 0; i < (int)ap_count - 1; i++) {
651 for (int j = i + 1; j < (int)ap_count; j++) {
652 if (aps[j].rssi > aps[i].rssi) {
653 wifi_ap_info_t tmp = aps[i];
654 aps[i] = aps[j];
655 aps[j] = tmp;
656 }
657 }
658 }
659
660 wifi_setup_set_aps(&s_wifi_setup, aps, (int)ap_count);
661 }
662
663 if (s_wifi_setup_active && s_wifi_setup.state == SETUP_CONNECTING) {
664 wifi_ap_record_t ap_info;
665 if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
666 wifi_setup_handle_connect_result(&s_wifi_setup, true, NULL);
667 }
668 }
669
322 switch (state) { 670 switch (state) {
323 case DISPLAY_BOOT: 671 case DISPLAY_BOOT:
324 render_boot_screen(); 672 render_boot_screen();
@@ -334,6 +682,34 @@ static void display_task(void *pvParameters) {
334 case DISPLAY_ERROR: 682 case DISPLAY_ERROR:
335 render_error_screen(); 683 render_error_screen();
336 break; 684 break;
685 case DISPLAY_WIFI_SETUP:
686 switch (s_wifi_setup.state) {
687 case SETUP_SCAN:
688 render_wifi_setup_scanning();
689 break;
690 case SETUP_LIST:
691 render_wifi_setup_list();
692 break;
693 case SETUP_PASSWORD:
694 render_wifi_setup_password();
695 break;
696 case SETUP_CONNECTING:
697 render_wifi_setup_connecting();
698 break;
699 case SETUP_SUCCESS:
700 render_wifi_setup_result();
701 vTaskDelay(pdMS_TO_TICKS(3000));
702 wifi_setup_handle_cancel(&s_wifi_setup);
703 s_wifi_setup_active = false;
704 s_state = DISPLAY_READY;
705 break;
706 case SETUP_FAILED:
707 render_wifi_setup_result();
708 break;
709 default:
710 break;
711 }
712 break;
337 } 713 }
338 714
339 vTaskDelay(pdMS_TO_TICKS(RENDER_INTERVAL_MS)); 715 vTaskDelay(pdMS_TO_TICKS(RENDER_INTERVAL_MS));
@@ -352,7 +728,15 @@ esp_err_t display_init(void) {
352 s_initialized = true; 728 s_initialized = true;
353 s_last_qr_switch = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; 729 s_last_qr_switch = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
354 730
355 xTaskCreatePinnedToCore(display_task, "display", 16384, NULL, 2, NULL, 1); 731 esp_err_t touch_ret = touch_init();
732 if (touch_ret == ESP_OK) {
733 s_touch_initialized = true;
734 ESP_LOGI(TAG, "Touch controller initialized");
735 } else {
736 ESP_LOGW(TAG, "Touch init failed (non-fatal): %s", esp_err_to_name(touch_ret));
737 }
738
739 xTaskCreatePinnedToCore(display_task, "display", 24576, NULL, 2, NULL, 1);
356 740
357 ESP_LOGI(TAG, "Display initialized"); 741 ESP_LOGI(TAG, "Display initialized");
358 return ESP_OK; 742 return ESP_OK;
@@ -393,3 +777,27 @@ void display_notify_payment(int amount_sats, int64_t allotment_ms) {
393 s_wallet_balance = nucula_wallet_balance(); 777 s_wallet_balance = nucula_wallet_balance();
394 display_set_state(DISPLAY_PAYMENT_RECEIVED); 778 display_set_state(DISPLAY_PAYMENT_RECEIVED);
395} 779}
780
781void display_notify_wifi_connected(const char *ip) {
782 if (s_wifi_setup_active && s_wifi_setup.state == SETUP_CONNECTING) {
783 wifi_setup_handle_connect_result(&s_wifi_setup, true, ip);
784 if (s_kb_state.cursor > 0) {
785 tollgate_config_add_wifi(s_wifi_setup.selected_ssid, s_kb_state.input);
786 }
787 }
788}
789
790void display_notify_wifi_disconnected(void) {
791 if (s_wifi_setup_active && s_wifi_setup.state == SETUP_CONNECTING) {
792 wifi_setup_handle_connect_result(&s_wifi_setup, false, NULL);
793 }
794}
795
796void display_enter_wifi_setup(void) {
797 if (!s_initialized) return;
798 s_wifi_setup_active = true;
799 wifi_setup_init(&s_wifi_setup);
800 kb_state_init(&s_kb_state);
801 s_wifi_scan_pending = true;
802 s_state = DISPLAY_WIFI_SETUP;
803}
diff --git a/main/display.h b/main/display.h
index 49b3ffd..e8824b6 100644
--- a/main/display.h
+++ b/main/display.h
@@ -9,7 +9,8 @@ typedef enum {
9 DISPLAY_BOOT, 9 DISPLAY_BOOT,
10 DISPLAY_READY, 10 DISPLAY_READY,
11 DISPLAY_PAYMENT_RECEIVED, 11 DISPLAY_PAYMENT_RECEIVED,
12 DISPLAY_ERROR 12 DISPLAY_ERROR,
13 DISPLAY_WIFI_SETUP
13} display_state_t; 14} display_state_t;
14 15
15typedef enum { 16typedef enum {
@@ -24,6 +25,9 @@ void display_update(const char *ap_ssid, int active_clients,
24 const char *mint_url, int price_per_step, 25 const char *mint_url, int price_per_step,
25 const char *wifi_status); 26 const char *wifi_status);
26void display_notify_payment(int amount_sats, int64_t allotment_ms); 27void display_notify_payment(int amount_sats, int64_t allotment_ms);
28void display_notify_wifi_connected(const char *ip);
29void display_notify_wifi_disconnected(void);
30void display_enter_wifi_setup(void);
27void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale); 31void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale);
28void display_render_qr(const char *text); 32void display_render_qr(const char *text);
29 33
diff --git a/main/tollgate_main.c b/main/tollgate_main.c
index 345295a..59d25f5 100644
--- a/main/tollgate_main.c
+++ b/main/tollgate_main.c
@@ -54,6 +54,7 @@ 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 display_notify_wifi_disconnected();
57 if (s_services_running) { 58 if (s_services_running) {
58 stop_services(); 59 stop_services();
59 display_set_state(DISPLAY_ERROR); 60 display_set_state(DISPLAY_ERROR);
@@ -102,6 +103,10 @@ static void ip_event_handler(void *arg, esp_event_base_t event_base,
102 s_retry_count = 0; 103 s_retry_count = 0;
103 xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); 104 xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
104 105
106 char ip_str[16];
107 snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&event->ip_info.ip));
108 display_notify_wifi_connected(ip_str);
109
105 esp_sntp_stop(); 110 esp_sntp_stop();
106 esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); 111 esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
107 esp_sntp_setservername(0, "pool.ntp.org"); 112 esp_sntp_setservername(0, "pool.ntp.org");
@@ -320,7 +325,8 @@ void app_main(void)
320 tcfg->mint_url, tcfg->price_per_step, "connecting..."); 325 tcfg->mint_url, tcfg->price_per_step, "connecting...");
321 326
322 if (tollgate_config_get_wifi(&(wifi_config_t){0}) != ESP_OK) { 327 if (tollgate_config_get_wifi(&(wifi_config_t){0}) != ESP_OK) {
323 ESP_LOGI(TAG, "No STA network configured, starting services immediately"); 328 ESP_LOGI(TAG, "No STA network configured, entering WiFi setup");
329 display_enter_wifi_setup();
324 xTaskCreate(services_start_task, "svc_start", 32768, NULL, 5, NULL); 330 xTaskCreate(services_start_task, "svc_start", 32768, NULL, 5, NULL);
325 } 331 }
326 332