#include #include #include #define MODULE display_module #include #include #include #include #include #include #include #include "bat_state_event.h" #include "ble_bond_multi_event.h" #include "datetime_event.h" #include "hid_led_event.h" #include "module_lifecycle.h" #include "mode_switch_event.h" #include "settings_mode_event.h" #include "settings_view_event.h" #include "theme_rgb_update_event.h" #include "theme_color.h" #include "ui/ui_page.h" #include "ui/ui_main.h" #include "ui/ui_settings.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); BUILD_ASSERT(DT_HAS_CHOSEN(zephyr_display), "Missing zephyr,display chosen node"); BUILD_ASSERT(DT_NODE_HAS_STATUS(DT_ALIAS(backlight), okay), "Missing backlight alias"); struct display_module_ctx { struct module_lifecycle_ctx lc; const struct device *display_dev; const struct device *backlight_dev; uint32_t backlight_idx; struct ui_main_model ui_model; struct ui_settings_page *settings_page; bool settings_active; bool lvgl_initialized; char date_text[DATETIME_EVENT_DATE_TEXT_LEN]; char time_text[DATETIME_EVENT_TIME_TEXT_LEN]; }; static int do_init(void); static int do_start(void); static int do_stop(void); static const struct module_lifecycle_cfg lifecycle_cfg = { .mode = ML_MODE_POWER, .stopped_state = MODULE_STATE_STANDBY, }; static const struct module_lifecycle_ops lifecycle_ops = { .do_init = do_init, .do_start = do_start, .do_stop = do_stop, }; static struct display_module_ctx ctx = { .lc = { .state = LC_UNINIT, .cfg = &lifecycle_cfg, .ops = &lifecycle_ops, }, .display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)), .backlight_dev = DEVICE_DT_GET(DT_PARENT(DT_ALIAS(backlight))), .backlight_idx = DT_NODE_CHILD_IDX(DT_ALIAS(backlight)), .ui_model = { .theme_color = LV_COLOR_MAKE(BLINKY_THEME_DEFAULT_R, BLINKY_THEME_DEFAULT_G, BLINKY_THEME_DEFAULT_B), .inactive_border_color = LV_COLOR_MAKE(0x3A, 0x44, 0x52), .mode = MODE_SWITCH_BLE, }, .date_text = "1970/01/01", .time_text = "00:00:00", }; static struct ui_page *main_page(void) { return ui_main_page_get(&ctx.ui_model, ctx.date_text, ctx.time_text); } static int backlight_set(bool on) { if (on) { return led_on(ctx.backlight_dev, ctx.backlight_idx); } return led_off(ctx.backlight_dev, ctx.backlight_idx); } static int do_init(void) { int err; LOG_INF("Display init on %s", ctx.display_dev->name); if (!device_is_ready(ctx.display_dev)) { LOG_ERR("Display device %s not ready", ctx.display_dev->name); return -ENODEV; } if (!device_is_ready(ctx.backlight_dev)) { LOG_ERR("Backlight device %s not ready", ctx.backlight_dev->name); return -ENODEV; } err = backlight_set(false); if (err) { LOG_ERR("Backlight off failed (%d)", err); return err; } return 0; } static int do_start(void) { int err; if (module_lifecycle_is_running(&ctx.lc)) { return 0; } if (!ctx.lvgl_initialized) { err = lvgl_init(); if (err) { LOG_ERR("lvgl_init failed (%d)", err); return err; } ctx.lvgl_initialized = true; lvgl_lock(); ui_page_init(main_page()); lvgl_unlock(); } lvgl_lock(); if (!ctx.settings_active) { ui_page_init(main_page()); } lvgl_unlock(); err = backlight_set(true); if (err) { LOG_ERR("Backlight enable failed (%d)", err); return err; } err = display_blanking_off(ctx.display_dev); if (err) { LOG_ERR("display_blanking_off failed (%d)", err); (void)backlight_set(false); return err; } LOG_INF("LVGL display started"); return 0; } static int do_stop(void) { if (!module_lifecycle_is_running(&ctx.lc)) { return 0; } (void)display_blanking_on(ctx.display_dev); (void)backlight_set(false); LOG_INF("LVGL display paused"); return 0; } static void refresh_ui(void) { if (!ctx.lvgl_initialized) { return; } lvgl_lock(); if (ctx.settings_active && (ctx.settings_page != NULL)) { ui_settings_show(ctx.settings_page, true); } else { ui_page_refresh(main_page()); } lvgl_unlock(); } static bool app_event_handler(const struct app_event_header *aeh) { if (is_bat_state_event(aeh)) { const struct bat_state_event *event = cast_bat_state_event(aeh); ctx.ui_model.battery_level = event->soc; ctx.ui_model.charging = event->charging; ctx.ui_model.full = event->full; refresh_ui(); return false; } if (is_mode_switch_event(aeh)) { const struct mode_switch_event *event = cast_mode_switch_event(aeh); ctx.ui_model.mode = event->mode; refresh_ui(); return false; } if (is_ble_bond_multi_event(aeh)) { refresh_ui(); return false; } if (is_hid_led_event(aeh)) { const struct hid_led_event *event = cast_hid_led_event(aeh); ctx.ui_model.led_mask = event->led_bm; refresh_ui(); return false; } if (is_theme_rgb_update_event(aeh)) { const struct theme_rgb_update_event *event = cast_theme_rgb_update_event(aeh); ctx.ui_model.theme_color = (lv_color_t)LV_COLOR_MAKE(event->theme.r, event->theme.g, event->theme.b); refresh_ui(); return false; } if (is_settings_mode_event(aeh)) { const struct settings_mode_event *event = cast_settings_mode_event(aeh); ctx.settings_active = event->active; if (!ctx.settings_active) { ctx.settings_page = NULL; } if (!ctx.lvgl_initialized) { return false; } lvgl_lock(); if (ctx.settings_active) { ui_page_deinit(main_page()); ui_settings_init(); if (ctx.settings_page != NULL) { ui_settings_show(ctx.settings_page, false); } } else { ui_settings_deinit(); ui_page_init(main_page()); ui_page_refresh(main_page()); } lvgl_unlock(); return false; } if (is_settings_view_event(aeh)) { const struct settings_view_event *event = cast_settings_view_event(aeh); ctx.settings_page = event->page; if (ctx.settings_active && ctx.lvgl_initialized) { lvgl_lock(); ui_settings_show(ctx.settings_page, event->animate); lvgl_unlock(); } return false; } if (is_datetime_event(aeh)) { const struct datetime_event *event = cast_datetime_event(aeh); strncpy(ctx.date_text, event->date_text, sizeof(ctx.date_text)); ctx.date_text[sizeof(ctx.date_text) - 1] = '\0'; strncpy(ctx.time_text, event->time_text, sizeof(ctx.time_text)); ctx.time_text[sizeof(ctx.time_text) - 1] = '\0'; refresh_ui(); return false; } if (is_module_state_event(aeh)) { const struct module_state_event *event = cast_module_state_event(aeh); if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); } return false; } if (is_power_down_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)module_set_lifecycle(&ctx.lc, LC_STOPPED); } return false; } if (is_wake_up_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); } return false; } return false; } APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, bat_state_event); APP_EVENT_SUBSCRIBE(MODULE, ble_bond_multi_event); APP_EVENT_SUBSCRIBE(MODULE, datetime_event); APP_EVENT_SUBSCRIBE(MODULE, hid_led_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event); APP_EVENT_SUBSCRIBE(MODULE, settings_mode_event); APP_EVENT_SUBSCRIBE(MODULE, settings_view_event); APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);