#include "ble_ui.h" #include "ble_core.h" #include "pins.h" #include #include #include #include static constexpr uint32_t COL_BG = 0x0A1016; static constexpr uint32_t COL_INK = 0xEAF0F6; static constexpr uint32_t COL_MUTED = 0x7A8B9C; static constexpr uint32_t COL_OK = 0x3DDC97; static constexpr uint32_t COL_WARN = 0xF0C040; static constexpr uint32_t COL_BAD = 0xE85D4C; static constexpr uint32_t COL_ACCENT = 0x4FC3F7; static constexpr uint32_t COL_BTN = 0x243140; static constexpr uint32_t COL_BTN_ACC = 0x1A4A6E; static constexpr uint32_t COL_BTN_WARN = 0x6B3A1E; static lv_obj_t *scr = nullptr; static lv_obj_t *lbl_title = nullptr; static lv_obj_t *lbl_state = nullptr; static lv_obj_t *lbl_name = nullptr; static lv_obj_t *lbl_addr = nullptr; static lv_obj_t *lbl_peer = nullptr; static lv_obj_t *lbl_echo = nullptr; static lv_obj_t *lbl_log = nullptr; static lv_obj_t *btn_adv = nullptr; static lv_obj_t *lbl_adv = nullptr; static BleCoreState g_last_state = BLE_CORE_IDLE; static bool g_last_echo = true; static char g_log[160] = "Waiting for phone..."; static uint32_t g_last_refresh_ms = 0; static void style_btn(lv_obj_t *btn, uint32_t bg) { lv_obj_set_style_radius(btn, 8, 0); lv_obj_set_style_bg_color(btn, lv_color_hex(bg), 0); lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); lv_obj_set_style_shadow_width(btn, 0, 0); lv_obj_set_style_border_width(btn, 0, 0); lv_obj_set_style_pad_all(btn, 0, 0); } static lv_obj_t *make_btn(lv_obj_t *parent, const char *text, uint32_t bg, lv_event_cb_t cb, int w, int h) { lv_obj_t *btn = lv_button_create(parent); lv_obj_set_size(btn, w, h); style_btn(btn, bg); lv_obj_t *lab = lv_label_create(btn); lv_label_set_text(lab, text); lv_obj_set_style_text_color(lab, lv_color_hex(COL_INK), 0); lv_obj_set_style_text_font(lab, &lv_font_montserrat_14, 0); lv_obj_center(lab); if (cb) { lv_obj_add_event_cb(btn, cb, LV_EVENT_CLICKED, nullptr); } return btn; } static void append_log(const char *line) { if (!line) { return; } char next[160]; snprintf(next, sizeof(next), "%s", line); // 保留最近两行:旧半 + 新 char combined[160]; snprintf(combined, sizeof(combined), "%s\n%s", g_log, next); // 若太长,只留新内容 if (strlen(combined) >= sizeof(g_log)) { snprintf(g_log, sizeof(g_log), "%s", next); } else { snprintf(g_log, sizeof(g_log), "%s", combined); } if (lbl_log) { lv_label_set_text(lbl_log, g_log); } } static void refresh_static() { if (lbl_name) { char buf[48]; snprintf(buf, sizeof(buf), "Name %s", ble_core_name()); lv_label_set_text(lbl_name, buf); } if (lbl_addr) { char buf[56]; String a = ble_core_address(); snprintf(buf, sizeof(buf), "Addr %s", a.length() ? a.c_str() : "-"); lv_label_set_text(lbl_addr, buf); } } static void refresh_state() { const BleCoreState st = ble_core_state(); g_last_state = st; uint32_t color = COL_MUTED; const char *text = "Idle"; if (st == BLE_CORE_ADVERTISING) { color = COL_WARN; text = "Advertising"; } else if (st == BLE_CORE_CONNECTED) { color = COL_OK; text = "Connected"; } if (lbl_state) { char buf[40]; snprintf(buf, sizeof(buf), "State %s", text); lv_label_set_text(lbl_state, buf); lv_obj_set_style_text_color(lbl_state, lv_color_hex(color), 0); } if (lbl_peer) { char buf[56]; if (ble_core_is_connected()) { String p = ble_core_peer_address(); snprintf(buf, sizeof(buf), "Peer %s", p.c_str()); } else { snprintf(buf, sizeof(buf), "Peer -"); } lv_label_set_text(lbl_peer, buf); } if (lbl_adv) { lv_label_set_text(lbl_adv, ble_core_is_advertising() || ble_core_is_connected() ? "Stop Adv" : "Start Adv"); } if (lbl_echo) { char buf[32]; snprintf(buf, sizeof(buf), "Echo %s", ble_core_echo() ? "ON" : "OFF"); lv_label_set_text(lbl_echo, buf); lv_obj_set_style_text_color( lbl_echo, lv_color_hex(ble_core_echo() ? COL_OK : COL_MUTED), 0); } g_last_echo = ble_core_echo(); } static void on_adv(lv_event_t *e) { (void)e; if (ble_core_is_connected()) { // 已连接时 Stop Adv 无意义;改为提示 append_log("Connected — disconnect on phone first"); return; } if (ble_core_is_advertising()) { ble_core_advertise_stop(); append_log("Advertising stopped"); } else { ble_core_advertise_start(); append_log("Advertising started"); } refresh_state(); } static void on_echo(lv_event_t *e) { (void)e; ble_core_set_echo(!ble_core_echo()); append_log(ble_core_echo() ? "Echo ON" : "Echo OFF"); refresh_state(); } static void on_ping(lv_event_t *e) { (void)e; if (!ble_core_is_connected()) { append_log("Not connected"); return; } if (ble_core_send_str("Hello from CYD\n")) { append_log("TX: Hello from CYD"); } else { append_log("TX failed"); } } static void on_clear(lv_event_t *e) { (void)e; snprintf(g_log, sizeof(g_log), "(cleared)"); if (lbl_log) { lv_label_set_text(lbl_log, g_log); } } void ble_ui_create() { scr = lv_obj_create(nullptr); lv_obj_set_style_bg_color(scr, lv_color_hex(COL_BG), 0); lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE); lv_screen_load(scr); lbl_title = lv_label_create(scr); lv_label_set_text(lbl_title, "CYD BLE"); lv_obj_set_style_text_color(lbl_title, lv_color_hex(COL_ACCENT), 0); lv_obj_set_style_text_font(lbl_title, &lv_font_montserrat_20, 0); lv_obj_set_pos(lbl_title, 10, 6); lbl_state = lv_label_create(scr); lv_obj_set_style_text_font(lbl_state, &lv_font_montserrat_14, 0); lv_obj_set_pos(lbl_state, 10, 34); lbl_name = lv_label_create(scr); lv_obj_set_style_text_color(lbl_name, lv_color_hex(COL_INK), 0); lv_obj_set_style_text_font(lbl_name, &lv_font_montserrat_14, 0); lv_obj_set_pos(lbl_name, 10, 54); lbl_addr = lv_label_create(scr); lv_obj_set_style_text_color(lbl_addr, lv_color_hex(COL_MUTED), 0); lv_obj_set_style_text_font(lbl_addr, &lv_font_montserrat_14, 0); lv_obj_set_pos(lbl_addr, 10, 74); lbl_peer = lv_label_create(scr); lv_obj_set_style_text_color(lbl_peer, lv_color_hex(COL_MUTED), 0); lv_obj_set_style_text_font(lbl_peer, &lv_font_montserrat_14, 0); lv_obj_set_pos(lbl_peer, 10, 94); lbl_echo = lv_label_create(scr); lv_obj_set_style_text_font(lbl_echo, &lv_font_montserrat_14, 0); lv_obj_set_pos(lbl_echo, 10, 114); lv_obj_t *hint = lv_label_create(scr); lv_label_set_text(hint, "Phone: nRF Connect / Serial BT Terminal"); lv_obj_set_style_text_color(hint, lv_color_hex(COL_MUTED), 0); lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0); lv_obj_set_pos(hint, 10, 134); lbl_log = lv_label_create(scr); lv_label_set_text(lbl_log, g_log); lv_label_set_long_mode(lbl_log, LV_LABEL_LONG_WRAP); lv_obj_set_width(lbl_log, UI_HOR_RES - 20); lv_obj_set_style_text_color(lbl_log, lv_color_hex(COL_INK), 0); lv_obj_set_style_text_font(lbl_log, &lv_font_montserrat_14, 0); lv_obj_set_pos(lbl_log, 10, 156); const int bw = 72; const int bh = 36; const int y = UI_VER_RES - bh - 8; btn_adv = make_btn(scr, "Stop Adv", COL_BTN_ACC, on_adv, bw, bh); lbl_adv = lv_obj_get_child(btn_adv, 0); lv_obj_set_pos(btn_adv, 8, y); lv_obj_t *btn_echo = make_btn(scr, "Echo", COL_BTN, on_echo, bw, bh); lv_obj_set_pos(btn_echo, 8 + bw + 6, y); lv_obj_t *btn_ping = make_btn(scr, "Ping", COL_BTN_ACC, on_ping, bw, bh); lv_obj_set_pos(btn_ping, 8 + 2 * (bw + 6), y); lv_obj_t *btn_clear = make_btn(scr, "Clear", COL_BTN_WARN, on_clear, bw, bh); lv_obj_set_pos(btn_clear, 8 + 3 * (bw + 6), y); refresh_static(); refresh_state(); } void ble_ui_tick() { BleRxPacket pkt; while (ble_core_poll_rx(&pkt)) { char line[BLE_CORE_RX_MAX + 8]; snprintf(line, sizeof(line), "RX: %s", pkt.data); append_log(line); } const uint32_t now = millis(); if (now - g_last_refresh_ms < 300) { return; } g_last_refresh_ms = now; if (ble_core_state() != g_last_state || ble_core_echo() != g_last_echo) { refresh_state(); } }