24 lv_obj_t *sl = lv_event_get_target(e);
25 lv_obj_t *lbl =
static_cast<lv_obj_t *
>(lv_event_get_user_data(e));
26 s_brightness =
static_cast<uint8_t
>(lv_slider_get_value(sl));
30 snprintf(b,
sizeof(b),
"%u%%",
static_cast<unsigned>(
s_brightness));
31 lv_label_set_text(lbl, b);
41 if (about) { lv_obj_clear_flag(
s_reset_btn, LV_OBJ_FLAG_HIDDEN); }
42 else { lv_obj_add_flag(
s_reset_btn, LV_OBJ_FLAG_HIDDEN); }
56 lv_obj_t *tabbar = lv_event_get_target(e);
59 uint16_t sel = lv_btnmatrix_get_selected_btn(tabbar);
67 lv_obj_t *tv = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 42);
69 lv_obj_align(tv, LV_ALIGN_TOP_MID, 0, 0);
73 lv_obj_set_style_bg_opa(tv, LV_OPA_TRANSP, LV_PART_MAIN);
74 lv_obj_set_style_bg_opa(lv_tabview_get_tab_btns(tv), LV_OPA_TRANSP,
77 lv_obj_t *t_screen = lv_tabview_add_tab(tv,
"Screen");
78 lv_obj_t *t_wifi = lv_tabview_add_tab(tv,
"Wi-Fi");
79 lv_obj_t *t_tx = lv_tabview_add_tab(tv,
"Tx");
80 lv_obj_t *t_about = lv_tabview_add_tab(tv,
"About");
81 lv_obj_t *pages[4] = { t_screen, t_wifi, t_tx, t_about };
82 for (
int i = 0; i < 4; i++) {
83 lv_obj_set_style_bg_opa(pages[i], LV_OPA_TRANSP, LV_PART_MAIN);
84 lv_obj_set_style_border_width(pages[i], 0, LV_PART_MAIN);
85 lv_obj_set_style_pad_all(pages[i],
TAB_PAD, LV_PART_MAIN);
86 lv_obj_clear_flag(pages[i], LV_OBJ_FLAG_SCROLLABLE);
89 lv_obj_t *scrollable[2] = { t_about, t_tx };
90 for (
int i = 0; i < 2; i++) {
91 lv_obj_add_flag(scrollable[i], LV_OBJ_FLAG_SCROLLABLE);
92 lv_obj_set_scroll_dir(scrollable[i], LV_DIR_VER);
93 lv_obj_set_scrollbar_mode(scrollable[i], LV_SCROLLBAR_MODE_AUTO);
98 lv_obj_t *columns[3] = { t_wifi, t_tx, t_about };
99 for (
int i = 0; i < 3; i++) {
100 lv_obj_set_flex_flow(columns[i], LV_FLEX_FLOW_COLUMN);
101 lv_obj_set_style_pad_row(columns[i], 14, LV_PART_MAIN);
104 lv_obj_set_flex_align(t_about, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER,
105 LV_FLEX_ALIGN_CENTER);
109 LV_ALIGN_TOP_LEFT, 0, 0);
111 LV_ALIGN_TOP_RIGHT, 0, 0);
112 lv_obj_t *sl = lv_slider_create(t_screen);
113 lv_obj_set_width(sl, 196);
114 lv_obj_align(sl, LV_ALIGN_TOP_MID, 0, 32);
115 lv_slider_set_range(sl, 10, 100);
117 lv_obj_set_style_bg_color(sl,
COL_BORDER, LV_PART_MAIN);
118 lv_obj_set_style_bg_color(sl,
COL_ACCENT, LV_PART_INDICATOR);
119 lv_obj_set_style_bg_color(sl,
COL_ACCENT, LV_PART_KNOB);
123 snprintf(b,
sizeof(b),
"%u%%",
static_cast<unsigned>(
s_brightness));
124 lv_label_set_text(pct, b);
128 lv_obj_t *calpill =
make_pill(t_screen,
"Touch",
"Calibrate the panel",
134 char cur_ssid[33] = {0};
137 cur_pass,
sizeof(cur_pass));
138 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(cur_pass),
sizeof(cur_pass));
144 have_wifi ? cur_ssid :
"Not configured");
146 lv_label_set_long_mode(wl, LV_LABEL_LONG_DOT);
152 const char *qual = (rssi >= -50) ?
"Excellent" :
153 (rssi >= -60) ?
"Good" :
154 (rssi >= -70) ?
"Fair" :
"Weak";
156 snprintf(sig,
sizeof(sig),
"%s (%d dBm)", qual,
157 static_cast<int>(rssi));
164 lv_obj_t *cpill =
make_pill(t_wifi,
"Configure",
"Open in a browser",
194 lv_obj_t *w =
make_label(t_tx,
"Not configured - this terminal cannot "
195 "take payments. Set it from the "
198 LV_ALIGN_DEFAULT, 0, 0);
199 lv_obj_set_width(w,
TAB_W);
200 lv_label_set_long_mode(w, LV_LABEL_LONG_WRAP);
207 snprintf(fee,
sizeof(fee),
"%u",
211 snprintf(fee,
sizeof(fee),
"%u",
219 lv_obj_t *tpill =
make_pill(t_tx,
"Configure",
220 tron ?
"Open in a browser" :
"Fees in a browser",
226 lv_obj_t *blogo = lv_img_create(t_about);
227 lv_img_set_src(blogo, &logo_small);
230 LV_ALIGN_DEFAULT, 0, 0);
250 lv_obj_t *upill =
make_pill(t_about,
"Update",
"From a browser",
258 "Crypto payment terminal\n"
259 "for Cryptnox cards\n\n"
260 "Based on cryptnox-sdk-esp32 1.0.0\n"
261 "(c) Cryptnox 2026 - Educational use only\n\n"
262 "Licensed under LGPL-3.0-or-later\n\n"
263 "Third-party: ESP-IDF (Apache-2.0),\n"
264 "LVGL (MIT), TFT_eSPI (FreeBSD/MIT),\n"
265 "XPT2046_Touchscreen (MIT)",
267 LV_ALIGN_DEFAULT, 0, 0);
268 lv_label_set_long_mode(about, LV_LABEL_LONG_WRAP);
269 lv_obj_set_width(about, 210);
270 lv_obj_set_style_text_align(about, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
281 lv_obj_add_event_cb(lv_tabview_get_tab_btns(tv),
tab_change_cb,
282 LV_EVENT_VALUE_CHANGED, NULL);
303 const lv_coord_t arm = 12;
304 static lv_point_t h[2], v[2];
305 h[0] = { (lv_coord_t)(cx - arm), cy }; h[1] = { (lv_coord_t)(cx + arm), cy };
306 v[0] = { cx, (lv_coord_t)(cy - arm) }; v[1] = { cx, (lv_coord_t)(cy + arm) };
307 for (
int i = 0; i < 2; i++) {
308 lv_obj_t *l = lv_line_create(lv_scr_act());
309 lv_line_set_points(l, (i == 0) ? h : v, 2);
310 lv_obj_set_style_line_width(l, 2, LV_PART_MAIN);
311 lv_obj_set_style_line_color(l,
COL_DANGER, LV_PART_MAIN);
312 lv_obj_set_pos(l, 0, 0);
340 first ?
"Tap the cross\nat the top left"
341 :
"Now the one at\nthe bottom right",
343 lv_obj_set_style_text_align(ask, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
344 lv_obj_align(ask, LV_ALIGN_CENTER, 0, -10);
346 "Use a stylus or a fingernail - the centre "
347 "of the cross, not near it.",
349 LV_ALIGN_CENTER, 0, 60);
350 lv_obj_set_width(hint, 200);
351 lv_label_set_long_mode(hint, LV_LABEL_LONG_WRAP);
352 lv_obj_set_style_text_align(hint, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
353 lv_obj_align(hint, LV_ALIGN_CENTER, 0, 60);
364 "Those two taps were\ntoo close together.\n\n"
365 "Nothing was changed.",
367 lv_obj_set_style_text_align(no, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
368 lv_obj_align(no, LV_ALIGN_CENTER, 0, -20);
378 lv_obj_set_style_text_align(q, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
379 lv_obj_align(q, LV_ALIGN_TOP_MID, 0, 70);
382 LV_ALIGN_TOP_MID, 0, 140);
437 s_modal = lv_obj_create(lv_layer_top());
438 lv_obj_remove_style_all(
s_modal);
440 lv_obj_set_style_bg_color(
s_modal, lv_color_black(), LV_PART_MAIN);
441 lv_obj_set_style_bg_opa(
s_modal, LV_OPA_70, LV_PART_MAIN);
442 lv_obj_clear_flag(
s_modal, LV_OBJ_FLAG_SCROLLABLE);
443 lv_obj_add_flag(
s_modal, LV_OBJ_FLAG_CLICKABLE);
445 lv_obj_t *card = lv_obj_create(
s_modal);
446 lv_obj_set_size(card, w, h);
448 lv_obj_set_style_bg_color(card,
COL_BG, LV_PART_MAIN);
449 lv_obj_set_style_radius(card, 14, LV_PART_MAIN);
450 lv_obj_set_style_border_width(card, 1, LV_PART_MAIN);
451 lv_obj_set_style_border_color(card,
COL_BORDER, LV_PART_MAIN);
452 lv_obj_set_style_pad_all(card, 8, LV_PART_MAIN);
453 lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE);
454 lv_obj_add_flag(card, LV_OBJ_FLAG_CLICKABLE);
461 "Erase all settings\n"
462 "(Wi-Fi, brightness, fees)\n"
465 LV_ALIGN_TOP_MID, 0, 12);
466 lv_obj_set_style_text_align(msg, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
474#define OTA_CARD_PAD 8
477lv_obj_t *
ota_text(lv_obj_t *card, lv_obj_t *above,
const char *txt,
478 lv_color_t col,
const lv_font_t *font) {
479 lv_obj_t *l =
make_label(card, txt, col, font, LV_ALIGN_TOP_MID, 0, 0);
481 lv_label_set_long_mode(l, LV_LABEL_LONG_WRAP);
482 lv_obj_set_style_text_align(l, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
484 lv_obj_align(l, LV_ALIGN_TOP_MID, 0, 4);
488 lv_obj_update_layout(card);
489 lv_obj_align_to(l, above, LV_ALIGN_OUT_BOTTOM_MID, 0,
OTA_GAP);
502void ota_fit_card(lv_obj_t *card, lv_obj_t *last, lv_coord_t buttons_h) {
503 lv_obj_update_layout(card);
504 lv_coord_t need = lv_obj_get_y(last) + lv_obj_get_height(last)
506 if (need > (
SCR_H - 16)) { need =
SCR_H - 16; }
507 lv_obj_set_height(card, need);
518 "The terminal's own Wi-Fi would not come up, so there is "
519 "nothing for a phone to join.\n\nRestart and try again.",
536 (void)lv_qrcode_update(qr, payload, strlen(payload));
537 lv_obj_set_style_border_color(qr,
COL_BG, LV_PART_MAIN);
538 lv_obj_set_style_border_width(qr, 4, LV_PART_MAIN);
540 lv_obj_align(qr, LV_ALIGN_TOP_MID, 0, 4);
546 snprintf(creds,
sizeof(creds),
"SSID: %s\nPassword: %s",
555 snprintf(note,
sizeof(note),
556 "Or open 192.168.4.1\nVenue Wi-Fi off while open\nCloses in %u min",
584 "That firmware is no longer waiting to be installed.\n\n"
585 "Nothing changed - this terminal is still on the version it was. "
586 "Open Update again and send the file once more.",
594 char version[40] =
"?";
596 if (!
ota_staged(version,
sizeof(version), &older)) {
return; }
602 lv_obj_t *cap =
ota_text(card, NULL,
603 older ?
"This is an OLDER version" :
"New firmware",
616 snprintf(body,
sizeof(body),
617 "Running %s.\n\nThe terminal restarts now. Not during a payment.",
619 sizeof(running_ver)));
643 lv_obj_clear_flag(p, LV_OBJ_FLAG_CLICKABLE);
644 lv_obj_set_style_bg_opa(p, LV_OPA_50, LV_PART_MAIN);
645 for (uint32_t i = 0; i < lv_obj_get_child_cnt(p); i++) {
646 lv_obj_set_style_text_color(lv_obj_get_child(p, i),
COL_DIM, LV_PART_MAIN);
663 LV_ALIGN_TOP_MID, 0, 2);
676 lv_coord_t y =
static_cast<lv_coord_t
>(22 + (i * (
PILL_H + 2)));
679 lv_obj_t *p =
make_pill(card, ni->
name, have ? sub :
"No payout set",
701 (void)snprintf(title,
sizeof(title),
"Coin on %s",
pos_net_info(net)->name);
705 static_cast<lv_coord_t
>(86 + (n * (
PILL_H + 2))));
713 if (a->
net != net) {
continue; }
714 lv_coord_t y =
static_cast<lv_coord_t
>(22 + (row * (
PILL_H + 2)));
738#define ADMIN_PENALTY_MAX_S 3600U
740 if (fails < 3U) {
return 0U; }
741 uint32_t shift =
static_cast<uint32_t
>(fails) - 3U;
742 if (shift > 12U) { shift = 12U; }
743 uint32_t secs = 1U << shift;
750static void wait_text(
char *out,
size_t n, uint32_t secs) {
751 if (secs < 120U) { (void)snprintf(out, n,
"%us",
static_cast<unsigned>(secs)); }
752 else { (void)snprintf(out, n,
"%u min",
static_cast<unsigned>((secs + 59U) / 60U)); }
778 const char *txt = lv_textarea_get_text(
s_admin_ta);
779 strncpy(code, (txt != NULL) ? txt :
"",
sizeof(code) - 1);
780 code[
sizeof(code) - 1] =
'\0';
786 (void)snprintf(msg,
sizeof(msg),
"At least %d digits",
803 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(code),
807 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(
s_admin_first),
817 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(
s_admin_first),
831 }
else if (wait_s > 0U) {
834 (void)snprintf(msg,
sizeof(msg),
"Too many tries - wait %s", w);
860 wait_text(w,
sizeof(w), penalty / 1000U);
861 (void)snprintf(msg,
sizeof(msg),
"Wrong code - wait %s", w);
869 CW_Utils::secure_wipe(
reinterpret_cast<uint8_t *
>(code),
sizeof(code));
873 lv_obj_t *bm = lv_event_get_target(e);
874 uint32_t
id = lv_btnmatrix_get_selected_btn(bm);
875 const char *txt = lv_btnmatrix_get_btn_text(bm,
id);
876 if ((txt == NULL) || (
s_admin_ta == NULL)) {
return; }
878 if (strcmp(txt, LV_SYMBOL_OK) == 0) {
880 }
else if (strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) {
882 }
else if ((txt[0] >=
'0') && (txt[0] <=
'9') && (txt[1] ==
'\0')) {
883 lv_textarea_add_char(
s_admin_ta,
static_cast<uint32_t
>(txt[0]));
935 static char title[24];
938 (void)snprintf(title,
sizeof(title),
"Browser %s", code);
940 (void)snprintf(title,
sizeof(title),
"%s",
pos_net_t
A network, as the picker's first step offers it.
static bool pos_chain_is_tron(pos_chain_t c)
true for the Tron selections; every other one is EVM.
static const pos_asset_t POS_ASSETS[]
static const pos_net_info_t * pos_net_info(pos_net_t net)
Names for net, or Ethereum's for a value out of range.
lv_font_t font_inter_14_medium
lv_font_t font_pjs_20_medium
lv_font_t font_pjs_20_semibold
lv_font_t font_pjs_28_semibold
bool net_wifi_rssi(int8_t *rssi_out)
Read the RSSI of the currently associated access point.
const char * ota_running_version(void)
The running firmware's version, from the image header.
bool ota_staged(char *version, size_t version_n, bool *older)
Fetch the version of an image that has been received and verified but not yet installed.
bool ota_last_update_failed(void)
Whether the last update was installed and then thrown away.
#define OTA_VERSION_SHOWN_MAX
Bytes needed by ota_version_display: the version, a 'v', a NUL.
static const char * ota_version_display(const char *v, char *buf, size_t n)
The display form of a version: 1.0.0 reads as v1.0.0 on screen.
void ui_refresh_addresses_for(uint8_t c)
Point the UI's address rows at the selected chain.
void prov_auth_resolve(bool grant)
Answer a pending authorisation request from the panel.
bool prov_pair_code(char *out, size_t out_size)
The 4-digit pairing code of the browser asking to be let in.
const char * prov_ap_pass(void)
AP passphrase, or "" while no portal is up.
const char * prov_qr_payload(void)
What the panel's QR code should carry.
const char * prov_ap_ssid(void)
AP SSID, or "" while no portal is up.
static ui_event_cb_t s_cb
prov_mode_t prov_mode(void)
Which mode is running, or PROV_MODE_OFF.
bool prov_start(prov_mode_t mode, ui_event_cb_t cb)
Raise the portal.
unsigned prov_window_left_min(void)
Minutes left before the portal closes itself, 0 once it has.
bool settings_check_admin_code(const char *code)
Check a candidate code, maintaining the failure counter.
bool settings_get_wifi(char *ssid, size_t ssid_n, char *pass, size_t pass_n)
Read the stored Wi-Fi credentials.
const char * settings_net_str(const char *testnet, const char *mainnet)
Pick the string belonging to the network the terminal is on.
pos_chain_t settings_get_chain(void)
Selected chain, or POS_CHAIN_ETH_USDC if never set.
uint32_t settings_get_priority_fee_gwei(void)
EIP-1559 max priority fee (tip) per gas, in Gwei.
bool settings_set_admin_code(const char *code)
Store a new admin code, with a fresh random salt.
uint8_t settings_admin_fail_count(void)
Consecutive failed unlock attempts, persisted.
void settings_set_brightness(uint8_t pct)
Persist the backlight level (0..100).
bool settings_has_payout(bool tron)
Whether an operator has actually set the payout address for a network.
uint32_t settings_get_max_fee_gwei(void)
EIP-1559 max fee per gas, in Gwei.
Names for one network, in both deployments.
static bool touch_cal_from_corners(int16_t r0x, int16_t r0y, int16_t r1x, int16_t r1y, int16_t inset, int16_t w, int16_t h, touch_cal_t *out)
const char * asset_caption(pos_chain_t c)
void set_wifi_progress(const char *caption, const char *name)
uint32_t s_admin_lock_start
char s_admin_first[ADMIN_CODE_MAX+1]
void request_screen(ui_screen_t s)
lv_obj_t * s_cal_countdown
lv_obj_t * s_fee_prio_lbl
const char * asset_network(pos_chain_t c)
volatile ui_screen_t s_req_screen
const char * asset_name(pos_chain_t c)
lv_obj_t * s_admin_eye_lbl
lv_obj_t * s_admin_note_lbl
@ UI_SCREEN_WIFI_CONNECTING
void build_admin_set(void)
static uint32_t admin_lock_remaining_s(void)
static bool cal_apply(void)
void open_network_picker(void)
#define ADMIN_PENALTY_MAX_S
Wait imposed after repeated wrong admin codes.
static void settings_bottom_bar(uint16_t tab)
void build_admin_unlock(void)
static void admin_set_note(const char *msg)
uint32_t admin_penalty_ms(uint8_t fails)
void settings_persist(void)
static void build_admin_screen(const char *title, bool allow_cancel, const char *hint)
void touch_cal_poll(void)
static void wait_text(char *out, size_t n, uint32_t secs)
static const BtnAction NET_ACT[POS_NET__COUNT]
static void admin_kbd_cb(lv_event_t *e)
void build_settings(void)
void open_coin_picker(pos_net_t net)
static void cal_target(lv_coord_t cx, lv_coord_t cy)
lv_obj_t * open_modal(lv_coord_t w, lv_coord_t h)
static void brightness_event_cb(lv_event_t *e)
static lv_obj_t * s_modal
static void tab_change_cb(lv_event_t *e)
lv_obj_t * ota_text(lv_obj_t *card, lv_obj_t *above, const char *txt, lv_color_t col, const lv_font_t *font)
static void pill_disable(lv_obj_t *p)
void build_ota_confirm(void)
static void admin_submit(void)
void ota_fit_card(lv_obj_t *card, lv_obj_t *last, lv_coord_t buttons_h)
void build_touch_cal(void)
void open_reset_confirm(void)
void open_portal_window(void)
bool touch_raw(int16_t *rx, int16_t *ry)
void backlight_set_pct(uint8_t pct)
Private to the ui*.cpp files: the state and helpers they share.
lv_obj_t * make_glyph_disc(lv_obj_t *parent, const char *sym, lv_color_t bg, lv_coord_t sz)
lv_obj_t * build_page(int step, bool band=true)
#define ADMIN_CODE_MIN_STORED
lv_obj_t * make_asset_badge(lv_obj_t *parent, pos_chain_t chain)
lv_obj_t * make_button(lv_obj_t *parent, const char *label, lv_color_t bg, lv_color_t fg, lv_coord_t w, lv_coord_t h, lv_align_t align, lv_coord_t x, lv_coord_t y, BtnAction act, const lv_font_t *font)
lv_obj_t * make_code_row(lv_obj_t *host, lv_obj_t *kb, uint32_t max_len, const char *hint, BtnAction eye_act, lv_obj_t **eye_lbl)
lv_obj_t * make_icon_button(const char *sym, BtnAction act, lv_obj_t *parent=NULL)
lv_obj_t * make_net_badge(lv_obj_t *parent, pos_net_t net)
lv_obj_t * make_label(lv_obj_t *parent, const char *txt, lv_color_t color, const lv_font_t *font, lv_align_t align, lv_coord_t x, lv_coord_t y)
lv_obj_t * make_card(lv_obj_t *parent)
lv_obj_t * make_numeric_keypad(lv_event_cb_t cb, lv_obj_t *parent=NULL, lv_coord_t w=CODE_KBD_W, lv_coord_t h=210)
void paint_page(lv_obj_t *obj)
Lay the sale-flow chrome and return the white card to build into.
void build_header(const char *title)
#define ACT_CHAIN_OF(chain)
lv_obj_t * make_pill(lv_obj_t *parent, const char *title, const char *sub, lv_coord_t w, lv_coord_t y, BtnAction act, bool leaf=false)
lv_obj_t * make_title(const char *txt, bool has_icon, lv_obj_t *parent=NULL)
Screen title, centred but never underneath the top-left icon button.
lv_obj_t * make_field(lv_obj_t *parent, const char *caption, const char *value, lv_color_t col=COL_TEXT)
#define SETTINGS_TAB_COUNT