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-20 02:14:45 +0530
committerYour Name <you@example.com>2026-05-20 02:14:45 +0530
commit899016795c389151e6b486ec470653f5688e5c5f (patch)
tree64b4c732d6f585552fd14df5f47890764c66ac1a /main/display.c
parent4f713120dbedd37a3512c2ec1af0766025a59d57 (diff)
feat: merge display-fix — touch driver, keyboard, WiFi setup UI
Squash-merge of feature/display-fix (27 commits): - AXS15231B touch driver with coordinate parsing (touch.c/h) - On-screen keyboard with layout/hit detection (keyboard.c/h) - WiFi setup state machine + config_add_wifi (wifi_setup.c/h) - Web-based WiFi setup via captive portal - Display rotation fix (stride=480), color fixes, DMA byte-swap - WiFi QR code on BOOT and ERROR screens - ERROR state transition after WiFi retries exhausted - Unit tests: test_touch, test_keyboard, test_wifi_setup - Integration test: wifi_setup.mjs - E2E test: wifi-setup.spec.mjs - Per-board hardware locks for flash targets Conflicts resolved: took master for config/cvm/api/main (more current), took display-fix for display/axs15231b (newer fixes).
Diffstat (limited to 'main/display.c')
-rw-r--r--main/display.c349
1 files changed, 280 insertions, 69 deletions
diff --git a/main/display.c b/main/display.c
index 2b6cc88..ccd08b7 100644
--- a/main/display.c
+++ b/main/display.c
@@ -2,7 +2,10 @@
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"
6#include "config.h"
5#include "esp_log.h" 7#include "esp_log.h"
8#include "esp_wifi.h"
6#include "freertos/FreeRTOS.h" 9#include "freertos/FreeRTOS.h"
7#include "freertos/task.h" 10#include "freertos/task.h"
8#include <string.h> 11#include <string.h>
@@ -12,15 +15,36 @@
12static const char *TAG = "display"; 15static const char *TAG = "display";
13 16
14#define QR_CYCLE_MS 5000 17#define QR_CYCLE_MS 5000
18#define RENDER_INTERVAL_MS 2000
19
20#define COLOR_BG 0x0000
21#define COLOR_WHITE 0xFFFF
22#define COLOR_CYAN 0x07FF
23#define COLOR_YELLOW 0xFFE0
24#define COLOR_GREEN 0x07E0
25#define COLOR_ORANGE 0xFD20
26#define COLOR_RED 0xF800
27#define COLOR_DIM 0x8410
15 28
16static volatile display_state_t s_state = DISPLAY_BOOT; 29static volatile display_state_t s_state = DISPLAY_BOOT;
17static char s_ap_ssid[32] = ""; 30static char s_ap_ssid[32] = "";
18static char s_portal_url[256] = ""; 31static char s_portal_url[256] = "";
32static char s_mint_url[256] = "";
33static char s_wifi_status[32] = "starting...";
19static int s_active_clients = 0; 34static int s_active_clients = 0;
20static uint64_t s_wallet_balance = 0; 35static uint64_t s_wallet_balance = 0;
36static int s_price_per_step = 0;
21static bool s_initialized = false; 37static bool s_initialized = false;
22static int64_t s_last_qr_switch = 0; 38static int64_t s_last_qr_switch = 0;
23static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI; 39static display_qr_mode_t s_qr_mode = DISPLAY_QR_WIFI;
40static int s_last_payment_sats = 0;
41static int64_t s_last_allotment_ms = 0;
42
43static uint16_t wallet_color(void) {
44 if (s_wallet_balance == 0) return COLOR_RED;
45 if (s_wallet_balance < 100) return COLOR_YELLOW;
46 return COLOR_GREEN;
47}
24 48
25static int qr_version_from_strlen(int len) { 49static int qr_version_from_strlen(int len) {
26 if (len <= 17) return 1; 50 if (len <= 17) return 1;
@@ -59,10 +83,60 @@ static int escape_wifi_field(const char *src, char *dst, int dst_size) {
59 return di; 83 return di;
60} 84}
61 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
62static void build_wifi_qr_string(char *out, int out_size) { 96static void build_wifi_qr_string(char *out, int out_size) {
63 char escaped_ssid[64]; 97 char escaped_ssid[64];
64 escape_wifi_field(s_ap_ssid, escaped_ssid, sizeof(escaped_ssid)); 98 escape_wifi_field(s_ap_ssid, escaped_ssid, sizeof(escaped_ssid));
65 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 }
107}
108
109static void render_qr_at(const char *text, int x_off, int y_off, int max_w, int max_h) {
110 int len = strlen(text);
111 int version = qr_version_from_strlen(len);
112 int px = qr_pixel_size(len);
113
114 uint16_t buf_size = qrcode_getBufferSize(version);
115 uint8_t *qr_buf = (uint8_t *)malloc(buf_size);
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);
66} 140}
67 141
68void 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) {
@@ -98,99 +172,206 @@ void display_render_text(int x, int y, const char *text, uint16_t fg, uint16_t b
98 } 172 }
99} 173}
100 174
101static void render_qr_at(const char *text, int x_off, int y_off, int max_w, int max_h) {
102 int len = strlen(text);
103 int version = qr_version_from_strlen(len);
104 int px = qr_pixel_size(len);
105
106 uint16_t buf_size = qrcode_getBufferSize(version);
107 uint8_t *qr_buf = (uint8_t *)malloc(buf_size);
108 if (!qr_buf) {
109 ESP_LOGE(TAG, "Failed to allocate QR buffer");
110 return;
111 }
112
113 QRCode qr;
114 if (qrcode_initText(&qr, qr_buf, version, ECC_LOW, text) != 0) {
115 ESP_LOGE(TAG, "QR generation failed");
116 free(qr_buf);
117 return;
118 }
119
120 int qr_px_w = qr.size * px;
121 int qr_px_h = qr.size * px;
122 int cx = x_off + (max_w - qr_px_w) / 2;
123 int cy = y_off + (max_h - qr_px_h) / 2;
124 if (cx < 0) cx = 0;
125 if (cy < 0) cy = 0;
126
127 for (int y = 0; y < qr.size; y++) {
128 for (int x = 0; x < qr.size; x++) {
129 bool mod = qrcode_getModule(&qr, x, y);
130 uint16_t color = mod ? 0xFFFF : 0x0000;
131 axs15231b_fill_rect(cx + x * px, cy + y * px, px, px, color);
132 }
133 }
134
135 free(qr_buf);
136}
137
138void display_render_qr(const char *text) { 175void display_render_qr(const char *text) {
139 int screen_w = axs15231b_get_width(); 176 int screen_w = axs15231b_get_width();
140 int screen_h = axs15231b_get_height(); 177 int screen_h = axs15231b_get_height();
141 axs15231b_fill_screen(0x0000); 178 axs15231b_fill_screen(COLOR_BG);
142 render_qr_at(text, 0, 0, screen_w, screen_h); 179 render_qr_at(text, 0, 0, screen_w, screen_h);
143 axs15231b_flush(); 180 axs15231b_flush();
144} 181}
145 182
146static void render_boot_screen(void) { 183static void render_boot_screen(void) {
147 axs15231b_fill_screen(0x0000); 184 int screen_w = axs15231b_get_width();
148 display_render_text(140, 100, "TollGate", 0xF79F, 0x0000, 3); 185 axs15231b_fill_screen(COLOR_BG);
149 display_render_text(140, 140, "starting...", 0xB5B6, 0x0000, 2); 186
187 char qr_text[320];
188 build_wifi_qr_string(qr_text, sizeof(qr_text));
189 render_qr_at(qr_text, 0, 10, screen_w, 220);
190
191 const char *title = "TollGate";
192 int title_w = strlen(title) * 8 * 2;
193 display_render_text((screen_w - title_w) / 2, 240, title, COLOR_CYAN, COLOR_BG, 2);
194
195 int status_w = strlen(s_wifi_status) * 8;
196 display_render_text((screen_w - status_w) / 2, 268, s_wifi_status, COLOR_YELLOW, COLOR_BG, 1);
197
198 snprintf(qr_text, sizeof(qr_text), "SSID: %s", s_ap_ssid);
199 int ssid_w = strlen(qr_text) * 8;
200 display_render_text((screen_w - ssid_w) / 2, 295, qr_text, COLOR_DIM, COLOR_BG, 1);
201
202 const char *hint = "Scan QR to connect";
203 int hint_w = strlen(hint) * 8;
204 display_render_text((screen_w - hint_w) / 2, 315, hint, COLOR_DIM, COLOR_BG, 1);
205
150 axs15231b_flush(); 206 axs15231b_flush();
151} 207}
152 208
153static void render_ready_screen(void) { 209static void render_ready_screen(void) {
154 axs15231b_fill_screen(0x0000);
155
156 int screen_w = axs15231b_get_width(); 210 int screen_w = axs15231b_get_width();
157 int screen_h = axs15231b_get_height(); 211 int text_area_y = 330;
158 int text_area_y = screen_h - 55; 212 axs15231b_fill_screen(COLOR_BG);
159 213
160 char qr_text[320]; 214 char qr_text[320];
161 const char *label;
162
163 if (s_qr_mode == DISPLAY_QR_WIFI) { 215 if (s_qr_mode == DISPLAY_QR_WIFI) {
164 build_wifi_qr_string(qr_text, sizeof(qr_text)); 216 build_wifi_qr_string(qr_text, sizeof(qr_text));
165 label = "Scan to connect";
166 } else { 217 } else {
167 strncpy(qr_text, s_portal_url, sizeof(qr_text) - 1); 218 strncpy(qr_text, s_portal_url, sizeof(qr_text) - 1);
168 qr_text[sizeof(qr_text) - 1] = '\0'; 219 qr_text[sizeof(qr_text) - 1] = '\0';
169 label = "Portal URL";
170 } 220 }
171 221
172 render_qr_at(qr_text, 0, 0, screen_w, text_area_y - 5); 222 render_qr_at(qr_text, 0, 5, screen_w, text_area_y - 10);
223
224 int y = text_area_y;
225 char line[48];
226
227 if (s_qr_mode == DISPLAY_QR_WIFI) {
228 snprintf(line, sizeof(line), "Scan to connect");
229 display_render_text(10, y, line, COLOR_CYAN, COLOR_BG, 1);
230 y += 16;
231
232 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid);
233 display_render_text(10, y, line, COLOR_WHITE, COLOR_BG, 1);
234 y += 16;
235 } else {
236 snprintf(line, sizeof(line), "Portal URL");
237 display_render_text(10, y, line, COLOR_CYAN, COLOR_BG, 1);
238 y += 16;
239
240 char domain[48];
241 extract_domain(s_mint_url, domain, sizeof(domain));
242 snprintf(line, sizeof(line), "Mint: %.30s", domain);
243 display_render_text(10, y, line, COLOR_ORANGE, COLOR_BG, 1);
244 y += 16;
245 }
246
247 snprintf(line, sizeof(line), "%d sats/min", s_price_per_step);
248 display_render_text(10, y, line, COLOR_ORANGE, COLOR_BG, 1);
249 y += 16;
250
251 snprintf(line, sizeof(line), "Wallet: %llu sats", (unsigned long long)s_wallet_balance);
252 display_render_text(10, y, line, wallet_color(), COLOR_BG, 1);
253 y += 16;
254
255 if (s_active_clients > 0) {
256 snprintf(line, sizeof(line), "Clients: %d", s_active_clients);
257 display_render_text(10, y, line, COLOR_GREEN, COLOR_BG, 1);
258 }
259
260 axs15231b_flush();
261}
262
263static void render_setup_pending_screen(void) {
264 int screen_w = axs15231b_get_width();
265 axs15231b_fill_screen(COLOR_BG);
173 266
174 display_render_text(10, text_area_y, label, 0xB5B6, 0x0000, 2); 267 char qr_text[320];
268 build_wifi_qr_string(qr_text, sizeof(qr_text));
269 render_qr_at(qr_text, 0, 5, screen_w, 280);
175 270
271 int y = 290;
176 char line[64]; 272 char line[64];
273
274 const char *title = "WiFi Setup";
275 int tw = strlen(title) * 8;
276 display_render_text((screen_w - tw) / 2, y, title, COLOR_CYAN, COLOR_BG, 1);
277 y += 20;
278
177 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid); 279 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid);
178 display_render_text(10, text_area_y + 20, line, 0xB5B6, 0x0000, 2); 280 display_render_text(10, y, line, COLOR_WHITE, COLOR_BG, 1);
281 y += 18;
282
283 const char *hint1 = "1. Connect to WiFi above";
284 display_render_text(10, y, hint1, COLOR_DIM, COLOR_BG, 1);
285 y += 16;
286
287 const char *hint2 = "2. Open browser, go to:";
288 display_render_text(10, y, hint2, COLOR_DIM, COLOR_BG, 1);
289 y += 18;
290
291 const tollgate_config_t *cfg = tollgate_config_get();
292 snprintf(line, sizeof(line), "http://%s/setup", cfg->ap_ip_str);
293 display_render_text(10, y, line, COLOR_YELLOW, COLOR_BG, 1);
294 y += 22;
295
296 const char *hint3 = "3. Configure upstream WiFi";
297 display_render_text(10, y, hint3, COLOR_DIM, COLOR_BG, 1);
179 298
180 axs15231b_flush(); 299 axs15231b_flush();
181} 300}
182 301
183static void render_payment_screen(void) { 302static void render_payment_screen(void) {
184 axs15231b_fill_screen(0x07E0); 303 int screen_w = axs15231b_get_width();
185 display_render_text(140, 100, "Paid!", 0x0000, 0x07E0, 3); 304 axs15231b_fill_screen(COLOR_BG);
186 display_render_text(130, 140, "Access granted", 0x0000, 0x07E0, 2); 305
306 axs15231b_fill_rect(0, 190, screen_w, 50, COLOR_GREEN);
307 const char *msg = "ACCESS GRANTED";
308 int msg_w = strlen(msg) * 8 * 2;
309 display_render_text((screen_w - msg_w) / 2, 202, msg, COLOR_WHITE, COLOR_GREEN, 2);
310
311 char line[48];
312
313 snprintf(line, sizeof(line), "Paid: %d sats", s_last_payment_sats);
314 int lw = strlen(line) * 8;
315 display_render_text((screen_w - lw) / 2, 270, line, COLOR_WHITE, COLOR_BG, 1);
316
317 int64_t secs = s_last_allotment_ms / 1000;
318 if (secs >= 60) {
319 snprintf(line, sizeof(line), "Time: %lld min", (long long)(secs / 60));
320 } else {
321 snprintf(line, sizeof(line), "Time: %lld sec", (long long)secs);
322 }
323 lw = strlen(line) * 8;
324 display_render_text((screen_w - lw) / 2, 290, line, COLOR_WHITE, COLOR_BG, 1);
325
326 snprintf(line, sizeof(line), "Wallet: %llu sats", (unsigned long long)s_wallet_balance);
327 lw = strlen(line) * 8;
328 display_render_text((screen_w - lw) / 2, 320, line, wallet_color(), COLOR_BG, 1);
329
187 axs15231b_flush(); 330 axs15231b_flush();
188} 331}
189 332
190static void render_error_screen(void) { 333static void render_error_screen(void) {
191 axs15231b_fill_screen(0xF800); 334 int screen_w = axs15231b_get_width();
192 display_render_text(120, 100, "No upstream", 0xFFFF, 0xF800, 3); 335 axs15231b_fill_screen(COLOR_BG);
193 display_render_text(130, 140, "Check config", 0xFFFF, 0xF800, 2); 336
337 char qr_text[320];
338 build_wifi_qr_string(qr_text, sizeof(qr_text));
339 render_qr_at(qr_text, 0, 5, screen_w, 150);
340
341 axs15231b_fill_rect(0, 160, screen_w, 36, COLOR_RED);
342 const char *msg = "NO UPSTREAM";
343 int msg_w = strlen(msg) * 8 * 2;
344 display_render_text((screen_w - msg_w) / 2, 170, msg, COLOR_WHITE, COLOR_RED, 2);
345
346 char line[64];
347 int lw;
348 int y = 210;
349
350 const char *l1 = "Internet unavailable";
351 lw = strlen(l1) * 8;
352 display_render_text((screen_w - lw) / 2, y, l1, COLOR_WHITE, COLOR_BG, 1);
353 y += 20;
354
355 const char *l3 = "AP still active";
356 lw = strlen(l3) * 8;
357 display_render_text((screen_w - lw) / 2, y, l3, COLOR_GREEN, COLOR_BG, 1);
358 y += 20;
359
360 snprintf(line, sizeof(line), "SSID: %s", s_ap_ssid);
361 lw = strlen(line) * 8;
362 display_render_text((screen_w - lw) / 2, y, line, COLOR_DIM, COLOR_BG, 1);
363 y += 20;
364
365 const tollgate_config_t *cfg = tollgate_config_get();
366 snprintf(line, sizeof(line), "http://%s/setup", cfg->ap_ip_str);
367 lw = strlen(line) * 8;
368 display_render_text((screen_w - lw) / 2, y, line, COLOR_YELLOW, COLOR_BG, 1);
369 y += 16;
370
371 const char *hint = "Scan QR to connect";
372 lw = strlen(hint) * 8;
373 display_render_text((screen_w - lw) / 2, y, hint, COLOR_DIM, COLOR_BG, 1);
374
194 axs15231b_flush(); 375 axs15231b_flush();
195} 376}
196 377
@@ -200,6 +381,14 @@ static void display_task(void *pvParameters) {
200 while (1) { 381 while (1) {
201 display_state_t state = s_state; 382 display_state_t state = s_state;
202 383
384 if (state == DISPLAY_READY) {
385 int64_t now = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
386 if ((now - s_last_qr_switch) >= QR_CYCLE_MS) {
387 s_qr_mode = (s_qr_mode == DISPLAY_QR_WIFI) ? DISPLAY_QR_PORTAL : DISPLAY_QR_WIFI;
388 s_last_qr_switch = now;
389 }
390 }
391
203 switch (state) { 392 switch (state) {
204 case DISPLAY_BOOT: 393 case DISPLAY_BOOT:
205 render_boot_screen(); 394 render_boot_screen();
@@ -209,21 +398,18 @@ static void display_task(void *pvParameters) {
209 break; 398 break;
210 case DISPLAY_PAYMENT_RECEIVED: 399 case DISPLAY_PAYMENT_RECEIVED:
211 render_payment_screen(); 400 render_payment_screen();
212 vTaskDelay(pdMS_TO_TICKS(2000)); 401 vTaskDelay(pdMS_TO_TICKS(3000));
213 s_state = DISPLAY_READY; 402 s_state = DISPLAY_READY;
214 break; 403 break;
215 case DISPLAY_ERROR: 404 case DISPLAY_ERROR:
216 render_error_screen(); 405 render_error_screen();
217 break; 406 break;
407 case DISPLAY_SETUP_PENDING:
408 render_setup_pending_screen();
409 break;
218 } 410 }
219 411
220 int64_t now = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; 412 vTaskDelay(pdMS_TO_TICKS(RENDER_INTERVAL_MS));
221 if (state == DISPLAY_READY && (now - s_last_qr_switch) >= QR_CYCLE_MS) {
222 s_qr_mode = (s_qr_mode == DISPLAY_QR_WIFI) ? DISPLAY_QR_PORTAL : DISPLAY_QR_WIFI;
223 s_last_qr_switch = now;
224 }
225
226 vTaskDelay(pdMS_TO_TICKS(1000));
227 } 413 }
228} 414}
229 415
@@ -239,7 +425,7 @@ esp_err_t display_init(void) {
239 s_initialized = true; 425 s_initialized = true;
240 s_last_qr_switch = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; 426 s_last_qr_switch = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
241 427
242 xTaskCreatePinnedToCore(display_task, "display", 16384, NULL, 2, NULL, 1); 428 xTaskCreatePinnedToCore(display_task, "display", 24576, NULL, 2, NULL, 1);
243 429
244 ESP_LOGI(TAG, "Display initialized"); 430 ESP_LOGI(TAG, "Display initialized");
245 return ESP_OK; 431 return ESP_OK;
@@ -250,7 +436,9 @@ void display_set_state(display_state_t state) {
250} 436}
251 437
252void display_update(const char *ap_ssid, int active_clients, 438void display_update(const char *ap_ssid, int active_clients,
253 uint64_t wallet_balance, const char *portal_url) { 439 uint64_t wallet_balance, const char *portal_url,
440 const char *mint_url, int price_per_step,
441 const char *wifi_status) {
254 if (ap_ssid) { 442 if (ap_ssid) {
255 strncpy(s_ap_ssid, ap_ssid, sizeof(s_ap_ssid) - 1); 443 strncpy(s_ap_ssid, ap_ssid, sizeof(s_ap_ssid) - 1);
256 s_ap_ssid[sizeof(s_ap_ssid) - 1] = '\0'; 444 s_ap_ssid[sizeof(s_ap_ssid) - 1] = '\0';
@@ -259,6 +447,29 @@ void display_update(const char *ap_ssid, int active_clients,
259 strncpy(s_portal_url, portal_url, sizeof(s_portal_url) - 1); 447 strncpy(s_portal_url, portal_url, sizeof(s_portal_url) - 1);
260 s_portal_url[sizeof(s_portal_url) - 1] = '\0'; 448 s_portal_url[sizeof(s_portal_url) - 1] = '\0';
261 } 449 }
450 if (mint_url) {
451 strncpy(s_mint_url, mint_url, sizeof(s_mint_url) - 1);
452 s_mint_url[sizeof(s_mint_url) - 1] = '\0';
453 }
454 if (wifi_status) {
455 strncpy(s_wifi_status, wifi_status, sizeof(s_wifi_status) - 1);
456 s_wifi_status[sizeof(s_wifi_status) - 1] = '\0';
457 }
458 if (price_per_step > 0) s_price_per_step = price_per_step;
262 s_active_clients = active_clients; 459 s_active_clients = active_clients;
263 s_wallet_balance = wallet_balance; 460 s_wallet_balance = wallet_balance;
264} 461}
462
463void display_notify_payment(int amount_sats, int64_t allotment_ms) {
464 s_last_payment_sats = amount_sats;
465 s_last_allotment_ms = allotment_ms;
466 s_wallet_balance = nucula_wallet_balance();
467 display_set_state(DISPLAY_PAYMENT_RECEIVED);
468}
469
470void display_notify_wifi_connected(const char *ip) {
471 (void)ip;
472}
473
474void display_notify_wifi_disconnected(void) {
475}