ESPHome  2024.9.0
lvgl_esphome.cpp
Go to the documentation of this file.
1 #include "esphome/core/defines.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/helpers.h"
4 #include "esphome/core/hal.h"
5 #include "lvgl_hal.h"
6 #include "lvgl_esphome.h"
7 
8 namespace esphome {
9 namespace lvgl {
10 static const char *const TAG = "lvgl";
11 
12 #if LV_USE_LOG
13 static void log_cb(const char *buf) {
14  esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, TAG, 0, "%.*s", (int) strlen(buf) - 1, buf);
15 }
16 #endif // LV_USE_LOG
17 
18 static const char *const EVENT_NAMES[] = {
19  "NONE",
20  "PRESSED",
21  "PRESSING",
22  "PRESS_LOST",
23  "SHORT_CLICKED",
24  "LONG_PRESSED",
25  "LONG_PRESSED_REPEAT",
26  "CLICKED",
27  "RELEASED",
28  "SCROLL_BEGIN",
29  "SCROLL_END",
30  "SCROLL",
31  "GESTURE",
32  "KEY",
33  "FOCUSED",
34  "DEFOCUSED",
35  "LEAVE",
36  "HIT_TEST",
37  "COVER_CHECK",
38  "REFR_EXT_DRAW_SIZE",
39  "DRAW_MAIN_BEGIN",
40  "DRAW_MAIN",
41  "DRAW_MAIN_END",
42  "DRAW_POST_BEGIN",
43  "DRAW_POST",
44  "DRAW_POST_END",
45  "DRAW_PART_BEGIN",
46  "DRAW_PART_END",
47  "VALUE_CHANGED",
48  "INSERT",
49  "REFRESH",
50  "READY",
51  "CANCEL",
52  "DELETE",
53  "CHILD_CHANGED",
54  "CHILD_CREATED",
55  "CHILD_DELETED",
56  "SCREEN_UNLOAD_START",
57  "SCREEN_LOAD_START",
58  "SCREEN_LOADED",
59  "SCREEN_UNLOADED",
60  "SIZE_CHANGED",
61  "STYLE_CHANGED",
62  "LAYOUT_CHANGED",
63  "GET_SELF_SIZE",
64 };
65 
66 std::string lv_event_code_name_for(uint8_t event_code) {
67  if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) {
68  return EVENT_NAMES[event_code];
69  }
70  return str_sprintf("%2d", event_code);
71 }
72 static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
73  // make sure all coordinates are even
74  if (area->x1 & 1)
75  area->x1--;
76  if (!(area->x2 & 1))
77  area->x2++;
78  if (area->y1 & 1)
79  area->y1--;
80  if (!(area->y2 & 1))
81  area->y2++;
82 }
83 
84 lv_event_code_t lv_api_event; // NOLINT
85 lv_event_code_t lv_update_event; // NOLINT
86 void LvglComponent::dump_config() { ESP_LOGCONFIG(TAG, "LVGL:"); }
87 void LvglComponent::set_paused(bool paused, bool show_snow) {
88  this->paused_ = paused;
89  this->show_snow_ = show_snow;
90  this->snow_line_ = 0;
91  if (!paused && lv_scr_act() != nullptr) {
92  lv_disp_trig_activity(this->disp_); // resets the inactivity time
93  lv_obj_invalidate(lv_scr_act());
94  }
95 }
96 void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) {
97  lv_obj_add_event_cb(obj, callback, event, this);
98 }
99 void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
100  lv_event_code_t event2) {
101  this->add_event_cb(obj, callback, event1);
102  this->add_event_cb(obj, callback, event2);
103 }
104 void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1,
105  lv_event_code_t event2, lv_event_code_t event3) {
106  this->add_event_cb(obj, callback, event1);
107  this->add_event_cb(obj, callback, event2);
108  this->add_event_cb(obj, callback, event3);
109 }
111  this->pages_.push_back(page);
112  page->setup(this->pages_.size() - 1);
113 }
114 void LvglComponent::show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time) {
115  if (index >= this->pages_.size())
116  return;
117  this->current_page_ = index;
118  lv_scr_load_anim(this->pages_[this->current_page_]->obj, anim, time, 0, false);
119 }
120 void LvglComponent::show_next_page(lv_scr_load_anim_t anim, uint32_t time) {
121  if (this->pages_.empty() || (this->current_page_ == this->pages_.size() - 1 && !this->page_wrap_))
122  return;
123  do {
124  this->current_page_ = (this->current_page_ + 1) % this->pages_.size();
125  } while (this->pages_[this->current_page_]->skip); // skip empty pages()
126  this->show_page(this->current_page_, anim, time);
127 }
128 void LvglComponent::show_prev_page(lv_scr_load_anim_t anim, uint32_t time) {
129  if (this->pages_.empty() || (this->current_page_ == 0 && !this->page_wrap_))
130  return;
131  do {
132  this->current_page_ = (this->current_page_ + this->pages_.size() - 1) % this->pages_.size();
133  } while (this->pages_[this->current_page_]->skip); // skip empty pages()
134  this->show_page(this->current_page_, anim, time);
135 }
136 void LvglComponent::draw_buffer_(const lv_area_t *area, const uint8_t *ptr) {
137  for (auto *display : this->displays_) {
138  display->draw_pixels_at(area->x1, area->y1, lv_area_get_width(area), lv_area_get_height(area), ptr,
139  display::COLOR_ORDER_RGB, LV_BITNESS, LV_COLOR_16_SWAP);
140  }
141 }
142 
143 void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
144  if (!this->paused_) {
145  auto now = millis();
146  this->draw_buffer_(area, (const uint8_t *) color_p);
147  ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area),
148  lv_area_get_height(area), (int) (millis() - now));
149  }
150  lv_disp_flush_ready(disp_drv);
151 }
152 IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeout) : timeout_(std::move(timeout)) {
153  parent->add_on_idle_callback([this](uint32_t idle_time) {
154  if (!this->is_idle_ && idle_time > this->timeout_.value()) {
155  this->is_idle_ = true;
156  this->trigger();
157  } else if (this->is_idle_ && idle_time < this->timeout_.value()) {
158  this->is_idle_ = false;
159  }
160  });
161 }
162 
163 #ifdef USE_LVGL_TOUCHSCREEN
164 LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time) {
165  lv_indev_drv_init(&this->drv_);
166  this->drv_.long_press_repeat_time = long_press_repeat_time;
167  this->drv_.long_press_time = long_press_time;
168  this->drv_.type = LV_INDEV_TYPE_POINTER;
169  this->drv_.user_data = this;
170  this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
171  auto *l = static_cast<LVTouchListener *>(d->user_data);
172  if (l->touch_pressed_) {
173  data->point.x = l->touch_point_.x;
174  data->point.y = l->touch_point_.y;
175  data->state = LV_INDEV_STATE_PRESSED;
176  } else {
177  data->state = LV_INDEV_STATE_RELEASED;
178  }
179  };
180 }
182  this->touch_pressed_ = !this->parent_->is_paused() && !tpoints.empty();
183  if (this->touch_pressed_)
184  this->touch_point_ = tpoints[0];
185 }
186 #endif // USE_LVGL_TOUCHSCREEN
187 
188 #ifdef USE_LVGL_KEY_LISTENER
189 LVEncoderListener::LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt) {
190  lv_indev_drv_init(&this->drv_);
191  this->drv_.type = type;
192  this->drv_.user_data = this;
193  this->drv_.long_press_time = lpt;
194  this->drv_.long_press_repeat_time = lprt;
195  this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
196  auto *l = static_cast<LVEncoderListener *>(d->user_data);
197  data->state = l->pressed_ ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
198  data->key = l->key_;
199  data->enc_diff = (int16_t) (l->count_ - l->last_count_);
200  l->last_count_ = l->count_;
201  data->continue_reading = false;
202  };
203 }
204 #endif // USE_LVGL_KEY_LISTENER
205 
206 #ifdef USE_LVGL_BUTTONMATRIX
207 void LvButtonMatrixType::set_obj(lv_obj_t *lv_obj) {
208  LvCompound::set_obj(lv_obj);
209  lv_obj_add_event_cb(
210  lv_obj,
211  [](lv_event_t *event) {
212  auto *self = static_cast<LvButtonMatrixType *>(event->user_data);
213  if (self->key_callback_.size() == 0)
214  return;
215  auto key_idx = lv_btnmatrix_get_selected_btn(self->obj);
216  if (key_idx == LV_BTNMATRIX_BTN_NONE)
217  return;
218  if (self->key_map_.count(key_idx) != 0) {
219  self->send_key_(self->key_map_[key_idx]);
220  return;
221  }
222  const auto *str = lv_btnmatrix_get_btn_text(self->obj, key_idx);
223  auto len = strlen(str);
224  while (len--)
225  self->send_key_(*str++);
226  },
227  LV_EVENT_PRESSED, this);
228 }
229 #endif // USE_LVGL_BUTTONMATRIX
230 
231 #ifdef USE_LVGL_KEYBOARD
232 static const char *const KB_SPECIAL_KEYS[] = {
233  "abc", "ABC", "1#",
234  // maybe add other special keys here
235 };
236 
237 void LvKeyboardType::set_obj(lv_obj_t *lv_obj) {
238  LvCompound::set_obj(lv_obj);
239  lv_obj_add_event_cb(
240  lv_obj,
241  [](lv_event_t *event) {
242  auto *self = static_cast<LvKeyboardType *>(event->user_data);
243  if (self->key_callback_.size() == 0)
244  return;
245 
246  auto key_idx = lv_btnmatrix_get_selected_btn(self->obj);
247  if (key_idx == LV_BTNMATRIX_BTN_NONE)
248  return;
249  const char *txt = lv_btnmatrix_get_btn_text(self->obj, key_idx);
250  if (txt == nullptr)
251  return;
252  for (const auto *kb_special_key : KB_SPECIAL_KEYS) {
253  if (strcmp(txt, kb_special_key) == 0)
254  return;
255  }
256  while (*txt != 0)
257  self->send_key_(*txt++);
258  },
259  LV_EVENT_PRESSED, this);
260 }
261 #endif // USE_LVGL_KEYBOARD
262 
264  // length of 2 lines in 32 bit units
265  // we write 2 lines for the benefit of displays that won't write one line at a time.
266  size_t line_len = this->disp_drv_.hor_res * LV_COLOR_DEPTH / 8 / 4 * 2;
267  for (size_t i = 0; i != line_len; i++) {
268  ((uint32_t *) (this->draw_buf_.buf1))[i] = random_uint32();
269  }
270  lv_area_t area;
271  area.x1 = 0;
272  area.x2 = this->disp_drv_.hor_res - 1;
273  if (this->snow_line_ == this->disp_drv_.ver_res / 2) {
274  area.y1 = static_cast<lv_coord_t>(random_uint32() % (this->disp_drv_.ver_res / 2) * 2);
275  } else {
276  area.y1 = this->snow_line_++ * 2;
277  }
278  // write 2 lines
279  area.y2 = area.y1 + 1;
280  this->draw_buffer_(&area, (const uint8_t *) this->draw_buf_.buf1);
281 }
282 
284  ESP_LOGCONFIG(TAG, "LVGL Setup starts");
285 #if LV_USE_LOG
286  lv_log_register_print_cb(log_cb);
287 #endif
288  lv_init();
289  lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id());
290  lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id());
291  auto *display = this->displays_[0];
292  size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_;
293  auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
294  auto *buf = lv_custom_mem_alloc(buf_bytes);
295  if (buf == nullptr) {
296 #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR
297  ESP_LOGE(TAG, "Malloc failed to allocate %zu bytes", buf_bytes);
298 #endif
299  this->mark_failed();
300  this->status_set_error("Memory allocation failure");
301  return;
302  }
303  lv_disp_draw_buf_init(&this->draw_buf_, buf, nullptr, buffer_pixels);
304  lv_disp_drv_init(&this->disp_drv_);
305  this->disp_drv_.draw_buf = &this->draw_buf_;
306  this->disp_drv_.user_data = this;
307  this->disp_drv_.full_refresh = this->full_refresh_;
308  this->disp_drv_.flush_cb = static_flush_cb;
309  this->disp_drv_.rounder_cb = rounder_cb;
310  switch (display->get_rotation()) {
312  break;
314  this->disp_drv_.sw_rotate = true;
315  this->disp_drv_.rotated = LV_DISP_ROT_90;
316  break;
318  this->disp_drv_.sw_rotate = true;
319  this->disp_drv_.rotated = LV_DISP_ROT_180;
320  break;
322  this->disp_drv_.sw_rotate = true;
323  this->disp_drv_.rotated = LV_DISP_ROT_270;
324  break;
325  }
326  display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);
327  this->disp_drv_.hor_res = (lv_coord_t) display->get_width();
328  this->disp_drv_.ver_res = (lv_coord_t) display->get_height();
329  ESP_LOGV(TAG, "sw_rotate = %d, rotated=%d", this->disp_drv_.sw_rotate, this->disp_drv_.rotated);
330  this->disp_ = lv_disp_drv_register(&this->disp_drv_);
331  for (const auto &v : this->init_lambdas_)
332  v(this);
333  this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0);
334  lv_disp_trig_activity(this->disp_);
335  ESP_LOGCONFIG(TAG, "LVGL Setup complete");
336 }
338  // update indicators
339  if (this->paused_) {
340  return;
341  }
342  this->idle_callbacks_.call(lv_disp_get_inactive_time(this->disp_));
343 }
345  if (this->paused_) {
346  if (this->show_snow_)
347  this->write_random_();
348  }
349  lv_timer_handler_run_in_period(5);
350 }
352  if (!lv_is_initialized()) {
353  ESP_LOGE(TAG, "LVGL call before component is initialised");
354  return true;
355  }
356  return false;
357 }
358 
359 #ifdef USE_LVGL_IMAGE
360 lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) {
361  if (img_dsc == nullptr)
362  img_dsc = new lv_img_dsc_t(); // NOLINT
363  img_dsc->header.always_zero = 0;
364  img_dsc->header.reserved = 0;
365  img_dsc->header.w = src->get_width();
366  img_dsc->header.h = src->get_height();
367  img_dsc->data = src->get_data_start();
368  img_dsc->data_size = image_type_to_width_stride(img_dsc->header.w * img_dsc->header.h, src->get_type());
369  switch (src->get_type()) {
371  img_dsc->header.cf = LV_IMG_CF_ALPHA_1BIT;
372  break;
373 
375  img_dsc->header.cf = LV_IMG_CF_ALPHA_8BIT;
376  break;
377 
379  img_dsc->header.cf = LV_IMG_CF_RGB888;
380  break;
381 
383 #if LV_COLOR_DEPTH == 16
384  img_dsc->header.cf = src->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
385 #else
386  img_dsc->header.cf = LV_IMG_CF_RGB565;
387 #endif
388  break;
389 
391 #if LV_COLOR_DEPTH == 32
392  img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR;
393 #else
394  img_dsc->header.cf = LV_IMG_CF_RGBA8888;
395 #endif
396  break;
397  }
398  return img_dsc;
399 }
400 #endif // USE_LVGL_IMAGE
401 
402 #ifdef USE_LVGL_ANIMIMG
403 void lv_animimg_stop(lv_obj_t *obj) {
404  auto *animg = (lv_animimg_t *) obj;
405  int32_t duration = animg->anim.time;
406  lv_animimg_set_duration(obj, 0);
407  lv_animimg_start(obj);
408  lv_animimg_set_duration(obj, duration);
409 }
410 #endif
411 void LvglComponent::static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
412  reinterpret_cast<LvglComponent *>(disp_drv->user_data)->flush_cb_(disp_drv, area, color_p);
413 }
414 } // namespace lvgl
415 } // namespace esphome
416 
417 size_t lv_millis(void) { return esphome::millis(); }
418 
419 #if defined(USE_HOST) || defined(USE_RP2040) || defined(USE_ESP8266)
420 void *lv_custom_mem_alloc(size_t size) {
421  auto *ptr = malloc(size); // NOLINT
422  if (ptr == nullptr) {
423 #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR
424  esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size);
425 #endif
426  }
427  return ptr;
428 }
429 void lv_custom_mem_free(void *ptr) { return free(ptr); } // NOLINT
430 void *lv_custom_mem_realloc(void *ptr, size_t size) { return realloc(ptr, size); } // NOLINT
431 #else
432 static unsigned cap_bits = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; // NOLINT
433 
434 void *lv_custom_mem_alloc(size_t size) {
435  void *ptr;
436  ptr = heap_caps_malloc(size, cap_bits);
437  if (ptr == nullptr) {
438  cap_bits = MALLOC_CAP_8BIT;
439  ptr = heap_caps_malloc(size, cap_bits);
440  }
441  if (ptr == nullptr) {
442 #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR
443  esphome::ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size);
444 #endif
445  return nullptr;
446  }
447 #ifdef ESPHOME_LOG_HAS_VERBOSE
448  esphome::ESP_LOGV(esphome::lvgl::TAG, "allocate %zu - > %p", size, ptr);
449 #endif
450  return ptr;
451 }
452 
453 void lv_custom_mem_free(void *ptr) {
454 #ifdef ESPHOME_LOG_HAS_VERBOSE
455  esphome::ESP_LOGV(esphome::lvgl::TAG, "free %p", ptr);
456 #endif
457  if (ptr == nullptr)
458  return;
459  heap_caps_free(ptr);
460 }
461 
462 void *lv_custom_mem_realloc(void *ptr, size_t size) {
463 #ifdef ESPHOME_LOG_HAS_VERBOSE
464  esphome::ESP_LOGV(esphome::lvgl::TAG, "realloc %p: %zu", ptr, size);
465 #endif
466  return heap_caps_realloc(ptr, size, cap_bits);
467 }
468 #endif
void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event)
std::string lv_event_code_name_for(uint8_t event_code)
void add_on_idle_callback(std::function< void(uint32_t)> &&callback)
Definition: lvgl_esphome.h:123
void set_paused(bool paused, bool show_snow)
bool has_transparency() const
Definition: image.h:46
void set_obj(lv_obj_t *lv_obj) override
int image_type_to_width_stride(int width, ImageType type)
Definition: image.h:32
ImageType get_type() const
Definition: image.cpp:129
std::vector< TouchPoint > TouchPoints_t
Definition: touchscreen.h:30
void * lv_custom_mem_alloc(size_t size)
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition: helpers.cpp:193
LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time)
STL namespace.
LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt)
void(_lv_event_t *) event_callback_t
Definition: lvgl_esphome.h:75
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
Definition: automation.h:95
void lv_custom_mem_free(void *ptr)
lv_event_code_t lv_update_event
lv_event_code_t lv_api_event
size_t lv_millis(void)
void update(const touchscreen::TouchPoints_t &tpoints) override
void setup(size_t index)
Definition: lvgl_esphome.h:64
virtual void set_obj(lv_obj_t *lv_obj)
Definition: lvgl_esphome.h:56
lv_img_dsc_t * lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc)
int get_width() const override
Definition: image.cpp:127
const uint8_t * get_data_start()
Definition: image.h:40
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:312
void add_page(LvPageType *page)
uint8_t type
bool lv_is_pre_initialise()
void set_obj(lv_obj_t *lv_obj) override
static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
Definition: log.cpp:11
IdleTrigger(LvglComponent *parent, TemplatableValue< uint32_t > timeout)
TemplatableValue< uint32_t > timeout_
Definition: lvgl_esphome.h:179
uint8_t l
Definition: bl0906.h:207
void draw_buffer_(const lv_area_t *area, const uint8_t *ptr)
std::string size_t len
Definition: helpers.h:292
std::vector< LvPageType * > pages_
Definition: lvgl_esphome.h:161
int get_height() const override
Definition: image.cpp:128
void * lv_custom_mem_realloc(void *ptr, size_t size)
void show_next_page(lv_scr_load_anim_t anim, uint32_t time)
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time)
void show_prev_page(lv_scr_load_anim_t anim, uint32_t time)
void lv_animimg_stop(lv_obj_t *obj)
std::vector< display::Display * > displays_
Definition: lvgl_esphome.h:156