#ifndef DISPLAY_H #define DISPLAY_H #include #include #include #include #include "panel.h" #define LVGL_TICK_PERIOD_MS 5 #define LVGL_TASK_STACK_SIZE (4 * 1024) #define LVGL_TASK_PRIORITY 2 class Display { struct ScopedLock { explicit ScopedLock() { _lock_acquire(&lv_lock_); } ~ScopedLock() { _lock_release(&lv_lock_); } // LVGL library is not thread-safe, this example calls LVGL APIs from tasks. // We must use a mutex to protect it. static _lock_t lv_lock_; }; struct Timer { explicit Timer(esp_timer_create_args_t args) : args_(args) { ESP_LOGI(TAG, "Creating esp_timer with name: %s", args.name); ESP_ERROR_CHECK(esp_timer_create(&args, &esp_timer_)); } ~Timer() { ESP_ERROR_CHECK(esp_timer_delete(esp_timer_)); } [[maybe_unused]] esp_timer_create_args_t args_{}; esp_timer_handle_t esp_timer_ = nullptr; }; struct TimeKeeper { using TimerHandle = Timer *; // Timers should only be accessed using this method. // For this reason managed_timers_ is private. TimerHandle get_handle(const char *name) { return &managed_timers_.at(name); } TimerHandle operator[](const char *name) { return get_handle(name); } TimerHandle create_timer(esp_timer_create_args_t args) { auto rt = managed_timers_.emplace(args.name, args); if (!rt.second) { ESP_LOGE(TAG, "Failed to insert timer into Display::managed_timers_"); return nullptr; } return &rt.first->second; } [[maybe_unused]] void stop_timer(const char *name) { ESP_ERROR_CHECK(esp_timer_stop(get_handle(name)->esp_timer_)); } [[maybe_unused]] void delete_timer(const char *name) { managed_timers_.erase(name); } [[maybe_unused]] void start_new_timer_periodic(esp_timer_create_args_t args, uint64_t period) { start_timer_periodic(create_timer(args)->args_.name, period); } [[maybe_unused]] void start_timer_periodic(const char *name, uint64_t period) { ESP_ERROR_CHECK( esp_timer_start_periodic(get_handle(name)->esp_timer_, period)); } [[maybe_unused]] void start_new_timer_once(esp_timer_create_args_t args, uint64_t timeout_us) { start_timer_once(create_timer(args)->args_.name, timeout_us); } [[maybe_unused]] void start_timer_once(const char *name, uint64_t timeout_us) { ESP_ERROR_CHECK( esp_timer_start_once(get_handle(name)->esp_timer_, timeout_us)); } private: // Timers should only be accessed using the get_handle method. // ~Timer() will delete the timer if called. std::unordered_map managed_timers_; }; public: explicit Display(IPanelDevice &device); ~Display() = default; [[nodiscard]] inline const lv_display_t *get() const { return lv_display_; } [[nodiscard]] inline lv_display_t *get() { return lv_display_; } [[nodiscard]] inline const lv_display_t *operator*() const { return get(); } [[nodiscard]] inline lv_display_t *operator*() { return get(); } void set_text(const char *text, const char *name, lv_label_long_mode_t long_mode = LV_LABEL_LONG_SCROLL_CIRCULAR, lv_align_t align = LV_ALIGN_TOP_MID); static bool lvgl_flush_ready(esp_lcd_panel_io_handle_t panel, esp_lcd_panel_io_event_data_t *data, void *user_ctx); static void lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *px_map); static void lvgl_increase_tick(void *arg); [[noreturn]] static void lvgl_port_task(void *arg); // Public static TimeKeeper for managing ESP timers across all displays. static TimeKeeper timers_; private: void register_draw_buffer(); static void register_lvgl_tick_timer(); Panel panel_; lv_display_t *lv_display_; // Draw buffer associated with the lv_display_t. void *lv_buf_; // Objects stored in the screen associated with this display. // @sa Display::set_text // @sa lv_display_get_screen_active std::unordered_map lv_objects_; }; #endif // DISPLAY_H