diff --git a/README.md b/README.md index d2598c3..10d9573 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Important information -If you like my work, please donate me a star on GitHub and consider sponsoring me!! +If you like my work, please donate me a star on GitHub and consider [sponsoring](https://www.paypal.com/donate/?hosted_button_id=FZDKSLQ46HJTU) me!! ## Introduction @@ -151,6 +151,22 @@ full_screen => {"icon_name", "lifetime", "screen_time"} void full_screen(string iconname, int =D_LIFETIME, int screen_time=D_SCREEN_TIME); ``` +##### bitmap screen + +For 8x32 images as text. You can generate this images with e.g. [Pixel Bitmap Creator (8x32)](https://pixelit.bastelbunker.de/PixelCreator) + +###### service via API + +```c +bitmap_screen => {"[0,4523,0,2342,0,..... (256 values 16bit values rgb565)]", "lifetime", "screen_time"} +``` + +###### Lambda + +```c +void bitmap_screen(string text, int =D_LIFETIME, int screen_time=D_SCREEN_TIME); +``` + #### Display Elements ![elements](./images/elements.png) @@ -470,6 +486,7 @@ ehmtxv2: brightness: 80 # percent time_format: "%H:%M" date_format: "%d.%m." + rtl: false # write vom left to right week_start_monday: true # false equals sunday scroll_count: 2 # scroll long text at least two times scroll_interval: 80 # milliseconds @@ -497,6 +514,8 @@ ehmtxv2: **special_font_xoffset** (optional, pixel): xoffset the text is aligned BASELINE_LEFT, the left defaults to `1` +**rtl** (optional, boolean): if `true` write from right to left (arabic, hebrew etc.). Default ist `false` + **matrix_component** (required, ID): ID of the addressable display **show_dow** (optional, bool): draw the day of week indicator on the bottom of the clock screen. Disable, e.g. if you want larger fonts, defaults to true. @@ -1032,7 +1051,9 @@ sensor: ## Breaking changes -### **nothing yet, since it is new** +### 2023.5.0 + +- removed the rtttl buzzer from the ulanzi easy template, because it often caused reboots! ## EspHoMaTriX in the media @@ -1065,7 +1086,7 @@ THE SOFTWARE IS PROVIDED "AS IS", use at your own risk! - **[aptonline](https://github.com/aptonline)** for his work on the ulanzi hardware - **[wsbtak](https://github.com/wsbtak)** for the work on the ulanzi hardware - **[ofirsnb](https://github.com/ofirsnb)** for his contributions -- **[darkpoet78](https://github.com/darkpoet78/MatrixClockFonts)** for his work on optimized fonts +- **[darkpoet78](https://github.com/darkpoet78/MatrixClockFonts)** for his work on optimized fonts and user support - **[pplucky](https://user-images.githubusercontent.com/16407309/224850723-634c9b2d-55d9-44f2-9f93-765c0485b090.GIF)** for his 8x32 GIF animation - **[dennisse](https://github.com/dennisse)** Auto brightness for the Ulanzi - **[geekofweek](https://github.com/geekofweek)** fixed sample YAML diff --git a/TODO.md b/TODO.md index e990520..aa5a08a 100644 --- a/TODO.md +++ b/TODO.md @@ -2,17 +2,20 @@ ## function +- [ ] screen_time on ms sec base +- [ ] scroll left to right +- [ ] seconds point moveable +- [ ] dynamic bitmap as JSON (works partially) +- [ ] fix find_free_icon to work for bitmap screen - [x] alarm on all screens but full screen - [x] indicator on all screens but full screen and clock - [x] refreshing an icon screen should extend the display time -- [ ] scroll left to right - [x] size of indicator and alarm - [x] center text - [x] alarm independent of screen - [x] del_screen with * and filter by type, do delete all clocks etc. - [x] force_screen with * and filter by type, do delete all clocks etc. - [x] gauge with color and del_gaugeall clocks etc. -- [ ] seconds point moveable - [x] seconds point to clock screen only - [x] rainbow icon and text - [x] del_slot still active? @@ -22,16 +25,16 @@ ## user experience - [ ] blueprints -- [x] Color in blueprints -- [ ] all modes in one blueprint - [ ] external HTML with more blueprint helpers (icons and modes) +- [ ] ~~all modes in one blueprint~~ +- [x] Color in blueprints - [x] default values for all functions - [x] provide sample font from [url](https://www.pentacom.jp/pentacom/bitfontmaker2/) - [x] start animation ## style -- [ ] in screen rename text_color to color +- [x] ~~in screen rename text_color to color~~ - [x] default_font before alarm parameter - [x] screen_time instead of showtime - [x] lifetime instead of durations diff --git a/components/ehmtxv2/EHMTX.cpp b/components/ehmtxv2/EHMTX.cpp index d01cade..c12d0dd 100644 --- a/components/ehmtxv2/EHMTX.cpp +++ b/components/ehmtxv2/EHMTX.cpp @@ -9,6 +9,7 @@ namespace esphome this->display_indicator = 0; this->display_alarm = 0; this->clock_time = 10; + this->clock_interval = 90; this->hold_time = 10; this->icon_count = 0; this->hue_ = 0; @@ -20,7 +21,9 @@ namespace esphome this->alarm_color = Color(CA_RED, CA_GREEN, CA_BLUE); this->gauge_color = Color(CD_RED, CD_GREEN, CD_BLUE); this->gauge_value = 0; - this->screen_pointer = 0; + this->next_action_time = 0; + this->last_scroll_time = 0; + this->screen_pointer = MAXQUEUE; for (uint8_t i = 0; i < MAXQUEUE; i++) { @@ -100,6 +103,40 @@ namespace esphome } } + void EHMTX::bitmap_screen(std::string text, int lifetime, int screen_time) + { + ESP_LOGD(TAG, "bitmap screen: lifetime: %d screen_time: %d", lifetime, screen_time); + const size_t CAPACITY = JSON_ARRAY_SIZE(256); + StaticJsonDocument doc; + deserializeJson(doc, text); + JsonArray array = doc.as(); + // extract the values + uint16_t i = 0; + for (JsonVariant v : array) + { + uint16_t buf = v.as(); + + unsigned char b = (((buf)&0x001F) << 3); + unsigned char g = (((buf)&0x07E0) >> 3); // Fixed: shift >> 5 and << 2 + unsigned char r = (((buf)&0xF800) >> 8); // shift >> 11 and << 3 + Color c = Color(r, g, b); + + this->bitmap[i++] = c; + } + + EHMTX_queue *screen = this->find_free_queue_element(); + + screen->text = ""; + screen->endtime = this->clock->now().timestamp + lifetime * 60; + screen->mode = MODE_BITMAP_SCREEN; + screen->screen_time_ = screen_time; + for (auto *t : on_add_screen_triggers_) + { + t->process("bitmap", (uint8_t)screen->mode); + } + screen->status(); + } + uint8_t EHMTX::find_icon(std::string name) { for (uint8_t i = 0; i < this->icon_count; i++) @@ -185,6 +222,7 @@ namespace esphome register_service(&EHMTX::rainbow_text_screen, "rainbow_text_screen", {"text", "lifetime", "screen_time", "default_font"}); register_service(&EHMTX::clock_screen, "clock_screen", {"lifetime", "screen_time", "default_font", "r", "g", "b"}); + register_service(&EHMTX::bitmap_screen, "bitmap_screen", {"text", "lifetime", "screen_time"}); register_service(&EHMTX::rainbow_clock_screen, "rainbow_clock_screen", {"lifetime", "screen_time", "default_font"}); register_service(&EHMTX::date_screen, "date_screen", {"lifetime", "screen_time", "default_font", "r", "g", "b"}); @@ -219,7 +257,7 @@ namespace esphome void EHMTX::blank_screen(int lifetime, int showtime) { auto scr = this->find_free_queue_element(); - scr->screen_time = showtime; + scr->screen_time_ = showtime; scr->mode = MODE_BLANK; scr->endtime = this->clock->now().timestamp + lifetime * 60; } @@ -228,22 +266,19 @@ namespace esphome { if (!this->is_running) { - if (this->clock->now().timestamp > 15) + if (this->clock->now().is_valid()) { - ESP_LOGD(TAG, "time sync => starting"); + ESP_LOGD(TAG, "time sync => start running"); + this->bitmap_screen("[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63519,63519,63519,63519,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63519,0,0,0,0,2016,0,0,0,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,0,0,63488,0,63488,0,0,0,63519,0,0,0,0,2016,2016,0,0,0,65514,0,65514,0,0,0,31,0,0,0,64512,0,0,64512,0,63488,63488,0,63488,63488,0,0,63519,63519,63519,0,0,2016,0,2016,0,65514,0,65514,0,65514,0,31,31,31,0,0,0,64512,64512,0,0,63488,63488,63488,63488,63488,0,0,63519,0,0,0,0,2016,0,2016,0,65514,0,65514,0,65514,0,0,31,0,0,0,0,64512,64512,0,0,0,63488,63488,63488,0,0,0,63519,63519,63519,63519,0,2016,0,2016,0,65514,0,65514,0,65514,0,0,0,31,31,0,64512,0,0,64512,0,0,0,63488,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]",1,10); + this->clock_screen(14 * 24 * 60, this->clock_time, false, C_RED, C_GREEN, C_BLUE); + this->date_screen(14 * 24 * 60, (int)this->clock_time / 2, false, C_RED, C_GREEN, C_BLUE); this->is_running = true; } } } - + void EHMTX::force_screen(std::string icon_name, int mode) { - // if (this->string_has_ending(icon_name, "*")) - // { - // // remove the * - // icon_name = icon_name.substr(0, icon_name.length() - 1); - // } - for (uint8_t i = 0; i < MAXQUEUE; i++) { if (this->queue[i]->mode == mode) @@ -259,7 +294,7 @@ namespace esphome { ESP_LOGD(TAG, "force_screen: found position: %d", i); this->queue[i]->last_time = 0; - this->queue[i]->endtime += this->queue[i]->screen_time; + this->queue[i]->endtime += this->queue[i]->screen_time_; this->next_action_time = this->clock->now().timestamp; ESP_LOGW(TAG, "force_screen: icon %s in mode %d", icon_name.c_str(), mode); } @@ -294,9 +329,9 @@ namespace esphome time_t ts = this->clock->now().timestamp; for (size_t i = 0; i < MAXQUEUE; i++) { - if ((this->queue[i]->mode == MODE_CLOCK)||(this->queue[i]->mode == MODE_RAINBOW_CLOCK)) + if ((this->queue[i]->mode == MODE_CLOCK) || (this->queue[i]->mode == MODE_RAINBOW_CLOCK)) { - if (ts > (this->queue[i]->last_time + this->clock_interval)) + if (ts > (this->queue[i]->last_time + this->clock_interval)) { hit = i; } @@ -322,7 +357,7 @@ namespace esphome this->queue[i]->endtime = 0; if (this->queue[i]->mode != MODE_EMPTY) { - ESP_LOGD(TAG, "remove expired queue element: removed slot %d: icon_name: %s text: %s", i, this->queue[i]->icon_name.c_str(), this->queue[i]->text.c_str()); + ESP_LOGD(TAG, "remove expired queue element: slot %d: mode: %d icon_name: %s text: %s", i, this->queue[i]->mode, this->queue[i]->icon_name.c_str(), this->queue[i]->text.c_str()); for (auto *t : on_expired_screen_triggers_) { infotext = ""; @@ -359,6 +394,7 @@ namespace esphome } } } + void EHMTX::tick() { this->hue_++; @@ -369,22 +405,36 @@ namespace esphome float red, green, blue; esphome::hsv_to_rgb(this->hue_, 0.8, 0.8, red, green, blue); this->rainbow_color = Color(uint8_t(255 * red), uint8_t(255 * green), uint8_t(255 * blue)); + time_t ts = this->clock->now().timestamp; if (this->is_running) { + if (millis() - this->last_scroll_time >= this->scroll_interval) + { + this->scroll_step++; + this->last_scroll_time = millis(); + if (this->scroll_step > this->queue[this->screen_pointer]->scroll_reset) + { + this->scroll_step = 0; + } + } + if (ts > this->next_action_time) { this->remove_expired_queue_element(); this->screen_pointer = this->find_last_clock(); + this->scroll_step = 0; + if (this->screen_pointer == MAXQUEUE) { this->screen_pointer = find_oldest_queue_element(); } + if (this->screen_pointer != MAXQUEUE) { - this->queue[this->screen_pointer]->shiftx_ = 0; - this->queue[this->screen_pointer]->last_time = ts + this->queue[this->screen_pointer]->screen_time; + + this->queue[this->screen_pointer]->last_time = ts + this->queue[this->screen_pointer]->screen_time_; if (this->queue[this->screen_pointer]->icon < this->icon_count) { this->icons[this->queue[this->screen_pointer]->icon]->set_frame(0); @@ -544,35 +594,14 @@ namespace esphome } EHMTX_queue *screen = this->find_icon_queue_element(icon); - int x, y, pixel, h; - if (default_font) - { - this->display->get_text_bounds(0, 0, text.c_str(), this->default_font, display::TextAlign::LEFT, &x, &y, &pixel, &h); - } - else - { - this->display->get_text_bounds(0, 0, text.c_str(), this->special_font, display::TextAlign::LEFT, &x, &y, &pixel, &h); - } - if (pixel < 23) - { - screen->centerx_ = ceil((22 - pixel) / 2); - screen->screen_time = screen_time; - } - else - { - screen->centerx_ = 0; - int display_duration = ceil((this->scroll_count * (TEXTSTARTOFFSET + pixel) * this->scroll_interval) / 1000); - screen->screen_time = (display_duration > screen_time) ? display_duration : screen_time; - } screen->text = text; - screen->pixels_ = pixel; screen->endtime = this->clock->now().timestamp + lifetime * 60; - screen->shiftx_ = 0; screen->text_color = Color(r, g, b); screen->default_font = default_font; screen->mode = MODE_ICON_SCREEN; screen->icon_name = iconname; screen->icon = icon; + screen->calc_scroll_time(text, screen_time); for (auto *t : on_add_screen_triggers_) { t->process(screen->icon_name, (uint8_t)screen->mode); @@ -596,34 +625,14 @@ namespace esphome } EHMTX_queue *screen = this->find_icon_queue_element(icon); - int x, y, pixel, h; - if (default_font) - { - this->display->get_text_bounds(0, 0, text.c_str(), this->default_font, display::TextAlign::LEFT, &x, &y, &pixel, &h); - } - else - { - this->display->get_text_bounds(0, 0, text.c_str(), this->special_font, display::TextAlign::LEFT, &x, &y, &pixel, &h); - } - if (pixel < 23) - { - screen->centerx_ = ceil((22 - pixel) / 2); - screen->screen_time = screen_time; - } - else - { - screen->centerx_ = 0; - int display_duration = ceil((this->scroll_count * (TEXTSTARTOFFSET + pixel) * this->scroll_interval) / 1000); - screen->screen_time = (display_duration > screen_time) ? display_duration : screen_time; - } screen->text = text; - screen->pixels_ = pixel; + screen->endtime = this->clock->now().timestamp + lifetime * 60; - screen->shiftx_ = 0; screen->default_font = default_font; screen->mode = MODE_RAINBOW_ICON; screen->icon_name = iconname; screen->icon = icon; + screen->calc_scroll_time(text, screen_time); for (auto *t : on_add_screen_triggers_) { t->process(screen->icon_name, (uint8_t)screen->mode); @@ -639,7 +648,14 @@ namespace esphome ESP_LOGD(TAG, "rainbow_clock_screen lifetime: %d screen_time: %d", lifetime, screen_time); screen->mode = MODE_RAINBOW_CLOCK; screen->default_font = default_font; - screen->screen_time = (screen_time > this->clock_interval)?screen_time:this->clock_interval-1; + if (this->clock_interval == 0 || (this->clock_interval > screen_time)) + { + screen->screen_time_ = screen_time; + } + else + { + screen->screen_time_ = this->clock_interval - 2; + } screen->endtime = this->clock->now().timestamp + lifetime * 60; screen->status(); } @@ -651,7 +667,7 @@ namespace esphome ESP_LOGD(TAG, "rainbow_date_screen lifetime: %d screen_time: %d", lifetime, screen_time); screen->mode = MODE_RAINBOW_DATE; screen->default_font = default_font; - screen->screen_time = screen_time; + screen->screen_time_ = screen_time; screen->endtime = this->clock->now().timestamp + lifetime * 60; screen->status(); } @@ -662,24 +678,21 @@ namespace esphome screen->text = text; screen->endtime = this->clock->now().timestamp + lifetime * 60; - screen->screen_time = screen_time; screen->default_font = default_font; screen->text_color = Color(r, g, b); screen->mode = MODE_TEXT_SCREEN; - screen->calc_scroll_time(); + screen->calc_scroll_time(text, screen_time); screen->status(); } void EHMTX::rainbow_text_screen(std::string text, int lifetime, int screen_time, bool default_font) { EHMTX_queue *screen = this->find_free_queue_element(); - screen->text = text; screen->endtime = this->clock->now().timestamp + lifetime * 60; - screen->screen_time = screen_time; screen->default_font = default_font; screen->mode = MODE_RAINBOW_TEXT; - screen->calc_scroll_time(); + screen->calc_scroll_time(text, screen_time); screen->status(); } @@ -701,7 +714,7 @@ namespace esphome screen->mode = MODE_FULL_SCREEN; screen->icon = icon; screen->icon_name = iconname; - screen->screen_time = screen_time; + screen->screen_time_ = screen_time; screen->endtime = this->clock->now().timestamp + lifetime * 60; for (auto *t : on_add_screen_triggers_) { @@ -719,7 +732,7 @@ namespace esphome ESP_LOGD(TAG, "clock_screen_color lifetime: %d screen_time: %d red: %d green: %d blue: %d", lifetime, screen_time, r, g, b); screen->mode = MODE_CLOCK; screen->default_font = default_font; - screen->screen_time = (screen_time > this->clock_interval)?screen_time:this->clock_interval-1; + screen->screen_time_ = screen_time; screen->endtime = this->clock->now().timestamp + lifetime * 60; screen->status(); } @@ -731,7 +744,7 @@ namespace esphome screen->text_color = Color(r, g, b); ESP_LOGD(TAG, "date_screen lifetime: %d screen_time: %d red: %d green: %d blue: %d", lifetime, screen_time, r, g, b); screen->mode = MODE_DATE; - screen->screen_time = screen_time; + screen->screen_time_ = screen_time; screen->default_font = default_font; screen->endtime = this->clock->now().timestamp + lifetime * 60; screen->status(); @@ -777,6 +790,15 @@ namespace esphome } } + void EHMTX::set_rtl(bool b) + { + this->rtl = b; + if (b) + { + ESP_LOGI(TAG, "show text right to left"); + } + } + void EHMTX::set_show_seconds(bool b) { this->show_seconds = b; @@ -906,6 +928,10 @@ namespace esphome { ESP_LOGCONFIG(TAG, "show date"); } + if (this->rtl) + { + ESP_LOGCONFIG(TAG, "RTL activated"); + } if (this->week_starts_monday) { ESP_LOGCONFIG(TAG, "weekstart: monday"); @@ -914,6 +940,10 @@ namespace esphome { ESP_LOGCONFIG(TAG, "weekstart: sunday"); } + if (this->clock->now().is_valid()) + { + this->is_running = true; + } } void EHMTX::add_icon(EHMTX_Icon *icon) diff --git a/components/ehmtxv2/EHMTX.h b/components/ehmtxv2/EHMTX.h index 9d1855d..598ebc3 100644 --- a/components/ehmtxv2/EHMTX.h +++ b/components/ehmtxv2/EHMTX.h @@ -20,10 +20,10 @@ const uint8_t MAXICONS = 90; const uint8_t TEXTSCROLLSTART = 8; const uint8_t TEXTSTARTOFFSET = (32 - 8); -const uint16_t POLLINGINTERVAL = 800; +const uint16_t POLLINGINTERVAL = 1000; static const char *const EHMTX_VERSION = "Version: 2023.5.0 beta"; static const char *const TAG = "EHMTXv2"; -enum show_mode : uint8_t { MODE_EMPTY = 0,MODE_BLANK = 1, MODE_CLOCK = 2, MODE_DATE = 3, MODE_FULL_SCREEN = 4, MODE_ICON_SCREEN = 5, MODE_TEXT_SCREEN = 6 , MODE_RAINBOW_ICON = 7,MODE_RAINBOW_TEXT = 8, MODE_RAINBOW_CLOCK = 9,MODE_RAINBOW_DATE=10 }; +enum show_mode : uint8_t { MODE_EMPTY = 0,MODE_BLANK = 1, MODE_CLOCK = 2, MODE_DATE = 3, MODE_FULL_SCREEN = 4, MODE_ICON_SCREEN = 5, MODE_TEXT_SCREEN = 6 , MODE_RAINBOW_ICON = 7,MODE_RAINBOW_TEXT = 8, MODE_RAINBOW_CLOCK = 9,MODE_RAINBOW_DATE=10,MODE_BITMAP_SCREEN=11 }; namespace esphome { @@ -37,7 +37,7 @@ namespace esphome class EHMTX : public PollingComponent, public api::CustomAPIDevice { protected: - float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } + float get_setup_priority() const override { return esphome::setup_priority::BEFORE_CONNECTION; } uint8_t brightness_; uint32_t boot_anim=0; uint8_t screen_pointer; @@ -57,7 +57,7 @@ namespace esphome EHMTX(); Color text_color, alarm_color, gauge_color,indicator_color,clock_color; Color today_color,weekday_color,rainbow_color; - int hue_; + uint16_t hue_=0; void dump_config(); std::string time_fmt; std::string date_fmt; @@ -67,9 +67,12 @@ namespace esphome bool display_gauge; bool is_running=false; bool show_date; + bool rtl; uint8_t gauge_value; uint16_t clock_time; + uint16_t scroll_step; uint8_t scroll_count; + Color bitmap[256]; void remove_expired_queue_element(); uint8_t find_oldest_queue_element(); uint8_t find_icon_in_queue(std::string); @@ -93,6 +96,7 @@ namespace esphome uint16_t frame_interval; // ms to next_frame() uint16_t clock_interval; uint16_t hold_time; // seconds display of screen_time to extend + uint8_t icon_count; // max iconnumber -1 unsigned long last_scroll_time; unsigned long last_rainbow_time; @@ -107,12 +111,14 @@ namespace esphome void skip_screen(); void hold_screen(int t=30); void set_display(addressable_light::AddressableLightDisplay *disp); - void set_clock_interval(uint16_t t=60); + void set_clock_interval(uint16_t t=90); + void get_string(std::string); void set_hold_time(uint16_t t=30); void set_clock_time(uint16_t t=10); void set_show_day_of_week(bool b); void set_show_seconds(bool b); void set_show_date(bool b); + void set_rtl(bool b); void set_font_offset(int8_t x, int8_t y); void set_week_start(bool b); void set_brightness(int b); @@ -138,13 +144,13 @@ namespace esphome void hide_gauge(); void hide_indicator(); void hide_alarm(); - void full_screen(std::string icon, int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME); void icon_screen(std::string icon, std::string text, int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME,bool default_font=true,int r=C_RED, int g=C_GREEN, int b=C_BLUE); void text_screen(std::string text, int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME, bool default_font=true, int r=C_RED, int g=C_GREEN, int b=C_BLUE); void clock_screen(int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME,bool default_font=true,int r=C_RED, int g=C_GREEN, int b=C_BLUE); void date_screen(int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME,bool default_font=true, int r=C_RED, int g=C_GREEN, int b=C_BLUE); void blank_screen(int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME); + void bitmap_screen(std::string text,int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME); void rainbow_icon_screen(std::string icon_name, std::string text, int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME, bool default_font=true); void rainbow_text_screen(std::string text, int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME, bool default_font=true); void rainbow_clock_screen(int lifetime=D_LIFETIME, int screen_time=D_SCREEN_TIME, bool default_font=true); @@ -173,13 +179,13 @@ namespace esphome public: uint8_t centerx_; - uint16_t shiftx_; uint16_t pixels_; - uint16_t screen_time; + uint16_t screen_time_; bool default_font; time_t endtime; time_t last_time; uint8_t icon; + uint16_t scroll_reset; Color text_color; show_mode mode; @@ -194,7 +200,8 @@ namespace esphome bool update_slot(uint8_t _icon); void update_screen(); void hold_slot(uint8_t _sec); - void calc_scroll_time(); + void calc_scroll_time(std::string,uint16_t ); + int xpos(); }; class EHMTXNextScreenTrigger : public Trigger diff --git a/components/ehmtxv2/EHMTX_queue.cpp b/components/ehmtxv2/EHMTX_queue.cpp index 433acb6..a46935e 100644 --- a/components/ehmtxv2/EHMTX_queue.cpp +++ b/components/ehmtxv2/EHMTX_queue.cpp @@ -9,8 +9,7 @@ namespace esphome this->endtime = 0; this->last_time = 0; this->centerx_ = 0; - this->shiftx_ = 0; - this->screen_time = 0; + this->screen_time_ = 0; this->mode = MODE_EMPTY; this->icon_name = ""; this->icon = 0; @@ -23,37 +22,40 @@ namespace esphome switch (this->mode) { case MODE_EMPTY: - ESP_LOGD(TAG, "queue: empty slot"); + ESP_LOGD(TAG, " empty slot"); break; case MODE_BLANK: - ESP_LOGD(TAG, "queue: blank screen for %d sec", this->screen_time); + ESP_LOGD(TAG, "queue: blank screen for %d sec", this->screen_time_); break; case MODE_CLOCK: - ESP_LOGD(TAG, "queue: clock for %d sec", this->screen_time); + ESP_LOGD(TAG, "queue: clock for: %d sec", this->screen_time_); break; case MODE_DATE: - ESP_LOGD(TAG, "queue: date for %d sec", this->screen_time); + ESP_LOGD(TAG, "queue: date for: %d sec", this->screen_time_); break; case MODE_FULL_SCREEN: - ESP_LOGD(TAG, "queue: full screen: %s for %d sec", this->icon_name.c_str(), this->screen_time); + ESP_LOGD(TAG, "queue: full screen: \"%s\" for: %d sec", this->icon_name.c_str(), this->screen_time_); break; case MODE_ICON_SCREEN: - ESP_LOGD(TAG, "queue: icon screen: %s text: %s for %d sec", this->icon_name.c_str(), this->text.c_str(), this->screen_time); + ESP_LOGD(TAG, "queue: icon screen: \"%s\" text: %s for: %d sec", this->icon_name.c_str(), this->text.c_str(), this->screen_time_); break; case MODE_TEXT_SCREEN: - ESP_LOGD(TAG, "queue: text text: %s for %d sec", this->text.c_str(), this->screen_time); + ESP_LOGD(TAG, "queue: text text: \"%s\" for: %d sec", this->text.c_str(), this->screen_time_); break; case MODE_RAINBOW_ICON: - ESP_LOGD(TAG, "queue: rainbow icon: %s text: %s for %d sec", this->icon_name.c_str(), this->text.c_str(), this->screen_time); + ESP_LOGD(TAG, "queue: rainbow icon: \"%s\" text: %s for: %d sec", this->icon_name.c_str(), this->text.c_str(), this->screen_time_); break; case MODE_RAINBOW_TEXT: - ESP_LOGD(TAG, "queue: rainbow text: %s for %d sec", this->text.c_str(), this->screen_time); + ESP_LOGD(TAG, "queue: rainbow text: \"%s\" for: %d sec", this->text.c_str(), this->screen_time_); break; case MODE_RAINBOW_CLOCK: - ESP_LOGD(TAG, "queue: clock for %d sec", this->screen_time); + ESP_LOGD(TAG, "queue: clock for: %d sec", this->screen_time_); break; case MODE_RAINBOW_DATE: - ESP_LOGD(TAG, "queue: date for %d sec", this->screen_time); + ESP_LOGD(TAG, "queue: date for: %d sec", this->screen_time_); + break; + case MODE_BITMAP_SCREEN: + ESP_LOGD(TAG, "queue: bitmap for: %d sec", this->screen_time_); break; default: ESP_LOGD(TAG, "queue: UPPS"); @@ -61,6 +63,57 @@ namespace esphome } } + int EHMTX_queue::xpos() + { + uint8_t width = 32; + uint8_t startx = 0; + int result = 0; + switch (this->mode) + { + case MODE_RAINBOW_ICON: + case MODE_ICON_SCREEN: + startx = 8; + break; + case MODE_TEXT_SCREEN: + case MODE_RAINBOW_TEXT: + // no correction + break; + default: + break; + } + + if (this->config_->display_gauge) + { + startx += 2; + } + width -= startx; + + if (this->config_->rtl) + { + if (this->pixels_ < width) + { + result = 32 - ceil((width - this->pixels_) / 2); + } + else + { + + result = startx + this->config_->scroll_step; + } + } + else + { + if (this->pixels_ < width) + { + result = startx + ceil((width - this->pixels_) / 2); + } + else + { + result = startx - this->config_->scroll_step + width; + } + } + return result; + } + void EHMTX_queue::update_screen() { if (millis() - this->config_->last_rainbow_time >= this->config_->rainbow_interval) @@ -76,31 +129,6 @@ namespace esphome this->config_->last_rainbow_time = millis(); } - if ((this->mode == MODE_ICON_SCREEN) || (this->mode == MODE_RAINBOW_ICON)) - { - if (millis() - this->config_->last_scroll_time >= this->config_->scroll_interval && this->pixels_ > TEXTSTARTOFFSET) - { - this->shiftx_++; - if (this->shiftx_ > this->pixels_ + TEXTSTARTOFFSET) - { - this->shiftx_ = 0; - } - this->config_->last_scroll_time = millis(); - } - } - if ((this->mode == MODE_TEXT_SCREEN) || (this->mode == MODE_RAINBOW_TEXT)) - { - if (millis() - this->config_->last_scroll_time >= this->config_->scroll_interval && this->pixels_ >= 32) - { - this->shiftx_++; - if (this->shiftx_ > this->pixels_ + 32) - { - this->shiftx_ = 0; - } - this->config_->last_scroll_time = millis(); - } - } - if (millis() - this->config_->last_anim_time >= this->config_->icons[this->icon]->frame_duration) { this->config_->icons[this->icon]->next_frame(); @@ -123,9 +151,18 @@ namespace esphome break; case MODE_BLANK: break; + case MODE_BITMAP_SCREEN: + for (uint8_t x = 0; x < 32; x++) + { + for (uint8_t y = 0; y < 8; y++) + { + this->config_->display->draw_pixel_at(x, y, this->config_->bitmap[x + y * 32]); + } + } + break; case MODE_RAINBOW_CLOCK: case MODE_CLOCK: - if (this->config_->clock->now().timestamp > 6000) // valid time + if (this->config_->clock->now().is_valid()) // valid time { color_ = (this->mode == MODE_RAINBOW_CLOCK) ? this->config_->rainbow_color : this->config_->clock_color; time_t ts = this->config_->clock->now().timestamp; @@ -135,7 +172,8 @@ namespace esphome { this->config_->display->draw_pixel_at(0, 0, color_); } - if (this->mode != MODE_RAINBOW_CLOCK){ + if (this->mode != MODE_RAINBOW_CLOCK) + { this->config_->draw_day_of_week(); } } @@ -146,7 +184,7 @@ namespace esphome break; case MODE_RAINBOW_DATE: case MODE_DATE: - if (this->config_->clock->now().timestamp > 6000) // valid time + if (this->config_->clock->now().is_valid()) { color_ = (this->mode == MODE_RAINBOW_DATE) ? this->config_->rainbow_color : this->config_->clock_color; time_t ts = this->config_->clock->now().timestamp; @@ -156,7 +194,8 @@ namespace esphome { this->config_->display->draw_pixel_at(0, 0, color_); } - if (this->mode != MODE_RAINBOW_DATE){ + if (this->mode != MODE_RAINBOW_DATE) + { this->config_->draw_day_of_week(); } } @@ -181,9 +220,16 @@ namespace esphome } color_ = (this->mode == MODE_RAINBOW_ICON) ? this->config_->rainbow_color : this->text_color; - - this->config_->display->print(this->centerx_ + TEXTSCROLLSTART - this->shiftx_ + extraoffset + xoffset, yoffset, font, color_, esphome::display::TextAlign::BASELINE_LEFT, - this->text.c_str()); + if (this->config_->rtl) + { + this->config_->display->print(this->xpos() + xoffset, yoffset, font, color_, esphome::display::TextAlign::BASELINE_RIGHT, + this->text.c_str()); + } + else + { + this->config_->display->print(this->xpos() + xoffset, yoffset, font, color_, esphome::display::TextAlign::BASELINE_LEFT, + this->text.c_str()); + } if (this->config_->display_gauge) { this->config_->display->image(2, 0, this->config_->icons[this->icon]); @@ -208,14 +254,22 @@ namespace esphome extraoffset += 2; } color_ = (this->mode == MODE_RAINBOW_TEXT) ? this->config_->rainbow_color : this->text_color; - this->config_->display->print(this->centerx_ - this->shiftx_ + xoffset + extraoffset, yoffset, font, color_, esphome::display::TextAlign::BASELINE_LEFT, - this->text.c_str()); + if (this->config_->rtl) + { + this->config_->display->print(this->xpos() + xoffset, yoffset, font, color_, esphome::display::TextAlign::BASELINE_RIGHT, + this->text.c_str()); + } + else + { + this->config_->display->print(this->xpos() + xoffset, yoffset, font, color_, esphome::display::TextAlign::BASELINE_LEFT, + this->text.c_str()); + } break; default: break; } this->update_screen(); - } + } } void EHMTX_queue::hold_slot(uint8_t _sec) @@ -225,11 +279,16 @@ namespace esphome } // TODO void EHMTX_queue::set_mode_icon() - - void EHMTX_queue::calc_scroll_time() + + void EHMTX_queue::calc_scroll_time(std::string text, uint16_t screen_time) { int x, y, w, h; float display_duration; + + uint8_t width = 32; + uint8_t startx = 0; + uint16_t max_steps = 0; + if (this->default_font) { this->config_->display->get_text_bounds(0, 0, text.c_str(), this->config_->default_font, display::TextAlign::LEFT, &x, &y, &w, &h); @@ -240,33 +299,46 @@ namespace esphome } this->pixels_ = w; - this->shiftx_ = 0; + this->centerx_ = 0; switch (this->mode) { case MODE_RAINBOW_TEXT: case MODE_TEXT_SCREEN: - display_duration = ceil((28+(this->config_->scroll_count * (32 + this->pixels_)) * this->config_->scroll_interval) / 1000); - this->screen_time = (display_duration > this->screen_time) ? display_duration : this->screen_time; if (this->pixels_ < 32) { + this->screen_time_ = screen_time; this->centerx_ = ceil((32 - this->pixels_) / 2); } - break; + else + { + max_steps = (this->config_->scroll_count + 1) * (width - startx) + this->config_->scroll_count * this->pixels_; + display_duration = ceil((max_steps * this->config_->scroll_interval) / 1000); + this->screen_time_ = (display_duration > screen_time) ? display_duration : screen_time; + } + break; case MODE_RAINBOW_ICON: case MODE_ICON_SCREEN: - display_duration = ceil(((28-TEXTSTARTOFFSET)+(this->config_->scroll_count * (TEXTSTARTOFFSET + this->pixels_)) * this->config_->scroll_interval) / 1000); - this->screen_time = (display_duration > this->screen_time) ? display_duration : this->screen_time; + startx = 8; if (this->pixels_ < 23) { + this->screen_time_ = screen_time; this->centerx_ = ceil((23 - this->pixels_) / 2); } - break; + else + { + max_steps = (this->config_->scroll_count + 1) * (width - startx) + this->config_->scroll_count * this->pixels_; + display_duration = ceil((max_steps * this->config_->scroll_interval) / 1000); + this->screen_time_ = (display_duration > screen_time) ? display_duration : screen_time; + } + break; default: break; } - - this->shiftx_ = 0; - ESP_LOGD(TAG, "display text: %s pixels %d calculated: %d", text.c_str(), this->pixels_, this->screen_time); + + this->scroll_reset = (width - startx) + this->pixels_; + ; + + ESP_LOGD(TAG, "calc_scroll_time: mode: %d text: \"%s\" pixels %d calculated: %d defined: %d max_steps: %d", this->mode, text.c_str(), this->pixels_, this->screen_time_, screen_time, this->scroll_reset); } } diff --git a/components/ehmtxv2/__init__.py b/components/ehmtxv2/__init__.py index 7964046..8e228f8 100644 --- a/components/ehmtxv2/__init__.py +++ b/components/ehmtxv2/__init__.py @@ -66,6 +66,7 @@ CONF_LIFETIME = "lifetime" CONF_ICONS = "icons" CONF_SHOWDOW = "show_dow" CONF_SHOWDATE = "show_date" +CONF_RTL = "rtl" CONF_FRAMEDURATION = "frame_duration" CONF_SCROLLCOUNT = "scroll_count" CONF_MATRIXCOMPONENT = "matrix_component" @@ -105,6 +106,9 @@ EHMTX_SCHEMA = cv.Schema({ ): cv.templatable(cv.positive_int), cv.Optional( CONF_HTML, default=False + ): cv.boolean, + cv.Optional( + CONF_RTL, default=False ): cv.boolean, cv.Optional( CONF_SHOW_SECONDS, default=False @@ -362,6 +366,7 @@ async def to_code(config): cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) cg.add(var.set_scroll_interval(config[CONF_SCROLLINTERVAL])) cg.add(var.set_rainbow_interval(config[CONF_SCROLLINTERVAL])) + cg.add(var.set_rtl(config[CONF_RTL])) cg.add(var.set_scroll_count(config[CONF_SCROLLCOUNT])) cg.add(var.set_frame_interval(config[CONF_FRAMEINTERVAL])) cg.add(var.set_week_start(config[CONF_WEEK_START_MONDAY])) diff --git a/copy2esphome/ulanzi-easy.yaml b/copy2esphome/ulanzi-easy.yaml index 1a2a49f..8732152 100644 --- a/copy2esphome/ulanzi-easy.yaml +++ b/copy2esphome/ulanzi-easy.yaml @@ -1,17 +1,15 @@ substitutions: devicename: ulanzi - ledpin: GPIO32 - buzzerpin: GPIO15 friendly_name: LED Matrix board: esp32dev # Pin definition from https://github.com/aptonline/PixelIt_Ulanzi battery_pin: GPIO34 + buzzer_pin: GPIO15 ldr_pin: GPIO35 matrix_pin: GPIO32 left_button_pin: GPIO26 mid_button_pin: GPIO27 right_button_pin: GPIO14 - buzzer_pin: GPIO15 scl_pin: GPIO22 sda_pin: GPIO21 @@ -64,7 +62,15 @@ esphome: on_boot: then: - ds1307.read_time: - + +output: + - platform: ledc + pin: $buzzer_pin + id: rtttl_out + +rtttl: + output: rtttl_out + esp32: board: esp32dev @@ -155,6 +161,7 @@ sensor: if (n > id(aab_max)) n = id(aab_max); if (n < id(aab_min)) n = id(aab_min); int c = id(rgb8x32)->get_brightness(); // current value + c = c>0?c:1 ; int d = (n - c) * 100 / c; // diff in % if ( abs(d) > 2 ) id(rgb8x32)->set_brightness(n); } @@ -168,14 +175,6 @@ wifi: web_server: -output: - - platform: ledc - pin: $buzzerpin - id: rtttl_out - -rtttl: - output: rtttl_out - i2c: sda: $sda_pin scl: $scl_pin @@ -188,7 +187,7 @@ light: type: GRB internal: true variant: WS2812 - pin: $ledpin + pin: $matrix_pin num_leds: 256 color_correct: [30%, 30%, 30%] gamma_correct: 2.0 diff --git a/tests/ehtmxv2-template.yaml b/tests/ehtmxv2-template.yaml index 245595a..cf577cc 100644 --- a/tests/ehtmxv2-template.yaml +++ b/tests/ehtmxv2-template.yaml @@ -117,6 +117,8 @@ ehmtxv2: time_format: "%H:%M" date_format: "%d.%m." show_seconds: false + clock_interval: 90 + rtl: true default_font_id: default_font special_font_id: default_font icons: diff --git a/tests/ulanzi-easy.yaml b/tests/ulanzi-easy.yaml index 1a2a49f..8732152 100644 --- a/tests/ulanzi-easy.yaml +++ b/tests/ulanzi-easy.yaml @@ -1,17 +1,15 @@ substitutions: devicename: ulanzi - ledpin: GPIO32 - buzzerpin: GPIO15 friendly_name: LED Matrix board: esp32dev # Pin definition from https://github.com/aptonline/PixelIt_Ulanzi battery_pin: GPIO34 + buzzer_pin: GPIO15 ldr_pin: GPIO35 matrix_pin: GPIO32 left_button_pin: GPIO26 mid_button_pin: GPIO27 right_button_pin: GPIO14 - buzzer_pin: GPIO15 scl_pin: GPIO22 sda_pin: GPIO21 @@ -64,7 +62,15 @@ esphome: on_boot: then: - ds1307.read_time: - + +output: + - platform: ledc + pin: $buzzer_pin + id: rtttl_out + +rtttl: + output: rtttl_out + esp32: board: esp32dev @@ -155,6 +161,7 @@ sensor: if (n > id(aab_max)) n = id(aab_max); if (n < id(aab_min)) n = id(aab_min); int c = id(rgb8x32)->get_brightness(); // current value + c = c>0?c:1 ; int d = (n - c) * 100 / c; // diff in % if ( abs(d) > 2 ) id(rgb8x32)->set_brightness(n); } @@ -168,14 +175,6 @@ wifi: web_server: -output: - - platform: ledc - pin: $buzzerpin - id: rtttl_out - -rtttl: - output: rtttl_out - i2c: sda: $sda_pin scl: $scl_pin @@ -188,7 +187,7 @@ light: type: GRB internal: true variant: WS2812 - pin: $ledpin + pin: $matrix_pin num_leds: 256 color_correct: [30%, 30%, 30%] gamma_correct: 2.0