87046eadf0
CI / platformio (pull_request) Failing after 9m37s
The project depended on espressif/esp-box (first-gen BOX: ST7789 +
TT21100 touch), which mis-initialises a real ESP32-S3-BOX-3 (ILI9341 +
GT911) and leaves the screen black. Switch the dependency to
espressif/esp-box-3 and use the generic bsp/esp-bsp.h header.
Also: the voice app started the display but never lit the backlight
("LCD backlight must be enabled separately" per the BSP). plip_ui_init
now calls bsp_display_brightness_init()/set(100) and uses a dark-blue
background with white text so the UI is actually visible.
Resolves clean (esp-box-3 3.2.0, esp_lcd_ili9341 2.0.2,
esp_lcd_touch_gt911 1.2.0); build green.
211 lines
7.2 KiB
C
211 lines
7.2 KiB
C
// plip_ui — implementation. See plip_ui.h for the design.
|
|
//
|
|
// LVGL 9 widget tree, built once under bsp_display_lock(). The plip_virtual
|
|
// state callback can fire from any task, so the callback re-takes the lock
|
|
// before touching widgets. Touch events (keypad, bottom button) already run
|
|
// in the LVGL task context, so they call plip_virtual_* directly.
|
|
|
|
#include "plip_ui.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "bsp/esp-bsp.h"
|
|
#include "esp_log.h"
|
|
#include "lvgl.h"
|
|
|
|
#include "plip_virtual.h"
|
|
|
|
#define TAG "plip_ui"
|
|
#define LOCK_MS 1000
|
|
|
|
static bool s_built = false;
|
|
static lv_obj_t *s_phone_view; // idle / ringing illustration
|
|
static lv_obj_t *s_keypad_view; // off-hook dial pad
|
|
static lv_obj_t *s_status_lbl; // line under the phone glyph
|
|
static lv_obj_t *s_number_lbl; // dialed digits
|
|
static lv_obj_t *s_hook_btn; // fixed bottom button
|
|
static lv_obj_t *s_hook_lbl; // its label
|
|
static char s_number[21] = "";
|
|
|
|
static const char *KEYS[] = {
|
|
"1", "2", "3",
|
|
"4", "5", "6",
|
|
"7", "8", "9",
|
|
"*", "0", "#",
|
|
};
|
|
|
|
// ---------- touch event handlers (LVGL task context) ----------
|
|
|
|
static void key_event_cb(lv_event_t *e)
|
|
{
|
|
const char *key = (const char *) lv_event_get_user_data(e);
|
|
size_t len = strlen(s_number);
|
|
if (len < sizeof(s_number) - 1) {
|
|
s_number[len] = key[0];
|
|
s_number[len + 1] = '\0';
|
|
lv_label_set_text(s_number_lbl, s_number);
|
|
// Report the running number to the master; "#" finalises (drop it
|
|
// from the buffer first). The master decides what a valid number is.
|
|
if (key[0] == '#' && len > 0) {
|
|
s_number[len] = '\0';
|
|
lv_label_set_text(s_number_lbl, s_number);
|
|
}
|
|
plip_virtual_dial(s_number);
|
|
}
|
|
}
|
|
|
|
static void hook_btn_event_cb(lv_event_t *e)
|
|
{
|
|
(void) e;
|
|
switch (plip_virtual_state()) {
|
|
case PLIP_HOOK_RINGING: plip_virtual_pickup(); break;
|
|
case PLIP_HOOK_OFF: plip_virtual_hangup(); break;
|
|
default: break; // idle: button is disabled, nothing to do
|
|
}
|
|
}
|
|
|
|
// ---------- view switching (must hold the display lock) ----------
|
|
|
|
static void apply_state_locked(plip_hook_state_t st)
|
|
{
|
|
switch (st) {
|
|
case PLIP_HOOK_OFF:
|
|
s_number[0] = '\0';
|
|
lv_label_set_text(s_number_lbl, "");
|
|
lv_obj_add_flag(s_phone_view, LV_OBJ_FLAG_HIDDEN);
|
|
lv_obj_clear_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);
|
|
lv_label_set_text(s_hook_lbl, "Raccrocher");
|
|
lv_obj_set_style_bg_color(s_hook_btn, lv_palette_main(LV_PALETTE_RED), 0);
|
|
lv_obj_clear_state(s_hook_btn, LV_STATE_DISABLED);
|
|
break;
|
|
case PLIP_HOOK_RINGING:
|
|
lv_obj_clear_flag(s_phone_view, LV_OBJ_FLAG_HIDDEN);
|
|
lv_obj_add_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);
|
|
lv_label_set_text(s_status_lbl, "Appel entrant…");
|
|
lv_label_set_text(s_hook_lbl, "Décrocher");
|
|
lv_obj_set_style_bg_color(s_hook_btn, lv_palette_main(LV_PALETTE_GREEN), 0);
|
|
lv_obj_clear_state(s_hook_btn, LV_STATE_DISABLED);
|
|
break;
|
|
case PLIP_HOOK_ON:
|
|
default:
|
|
lv_obj_clear_flag(s_phone_view, LV_OBJ_FLAG_HIDDEN);
|
|
lv_obj_add_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);
|
|
lv_label_set_text(s_status_lbl, "PLIP — en attente");
|
|
lv_label_set_text(s_hook_lbl, "—");
|
|
lv_obj_set_style_bg_color(s_hook_btn, lv_palette_darken(LV_PALETTE_GREY, 2), 0);
|
|
lv_obj_add_state(s_hook_btn, LV_STATE_DISABLED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// plip_virtual callback — arbitrary task context, so take the lock here.
|
|
static void on_hook_state(plip_hook_state_t st)
|
|
{
|
|
if (bsp_display_lock(LOCK_MS)) {
|
|
apply_state_locked(st);
|
|
bsp_display_unlock();
|
|
}
|
|
}
|
|
|
|
// ---------- widget tree ----------
|
|
|
|
static void build_phone_view(lv_obj_t *parent)
|
|
{
|
|
s_phone_view = lv_obj_create(parent);
|
|
lv_obj_remove_style_all(s_phone_view);
|
|
lv_obj_set_size(s_phone_view, LV_PCT(100), LV_PCT(100));
|
|
lv_obj_set_flex_flow(s_phone_view, LV_FLEX_FLOW_COLUMN);
|
|
lv_obj_set_flex_align(s_phone_view, LV_FLEX_ALIGN_CENTER,
|
|
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
|
|
lv_obj_t *glyph = lv_label_create(s_phone_view);
|
|
lv_label_set_text(glyph, LV_SYMBOL_CALL);
|
|
lv_obj_set_style_text_font(glyph, &lv_font_montserrat_48, 0);
|
|
lv_obj_set_style_text_color(glyph, lv_palette_main(LV_PALETTE_BLUE), 0);
|
|
|
|
s_status_lbl = lv_label_create(s_phone_view);
|
|
lv_label_set_text(s_status_lbl, "PLIP — en attente");
|
|
lv_obj_set_style_pad_top(s_status_lbl, 12, 0);
|
|
}
|
|
|
|
static void build_keypad_view(lv_obj_t *parent)
|
|
{
|
|
s_keypad_view = lv_obj_create(parent);
|
|
lv_obj_remove_style_all(s_keypad_view);
|
|
lv_obj_set_size(s_keypad_view, LV_PCT(100), LV_PCT(100));
|
|
lv_obj_set_flex_flow(s_keypad_view, LV_FLEX_FLOW_COLUMN);
|
|
lv_obj_set_flex_align(s_keypad_view, LV_FLEX_ALIGN_CENTER,
|
|
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
lv_obj_add_flag(s_keypad_view, LV_OBJ_FLAG_HIDDEN);
|
|
|
|
s_number_lbl = lv_label_create(s_keypad_view);
|
|
lv_label_set_text(s_number_lbl, "");
|
|
lv_obj_set_style_text_font(s_number_lbl, &lv_font_montserrat_24, 0);
|
|
lv_obj_set_style_pad_bottom(s_number_lbl, 6, 0);
|
|
|
|
lv_obj_t *grid = lv_obj_create(s_keypad_view);
|
|
lv_obj_remove_style_all(grid);
|
|
lv_obj_set_size(grid, 240, 150);
|
|
lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP);
|
|
lv_obj_set_flex_align(grid, LV_FLEX_ALIGN_CENTER,
|
|
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
|
|
for (size_t i = 0; i < sizeof(KEYS) / sizeof(KEYS[0]); i++) {
|
|
lv_obj_t *btn = lv_button_create(grid);
|
|
lv_obj_set_size(btn, 64, 40);
|
|
lv_obj_add_event_cb(btn, key_event_cb, LV_EVENT_CLICKED,
|
|
(void *) KEYS[i]);
|
|
lv_obj_t *lbl = lv_label_create(btn);
|
|
lv_label_set_text(lbl, KEYS[i]);
|
|
lv_obj_center(lbl);
|
|
}
|
|
}
|
|
|
|
static void build_hook_button(lv_obj_t *parent)
|
|
{
|
|
s_hook_btn = lv_button_create(parent);
|
|
lv_obj_set_size(s_hook_btn, LV_PCT(96), 44);
|
|
lv_obj_align(s_hook_btn, LV_ALIGN_BOTTOM_MID, 0, -4);
|
|
lv_obj_add_event_cb(s_hook_btn, hook_btn_event_cb, LV_EVENT_CLICKED, NULL);
|
|
|
|
s_hook_lbl = lv_label_create(s_hook_btn);
|
|
lv_label_set_text(s_hook_lbl, "—");
|
|
lv_obj_center(s_hook_lbl);
|
|
}
|
|
|
|
esp_err_t plip_ui_init(void)
|
|
{
|
|
if (s_built) return ESP_OK;
|
|
if (!bsp_display_lock(LOCK_MS)) {
|
|
ESP_LOGE(TAG, "display lock failed");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
// The voice app starts the display but never lights the backlight — turn
|
|
// it on (and to full brightness) or the whole UI is invisible.
|
|
bsp_display_brightness_init();
|
|
bsp_display_brightness_set(100);
|
|
|
|
lv_obj_t *scr = lv_screen_active();
|
|
lv_obj_set_style_bg_color(scr, lv_color_hex(0x101020), 0);
|
|
lv_obj_set_style_text_color(scr, lv_color_white(), 0);
|
|
|
|
// Content area above the fixed bottom button.
|
|
lv_obj_t *content = lv_obj_create(scr);
|
|
lv_obj_remove_style_all(content);
|
|
lv_obj_set_size(content, LV_PCT(100), 188);
|
|
lv_obj_align(content, LV_ALIGN_TOP_MID, 0, 0);
|
|
|
|
build_phone_view(content);
|
|
build_keypad_view(content);
|
|
build_hook_button(scr);
|
|
|
|
apply_state_locked(plip_virtual_state());
|
|
bsp_display_unlock();
|
|
|
|
plip_virtual_register_state_cb(on_hook_state);
|
|
s_built = true;
|
|
ESP_LOGI(TAG, "phone UI ready");
|
|
return ESP_OK;
|
|
}
|