#include "lvgl/lvgl.h" #include "lv_drivers/display/fbdev.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DISP_BUF_SIZE (320 * 240) LV_IMG_DECLARE(logo); /* ===================== UI Objects ===================== */ static lv_obj_t *hours_label; static lv_obj_t *minutes_label; static lv_obj_t *seconds_label; static lv_obj_t *colon1_label; static lv_obj_t *colon2_label; static lv_obj_t *eth_ip_label; static lv_obj_t *wifi_ip_label; static lv_obj_t *custom_text_label; static volatile bool show_custom_text = false; static volatile bool custom_text_blink = false; static volatile bool custom_text_visible = true; static char custom_text_buf[256] = {0}; #define LVGL_CMD_FIFO "/tmp/lvgl_cmd" #define LOGO_SHOW_SECONDS 5 /* ===================== Dark Theme ===================== */ static void apply_dark_theme(void) { lv_obj_t *scr = lv_disp_get_scr_act(NULL); lv_obj_set_style_bg_color(scr, lv_color_black(), 0); lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); } /* ===================== Time UI ===================== */ static void create_time_labels(lv_obj_t *parent) { static lv_style_t style; lv_style_init(&style); lv_style_set_text_font(&style, &lv_font_montserrat_48); lv_style_set_text_color(&style, lv_color_white()); hours_label = lv_label_create(parent); minutes_label = lv_label_create(parent); seconds_label = lv_label_create(parent); colon1_label = lv_label_create(parent); colon2_label = lv_label_create(parent); lv_obj_add_style(hours_label, &style, 0); lv_obj_add_style(minutes_label, &style, 0); lv_obj_add_style(seconds_label, &style, 0); lv_obj_add_style(colon1_label, &style, 0); lv_obj_add_style(colon2_label, &style, 0); lv_label_set_text(colon1_label, ":"); lv_label_set_text(colon2_label, ":"); lv_obj_align(hours_label, LV_ALIGN_CENTER, -80, -25); lv_obj_align_to(colon1_label, hours_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0); lv_obj_align_to(minutes_label, colon1_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0); lv_obj_align_to(colon2_label, minutes_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0); lv_obj_align_to(seconds_label, colon2_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0); lv_obj_add_flag(hours_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(minutes_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(seconds_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(colon1_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(colon2_label, LV_OBJ_FLAG_HIDDEN); } static void update_time_labels(int h, int m, int s) { char buf[4]; snprintf(buf, sizeof(buf), "%02d", h); lv_label_set_text(hours_label, buf); snprintf(buf, sizeof(buf), "%02d", m); lv_label_set_text(minutes_label, buf); snprintf(buf, sizeof(buf), "%02d", s); lv_label_set_text(seconds_label, buf); lv_obj_align_to(colon1_label, hours_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0); lv_obj_align_to(minutes_label, colon1_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0); lv_obj_align_to(colon2_label, minutes_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0); lv_obj_align_to(seconds_label, colon2_label, LV_ALIGN_OUT_RIGHT_MID,6, 0); } static void create_custom_text_label(lv_obj_t *parent) { static lv_style_t style; lv_style_init(&style); lv_style_set_text_font(&style, &lv_font_montserrat_48); lv_style_set_text_color(&style, lv_color_white()); custom_text_label = lv_label_create(parent); lv_obj_add_style(custom_text_label, &style, 0); lv_label_set_text(custom_text_label, ""); lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25); lv_obj_add_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN); } static void show_time_row(bool visible) { if(visible) { lv_obj_clear_flag(hours_label, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(minutes_label, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(seconds_label, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(colon1_label, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(colon2_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN); } else { lv_obj_add_flag(hours_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(minutes_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(seconds_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(colon1_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(colon2_label, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN); } } static void apply_show_text_async(void *arg) { const char *text = (const char *)arg; show_custom_text = true; custom_text_visible = true; lv_label_set_text(custom_text_label, text ? text : ""); lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25); show_time_row(false); lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN); } static void apply_show_ap_async(void *arg) { (void)arg; show_custom_text = true; custom_text_blink = true; custom_text_visible = true; lv_label_set_text(custom_text_label, "AP"); lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25); show_time_row(false); lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN); } static void apply_show_time_async(void *arg) { (void)arg; show_custom_text = false; custom_text_blink = false; custom_text_visible = true; show_time_row(true); } static void *fifo_thread(void *arg) { (void)arg; unlink(LVGL_CMD_FIFO); if (mkfifo(LVGL_CMD_FIFO, 0666) < 0 && errno != EEXIST) { perror("mkfifo lvgl_cmd"); return NULL; } while (1) { int fd = open(LVGL_CMD_FIFO, O_RDONLY); if (fd < 0) { usleep(200000); continue; } FILE *fp = fdopen(fd, "r"); if (!fp) { close(fd); usleep(200000); continue; } char line[256]; while (fgets(line, sizeof(line), fp)) { size_t len = strlen(line); while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { line[--len] = '\0'; } if (strcmp(line, "show_ap") == 0) { lv_async_call(apply_show_ap_async, NULL); } else if (strcmp(line, "show_conn") == 0) { strncpy(custom_text_buf, "Conn", sizeof(custom_text_buf) - 1); custom_text_buf[sizeof(custom_text_buf) - 1] = '\0'; custom_text_blink = true; lv_async_call(apply_show_text_async, custom_text_buf); } else if (strcmp(line, "show_err0") == 0) { strncpy(custom_text_buf, "Err0", sizeof(custom_text_buf) - 1); custom_text_buf[sizeof(custom_text_buf) - 1] = '\0'; custom_text_blink = false; lv_async_call(apply_show_text_async, custom_text_buf); } else if (strncmp(line, "show_pin:", 9) == 0) { strncpy(custom_text_buf, line + 9, sizeof(custom_text_buf) - 1); custom_text_buf[sizeof(custom_text_buf) - 1] = '\0'; custom_text_blink = true; lv_async_call(apply_show_text_async, custom_text_buf); } else if (strncmp(line, "show_text:", 10) == 0) { strncpy(custom_text_buf, line + 10, sizeof(custom_text_buf) - 1); custom_text_buf[sizeof(custom_text_buf) - 1] = '\0'; custom_text_blink = false; lv_async_call(apply_show_text_async, custom_text_buf); } else if (strcmp(line, "show_time") == 0) { lv_async_call(apply_show_time_async, NULL); } } fclose(fp); } return NULL; } /* ===================== IP UI ===================== */ static void create_ip_labels(lv_obj_t *parent) { static lv_style_t style; lv_style_init(&style); lv_style_set_text_font(&style, &lv_font_montserrat_20); lv_style_set_text_color(&style, lv_color_white()); eth_ip_label = lv_label_create(parent); wifi_ip_label = lv_label_create(parent); lv_obj_add_style(eth_ip_label, &style, 0); lv_obj_add_style(wifi_ip_label, &style, 0); lv_label_set_text(eth_ip_label, "ETH : --"); lv_label_set_text(wifi_ip_label, "WiFi: --"); lv_obj_align(eth_ip_label, LV_ALIGN_CENTER, 0, 35); lv_obj_align(wifi_ip_label, LV_ALIGN_CENTER, 0, 60); lv_obj_add_flag(eth_ip_label, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(wifi_ip_label, LV_OBJ_FLAG_HIDDEN); } /* ===================== IP Logic ===================== */ static void get_ip(const char *ifname, char *out, size_t len) { struct ifaddrs *ifaddr, *ifa; out[0] = 0; if (getifaddrs(&ifaddr)) return; for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) continue; if (ifa->ifa_addr->sa_family == AF_INET && strcmp(ifa->ifa_name, ifname) == 0) { inet_ntop(AF_INET, &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, out, len); break; } } freeifaddrs(ifaddr); } static void update_ip_ui(void) { char eth[32] = {0}, wifi[32] = {0}; get_ip("enP4p65s0", eth, sizeof(eth)); if (eth[0] == '\0') { get_ip("eth0", eth, sizeof(eth)); } get_ip("wlan0", wifi, sizeof(wifi)); if (eth[0]) { lv_label_set_text_fmt(eth_ip_label, "ETH : %s", eth); } else { lv_label_set_text(eth_ip_label, "ETH : Not Connected"); } if (wifi[0]) { lv_label_set_text_fmt(wifi_ip_label, "WiFi: %s", wifi); } else { lv_label_set_text(wifi_ip_label, "WiFi: Not Connected"); } } /* ===================== Netlink Thread ===================== */ static void *netlink_thread(void *arg) { int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR, }; bind(sock, (struct sockaddr *)&addr, sizeof(addr)); char buf[4096]; while (1) { recv(sock, buf, sizeof(buf), 0); lv_async_call((lv_async_cb_t)update_ip_ui, NULL); } return NULL; } /* ===================== LVGL Tick ===================== */ uint32_t custom_tick_get(void) { static uint64_t start = 0; struct timeval tv; gettimeofday(&tv, NULL); uint64_t now = tv.tv_sec * 1000ULL + tv.tv_usec / 1000; if (!start) start = now; return now - start; } /* ===================== Main ===================== */ int main(void) { lv_init(); fbdev_init(); static lv_color_t buf[DISP_BUF_SIZE]; static lv_disp_draw_buf_t draw_buf; lv_disp_draw_buf_init(&draw_buf, buf, NULL, DISP_BUF_SIZE); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.draw_buf = &draw_buf; disp_drv.flush_cb = fbdev_flush; disp_drv.hor_res = 320; disp_drv.ver_res = 240; lv_disp_drv_register(&disp_drv); apply_dark_theme(); lv_obj_t *scr = lv_disp_get_scr_act(NULL); lv_obj_t *img = lv_img_create(scr); lv_img_set_src(img, &logo); lv_obj_center(img); create_time_labels(scr); create_custom_text_label(scr); create_ip_labels(scr); pthread_t tid; pthread_create(&tid, NULL, netlink_thread, NULL); pthread_detach(tid); pthread_t fifo_tid; pthread_create(&fifo_tid, NULL, fifo_thread, NULL); pthread_detach(fifo_tid); time_t start = time(NULL); int last_sec = -1; bool shown = false; while (1) { time_t now = time(NULL); if (!shown && now - start >= LOGO_SHOW_SECONDS) { lv_obj_add_flag(img, LV_OBJ_FLAG_HIDDEN); show_time_row(true); lv_obj_clear_flag(eth_ip_label, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(wifi_ip_label, LV_OBJ_FLAG_HIDDEN); update_ip_ui(); shown = true; } struct tm *tm = localtime(&now); if (!show_custom_text && tm->tm_sec != last_sec) { update_time_labels(tm->tm_hour, tm->tm_min, tm->tm_sec); last_sec = tm->tm_sec; } static uint32_t last_blink_ms = 0; uint32_t now_ms = lv_tick_get(); if (custom_text_blink && now_ms - last_blink_ms >= 500) { last_blink_ms = now_ms; custom_text_visible = !custom_text_visible; if (custom_text_visible) { lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN); } else { lv_obj_add_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN); } } lv_timer_handler(); usleep(10000); } }