Files
ESP32_ZACUS/box3_voice/main/plip_ui.c
T
Claude Worker claude2 87046eadf0
CI / platformio (pull_request) Failing after 9m37s
fix(box3): BOX-3 BSP + backlight
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.
2026-06-10 13:20:59 +02:00

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;
}