2026-04-11 14:28:34 +08:00
|
|
|
#include <errno.h>
|
2026-04-11 13:41:35 +08:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
|
|
|
|
|
#include <app_event_manager.h>
|
|
|
|
|
|
2026-04-11 14:28:34 +08:00
|
|
|
#define MODULE display_module
|
2026-04-11 13:41:35 +08:00
|
|
|
#include <caf/events/module_state_event.h>
|
|
|
|
|
#include <caf/events/power_event.h>
|
|
|
|
|
|
2026-04-11 14:28:34 +08:00
|
|
|
#include <lvgl_zephyr.h>
|
2026-04-11 13:41:35 +08:00
|
|
|
#include <zephyr/device.h>
|
|
|
|
|
#include <zephyr/drivers/display.h>
|
|
|
|
|
#include <zephyr/drivers/led.h>
|
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
|
|
2026-04-11 16:40:54 +08:00
|
|
|
#include "bat_state_event.h"
|
2026-04-25 15:40:49 +08:00
|
|
|
#include "ble_bond_multi_event.h"
|
2026-04-13 16:43:17 +08:00
|
|
|
#include "datetime_event.h"
|
2026-04-11 16:40:54 +08:00
|
|
|
#include "hid_led_event.h"
|
2026-04-17 19:12:57 +08:00
|
|
|
#include "module_lifecycle.h"
|
2026-04-11 16:40:54 +08:00
|
|
|
#include "mode_switch_event.h"
|
2026-04-23 15:12:29 +08:00
|
|
|
#include "settings_mode_event.h"
|
|
|
|
|
#include "settings_view_event.h"
|
2026-04-13 16:43:17 +08:00
|
|
|
#include "theme_rgb_update_event.h"
|
2026-04-13 15:56:45 +08:00
|
|
|
#include "theme_color.h"
|
2026-04-23 18:46:55 +08:00
|
|
|
#include "ui/ui_page.h"
|
2026-04-11 14:28:34 +08:00
|
|
|
#include "ui/ui_main.h"
|
2026-04-23 15:12:29 +08:00
|
|
|
#include "ui/ui_settings.h"
|
2026-04-11 13:41:35 +08:00
|
|
|
|
2026-04-11 14:28:34 +08:00
|
|
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
2026-04-11 13:41:35 +08:00
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
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;
|
2026-04-23 18:46:55 +08:00
|
|
|
struct ui_settings_page *settings_page;
|
2026-04-23 15:12:29 +08:00
|
|
|
bool settings_active;
|
2026-04-17 19:12:57 +08:00
|
|
|
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",
|
2026-04-11 16:40:54 +08:00
|
|
|
};
|
2026-04-11 13:41:35 +08:00
|
|
|
|
2026-04-23 18:46:55 +08:00
|
|
|
static struct ui_page *main_page(void)
|
|
|
|
|
{
|
|
|
|
|
return ui_main_page_get(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 14:28:34 +08:00
|
|
|
static int backlight_set(bool on)
|
2026-04-11 13:41:35 +08:00
|
|
|
{
|
2026-04-11 14:28:34 +08:00
|
|
|
if (on) {
|
2026-04-17 19:12:57 +08:00
|
|
|
return led_on(ctx.backlight_dev, ctx.backlight_idx);
|
2026-04-11 13:41:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
return led_off(ctx.backlight_dev, ctx.backlight_idx);
|
2026-04-11 13:41:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_init(void)
|
2026-04-11 13:41:35 +08:00
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
LOG_INF("Display init on %s", ctx.display_dev->name);
|
2026-04-11 13:41:35 +08:00
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!device_is_ready(ctx.display_dev)) {
|
|
|
|
|
LOG_ERR("Display device %s not ready", ctx.display_dev->name);
|
2026-04-11 13:41:35 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!device_is_ready(ctx.backlight_dev)) {
|
|
|
|
|
LOG_ERR("Backlight device %s not ready", ctx.backlight_dev->name);
|
2026-04-11 13:41:35 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 14:28:34 +08:00
|
|
|
err = backlight_set(false);
|
2026-04-11 13:41:35 +08:00
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("Backlight off failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_start(void)
|
2026-04-11 13:41:35 +08:00
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_running(&ctx.lc)) {
|
2026-04-11 13:41:35 +08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!ctx.lvgl_initialized) {
|
2026-04-11 14:28:34 +08:00
|
|
|
err = lvgl_init();
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("lvgl_init failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2026-04-11 13:41:35 +08:00
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.lvgl_initialized = true;
|
2026-04-11 14:28:34 +08:00
|
|
|
|
|
|
|
|
lvgl_lock();
|
2026-04-23 18:46:55 +08:00
|
|
|
ui_page_init(main_page());
|
2026-04-11 14:28:34 +08:00
|
|
|
lvgl_unlock();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 18:46:55 +08:00
|
|
|
lvgl_lock();
|
|
|
|
|
if (!ctx.settings_active) {
|
|
|
|
|
ui_page_init(main_page());
|
|
|
|
|
}
|
|
|
|
|
lvgl_unlock();
|
|
|
|
|
|
2026-04-11 14:28:34 +08:00
|
|
|
err = backlight_set(true);
|
2026-04-11 13:41:35 +08:00
|
|
|
if (err) {
|
|
|
|
|
LOG_ERR("Backlight enable failed (%d)", err);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
err = display_blanking_off(ctx.display_dev);
|
2026-04-11 13:41:35 +08:00
|
|
|
if (err) {
|
2026-04-11 14:28:34 +08:00
|
|
|
LOG_ERR("display_blanking_off failed (%d)", err);
|
|
|
|
|
(void)backlight_set(false);
|
2026-04-11 13:41:35 +08:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 14:28:34 +08:00
|
|
|
LOG_INF("LVGL display started");
|
2026-04-11 13:41:35 +08:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_stop(void)
|
2026-04-11 13:41:35 +08:00
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!module_lifecycle_is_running(&ctx.lc)) {
|
|
|
|
|
return 0;
|
2026-04-11 13:41:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
(void)display_blanking_on(ctx.display_dev);
|
2026-04-11 14:28:34 +08:00
|
|
|
(void)backlight_set(false);
|
|
|
|
|
LOG_INF("LVGL display paused");
|
2026-04-17 19:12:57 +08:00
|
|
|
|
|
|
|
|
return 0;
|
2026-04-11 13:41:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 16:40:54 +08:00
|
|
|
static void refresh_ui(void)
|
|
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!ctx.lvgl_initialized) {
|
2026-04-11 16:40:54 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lvgl_lock();
|
2026-04-23 18:46:55 +08:00
|
|
|
if (ctx.settings_active && (ctx.settings_page != NULL)) {
|
|
|
|
|
ui_settings_show(ctx.settings_page, true);
|
2026-04-23 15:12:29 +08:00
|
|
|
} else {
|
2026-04-23 18:46:55 +08:00
|
|
|
ui_page_refresh(main_page());
|
2026-04-23 15:12:29 +08:00
|
|
|
}
|
2026-04-11 16:40:54 +08:00
|
|
|
lvgl_unlock();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 13:41:35 +08:00
|
|
|
static bool app_event_handler(const struct app_event_header *aeh)
|
|
|
|
|
{
|
2026-04-11 16:40:54 +08:00
|
|
|
if (is_bat_state_event(aeh)) {
|
|
|
|
|
const struct bat_state_event *event = cast_bat_state_event(aeh);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.ui_model.battery_level = event->soc;
|
|
|
|
|
ctx.ui_model.charging = event->charging;
|
|
|
|
|
ctx.ui_model.full = event->full;
|
2026-04-11 16:40:54 +08:00
|
|
|
refresh_ui();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_mode_switch_event(aeh)) {
|
|
|
|
|
const struct mode_switch_event *event = cast_mode_switch_event(aeh);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.ui_model.mode = event->mode;
|
2026-04-11 16:40:54 +08:00
|
|
|
refresh_ui();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 15:40:49 +08:00
|
|
|
if (is_ble_bond_multi_event(aeh)) {
|
|
|
|
|
refresh_ui();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 16:40:54 +08:00
|
|
|
if (is_hid_led_event(aeh)) {
|
|
|
|
|
const struct hid_led_event *event = cast_hid_led_event(aeh);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.ui_model.led_mask = event->led_bm;
|
2026-04-11 16:40:54 +08:00
|
|
|
refresh_ui();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 16:43:17 +08:00
|
|
|
if (is_theme_rgb_update_event(aeh)) {
|
|
|
|
|
const struct theme_rgb_update_event *event =
|
|
|
|
|
cast_theme_rgb_update_event(aeh);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.ui_model.theme_color = (lv_color_t)LV_COLOR_MAKE(event->theme.r,
|
|
|
|
|
event->theme.g,
|
|
|
|
|
event->theme.b);
|
2026-04-23 15:12:29 +08:00
|
|
|
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;
|
2026-04-23 18:46:55 +08:00
|
|
|
if (!ctx.settings_active) {
|
|
|
|
|
ctx.settings_page = NULL;
|
|
|
|
|
}
|
2026-04-23 15:12:29 +08:00
|
|
|
if (!ctx.lvgl_initialized) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lvgl_lock();
|
2026-04-23 18:46:55 +08:00
|
|
|
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());
|
2026-04-23 15:12:29 +08:00
|
|
|
}
|
|
|
|
|
lvgl_unlock();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_settings_view_event(aeh)) {
|
|
|
|
|
const struct settings_view_event *event =
|
|
|
|
|
cast_settings_view_event(aeh);
|
|
|
|
|
|
2026-04-23 18:46:55 +08:00
|
|
|
ctx.settings_page = event->page;
|
2026-04-23 15:12:29 +08:00
|
|
|
if (ctx.settings_active && ctx.lvgl_initialized) {
|
|
|
|
|
lvgl_lock();
|
2026-04-23 18:46:55 +08:00
|
|
|
ui_settings_show(ctx.settings_page, event->animate);
|
2026-04-23 15:12:29 +08:00
|
|
|
lvgl_unlock();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 16:43:17 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_datetime_event(aeh)) {
|
|
|
|
|
const struct datetime_event *event = cast_datetime_event(aeh);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
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';
|
2026-04-13 16:43:17 +08:00
|
|
|
refresh_ui();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 13:41:35 +08:00
|
|
|
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)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
|
2026-04-11 13:41:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_power_down_event(aeh)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
|
|
|
|
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
|
2026-04-11 13:41:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_wake_up_event(aeh)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
|
|
|
|
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
|
2026-04-11 13:41:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
2026-04-11 16:40:54 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, bat_state_event);
|
2026-04-25 15:40:49 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, ble_bond_multi_event);
|
2026-04-13 16:43:17 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, datetime_event);
|
2026-04-11 16:40:54 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, hid_led_event);
|
2026-04-11 13:41:35 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
2026-04-11 16:40:54 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event);
|
2026-04-23 15:12:29 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, settings_mode_event);
|
|
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, settings_view_event);
|
2026-04-13 16:43:17 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event);
|
2026-04-11 13:41:35 +08:00
|
|
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
|
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|