diff --git a/CHANGELOG.md b/CHANGELOG.md index d7164b1..0dfae8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,36 @@ # Changelog +## 2023.3.5 +- new: show_seconds indicator top left corner +- breaking: removed automatic scaling of images +- new: support 8x32 icons without text +- breaking: added status,display_on,display_off as default service => remove these from your yaml +- breaking: added indicator_on/off as default service => remove these from your yaml +- breaking: added alarm_color,text_color,clock_color as default service => remove these from your yaml +- breaking: gauge is also schown while the clock is displayed but without moving the screen to the right +- breaking: show_icons as default service => remove from yaml + ## 2023.3.4 -added: option to not display clock/date #53 -added: dynamic set_show_clock -added: on_next_clock trigger +- added: option to not display clock/date #53 +- added: dynamic set_show_clock +- added: on_next_clock trigger ## 2023.3.3 -fixed: force_screen skips imediatly to the selected screen -added: hold_time configurable +- fixed: force_screen skips imediatly to the selected screen +- added: hold_time configurable ## 2023.3.2 -added: hold_screen for 20 additional seconds +- added: hold_screen for 20 additional seconds ## 2023.3.1 -added: del_screen with wildcards -changed: maximum icons to 80 -fixed: skip_next -fixed: show_all_icons on boot +- added: del_screen with wildcards +- changed: maximum icons to 80 +- fixed: skip_next +- fixed: show_all_icons on boot ## 2023.3.0 diff --git a/README.md b/README.md index a577e9a..039575e 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,12 @@ There are some "RGB-matrix" status displays/clocks out there, the commercial one The other d.i.y. solutions have their pros and cons. I tried both and used AwTrix for a long time. But the cons are so big (in my opinion) that I started an esphome.io variant targeted to an optimized Home Assistant integration. The main reason, for me is the Home Assistant integration! -There is a little hype around the Ulanzi TC001 pixel clock. This hardware can be used with **EspHoMaTriX** (with some limitations). You can connect the device and flash it via USB-C. As a starting point you can use the [``UlanziTC001.yaml``](https://github.com/lubeda/EsphoMaTrix/blob/main/UlanziTC001.yaml). Yet the LDR and battery sensor are not perfectly supported. For another use of the hardware see [PixelIT_Ulanzi](https://github.com/aptonline/PixelIt_Ulanzi) firmware. +There is a little hype around the Ulanzi TC001 pixel clock. This hardware can be used with **EspHoMaTriX** (with some limitations). You can connect the device and flash it via USB-C. As a starting point you can use the [``UlanziTC001.yaml``](https://github.com/lubeda/EsphoMaTrix/blob/main/UlanziTC001.yaml). Yet the LDR and battery sensor are not perfectly supported. For another use of the hardware see [PixelIT_Ulanzi](https://github.com/aptonline/PixelIt_Ulanzi) or [AWTRIX-LIGHT](https://github.com/Blueforcer/awtrix-light) firmwares. See this German tutorial video with information on setting up your display [RGB-LED Status Display für Home Assistant mit ESPHome | ESPHoMaTrix](https://www.youtube.com/watch?v=DTd9vAhet9A). +Another german tutorial video focused at the Ulanzi [Smarte Pixel Clock über Home Assistant steuern - Entitäten / Icons und mehr in der Ulanzi](https://www.youtube.com/watch?v=LgaT0mNbl34) + See this [nice article](https://blakadder.com/esphome-pixel-clock/) about EsphoMaTrix on a Ulanzi TC001 from [blakadder](https://github.com/blakadder). See this english discussions: @@ -26,12 +28,11 @@ See this english discussions: Or in german: [Showroom](https://community.simon42.com/t/8x32-pixel-uhr-mit-homeassistant-anbindung/1076) - ### State It is a working solution with core functionality coded. Advanced features, like automatic brightness control can be done with esphome actions and automations. -See it in action on [YouTube](https://www.youtube.com/watch?v=ZyaFj7ArIdY) (boring, no sound but subtitles). +See it in action on [YouTube](https://www.youtube.com/watch?v=ZyaFj7ArIdY) (no sound but subtitles). ### Features @@ -93,7 +94,7 @@ font: ### Icons and Animations -Download and install all needed icons (.jpg/.png) and animations (.gif) under the `ehmtx:` key. All icons are automagically scaled to 8x8 on compile-time. This doesn't work well for gif's, you will have to resize them beforehand! +Download and install all needed icons (.jpg/.png) and animations (.gif) under the `ehmtx:` key. All icons have to be 8x8 or 8x32 piksels. You can also specify an URL to directly download the image file. The URLs will only be downloaded once at compile time, so there is no additional traffic on the hosting website. @@ -123,7 +124,7 @@ First defined icon will be used as a fallback icon in case of an error, e.g. if GIFs are limited to 16 frames to limit the flash space. The first icon in your list is the fallback. -All other solutions provide ready made icons, especially Lametric has a big database of [icons](https://developer.lametric.com/icons). Please check the copyright of the icons you use. The amount of icons is limited to 64 in the code and also by the flash space and the RAM of your board. +All other solutions provide ready made icons, especially Lametric has a big database of [icons](https://developer.lametric.com/icons). Please check the copyright of the icons you use. The amount of icons is limited to 80 in the code and also by the flash space and the RAM of your board. ***Parameters*** @@ -165,17 +166,6 @@ esphome: id(rgb8x32)->show_all_icons(); ``` -Here you can show all of your icons via a service call: - -```yaml -api: - services: - - service: icons - then: - lambda: |- - id(rgb8x32)->show_all_icons(); -``` - ### esphome custom component #### local use @@ -266,6 +256,8 @@ binary_sensor: **date_format** (Optional, string): formats the date display with [strftime syntax](https://esphome.io/components/time.html?highlight=strftime), defaults `"%d.%m."` (use `"%m.%d."` for the US) +**show_seconds** (Optional, boolean): toggle an indicator for seconds while the clock is displayed (default: false)) + **time_format** (Optional, string): formats the date display with [strftime syntax](https://esphome.io/components/time.html?highlight=strftime), defaults `"%H:%M"` (use `"%I:%M%p"` for the US) **yoffset** (Optional, pixel): yoffset the text is aligned BASELINE_LEFT, the baseline defaults to `6` @@ -320,7 +312,7 @@ There is a trigger available to do some local magic. The trigger ```on_next_scre See the examples: -##### Write information to log +##### Write information to homeassistant log ```yaml ehmtx: @@ -407,7 +399,7 @@ Force the selected screen ```icon_name``` to be displayed next. Afterwards the l icon_name: !lambda return icon_name; ``` -##### Set (text/alarm/clock/weekday/today) color action +##### Set (alarm/clock/gauge/text/today/weekday) color action Sets the color of the select element @@ -423,11 +415,12 @@ You have to use use id of your ehmtx component, e.g. `rgb8x32` valid elements: -- `ehmtx.text.color:` - `ehmtx.alarm.color:` - `ehmtx.clock.color:` -- `ehmtx.weekday.color:` +- `ehmtx.gauge.color:` +- `ehmtx.text.color:` - `ehmtx.today.color:` +- `ehmtx.weekday.color:` - ```red, green, blue```: the color components (`0..255`) _(default = `80`)_ *Example* @@ -458,29 +451,6 @@ esphome: blue: !lambda return 30; ``` -##### Indicator on - -The indicator is a static colored corner on the display. - -You have to use use id of your ehmtx component, e.g. ```rgb8x32``` - -```yaml - - ehmtx.indicator.on: - id: rgb8x32 - red: !lambda return r; - green: !lambda return g; - blue: !lambda return b; -``` - -- ```red, green, blue```: the color components (0..255) (default=80) - -##### Indicator off - -```yaml - - ehmtx.indicator.off: - id: rgb8x32 -``` - ##### Add screen to loop ```yaml @@ -506,7 +476,7 @@ Adapt all other data in the yaml to your needs, I use GPIO04/GPIO16 (esp8266/ESP ## Integration in Home Assistant -To control your display it has to be integrated in Home Assistant. Then it provides at least three services, all prefixed with the configured `devicename` e.g. "ehmtx". See the [sample yaml](https://github.com/lubeda/EsphoMaTrix/blob/main/ehmtx32.yaml) for the default services, but you can add your own. +To control your display it has to be integrated in Home Assistant. Then it provides a number of services, all prefixed with the configured `devicename` e.g. "ehmtx". See the default services marked as **(D)** [below](https://github.com/lubeda/EsphoMaTrix#services), but you can add your own. ### Use the light component @@ -529,7 +499,7 @@ light: ### Services -All communication with Home Assistant use the homeasistant-api. The services are defined in the yaml. To define the services you need the id of the ehmtx-component e.g. ```id(rgb8x32)```. +All communication with Home Assistant use the homeasistant-api. The services can be provided by default or also defined additionally in the yaml. To define the additional services you need the id of the ehmtx-component e.g. ```id(rgb8x32)```. *Example* @@ -545,7 +515,7 @@ api: id(rgb8x32)->add_screen(icon_name, text, 7, true); // 7 Minutes alarm=true ``` -Service **_brightness** +**(D)** Service **brightness** Sets the overall brightness of the display (`0..255`) @@ -569,7 +539,7 @@ number: id(rgb8x32)->set_brightness(x); ``` -Service **_screen** +Service **screen** Queues a screen with an icon/animation and a text. There can only be one text per icon id. If you need to show e.g. an indoor and an outdoor temperature you have to use different icon id's! @@ -580,7 +550,7 @@ _parameters:_ - ```icon_name```: The number of the predefined icons (see installation) - ```text```: The text to be displayed -Service **_screen_t** +Service **screen_t** Same as above with a special duration paremeter. E.g. to indicate someone's birthday you can use `24*60` for 1440 minutes. @@ -590,7 +560,7 @@ _parameters:_ - ```text```: The text to be displayed - ```duration```: The lifetime in minutes -Service **_alarm** +Service **alarm** Alarm is like a regular screen but it is displayed two minutes longer and has a red text color and a red marker in the upper right corner. @@ -599,7 +569,7 @@ _parameters:_ - ```icon_name```: The name of the predefined icon id (see installation) - ```text```: The text to be displayed -Service **del_screen** +**(D)** Service **del_screen** Removes a screen from the display by icon name. If this screen is actually display while sending this command the screen will be displayed until its "show_screen"-time has ended. @@ -611,7 +581,9 @@ _parameters:_ - ```icon_name```: Icon `id` defined in the yaml (see installation) -Service **indicator_on** +**(D)** Service **indicator_on** / **indicator_off** + +Turns indicator on/off Display a colored corner on all screens and the clock. You can define the color by parameter. @@ -621,13 +593,19 @@ _parameters:_ - ```g``` green in 0..255 - ```b``` blue in 0..255 -Service **indicator_off** +**(D)** Service **alarm_color** / **clock_color** / **gauge_color** / **text_color** / **today_color** / **weekday_color** -removes the indicator +Set the color of the named text-type -Service **display_on** / **display_off** +_parameters:_ -turns the display on or off +- ```r``` red in 0..255 +- ```g``` green in 0..255 +- ```b``` blue in 0..255 + +**(D)** Service **display_on** / **display_off** + +Turns the display on or off There's an easier way in using a switch component: @@ -666,7 +644,7 @@ binary_sensor: Service **hold_screen** -displays the current screen for configured ammount (see **hold_time**) (default=20) seconds longer. +Displays the current screen for configured ammount (see **hold_time**) (default=20) seconds longer. e.g. on the Ulanzi TC001 @@ -682,7 +660,7 @@ binary_sensor: ``` -Service **status** +**(D)** Service **status** This service displays the running queue and a list of icons in the logs @@ -699,6 +677,19 @@ This service displays the running queue and a list of icons in the logs [13:10:10][I][EHMTX:186]: status icon: 4 name: rain ``` +**(D)** Service **show_all_icons** + +Display all of your icons sequentially by ID. + +Service **gauge_value** / **gauge_off** + +**(D)** Turns gauge on/off +Displays a colored gauge. You can define the color by parameter. + +_parameters:_ + +- ```percent``` gauge percentage + ### Use in Home Assistant automations The easiest way to use ehmtx as a status display is to use the icon names as trigger id. In my example i have an icon named "wind" when the sensor.wind_speed has a new state this automation sends the new data to the screen with the icon named "wind" and so on. @@ -902,6 +893,11 @@ There is an optional [notifier custom component](https://github.com/lubeda/EHMTX - 2022.6.1 removed image types only `rgb565` is valid! - 2023.2.0 removed awtrix icon `awtrixid` support +- 2023.3.5 removed automatic scaling of images and animations +- 2023.3.5 added status,display_on,display_off as default service => remove these from your yaml +- 2023.3.5 added indicator_on/off as default services => remove these from your yaml +- 2023.3.5 added *_color as default services => remove these from your yaml +- 2023.3.5 added show_all_icons,gauge_percent/gauge_off as default services => remove these from your yaml ## Usage The integration works with the Home Assistant api so, after boot of the device, it takes a few seconds until the service calls start working. diff --git a/UlanziTC001.yaml b/UlanziTC001.yaml index 84be97e..069db55 100644 --- a/UlanziTC001.yaml +++ b/UlanziTC001.yaml @@ -63,12 +63,6 @@ logger: # Enable Home Assistant API api: services: - - service: gauge - variables: - val: int - then: - lambda: |- - id(rgb8x32).set_gauge_value(val); - service: alarm variables: icon_name: string @@ -87,70 +81,7 @@ api: text: !lambda return text; icon_name: !lambda return icon_name; alarm: false - - service: brightness - variables: - brightness: int - then: - lambda: |- - id(rgb8x32)->set_brightness(brightness); - - service: status - then: - lambda: |- - id(rgb8x32)->get_status(); - - service: del_screen - variables: - icon_name: string - then: - - ehmtx.delete.screen: - id: rgb8x32 - icon_name: !lambda return icon_name; - - service: indicator_on - variables: - r: int - g: int - b: int - then: - - ehmtx.indicator.on: - id: rgb8x32 - red: !lambda return r; - green: !lambda return g; - blue: !lambda return b; - - service: force_screen - variables: - icon_name: string - then: - - ehmtx.force.screen: - id: rgb8x32 - icon_name: !lambda return icon_name; - - service: text_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_text_color(r,g,b); - - service: alarm_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_alarm_color(r,g,b); - - service: indicator_off - then: - - ehmtx.indicator.off: - id: rgb8x32 - - service: display_on - then: - - ehmtx.display.on: - id: rgb8x32 - - service: display_off - then: - - ehmtx.display.off: - id: rgb8x32 - + number: - platform: template name: "$devicename brightness" diff --git a/components/ehmtx/EHMTX.cpp b/components/ehmtx/EHMTX.cpp index 8cf77b7..6c1e326 100755 --- a/components/ehmtx/EHMTX.cpp +++ b/components/ehmtx/EHMTX.cpp @@ -15,9 +15,11 @@ namespace esphome this->alarm_color = Color(200, 50, 50); this->gauge_color = Color(100, 100, 200); this->gauge_value = 0; + this->show_indicator = false; this->icon_count = 0; this->last_clock_time = 0; this->show_icons = false; + this->show_display = true; #ifdef USE_EHMTX_SELECT this->select = NULL; @@ -30,7 +32,8 @@ namespace esphome if (icon_id < MAXICONS) { this->store->force_next_screen(icon_id); - ESP_LOGD(TAG, "force next screen: %s", name.c_str()); + this->next_action_time = this->clock->now().timestamp + this->screen_time; + ESP_LOGD(TAG, "force next screen: %s for %d sec", name.c_str(),this->screen_time); } } @@ -44,9 +47,10 @@ namespace esphome this->date_fmt = s; } - void EHMTX::set_indicator_color(int r, int g, int b) + void EHMTX::set_indicator_on(int r, int g, int b) { this->indicator_color = Color((uint8_t)r & 248, (uint8_t)g & 252, (uint8_t)b & 248); + this->show_indicator = true; ESP_LOGD(TAG, "indicator r: %d g: %d b: %d", r, g, b); } @@ -86,13 +90,16 @@ namespace esphome ESP_LOGD(TAG, "text color r: %d g: %d b: %d", r, g, b); } - bool EHMTX::string_has_ending(std::string const &fullString, std::string const &ending) + bool EHMTX::string_has_ending(std::string const &fullString, std::string const &ending) { - if (fullString.length() >= ending.length()) { - return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); - } else { - return false; - } + if (fullString.length() >= ending.length()) + { + return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } } uint8_t EHMTX::find_icon(std::string name) @@ -115,12 +122,6 @@ namespace esphome ESP_LOGD(TAG, "indicator off"); } - void EHMTX::set_indicator_on() - { - this->show_indicator = true; - ESP_LOGD(TAG, "indicator on"); - } - void EHMTX::set_display_off() { this->show_display = false; @@ -138,26 +139,30 @@ namespace esphome this->show_gauge = false; ESP_LOGD(TAG, "gauge off"); } - void EHMTX::set_gauge_value(uint8_t val) + void EHMTX::set_gauge_value(int percent) { this->show_gauge = false; - if ((val > 0) && (val <= 100)) + if (percent <= 100) { this->show_gauge = true; - this->gauge_value = (uint8_t)(100 - val) * 7 / 100; - ESP_LOGD(TAG, "gauge value: %d => %d",val, this->gauge_value); + this->gauge_value = percent; // (uint8_t)(100 - percent) * 7 / 100; + ESP_LOGD(TAG, "gauge value: %d => %d", percent, this->gauge_value); } } void EHMTX::draw_clock() { if (this->clock->now().timestamp > 6000) // valid time - { + { time_t ts = this->clock->now().timestamp; if (!this->show_date or ((this->next_action_time - ts) < this->clock_time)) { this->display->strftime(this->xoffset + 15, this->yoffset, this->font, this->clock_color, display::TextAlign::BASELINE_CENTER, this->time_fmt.c_str(), this->clock->now()); + if ((this->clock->now().second % 2 == 0) && this->show_seconds) + { + this->display->draw_pixel_at(0, 0, this->clock_color); + } } else { @@ -172,8 +177,67 @@ namespace esphome } } + void EHMTX::draw_gauge() + { + if (this->show_gauge) + { + auto height = 8; + if (this->gauge_value > 85) + { + height = 0; + } + else if (this->gauge_value > 71) + { + height = 1; + } + else if (this->gauge_value > 55) + { + height = 2; + } + else if (this->gauge_value > 45) + { + height = 3; + } + else if (this->gauge_value > 30) + { + height = 4; + } + else if (this->gauge_value > 19) + { + height = 5; + } + else if (this->gauge_value > 10) + { + height = 6; + } + + this->display->line(0, 7, 0, 0, esphome::display::COLOR_OFF); + this->display->line(0, 7, 0, height, this->gauge_color); + this->display->line(1, 7, 1, 0, esphome::display::COLOR_OFF); + } + } + void EHMTX::setup() { + register_service(&EHMTX::get_status, "status"); + register_service(&EHMTX::set_display_on, "display_on"); + register_service(&EHMTX::set_display_off, "display_off"); + register_service(&EHMTX::show_all_icons, "show_icons"); + register_service(&EHMTX::set_indicator_on, "indicator_on", {"r", "g", "b"}); + register_service(&EHMTX::set_indicator_off, "indicator_off"); + register_service(&EHMTX::set_gauge_off, "gauge_off"); + register_service(&EHMTX::set_alarm_color, "alarm_color", {"r", "g", "b"}); + register_service(&EHMTX::set_text_color, "text_color", {"r", "g", "b"}); + register_service(&EHMTX::set_clock_color, "clock_color", {"r", "g", "b"}); + register_service(&EHMTX::set_today_color, "today_color", {"r", "g", "b"}); + register_service(&EHMTX::set_gauge_color, "gauge_color", {"r", "g", "b"}); + register_service(&EHMTX::set_weekday_color, "weekday_color", {"r", "g", "b"}); + register_service(&EHMTX::add_screen, "add_screen", {"icon_name", "text", "lifetime", "alarm"}); + register_service(&EHMTX::force_screen, "force_screen", {"icon_name"}); + register_service(&EHMTX::del_screen, "del_screen", {"icon_name"}); + register_service(&EHMTX::set_gauge_value, "gauge_value", {"percent"}); + register_service(&EHMTX::set_brightness, "brightness", {"value"}); + #ifdef USE_EHMTX_SELECT if (this->select != NULL) { @@ -214,13 +278,15 @@ namespace esphome } else { - if (this->clock_time == 0) { + if (this->clock_time == 0) + { this->has_active_screen = this->store->move_next(); - this->show_screen = true; + this->show_screen = true; } - else { + else + { this->show_screen = false; - + if (!(ts - this->last_clock_time > this->clock_interval)) // force clock if last time more the 60s old { this->has_active_screen = this->store->move_next(); @@ -233,7 +299,7 @@ namespace esphome if (this->show_screen == false) { - ESP_LOGD(TAG, "next action: show clock/date for %d/%d sec",this->clock_time, this->screen_time-this->clock_time); + ESP_LOGD(TAG, "next action: show clock/date for %d/%d sec", this->clock_time, this->screen_time - this->clock_time); for (auto *t : on_next_clock_triggers_) { t->process(); @@ -243,15 +309,17 @@ namespace esphome } else { - if (this->has_active_screen) { - ESP_LOGD(TAG, "next action: show screen \"%s\" for %d sec", this->icons[this->store->current()->icon]->name.c_str() ,this->store->current()->display_duration); + if (this->has_active_screen) + { + ESP_LOGD(TAG, "next action: show screen \"%s\" for %d sec", this->icons[this->store->current()->icon]->name.c_str(), this->store->current()->display_duration); this->next_action_time = ts + this->store->current()->display_duration; for (auto *t : on_next_screen_triggers_) { t->process(this->icons[this->store->current()->icon]->name, this->store->current()->text); } } - else { + else + { // Try again immediately, we don't have a screen so want to display it immediately when the first one is sent this->next_action_time = ts; } @@ -277,10 +345,10 @@ namespace esphome void EHMTX::hold_screen() { - this->next_action_time+=this->hold_time; + this->next_action_time += this->hold_time; this->store->hold_current(this->hold_time); } - + void EHMTX::get_status() { time_t ts = this->clock->now().timestamp; @@ -336,12 +404,13 @@ namespace esphome this->scroll_intervall = si; } - void EHMTX::del_screen(std::string iname) + void EHMTX::del_screen(std::string icon_name) { // if has ending of * - if (this->string_has_ending(iname, "*")) { + if (this->string_has_ending(icon_name, "*")) + { // remove the * - std::string comparename = iname.substr(0, iname.length()-1); + std::string comparename = icon_name.substr(0, icon_name.length() - 1); // iterate through the icons, comparing start only for (uint8_t i = 0; i < this->icon_count; i++) @@ -353,13 +422,14 @@ namespace esphome } } } - else { - uint8_t icon = this->find_icon(iname.c_str()); + else + { + uint8_t icon = this->find_icon(icon_name.c_str()); this->store->delete_screen(icon); } } - void EHMTX::add_screen(std::string iconname, std::string text, uint16_t duration, bool alarm) + void EHMTX::add_screen(std::string iconname, std::string text, int duration, bool alarm) { uint8_t icon = this->find_icon(iconname.c_str()); this->internal_add_screen(icon, text, duration, alarm); @@ -381,11 +451,6 @@ namespace esphome screen->set_text(text, icon, w, duration); } - void EHMTX::set_default_brightness(uint8_t b) - { - this->brightness_ = b; - } - void EHMTX::set_show_date(bool b) { this->show_date = b; @@ -399,6 +464,19 @@ namespace esphome } } + void EHMTX::set_show_seconds(bool b) + { + this->show_seconds = b; + if (b) + { + ESP_LOGI(TAG, "show seconds"); + } + else + { + ESP_LOGI(TAG, "don't show seconds"); + } + } + void EHMTX::set_show_day_of_week(bool b) { this->show_day_of_week = b; @@ -425,12 +503,15 @@ namespace esphome } } - void EHMTX::set_brightness(uint8_t b) + void EHMTX::set_brightness(int value) { - this->brightness_ = b; - float br = (float)b / (float)255; - ESP_LOGI(TAG, "set_brightness %d => %.2f %%", b, 100 * br); - this->display->get_light()->set_correction(br, br, br, br); + if (value < 256) + { + this->brightness_ = value; + float br = (float)value / (float)255; + ESP_LOGI(TAG, "set_brightness %d => %.2f %%", value, 100 * br); + this->display->get_light()->set_correction(br,br,br); + } } uint8_t EHMTX::get_brightness() @@ -552,7 +633,8 @@ namespace esphome void EHMTX::draw() { - if (this->show_display) { + if (this->show_display) + { if (this->show_icons) { this->icon_screen->draw(); @@ -569,6 +651,7 @@ namespace esphome else { this->draw_clock(); + this->draw_gauge(); } } @@ -592,5 +675,4 @@ namespace esphome this->trigger(); } - } diff --git a/components/ehmtx/EHMTX.h b/components/ehmtx/EHMTX.h index 4109c8b..d753c14 100755 --- a/components/ehmtx/EHMTX.h +++ b/components/ehmtx/EHMTX.h @@ -8,7 +8,7 @@ const uint8_t TEXTSCROLLSTART = 8; const uint8_t TEXTSTARTOFFSET = (32 - 8); const uint16_t TICKINTERVAL = 1000; // each 1000ms -static const char *const EHMTX_VERSION = "Version: 2023.3.4"; +static const char *const EHMTX_VERSION = "Version: 2023.3.5"; static const char *const TAG = "EHMTX"; namespace esphome @@ -20,8 +20,7 @@ namespace esphome class EHMTXNextScreenTrigger; class EHMTXNextClockTrigger; - class EHMTX : public PollingComponent - { + class EHMTX : public PollingComponent, public api::CustomAPIDevice { protected: float get_setup_priority() const override { return esphome::setup_priority::AFTER_CONNECTION; } uint8_t brightness_; @@ -65,6 +64,7 @@ namespace esphome int8_t yoffset, xoffset; uint8_t find_icon(std::string name); bool string_has_ending(std::string const &fullString, std::string const &ending); + bool show_seconds; uint16_t duration; // in minutes how long is a screen valid uint16_t scroll_intervall; // ms to between scrollsteps uint16_t anim_intervall; // ms to next_frame() @@ -91,13 +91,13 @@ namespace esphome void set_hold_time(uint16_t t); void set_clock_interval(uint16_t t); void set_show_day_of_week(bool b); + void set_show_seconds(bool b); void set_show_date(bool b); void set_font_offset(int8_t x, int8_t y); void set_week_start(bool b); - void set_default_brightness(uint8_t b); - void set_brightness(uint8_t b); + void set_brightness(int b); // int because of register_service! uint8_t get_brightness(); - void add_screen(std::string icon, std::string text, uint16_t duration, bool alarm); + void add_screen(std::string icon, std::string text, int duration, bool alarm); void del_screen(std::string iname); void set_clock(time::RealTimeClock *clock); void set_font(display::Font *font); @@ -107,10 +107,9 @@ namespace esphome void set_indicator_off(); void set_time_format(std::string s); void set_date_format(std::string s); - void set_indicator_on(); - void set_indicator_color(int r, int g, int b); + void set_indicator_on(int r, int g, int b); void set_gauge_off(); - void set_gauge_value(uint8_t v); + void set_gauge_value(int v); // int because of register_service void set_gauge_color(int r, int g, int b); void set_text_color(int r, int g, int b); void set_clock_color(int r, int g, int b); @@ -119,6 +118,7 @@ namespace esphome void set_alarm_color(int r, int g, int b); void set_icon_count(uint8_t ic); void draw_clock(); + void draw_gauge(); void add_on_next_screen_trigger(EHMTXNextScreenTrigger *t) { this->on_next_screen_triggers_.push_back(t); } void add_on_next_clock_trigger(EHMTXNextClockTrigger *t) { this->on_next_clock_triggers_.push_back(t); } void setup(); @@ -251,8 +251,7 @@ namespace esphome void play(Ts... x) override { - this->parent_->set_indicator_on(); - this->parent_->set_indicator_color(this->red_.value(x...), this->green_.value(x...), this->blue_.value(x...)); + this->parent_->set_indicator_on(this->red_.value(x...), this->green_.value(x...), this->blue_.value(x...)); } protected: @@ -467,6 +466,7 @@ namespace esphome EHMTX_Icon(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, display::ImageType type, std::string icon_name, bool revers, uint16_t frame_duration); std::string name; uint16_t frame_duration; + bool fullscreen; void next_frame(); bool reverse; }; diff --git a/components/ehmtx/EHMTX_icons.cpp b/components/ehmtx/EHMTX_icons.cpp index d667cb1..76a46ef 100644 --- a/components/ehmtx/EHMTX_icons.cpp +++ b/components/ehmtx/EHMTX_icons.cpp @@ -9,6 +9,7 @@ namespace esphome this->name = icon_name; this->reverse = revers; this->frame_duration = frame_duration; + this->fullscreen = width == 32; this->counting_up = true; } diff --git a/components/ehmtx/EHMTX_screen.cpp b/components/ehmtx/EHMTX_screen.cpp index bf3812b..36c30be 100644 --- a/components/ehmtx/EHMTX_screen.cpp +++ b/components/ehmtx/EHMTX_screen.cpp @@ -23,12 +23,11 @@ namespace esphome return false; } - void EHMTX_screen::reset_shiftx() + void EHMTX_screen::reset_shiftx() { this->shiftx_ = 0; } - void EHMTX_screen::update_screen() { if (millis() - this->config_->last_scroll_time >= this->config_->scroll_intervall && this->pixels_ > TEXTSTARTOFFSET) @@ -73,31 +72,33 @@ namespace esphome extraoffset += 2; } - if (this->alarm) - { - this->config_->display->print(TEXTSCROLLSTART - this->shiftx_ + extraoffset + this->config_->xoffset, this->config_->yoffset, this->config_->font, this->config_->alarm_color, esphome::display::TextAlign::BASELINE_LEFT, - this->text.c_str()); - } - else + if (!this->config_->icons[this->icon]->fullscreen) { - this->config_->display->print(TEXTSCROLLSTART - this->shiftx_ + extraoffset + this->config_->xoffset, this->config_->yoffset, this->config_->font, this->config_->text_color, esphome::display::TextAlign::BASELINE_LEFT, - this->text.c_str()); + if (this->alarm) + { + this->config_->display->print(TEXTSCROLLSTART - this->shiftx_ + extraoffset + this->config_->xoffset, this->config_->yoffset, this->config_->font, this->config_->alarm_color, esphome::display::TextAlign::BASELINE_LEFT, + this->text.c_str()); + } + else + { + this->config_->display->print(TEXTSCROLLSTART - this->shiftx_ + extraoffset + this->config_->xoffset, this->config_->yoffset, this->config_->font, this->config_->text_color, esphome::display::TextAlign::BASELINE_LEFT, + this->text.c_str()); + } } - if (this->alarm) { this->config_->display->draw_pixel_at(30, 0, this->config_->alarm_color); this->config_->display->draw_pixel_at(31, 1, this->config_->alarm_color); this->config_->display->draw_pixel_at(31, 0, this->config_->alarm_color); } - + if (this->config_->show_gauge) { - this->config_->display->line(0, 7, 0, 0, esphome::display::COLOR_OFF); - this->config_->display->line(0, 7, 0, this->config_->gauge_value, this->config_->gauge_color); - this->config_->display->line(1, 7, 1, 0, esphome::display::COLOR_OFF); + this->config_->draw_gauge(); this->config_->display->image(2, 0, this->config_->icons[this->icon]); - this->config_->display->line(10, 0, 10, 7, esphome::display::COLOR_OFF); + if (! this->config_->icons[this->icon]->fullscreen) { + this->config_->display->line(10, 0, 10, 7, esphome::display::COLOR_OFF); + } } else { @@ -114,7 +115,7 @@ namespace esphome void EHMTX_screen::hold_slot(uint8_t _sec) { - this->endtime += _sec; + this->endtime += _sec; ESP_LOGD(TAG, "hold for %d secs", _sec); } @@ -125,7 +126,7 @@ namespace esphome this->shiftx_ = 0; float dd = ceil((2 * (TEXTSTARTOFFSET + pixel) * this->config_->scroll_intervall) / 1000); this->display_duration = (dd > this->config_->screen_time) ? dd : this->config_->screen_time; - ESP_LOGD(TAG, "display length text: %s pixels %d calculated: %d default: %d", text.c_str(),pixel, this->display_duration, this->config_->screen_time); + ESP_LOGD(TAG, "display length text: %s pixels %d calculated: %d default: %d", text.c_str(), pixel, this->display_duration, this->config_->screen_time); this->endtime = this->config_->clock->now().timestamp + et * 60; this->icon = icon; } diff --git a/components/ehmtx/__init__.py b/components/ehmtx/__init__.py index 2dfc1cd..e533e60 100755 --- a/components/ehmtx/__init__.py +++ b/components/ehmtx/__init__.py @@ -18,18 +18,25 @@ DEPENDENCIES = ["display", "light", "api"] AUTO_LOAD = ["ehmtx"] IMAGE_TYPE_RGB565 = 4 -MAXFRAMES = 20 -MAXICONS = 80 +MAXFRAMES = 110 +MAXICONS = 90 ICONWIDTH = 8 ICONHEIGHT = 8 -ICONBUFFERSIZE = ICONWIDTH * ICONHEIGHT -ICONSIZE = [ICONWIDTH,ICONHEIGHT] -SVG_START = '' - +ICONBUFFERSIZE = ICONWIDTH * ICONHEIGHT * 4 +SVG_ICONSTART = '' +SVG_FULLSCREENSTART = '' SVG_END = "" -def rgb888_svg(x,y,r,g,b): - return f"" +logging.warning(f"") +logging.warning(f"If you are upgrading EsphoMaTrix from an older version to 2023.3.5,") +logging.warning(f"you have to remove the service-definitions from the yaml. Remove following") +logging.warning(f"services (also see CHANGELOG.md and README.md):") +logging.warning(f"=========================================================================") +logging.warning(f"status, display_on, display_off, show_icons, indicator_on, indicator_off,") +logging.warning(f"gauge_off, alarm_color, text_color, clock_color, today_color, gauge_color,") +logging.warning(f"weekday_color, add_screen, force_screen, del_screen, gauge_value, brightness") +logging.warning(f"=========================================================================") +logging.warning(f"") def rgb565_svg(x,y,r,g,b): return f"> 2)},{(g << 2) | (g >> 4)},{(b << 3) | (b >> 2)});\" x=\"{x*10}\" y=\"{y*10}\" width=\"10\" height=\"10\"/>" @@ -70,6 +77,7 @@ def rgb565_svg(x,y,r,g,b): CONF_SELECT = "ehmtxselect" CONF_ON_NEXT_SCREEN = "on_next_screen" CONF_ON_NEXT_CLOCK = "on_next_clock" +CONF_SHOW_SECONDS = "show_seconds" CONF_WEEK_ON_MONDAY = "week_start_monday" CONF_ICON = "icon_name" CONF_TEXT = "text" @@ -95,6 +103,9 @@ def rgb565_svg(x,y,r,g,b): cv.Optional( CONF_HTML, default=False ): cv.boolean, + cv.Optional( + CONF_SHOW_SECONDS, default=False + ): cv.boolean, cv.Optional( CONF_SHOWDATE, default=True ): cv.boolean, @@ -455,11 +466,25 @@ async def ehmtx_set_display_off_action_to_code(config, action_id, template_arg, async def to_code(config): - from PIL import Image + from PIL import Image, ImageSequence + + def openImageFile(path): + try: + return Image.open(path) + except Exception as e: + raise core.EsphomeError(f" ICONS: Could not load image file {path}: {e}") + + def thumbnails(frames): + for frame in frames: + thumbnail = frame.copy() + thumbnail.thumbnail((32,8), Image.ANTIALIAS) + yield thumbnail + var = cg.new_Pvariable(config[CONF_ID]) html_string = F"{CORE.config_path}" html_string += '''\ \ ''' for conf in config[CONF_ICONS]: @@ -467,7 +492,7 @@ async def to_code(config): if CONF_FILE in conf: path = CORE.relative_config_path(conf[CONF_FILE]) try: - image = Image.open(path) + image = openImageFile(path) except Exception as e: raise core.EsphomeError(f" ICONS: Could not load image file {path}: {e}") elif CONF_LAMEID in conf: @@ -482,88 +507,100 @@ async def to_code(config): image = Image.open(io.BytesIO(r.content)) width, height = image.size - - if (width != ICONWIDTH) or (height != ICONHEIGHT): - image = image.resize(ICONSIZE) - width, height = image.size if hasattr(image, 'n_frames'): frames = min(image.n_frames, MAXFRAMES) else: frames = 1 - - if (conf[CONF_DURATION] == 0): - try: - duration = image.info['duration'] - except: - duration = config[CONF_ANIMINTERVALL] + + if ((width != 4*ICONWIDTH) or (width != ICONWIDTH)) and (height != ICONHEIGHT): + logging.warning(f" icon wrong size valid 8x8 or 8x32: {conf[CONF_ID]} skipped!") else: - duration = conf[CONF_DURATION] - - html_string += F"
{conf[CONF_ID]} - ({duration} ms):
" - - pos = 0 - frameIndex = 0 - html_string += f"
" - data = [0 for _ in range(ICONBUFFERSIZE * 2 * frames)] - for frameIndex in range(frames): - html_string += SVG_START - image.seek(frameIndex) - frame = image.convert("RGB") - pixels = list(frame.getdata()) - if len(pixels) != ICONBUFFERSIZE: - raise core.EsphomeError( - f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" - ) - i = 0 - for pix in pixels: - R = pix[0] >> 3 - G = pix[1] >> 2 - B = pix[2] >> 3 - x = (i % ICONWIDTH) - y = i//ICONHEIGHT - i +=1 - rgb = (R << 11) | (G << 5) | B - html_string += rgb565_svg(x,y,R,G,B) - data[pos] = rgb >> 8 - pos += 1 - data[pos] = rgb & 255 - pos += 1 - html_string += SVG_END - html_string += f"
" - - rhs = [HexInt(x) for x in data] - - prog_arr = cg.progmem_array(conf[CONF_RAW_DATA_ID], rhs) - - cg.new_Pvariable( - conf[CONF_ID], - prog_arr, - width, - height, - frames, - espImage.IMAGE_TYPE["RGB565"], - str(conf[CONF_ID]), - conf[CONF_PINGPONG], - duration, - ) - - cg.add(var.add_icon(RawExpression(str(conf[CONF_ID])))) + if (conf[CONF_DURATION] == 0): + try: + duration = image.info['duration'] + except: + duration = config[CONF_ANIMINTERVALL] + else: + duration = conf[CONF_DURATION] + + html_string += F"
{conf[CONF_ID]} - ({duration} ms):
" + + pos = 0 + frameIndex = 0 + html_string += f"
" + data = [0 for _ in range(ICONBUFFERSIZE * 2 * frames)] + for frameIndex in range(frames): + + image.seek(frameIndex) + frame = image.convert("RGB") + pixels = list(frame.getdata()) + width, height = image.size + if width == 8: + html_string += SVG_ICONSTART + else: + html_string += SVG_FULLSCREENSTART + i = 0 + for pix in pixels: + R = pix[0] >> 3 + G = pix[1] >> 2 + B = pix[2] >> 3 + x = (i % width) + y = i//width + i +=1 + rgb = (R << 11) | (G << 5) | B + html_string += rgb565_svg(x,y,R,G,B) + data[pos] = rgb >> 8 + pos += 1 + data[pos] = rgb & 255 + pos += 1 + html_string += SVG_END + html_string += f"
" + + rhs = [HexInt(x) for x in data] + + prog_arr = cg.progmem_array(conf[CONF_RAW_DATA_ID], rhs) + + cg.new_Pvariable( + conf[CONF_ID], + prog_arr, + width, + height, + frames, + espImage.IMAGE_TYPE["RGB565"], + str(conf[CONF_ID]), + conf[CONF_PINGPONG], + duration, + ) + + cg.add(var.add_icon(RawExpression(str(conf[CONF_ID])))) html_string += "" if config[CONF_HTML]: try: - with open(CORE.config_path.replace(".yaml","") + ".html", 'w') as f: + htmlfn = CORE.config_path.replace(".yaml","") + ".html" + with open(htmlfn, 'w') as f: f.truncate() f.write(html_string) f.close() + logging.info(f"EsphoMaTrix: wrote html-file with icon preview: {htmlfn}") + except: - print("Error writing HTML file") - + logging.warning(f"EsphoMaTrix: Error writing HTML file: {htmlfn}") + + disp = await cg.get_variable(config[CONF_DISPLAY]) + cg.add(var.set_display(disp)) + + f = await cg.get_variable(config[CONF_FONT_ID]) + cg.add(var.set_font(f)) + + ehmtxtime = await cg.get_variable(config[CONF_TIME]) + cg.add(var.set_clock(ehmtxtime)) + cg.add(var.set_show_clock(config[CONF_SHOWCLOCK])) cg.add(var.set_clock_interval(config[CONF_CLOCK_INTERVAL])) - cg.add(var.set_default_brightness(config[CONF_BRIGHTNESS])) + cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) cg.add(var.set_screen_time(config[CONF_SHOWSCREEN])) cg.add(var.set_duration(config[CONF_DURATION])) cg.add(var.set_scroll_intervall(config[CONF_SCROLLINTERVALL])) @@ -574,17 +611,9 @@ async def to_code(config): cg.add(var.set_show_day_of_week(config[CONF_SHOWDOW])) cg.add(var.set_hold_time(config[CONF_HOLD_TIME])) cg.add(var.set_show_date(config[CONF_SHOWDATE])) + cg.add(var.set_show_seconds(config[CONF_SHOW_SECONDS])) cg.add(var.set_font_offset(config[CONF_XOFFSET], config[CONF_YOFFSET])) - disp = await cg.get_variable(config[CONF_DISPLAY]) - cg.add(var.set_display(disp)) - - f = await cg.get_variable(config[CONF_FONT_ID]) - cg.add(var.set_font(f)) - - ehmtxtime = await cg.get_variable(config[CONF_TIME]) - cg.add(var.set_clock(ehmtxtime)) - if (config.get(CONF_SELECT)): ehmtxselect = await cg.get_variable(config[CONF_SELECT]) cg.add(var.set_select(ehmtxselect)) diff --git a/ehmtx32.yaml b/ehmtx32.yaml index d811e03..b8cc900 100644 --- a/ehmtx32.yaml +++ b/ehmtx32.yaml @@ -45,60 +45,7 @@ api: then: lambda: |- id(rgb8x32)->add_screen(icon_name, text, 5, false); - - service: brightness - variables: - brightness: int - then: - lambda: |- - id(rgb8x32)->set_brightness(brightness); - - service: status - then: - lambda: |- - id(rgb8x32)->get_status(); - - service: del_screen - variables: - icon_name: string - then: - lambda: |- - id(rgb8x32)->del_screen(icon_name); - - service: indicator_on - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_indicator_color(r,g,b); - id(rgb8x32)->set_indicator_on(); - - service: text_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_text_color(r,g,b); - - service: alarm_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_alarm_color(r,g,b); - - service: indicator_off - then: - lambda: |- - id(rgb8x32)->set_indicator_off(); - - service: display_on - then: - - ehmtx.display.on: - id: rgb8x32 - - service: display_off - then: - - ehmtx.display.off: - id: rgb8x32 - + ota: password: !secret ota_password diff --git a/ehmtx8266-color.yaml b/ehmtx8266-color.yaml index c5c869a..7ad4b14 100644 --- a/ehmtx8266-color.yaml +++ b/ehmtx8266-color.yaml @@ -75,16 +75,6 @@ api: text: !lambda return text; icon_name: !lambda return icon_name; alarm: false - - service: brightness - variables: - brightness: int - then: - lambda: |- - id(rgb8x32)->set_brightness(brightness); - - service: status - then: - lambda: |- - id(rgb8x32)->get_status(); - service: del_screen variables: icon_name: string @@ -92,17 +82,6 @@ api: - ehmtx.delete.screen: id: rgb8x32 icon_name: !lambda return icon_name; - - service: indicator_on - variables: - r: int - g: int - b: int - then: - - ehmtx.indicator.on: - id: rgb8x32 - red: !lambda return r; - green: !lambda return g; - blue: !lambda return b; - service: force_screen variables: icon_name: string @@ -110,45 +89,7 @@ api: - ehmtx.force.screen: id: rgb8x32 icon_name: !lambda return icon_name; - - service: text_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_text_color(r,g,b); - - service: clock_color - variables: - r: int - g: int - b: int - then: - - ehmtx.clock.color: - id: rgb8x32 - red: !lambda return r; - green: !lambda return g; - blue: !lambda return b; - - service: alarm_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_alarm_color(r,g,b); - - service: indicator_off - then: - - ehmtx.indicator.off: - id: rgb8x32 - - service: display_on - then: - - ehmtx.display.on: - id: rgb8x32 - - service: display_off - then: - - ehmtx.display.off: - id: rgb8x32 + ota: password: !secret ota_password diff --git a/ehmtx8266-select.yaml b/ehmtx8266-select.yaml index 94f7762..a34fd60 100644 --- a/ehmtx8266-select.yaml +++ b/ehmtx8266-select.yaml @@ -70,17 +70,6 @@ api: - ehmtx.delete.screen: id: rgb8x32 icon_name: !lambda return icon_name; - - service: indicator_on - variables: - r: int - g: int - b: int - then: - - ehmtx.indicator.on: - id: rgb8x32 - red: !lambda return r; - green: !lambda return g; - blue: !lambda return b; - service: force_screen variables: icon_name: string @@ -104,18 +93,6 @@ api: then: lambda: |- id(rgb8x32)->set_alarm_color(r,g,b); - - service: indicator_off - then: - - ehmtx.indicator.off: - id: rgb8x32 - - service: display_on - then: - - ehmtx.display.on: - id: rgb8x32 - - service: display_off - then: - - ehmtx.display.off: - id: rgb8x32 number: - platform: template diff --git a/fullfeaturetest.yaml b/fullfeaturetest.yaml index 8e21a69..612a95a 100644 --- a/fullfeaturetest.yaml +++ b/fullfeaturetest.yaml @@ -139,19 +139,7 @@ api: icon_name: !lambda return icon_name; duration: !lambda return duration; alarm: false - - - service: brightness - variables: - brightness: int - then: - lambda: |- - id(rgb8x32)->set_brightness(brightness); - - - service: status - then: - lambda: |- - id(rgb8x32)->get_status(); - + - service: a_del_screen variables: icon_name: string @@ -160,19 +148,8 @@ api: id: rgb8x32 icon_name: !lambda return icon_name; - - service: indicator_on - variables: - r: int - g: int - b: int - then: - - ehmtx.indicator.on: - id: rgb8x32 - red: !lambda return r; - green: !lambda return g; - blue: !lambda return b; - - - service: force_screen + + - service: force_screen variables: icon_name: string then: @@ -207,38 +184,6 @@ api: lambda: |- id(rgb8x32)->set_alarm_color(r,g,b); - - service: weekday_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_weekday_color(r,g,b); - - - service: today_color - variables: - r: int - g: int - b: int - then: - lambda: |- - id(rgb8x32)->set_today_color(r,g,b); - - - service: indicator_off - then: - - ehmtx.indicator.off: - id: rgb8x32 - - - service: display_on - then: - - ehmtx.display.on: - id: rgb8x32 - - - service: display_off - then: - - ehmtx.display.off: - id: rgb8x32 number: - platform: template