diff options
| author | Your Name <you@example.com> | 2026-05-18 19:25:32 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-18 19:25:32 +0530 |
| commit | e1da6ffc0221abf4d0fb3a8c3e6be0b90d69397a (patch) | |
| tree | 3a0055b5af2bb09492c8c38fdf2712922ecc7123 /main | |
| parent | f0f8cca10196b265c3b453f71322239d2ecaf4ae (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.c | 168 | ||||
| -rw-r--r-- | main/display.h | 4 | ||||
| -rw-r--r-- | main/tollgate_main.c | 9 |
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 @@ | |||
| 12 | static const char *TAG = "display"; | 12 | static 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 | ||
| 16 | static volatile display_state_t s_state = DISPLAY_BOOT; | 26 | static volatile display_state_t s_state = DISPLAY_BOOT; |
| 17 | static char s_ap_ssid[32] = ""; | 27 | static char s_ap_ssid[32] = ""; |
| 18 | static char s_portal_url[256] = ""; | 28 | static char s_portal_url[256] = ""; |
| 29 | static char s_mint_url[256] = ""; | ||
| 30 | static char s_wifi_status[32] = "starting..."; | ||
| 19 | static int s_active_clients = 0; | 31 | static int s_active_clients = 0; |
| 20 | static uint64_t s_wallet_balance = 0; | 32 | static uint64_t s_wallet_balance = 0; |
| 33 | static int s_price_per_step = 0; | ||
| 21 | static bool s_initialized = false; | 34 | static bool s_initialized = false; |
| 22 | static int64_t s_last_qr_switch = 0; | 35 | static int64_t s_last_qr_switch = 0; |
| 23 | static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI; | 36 | static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI; |
| 24 | static display_state_t s_rendered_state = DISPLAY_BOOT; | ||
| 25 | static display_qr_mode_t s_rendered_qr_mode = DISPLAY_QR_WIFI; | ||
| 26 | static bool s_force_render = true; | ||
| 27 | 37 | ||
| 28 | static int qr_version_from_strlen(int len) { | 38 | static 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 | ||
| 81 | static 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 | |||
| 71 | void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale) { | 91 | void 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 | ||
| 169 | static 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 | |||
| 149 | static void render_boot_screen(void) { | 175 | static 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 | ||
| 156 | static void render_ready_screen(void) { | 189 | static 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 | ||
| 186 | static void render_payment_screen(void) { | 243 | static 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 | ||
| 193 | static void render_error_screen(void) { | 269 | static 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 | ||
| 253 | void display_set_state(display_state_t state) { | 353 | void display_set_state(display_state_t state) { |
| 254 | s_state = state; | 354 | s_state = state; |
| 255 | s_force_render = true; | ||
| 256 | } | 355 | } |
| 257 | 356 | ||
| 258 | void display_update(const char *ap_ssid, int active_clients, | 357 | void 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 { | |||
| 20 | esp_err_t display_init(void); | 20 | esp_err_t display_init(void); |
| 21 | void display_set_state(display_state_t state); | 21 | void display_set_state(display_state_t state); |
| 22 | void display_update(const char *ap_ssid, int active_clients, | 22 | 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, | ||
| 25 | const char *wifi_status); | ||
| 24 | void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale); | 26 | void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t bg, int scale); |
| 25 | void display_render_qr(const char *text); | 27 | void 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 | ||
| 179 | static void stop_services(void) | 184 | static void stop_services(void) |