+
+###### service
+
+{"icon_name", "lifetime", "screen_time"}
+
+###### api
+
+```c
+void EHMTX::fullscreen(std::string iconname, int lifetime, int screen_time)
+```
+
+
+#### Installation
+
+##### **EspHoMaTriXv2** custom component
+
+**EspHoMaTriXv2** is a custom component, you have to include it in your yaml configuration. To always use the newest features you should use the repo, to use a stable version you copy a working version to your esphome installation.
+
+###### local use
+
+If you download the components-folder from the repo and install it in your esphome you have a stable installation. But if there are new features, you won't see them. If needed customize the yaml to your folder structure.
+
+```yaml
+external_components:
+ - source:
+ type: local
+ path: components # e.g. /config/esphome/components
+```
+
+##### use from repo
+
+Use the github repo as component. Esphome refreshes the external components "only" once a day, perhaps you have to refresh it manually. In this mode there may be breaking changes, so read the changelog and check to logs while installing the firmware.
+
+```yaml
+external_components:
+ - source:
+ type: git
+ url: https://github.com/lubeda/EspHoMaTriXv2
+ ref: release # optional select a special branch or tag
+```
+
+##### Addressable_light component
+
+The **EspHoMaTriXv2** component requires a 8x32 pixel addressable_light, it is referenced by the id `matrix_component`.
+
+See the default [options](https://esphome.io/components/display/index.html)
+
+There are some different matrices-types on the market, to adapt them to **EspHoMaTriXv2** you have to find the proper pixelmapper. If there is garbage on your display try the other `pixel_mapper`. Here are the most common types for flexible 8x32 matrices:
+
+#### Type 1
+
+Common for 8x32 RGB flexible matrices.
+
+under the display tag specify this pixelmapper:
+
+```yaml
+display:
+ - platform: addressable_light
+ .....
+ pixel_mapper: |-
+ if (x % 2 == 0) {
+ return (x * 8) + y;
+ }
+ return (x * 8) + (7 - y);
+ .....
+```
+
+#### Type 2 (e.g. Ulanzi TC001)
+
+Under the display tag specify this pixelmapper:
+
+```yaml
+display:
+ - platform: addressable_light
+ .....
+ pixel_mapper: |-
+ if (y % 2 == 0) {
+ return (y * 32) + x;
+ }
+ return (y * 32) + (31 - x);
+ .....
+```
+
+#### Type 3 (daisychained 8x8 panels)
+
+```yaml
+display:
+ - platform: addressable_light
+ .....
+ pixel_mapper: |-
+ return ((int) (x/8 * 64)) + x % 8 + y * 8;
+ .....
+```
+
+#### How to configure the pixel_mapper
+
+You have to configure this `lambda` under the `display:` section to use the **EspHoMaTriXv2** component
+
+```yaml
+display:
+ - platform: addressable_light
+ id: ehmtx_display
+ .....
+ auto_clear_enabled: true
+ lambda: |-
+ id(rgb8x32)->tick();
+ id(rgb8x32)->draw();
+```
+
+#### Light component
+
+The light component is used by the addressable_light component and referenced by id under `addressable_light_id:`.
+
+To use the light component directly from home assistant add the sample lambdas```on_turn_on``` and ```on_turn_off``` to the light component.
+
+***Sample***
+
+```yaml
+light:
+ - platform: neopixelbus
+ id: ehmtx_light
+ ....
+ on_turn_on:
+ lambda: |-
+ id(rgb8x32)->set_enabled(false);
+ on_turn_off:
+ lambda: |-
+ id(rgb8x32)->set_enabled(true);
+```
+
+To hide the light component in home assistant use: `internal: true`
+
+```yaml
+light:
+ - platform: neopixelbus
+ id: ehmtx_light
+ internal: true
+ ...
+```
+
+#### Time component
+
+Since it is a clock you need a time component e.g. [homeassistant](https://esphome.io/components/time/homeassistant.html). It is referenced by its id under `time_component:` The display shows `!t!` until the time source is synchronized and valid.
+
+#### Font
+
+Download a small "pixel" TTF-font, i use ["monobit.ttf"](https://www.google.com/search?q=monobit.ttf). You can modify this font with [FontForge](https://fontforge.org/) and added **€** on base of a **E** and so on. Due to copyright I can't provide my modified version :-(. Not all fonts are suitable for this minimalistic display. There are public domain fonts wich work well on the display e.g. [DMDSmall](https://www.pentacom.jp/pentacom/bitfontmaker2/gallery/?id=5993), details on alternative fonts are [here](https://blakadder.com/esphome-pixel-clock/#fonts).
+
+DarkPoet78 is providing special fonts for 8x32 matrices in his [repo](https://github.com/darkpoet78/MatrixClockFonts)
+
+```yaml
+font:
+ - file: monobit.ttf
+ id: EHMTX_font
+ size: 16
+ glyphs: |
+ !"%()+*=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz€@
+```
+
+#### Icons and Animations
+
+Download and install all needed icons (.jpg/.png) and animations (.gif) under the `ehmtx:` key. All icons have to be 8x8 or 8x32 pixels in size. If necessary scale them with gimp, check "as animation" for gifs.
+
+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.
+
+There are maximum 90 icons possible.
+
+***Sample***
+
+```yaml
+emhtx:
+ icons:
+ - id: boot
+ file: icons/rocket.gif
+ duration: 75
+ - id: temp
+ file: temperature.png
+ - id: yoga
+ file: icons/yoga-bridge.gif
+ pingpong: true
+ - id: garage
+ file: garage.gif
+ duration: 100
+ - id: homeassistant
+ url: https://github.com/home-assistant/assets/raw/master/logo/logo-special.png
+```
+
+The id of the icons is used later to configure the screens to display. So you should name them wisely. If you like to group icons you should prefix them e.g. with "weather_" (see Service **del_screen**)
+
+The first defined icon will be used as a fallback icon, in case of an error, e.g. if you use a non-existing icon id.
+
+GIFs are limited to 110 frames to limit the used amount of flash space.
+
+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 maximum amount of icons is limited to 90 in the code and also by the flash space and the RAM of your board.
+
+See also [icon parameter](#icons)
+
+#### Configuration
+
+##### ehmtx component
+
+This component is highly customizable.
+
+***Example***
+
+```yaml
+ehmtx:
+ id: rgb8x32
+ clock_time: 7
+ screen_time: 9
+ show_seconds: true
+ matrix_component: ehmtx_display
+ time_component: ehmtx_time
+ icons2html: true
+ default_font_id: default_font
+ special_font_id: special_font
+ special_font_yoffset: 7
+ default_font_yoffset: 6
+ brightness: 80 # percent
+ time_format: "%H:%M"
+ date_format: "%d.%m."
+ week_start_monday: true # false equals sunday
+ scroll_count: 2 # scroll long text at least two times
+ scroll_interval: 80 # milliseconds
+ frame_interval: 192 # milliseconds
+ icons:
+ .....
+```
+
+***Parameters***
+
+**id** (required, ID): Manually specify the ID used for code generation and in service definitions.
+
+**clock_time** (optional, seconds): duration to display the clock after this time the date is display until next "show_screen". If `show_date` is false and `clock_time` > 0 the clock will be display as long as a normal screen! Setting `clock_time` to 0 will not show the clock or date. If there are no screens ind the queue the display will be blank until the next screen is sent.
+
+**screen_time** (optional, seconds): default duration to display a screen or a clock/date sequence, a long text will be scrolled at least `scroll_count` times (default: 10 seconds). This may be overwritten by the add_screen service.
+
+**hold_time** (optional, seconds): extends the display time of the current screen in seconds (default=20). Used in services or automations, see `hold_screen`
+
+**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)
+
+**default_font_yoffset** (optional, pixel): yoffset the text is aligned BASELINE_LEFT, the baseline defaults to `6`
+
+**default_font_xoffset** (optional, pixel): xoffset the text is aligned BASELINE_LEFT, the left defaults to `1`
+
+**special_font_yoffset** (optional, pixel): yoffset the text is aligned BASELINE_LEFT, the baseline defaults to `6`
+
+**special_font_xoffset** (optional, pixel): xoffset the text is aligned BASELINE_LEFT, the left defaults to `1`
+
+**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.
+
+**time_component** (required, ID): ID of the time component. The display shows `!t!` until the time source is valid.
+
+**default_font** (required, ID): ID of the default font
+
+**special_font** (required, ID): ID of the special font, you can reuse your default font, but sometimes its nice to have a specialer font to minimise scrolling
+
+**week_start_monday** (optional, bool): default monday is first day of week, false => Sunday
+
+**scroll_interval** (optional, ms): the interval in ms to scroll the text (default=80), should be a multiple of the ```update_interval``` from the [display](https://esphome.io/components/display/addressable_light.html)
+
+**frame_interval** (optional, ms): the interval in ms to display the next animation/icon frame (default = 192), should be a multiple of the ```update_interval``` from the [display](https://esphome.io/components/display/addressable_light.html). Can be overwritten per icon/gif see [icons](#icons-and-animations) parameter `frame_duration`
+
+**icons2html** (optional, boolean): If true, generate the html (_filename_.html) file to show all included icons. (default = `false`)
+
+***Example output:***
+
+
+### icons
+
+***Parameters***
+See [icon details](#icons-and-animations)
+
+- **frame_duration** (optional, ms): in case of a gif file the component tries to read the default interval for each frame. The default/fallback interval is 192 ms. In case you need to override the default value set the duration per icon.
+- **pingpong** (optional, boolean): in case of a gif file you can reverse the frames instead of starting from the first frame.
+
+- **file** (Exlusive, filename): a local filename
+- **url** (Exclusive, url): an URL to download the icon
+- **lameid** (Exclusive, number): the ID from the lametric icon database
+
+## Control your display
+
+A lot of features are accessible with actions, you can use in your yaml
+
+### Local actions/lambdas
+
+#### Add screen to your display queue
+
+You can add screens locally and display data directly from any local sensor. See this sample:
+
+##### Lambda
+
+Take care that the ```char text[30];``` has enough space to store the formated text.
+
+```yaml
+sensor:
+ - platform: bh1750
+ id: sensorlx
+ ...
+ on_value:
+ then:
+ lambda: |-
+ char text[30];
+ sprintf(text,"Light: %2.1f lx", id(sensorlx).state);
+ // 5 Minutes,each time at least 11 seconds,no alarm
+ id(rgb8x32)->icon_screen("sun", text); // uses default values for color etc.
+```
+
+
+#### Set (alarm/clock/gauge/text/today/weekday) color action
+
+Sets the default color of the selected element
+
+##### Lambda set text color
+
+```yaml
+ lamda:
+ id(rgb8x32)->set_text_color(200,45,12);
+```
+
+##### Force screen
+
+Force the selected [icon_screen](#icon-screen) ```icon_name``` to be displayed next. Afterwards the loop is continuing from this screen. e.g. helpfull for alarms. Or after an update of the value/text.
+
+```yaml
+ id(rgb8x32)->force_screen("homeassistant");
+```
+
+##### Change configuration during runtime
+
+_Configuration variables/functions:_
+
+Experienced programmers can use this public methods:
+
+```c
+```
+
+### Local trigger
+
+To use the display without homeassistant automations, you may use the [advanced functionality](#change-configuration-during-runtime) with triggers. The triggers can be fired by sensors, time or by the ehmtx component.
+
+#### on_next_screen
+
+There is a trigger available to do some local magic. The trigger ```on_next_screen``` is triggered every time a new screen is displayed (it doesn't trigger on the clock/date display!!). In lambda's you can use two local string variables:
+
+**x** (Name of the icon, std::string): value to use in lambda
+
+**y** (displayed text, std::string): value to use in lambda
+
+See the examples:
+
+##### Write information to esphome log
+
+```yaml
+ehmtx:
+ ....
+ on_next_screen:
+ lambda: |-
+ ESP_LOGD("TriggerTest","Iconname: %s",x.c_str());
+ ESP_LOGI("TriggerTest","Text: %s",y.c_str());
+```
+
+##### Send an event to Home Assistant
+
+To send data back to home assistant you can use events.
+
+```yaml
+ehmtx:
+ ....
+ on_next_screen:
+ - homeassistant.event:
+ event: esphome.next_screen
+ data_template:
+ iconname: !lambda "return x.c_str();"
+ text: !lambda "return y.c_str();"
+```
+
+***Result***
+
+
+
+#### on_next_clock
+
+The trigger ```on_next_clock``` is triggered every time a new clock display circle starts.
+See the examples:
+
+##### Change something for each clock circle
+
+```yaml
+ehmtx:
+ ....
+ on_next_clock:
+ lambda: |-
+ id(rgb8x32)->.......
+```
+
+**(D)** Service **brightness**
+
+Sets the overall brightness of the display (`0..255`)
+
+_parameters:_
+
+- ```brightness```: from dark to bright (`0..255`) (default = `80`) as set in the light component by ```color_correct: [30%, 30%, 30%]```
+
+There's an easier way by using a number component:
+
+```yaml
+number:
+ - platform: template
+ name: "LED brightness"
+ min_value: 0
+ max_value: 255
+ step: 1
+ lambda: |-
+ return id(rgb8x32)->get_brightness();
+ set_action:
+ lambda: |-
+ id(rgb8x32)->set_brightness(x);
+```
+
+
+**(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.
+
+optionally you can suffix a * to the icon name to perform a wildcard delete which will delete all screens beginning with the icon_name specified.
+
+For example if you have multiple icons named weather_sunny, weather_rain & weather_cloudy, you can issue a del_screen weather_* to remove whichever screen is currently in a slot and replace it with a new weather screen.
+
+_parameters:_
+
+- ```icon_name```: Icon `id` defined in the yaml (see installation)
+- ```mode```: The mode is for internal purposes use 5 for icon_screen
+
+**(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.
+
+_parameters:_
+
+- ```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:
+
+***Sample***
+
+```yaml
+switch:
+ - platform: template
+ name: "$devicename Display"
+ icon: "mdi:power"
+ restore_mode: ALWAYS_ON
+ lambda: |-
+ return id(rgb8x32)->show_display;
+ turn_on_action:
+ lambda: |-
+ id(rgb8x32)->set_display_on();
+ turn_off_action:
+ lambda: |-
+ id(rgb8x32)->set_display_off();
+```
+
+Service **skip_screen**
+
+If there is more than one screen in the queue, skip to the next screen.
+
+e.g. on the Ulanzi TC001
+
+```yaml
+binary_sensor:
+ - platform: gpio
+ pin:
+ number: $left_button_pin
+ inverted: true
+ on_press:
+ lambda:
+ id(rgb8x32)->skip_screen();
+```
+
+Service **hold_screen**
+
+Displays the current screen for configured ammount (see **hold_time**) (default=20) seconds longer.
+
+e.g. on the Ulanzi TC001
+
+```
+binary_sensor:
+ - platform: gpio
+ pin:
+ number: $right_button_pin
+ inverted: true
+ on_press:
+ lambda:
+ id(rgb8x32)->hold_screen();
+```
+
+**(D)** Service **status**
+
+This service displays the running queue and a list of icons in the logs
+
+```log
+[13:10:10][I][EHMTX:175]: status status: 1 as: 1
+[13:10:10][I][EHMTX:176]: status screen count: 3
+[13:10:10][I][EHMTX:181]: status slot: 0 icon: 36 text: 47.9°C end: 400
+[13:10:10][I][EHMTX:181]: status slot: 1 icon: 23 text: Supa langer Text end: 310
+[13:10:10][I][EHMTX:181]: status slot: 2 icon: 1 text: 10.3°C end: 363
+[13:10:10][I][EHMTX:186]: status icon: 0 name: boot
+[13:10:10][I][EHMTX:186]: status icon: 1 name: temp
+[13:10:10][I][EHMTX:186]: status icon: 2 name: garage
+[13:10:10][I][EHMTX:186]: status icon: 3 name: wind
+[13:10:10][I][EHMTX:186]: status icon: 4 name: rain
+```
+
+Service **show_gauge** / **del_gauge_off**
+
+**(D)** Turns gauge on/off
+Displays a colored gauge at the left side of the display. You can define the color by parameter.
+
+_parameters:_
+
+- ```percent``` gauge percentage
+
+## Integration in Home Assistant
+
+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)** [above](#services), but you can add your own (see alarm and screen).
+
+### Services
+
+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)```.
+
+#### Overview of default services
+
+These services are the same as the local services, so you can adapt the documentation there
+
+ |name|parameter|
+ |----|----|
+ |`get_status`|*none*|
+ |`set_display_on`|*none*|
+ |`set_display_off`|*none*|
+ |`show_all_icons`|*none*|
+ |`hold_screen`|*none*|
+ |`set_indicator_on`| {"r", "g", "b"}|
+ |`set_indicator_off`|*none*|
+ |`set_gauge_value`| {"percent"}|
+ |`set_gauge_off`|*none*|
+ |`set_alarm_color`| {"r", "g", "b"}|
+ |`set_text_color` | {"r", "g", "b"}|
+ |`set_clock_color`| {"r", "g", "b"}|
+ |`set_today_color`| {"r", "g", "b"}|
+ |`set_gauge_color`| {"r", "g", "b"}|
+ |`set_weekday_color` |{"r", "g", "b"}|
+ |`set_screen_color` |{"icon_name","r", "g", "b"}|
+ |`add_screen` |{"icon_name", "text", "lifetime","screen_time", "alarm"}|
+ |`force_screen`| {"icon_name"}|
+ |`del_screen`| {"icon_name"}|
+ |`set_brightness`| {"value"}|
+
+### 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.
+
+```yaml
+alias: EHMTX 8266 Test
+description: ''
+trigger:
+ - platform: numeric_state
+ entity_id: sensor.wind_speed
+ id: wind
+ - platform: state
+ entity_id: sensor.actual_temperature
+ id: temp
+ - platform: state
+ entity_id: sensor.wg_cover_device
+ id: cover
+condition: []
+action:
+ - service: esphome.ehmtx8266_screen
+ data:
+ icon_name: '{{trigger.id}}'
+ text: >-
+ {{trigger.to_state.state}}{{trigger.to_state.attributes.unit_of_measurement}}
+mode: queued
+max: 10
+```
+
+## Tipps
+
+### Display precision after home assistant 2023.3.0
+
+See [templating](https://www.home-assistant.io/docs/configuration/templating/#states) for possibilities to optimize the output
+e.g.
+```{{ states(sensor.solarpower, rounded=True) }} kWh```
+
+### Specific icons per condition
+
+Add an icon per weather condition to the ehmtx component
+
+```yaml
+ - id: weather_clear_night
+ lameid: 52163
+ - id: weather_cloudy
+ lameid: 25991
+ - id: weather_fog
+ lameid: 52167
+ ......
+```
+
+Sample automation to show the weather with local temperature
+
+```yaml
+alias: EHMTX weather
+description: weather with icon per condition
+trigger:
+ - platform: state
+ entity_id: weather.metno
+action:
+ - service: esphome.ulanzi_del_screen
+ data:
+ icon_name: weather_*
+ mode: 5
+ - service: esphome.ulanzi_icon_screen
+ data:
+ icon_name: weather_{{ trigger.to_state.state }}
+ text: >-
+ {{ states("sensor.external_actual_temperature") }}°C
+ ....
+```
+
+or another sample automation for the trashcan type
+
+```yaml
+alias: "EHMTX Müllanzeige"
+description: Anzeige welche Tonne raus muss. iconnamen gekürzt
+trigger:
+ - platform: time
+ at:
+ - "06:30"
+ - "08:30"
+ - "10:30"
+ - "15:00"
+ - "17:00"
+ - "19:00"
+condition:
+ - condition: numeric_state
+ entity_id: sensor.mulltrigger
+ below: "3"
+action:
+ - service: esphome.ulanzi_del_screen
+ data:
+ icon_name: trash_*
+ mode: 5
+ - service: esphome.ulanzi_icon_screen
+ data:
+ icon_name: >-
+ trash_{{ states("sensor.mulldetails") | replace("Biotonne", "brow")|
+ replace("Papiertonne","blue")| replace("Restmüll", "grey")|
+ replace("gelbe Tonne","yell|") | truncate(4,true,"") }}
+ text: >-
+ {{ states("sensor.mulldetails")|replace(" in","")|replace(" days","
+ Tagen") | replace ("0 Tagen","heute") | replace ("1 Tagen","morgen")}}
+ lifetime: 120
+ ...
+```
+
+### Integrate in Home Assistant UI
+
+Add entities to Home Assistant UI for interactive control of your display
+
+#### Brightness
+
+```yaml
+number:
+ - platform: template
+ name: "$devicename brightness"
+ min_value: 0
+ max_value: 255
+ step: 1
+ lambda: |-
+ return id(rgb8x32)->get_brightness();
+ set_action:
+ lambda: |-
+ id(rgb8x32)->set_brightness(x);
+```
+
+#### Display switch
+
+```yaml
+switch:
+ - platform: template
+ name: "$devicename Display"
+ icon: "mdi:power"
+ restore_mode: ALWAYS_ON
+ lambda: |-
+ return id(rgb8x32)->show_display;
+ turn_on_action:
+ lambda: |-
+ id(rgb8x32)->set_display_on();
+ turn_off_action:
+ lambda: |-
+ id(rgb8x32)->set_display_off();
+```
+
+### automatic brightness
+
+Awtrix and PixelIt have hardcoded functionality. EHMTX is also capable to build something like that by lambdas. But this is all your freedom. See the Ulanzi simple yaml as guide.
+
+Example: automatic brightness control with an bh1570 sensor
+
+```yaml
+sensor:
+ - platform: bh1570
+ # ...
+ on_value:
+ then:
+ lambda: |-
+ if (x > 200)
+ {
+ id(rgb8x32)->set_brightness(50);
+ } else {
+ id(rgb8x32)->set_brightness(250);
+ }
+```
+
+## Breaking changes
+
+
+## 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. If you see a growing green rectangle after boot you have to wait a bit until the api is connected etc.
+
+
+
+## Disclaimer
+
+THE SOFTWARE IS PROVIDED "AS IS", use at your own risk!
+
+## Thanks
+
+- **[blakadder](https://github.com/blakadder)** for his contribution (cleanup README.md,fixed sample)
+- **[andrew-codechimp](https://github.com/andrew-codechimp)** for his contribution (display on/off & del_screen "*" & show_clock with 0)
+- **[jd1](https://github.com/jd1)** for his contributions
+- **[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
+- ** everbody that found bugs/issues and reported them!
+
+## Special thanks to all sponsors
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..60eedf2
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,45 @@
+# To Do's
+
+## function
+
+- [ ] alarm on all screen but fullscreen
+- [ ] indicator on all screen but fullscreen and clock
+- [ ] refreshing an icon screen should extend the display time
+- [ ] scroll left to right
+- [x] center text
+- [x] alarm unabhängig von 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 mit farbe und del_gaugeall clocks etc.
+- [ ] second point moveable
+- [ ] second point to clock screen
+
+## ux
+
+- [ ] blueprints
+- [ ] Color in blueprints
+- [ ] all modes in one blueprint
+- [x] default values for all functions
+- [ ] provide sample font from [url](https://www.pentacom.jp/pentacom/bitfontmaker2/)
+- [x] start animation
+
+## style
+
+- [ ] in screen rename text_color to color
+- [x] default_font before alarm paramater
+- [x] screen_time instead of showtime
+- [x] lifetime instead of durations
+- [x] remove actions in favor of functions
+
+## documentation
+
+- [ ] simple mode sample
+- [ ] enhanced mode sample
+- [ ] remake documentation
+- [ ] more explanation for fonts
+- [ ] reference to https://www.pentacom.jp/pentacom/bitfontmaker2/gallery/?id=13768
+
+## noch prüfen
+
+- [ ] Timer display with formation H:M:S
+- [ ] find a way for automastically add smalfont
\ No newline at end of file
diff --git a/Ulanzi-simple.yaml b/Ulanzi-simple.yaml
new file mode 100644
index 0000000..b7866d7
--- /dev/null
+++ b/Ulanzi-simple.yaml
@@ -0,0 +1,207 @@
+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
+ 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
+
+external_components:
+ - source:
+ type: git
+ url: https://github.com/lubeda/EsphoMaTrix
+ refresh: 60s
+ components: [ ehmtx ]
+
+esphome:
+ comment: "ehmtx from LuBeDa"
+ name: $devicename
+ on_boot:
+ then:
+ - ds1307.read_time:
+
+esp32:
+ board: esp32dev
+
+font:
+ - file: MatrixClock-Chunky8.bdf
+ id: default_font
+ glyphs: |
+ !?"%()+*=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnÖÄÜöäüopqrstuvwxyz@<>ß§/
+ - file: MatrixClock-Chunky6.bdf
+ id: special_font
+
+binary_sensor:
+ - platform: status
+ name: "$devicename Status"
+ - platform: gpio
+ pin:
+ number: $left_button_pin
+ inverted: true
+ name: "Left button"
+ - platform: gpio
+ pin:
+ inverted: true
+ number: $mid_button_pin
+ mode: INPUT_PULLUP
+ name: "Middle button"
+ - platform: gpio
+ pin:
+ number: $right_button_pin
+ inverted: true
+ name: "Right button"
+
+logger:
+ level: WARNING
+
+# Enable Home Assistant API
+api:
+ services:
+ - service: alarm
+ variables:
+ icon_name: string
+ text: string
+ then:
+ lambda: |-
+ id(rgb8x32)->add_icon_screen(icon_name,text, 5, 30, true, true, 200, 50, 50);
+ id(rgb8x32)->force_icon_screen(icon_name);
+ - service: screen
+ variables:
+ icon_name: string
+ text: string
+ then:
+ id(rgb8x32)->add_icon_screen(icon_name,text, 5, 20, false, true, 240, 240, 240);
+ - service: tune
+ variables:
+ tune: string
+ then:
+ - rtttl.play:
+ rtttl: !lambda 'return tune;'
+
+sensor:
+ - platform: sht3xd
+ temperature:
+ name: "$devicename Temperature"
+ humidity:
+ name: "$devicename Relative Humidity"
+ update_interval: 60s
+ - platform: adc
+ pin: $battery_pin
+ name: "$devicename Battery"
+ id: battery_voltage
+ update_interval: 10s
+ device_class: battery
+ accuracy_decimals: 0
+ attenuation: auto
+ filters:
+ - sliding_window_moving_average:
+ window_size: 15
+ send_every: 15
+ send_first_at: 1
+ - multiply: 1.6
+ - lambda: |-
+ auto r = ((x - 3) / 0.69 * 100.00);
+ if (r >= 100) return 100;
+ if (r > 0) return r;
+ if (r <= 0) return 1;
+ return 0;
+ unit_of_measurement: '%'
+ - platform: adc
+ id: light_sensor
+ name: "$devicename Illuminance"
+ pin: $ldr_pin
+ update_interval: 10s
+ attenuation: auto
+ unit_of_measurement: lx
+ device_class: illuminance
+ accuracy_decimals: 0
+ filters:
+ - lambda: |-
+ return (x / 10000.0) * 2000000.0 - 15 ;
+
+ota:
+ password: !secret ota_password
+
+wifi:
+ ssid: !secret wifi_ssid
+ password: !secret wifi_password
+
+web_server:
+
+output:
+ - platform: ledc
+ pin: $buzzerpin
+ id: rtttl_out
+
+rtttl:
+ output: rtttl_out
+
+i2c:
+ sda: $sda_pin
+ scl: $scl_pin
+ scan: true
+ id: i2cbus
+
+light:
+ - platform: neopixelbus
+ id: ehmtx_light
+ type: GRB
+ internal: true
+ variant: WS2812
+ pin: $ledpin
+ num_leds: 256
+ color_correct: [30%, 30%, 30%]
+ gamma_correction: 1.0
+ name: "$devicename Light"
+ restore_mode: ALWAYS_OFF
+
+time:
+ - platform: homeassistant
+ on_time_sync:
+ then:
+ ds1307.write_time:
+ - platform: ds1307
+ update_interval: never
+ id: ehmtx_time
+
+display:
+ - platform: addressable_light
+ id: ehmtx_display
+ addressable_light_id: ehmtx_light
+ width: 32
+ height: 8
+ internal: true
+ pixel_mapper: |-
+ if (y % 2 == 0) {
+ return (y * 32) + x;
+ }
+ return (y * 32) + (31 - x);
+ rotation: 0°
+ update_interval: 16ms
+ auto_clear_enabled: true
+ lambda: |-
+ id(rgb8x32)->tick();
+ id(rgb8x32)->draw();
+
+ehmtx:
+ id: rgb8x32
+ clock_time: 8
+ screen_time: 10
+ hold_time: 27
+ icons2html: true
+ yoffset: 7
+ matrix_component: ehmtx_display
+ time_component: ehmtx_time
+ time_format: "%H:%M"
+ show_seconds: false
+ font_id: special_font
+ icons: !include ehmtx_icons.yaml
diff --git a/components/ehmtx/BP colored state.yaml b/components/ehmtx/BP colored state.yaml
new file mode 100644
index 0000000..af98fb7
--- /dev/null
+++ b/components/ehmtx/BP colored state.yaml
@@ -0,0 +1,239 @@
+blueprint:
+ name: |-
+ ehmtxv2: show state with unit on on 8x32 RGB-display
+ description: This blueprint is triggered by a state change. If the state is between certain values it will be displayed with special colors or no state change will be displayed
+ domain: automation
+ input:
+ ehmtx_device:
+ name: which device to display at
+ selector:
+ device:
+ integration: esphome
+ trigger_sensor:
+ name: which state to show
+ description: This sensor state will be displayed
+ selector:
+ entity:
+ domain: sensor
+ default_color:
+ name: default text color
+ description: this is the default textcolor for values within the low and the high value
+ selector:
+ color_rgb:
+ default: [240, 240, 240]
+ lifetime:
+ name: how many minutes is this screen in the loop
+ selector:
+ number:
+ min: 1
+ max: 1440
+ step: 1
+ default: 2
+ screen_time:
+ name: minimum display time per loop in seconds
+ selector:
+ number:
+ min: 6
+ max: 120
+ step: 2
+ default: 12
+ icon_name:
+ name: the icon
+ selector:
+ select:
+ mode: dropdown
+ options:
+ [
+ "error",
+ "lamp",
+ "sonos",
+ "print3d",
+ "internet",
+ "speaker",
+ "alien",
+ "temp",
+ "garage",
+ "door",
+ "wind",
+ "rain",
+ "shop",
+ "phone",
+ "fire",
+ "alexa",
+ "tv",
+ "frost",
+ "muell",
+ "cookit",
+ "nature",
+ "work",
+ "bike",
+ "school",
+ "amazon",
+ "post",
+ "money",
+ "power",
+ "solar",
+ "yoga",
+ "startrek",
+ "energy",
+ "sun",
+ "diesel",
+ "benzine10",
+ "vacuum",
+ "rainprecip",
+ "iss",
+ "thunder",
+ "nina_warning",
+ "birthday",
+ "firework",
+ "coffee",
+ "lightning",
+ "xmastree",
+ "sauna",
+ "trash_grey",
+ "trash_blue",
+ "trash_yell",
+ "trash_brow",
+ "weather_clear_night",
+ "weather_cloudy",
+ "weather_fog",
+ "weather_lightingrainy",
+ "weather_partlycloudy",
+ "weather_pouring",
+ "weather_rainy",
+ "weather_snowy",
+ "weather_snowy_rainy",
+ "weather_sunny",
+ "f_mario",
+ "f_onair",
+ "f_bat",
+ "f_matrix",
+ "f_invaders",
+ "f_amongus",
+ "theodor",
+ "f_sleeping",
+ "mops",
+ "girl",
+ "timer",
+ "lasticon",
+ ]
+ default: homeassistant
+ use_colors:
+ name: Use colors based on state values
+ selector:
+ boolean:
+ default: true
+ low_value:
+ name: below this vaule is a special color
+ selector:
+ number:
+ mode: box
+ min: -10000
+ max: +10000
+ default: 0
+ low_color:
+ name: Low color
+ description: the color for values below the limit
+ selector:
+ color_rgb:
+ default: [40, 240, 40]
+ high_value:
+ name: upper limit
+ selector:
+ number:
+ mode: box
+ min: -10000
+ max: +10000
+ default: 0
+ high_color:
+ name: High Color
+ description: Text color for values above the defined value
+ selector:
+ color_rgb:
+ default: [240, 40, 40]
+ default_font:
+ name: true uses the default, false the specialer font
+ selector:
+ boolean:
+ default: true
+
+variables:
+ display: !input ehmtx_device
+ def_color: !input default_color
+ lo_color: !input low_color
+ hi_color: !input high_color
+
+trigger:
+ - platform: state
+ entity_id: !input trigger_sensor
+
+action:
+ - if:
+ - condition: template
+ value_template: "{{ not use_colors }}"
+ then:
+ - service: esphome.{{ device_attr(display, "name") }}_icon_screen
+ data:
+ icon_name: !input icon_name
+ screen_time: !input screen_time
+ lifetime: !input lifetime
+ text: "{{trigger.to_state.state}} {{trigger.to_state.attributes.unit_of_measurement}}"
+ default_font: !input default_font
+ r: |-
+ {{ def_color[0] }}
+ g: |-
+ {{ def_color[1] }}
+ b: |-
+ {{ def_color[2] }}
+ else:
+ - choose:
+ - conditions:
+ - condition: numeric_state
+ entity_id: !input trigger_sensor
+ below: !input low_value
+ sequence:
+ - service: esphome.{{ device_attr(display, "name") }}_icon_screen
+ data:
+ icon_name: !input icon_name
+ screen_time: !input screen_time
+ lifetime: !input lifetime
+ text: "{{trigger.to_state.state}} {{trigger.to_state.attributes.unit_of_measurement}}"
+ default_font: !input default_font
+ r: |-
+ {{ lo_color[0] }}
+ g: |-
+ {{ lo_color[1] }}
+ b: |-
+ {{ lo_color[2] }}
+ - conditions:
+ - condition: numeric_state
+ entity_id: !input trigger_sensor
+ above: !input high_value
+ sequence:
+ - service: esphome.{{ device_attr(display, "name") }}_icon_screen
+ data:
+ icon_name: !input icon_name
+ screen_time: !input screen_time
+ lifetime: !input lifetime
+ text: "{{trigger.to_state.state}} {{trigger.to_state.attributes.unit_of_measurement}}"
+ default_font: !input default_font
+ r: |-
+ {{ hi_color[0] }}
+ g: |-
+ {{ hi_color[1] }}
+ b: |-
+ {{ hi_color[2] }}
+ default:
+ - service: esphome.{{ device_attr(display, "name") }}_icon_screen
+ data:
+ icon_name: !input icon_name
+ screen_time: !input screen_time
+ lifetime: !input lifetime
+ text: "{{trigger.to_state.state}} {{trigger.to_state.attributes.unit_of_measurement}}"
+ default_font: !input default_font
+ r: |-
+ {{ def_color[0] }}
+ g: |-
+ {{ def_color[1] }}
+ b: |-
+ {{ def_color[2] }}
diff --git a/components/ehmtx/BP del_screen.yaml b/components/ehmtx/BP del_screen.yaml
new file mode 100644
index 0000000..a33e600
--- /dev/null
+++ b/components/ehmtx/BP del_screen.yaml
@@ -0,0 +1,212 @@
+blueprint:
+ name: |-
+ ehmtxv2':' delete screen from 8x32 RGB-display
+ description: This blueprint is triggered by a state change. If the state is between certain values it will be displayed with special colors or no state change will be displayed
+ domain: automation
+ input:
+ ehmtx_device:
+ name: which device to display at
+ selector:
+ device:
+ integration: esphome
+ mode:
+ name: the mode
+ selector:
+ select:
+ custom_value: true
+ mode: dropdown
+ options:
+ - label: MODE_ICONSCREEN
+ value: 5
+ - label: MODE_BLANK
+ value: 1
+ - label: MODE_CLOCK
+ value: 2
+ - label: MODE_DATE
+ value: 3
+ - label: MODE_FULLSCREEN
+ value: 4
+ - label: MODE_TEXT
+ value: 6
+ default: 5
+ icon_name:
+ name: the icon
+ selector:
+ select:
+ mode: dropdown
+ options:
+ [
+ "*",
+ "error",
+ "github",
+ "precipitation",
+ "nina",
+ "waschmaschine",
+ "car",
+ "lamp",
+ "sonos",
+ "print3d",
+ "internet",
+ "speaker",
+ "alien",
+ "temp",
+ "garage",
+ "door",
+ "wind",
+ "rain",
+ "shop",
+ "phone",
+ "fire",
+ "alexa",
+ "tv",
+ "frost",
+ "muell",
+ "cookit",
+ "nature",
+ "work",
+ "bike",
+ "school",
+ "amazon",
+ "post",
+ "money",
+ "power",
+ "solar",
+ "yoga",
+ "startrek",
+ "energy",
+ "sun",
+ "diesel",
+ "benzine10",
+ "vacuum",
+ "rainprecip",
+ "iss",
+ "thunder",
+ "nina_warning",
+ "birthday",
+ "firework",
+ "coffee",
+ "lightning",
+ "xmastree",
+ "sauna",
+ "trash_grey",
+ "trash_blue",
+ "trash_yell",
+ "trash_brow",
+ "weather_clear_night",
+ "weather_cloudy",
+ "weather_fog",
+ "weather_lightingrainy",
+ "weather_partlycloudy",
+ "weather_pouring",
+ "weather_rainy",
+ "weather_snowy",
+ "weather_snowy_rainy",
+ "weather_sunny",
+ "f_mario",
+ "f_onair",
+ "f_bat",
+ "f_matrix",
+ "f_invaders",
+ "f_amongus",
+ "theodor",
+ "f_sleeping",
+ "mops",
+ "girl",
+ "timer",
+ "lasticon",
+ ]
+ default: homeassistant
+ low_value:
+ name: below this vaule is a special color
+ selector:
+ number:
+ mode: box
+ min: -10000
+ max: +10000
+ low_color:
+ name: Low color
+ description: the color for values below the limit
+ selector:
+ color_rgb:
+ default: [40, 240, 40]
+ high_value:
+ name: upper limit
+ selector:
+ number:
+ mode: box
+ min: -10000
+ max: +10000
+ high_color:
+ name: High Color
+ description: Text color for values above the defined value
+ selector:
+ color_rgb:
+ default: [240, 40, 40]
+ default_font:
+ name: true uses the default, false the specialer font
+ selector:
+ boolean:
+ default: true
+
+variables:
+ display: !input ehmtx_device
+ def_color: !input default_color
+ lo_color: !input low_color
+ hi_color: !input high_color
+
+trigger:
+ - platform: state
+ entity_id: !input trigger_sensor
+
+action:
+ - choose:
+ - conditions:
+ - condition: numeric_state
+ entity_id: !input trigger_sensor
+ below: !input low_value
+ sequence:
+ - service: esphome.{{ device_attr(display, "name") }}_icon_screen
+ data:
+ icon_name: !input icon_name
+ screen_time: !input screen_time
+ lifetime: !input lifetime
+ text: "{{trigger.to_state.state}} {{trigger.to_state.attributes.unit_of_measurement}}"
+ default_font: !input default_font
+ r: |-
+ {{ lo_color[0] }}
+ g: |-
+ {{ lo_color[1] }}
+ b: |-
+ {{ lo_color[2] }}
+ - conditions:
+ - condition: numeric_state
+ entity_id: !input trigger_sensor
+ above: !input high_value
+ sequence:
+ - service: esphome.{{ device_attr(display, "name") }}_icon_screen
+ data:
+ icon_name: !input icon_name
+ screen_time: !input screen_time
+ lifetime: !input lifetime
+ text: "{{trigger.to_state.state}} {{trigger.to_state.attributes.unit_of_measurement}}"
+ default_font: !input default_font
+ r: |-
+ {{ hi_color[0] }}
+ g: |-
+ {{ hi_color[1] }}
+ b: |-
+ {{ hi_color[2] }}
+ default:
+ - service: esphome.{{ device_attr(display, "name") }}_icon_screen
+ data:
+ icon_name: !input icon_name
+ screen_time: !input screen_time
+ lifetime: !input lifetime
+ text: "{{trigger.to_state.state}} {{trigger.to_state.attributes.unit_of_measurement}}"
+ default_font: !input default_font
+ r: |-
+ {{ def_color[0] }}
+ g: |-
+ {{ def_color[1] }}
+ b: |-
+ {{ def_color[2] }}
diff --git a/components/ehmtx/EHMTX.cpp b/components/ehmtx/EHMTX.cpp
new file mode 100644
index 0000000..8f28eef
--- /dev/null
+++ b/components/ehmtx/EHMTX.cpp
@@ -0,0 +1,826 @@
+#include "esphome.h"
+
+namespace esphome
+{
+ EHMTX::EHMTX() : PollingComponent(TICKINTERVAL)
+ {
+ this->show_display = true;
+ this->display_gauge = false;
+ this->display_indicator = false;
+ this->display_alarm = false;
+ this->icon_count = 0;
+ this->text_color = Color(C_RED, C_GREEN, C_BLUE);
+ this->today_color = Color(C_RED, C_GREEN, C_BLUE);
+ this->weekday_color = Color(CD_RED, CD_GREEN, CD_BLUE);
+ this->clock_color = Color(C_RED, C_GREEN, C_BLUE);
+
+ 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;
+
+ for (uint8_t i = 0; i < MAXQUEUE; i++)
+ {
+ this->queue[i] = new EHMTX_queue(this);
+ }
+
+ this->is_running =false;
+ }
+
+ void EHMTX::set_time_format(std::string s)
+ {
+ this->time_fmt = s;
+ }
+
+ void EHMTX::set_date_format(std::string s)
+ {
+ this->date_fmt = s;
+ }
+
+ void EHMTX::show_indicator(int r, int g, int b)
+ {
+ this->indicator_color = Color((uint8_t)r & 248, (uint8_t)g & 252, (uint8_t)b & 248);
+ this->display_indicator = true;
+ ESP_LOGD(TAG, "show_indicator r: %d g: %d b: %d", r, g, b);
+ }
+
+ void EHMTX::hide_indicator()
+ {
+ this->display_indicator = false;
+ ESP_LOGD(TAG, "hide_indicator");
+ }
+
+ void EHMTX::set_display_off()
+ {
+ this->show_display = false;
+ ESP_LOGD(TAG, "display off");
+ }
+
+ void EHMTX::set_display_on()
+ {
+ this->show_display = true;
+ ESP_LOGD(TAG, "display on");
+ }
+
+ void EHMTX::set_today_color(int r, int g, int b)
+ {
+ this->today_color = Color((uint8_t)r & 248, (uint8_t)g & 252, (uint8_t)b & 248);
+ ESP_LOGD(TAG, "default today color r: %d g: %d b: %d", r, g, b);
+ }
+
+ void EHMTX::set_weekday_color(int r, int g, int b)
+ {
+ this->weekday_color = Color((uint8_t)r & 248, (uint8_t)g & 252, (uint8_t)b & 248);
+ ESP_LOGD(TAG, "default weekday color: %d g: %d b: %d", r, g, b);
+ }
+
+ void EHMTX::set_clock_color(int r, int g, int b)
+ {
+ this->clock_color = Color((uint8_t)r & 248, (uint8_t)g & 252, (uint8_t)b & 248);
+ ESP_LOGD(TAG, "default clock color r: %d g: %d b: %d", r, g, b);
+ }
+
+ void EHMTX::set_text_color(int r, int g, int b)
+ {
+ this->text_color = Color((uint8_t)r & 248, (uint8_t)g & 252, (uint8_t)b & 248);
+ ESP_LOGD(TAG, "default text color r: %d g: %d b: %d", r, g, b);
+ }
+
+ 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;
+ }
+ }
+
+ uint8_t EHMTX::find_icon(std::string name)
+ {
+ for (uint8_t i = 0; i < this->icon_count; i++)
+ {
+ if (strcmp(this->icons[i]->name.c_str(), name.c_str()) == 0)
+ {
+ ESP_LOGD(TAG, "icon: %s found id: %d", name.c_str(), i);
+ return i;
+ }
+ }
+ ESP_LOGW(TAG, "icon: %s not found", name.c_str());
+ return MAXICONS;
+ }
+
+ uint8_t EHMTX::find_icon_in_queue(std::string name)
+ {
+ for (uint8_t i = 0; i < MAXQUEUE; i++)
+ {
+ if (strcmp(this->queue[i]->icon_name.c_str(), name.c_str()) == 0)
+ {
+ ESP_LOGD(TAG, "find_icon_in_queue: icon: %s at position %d", name.c_str(), i);
+ return i;
+ }
+ }
+ ESP_LOGW(TAG, "find_icon_in_queue: icon: %s not found", name.c_str());
+ return MAXICONS;
+ }
+
+ void EHMTX::hide_gauge()
+ {
+ this->display_gauge = false;
+ ESP_LOGD(TAG, "hide gauge");
+ }
+
+ void EHMTX::show_gauge(int percent, int r, int g, int)
+ {
+ this->display_gauge = false;
+ if (percent <= 100)
+ {
+ this->display_gauge = true;
+ this->gauge_value = percent; // (uint8_t)(100 - percent) * 7 / 100;
+ ESP_LOGD(TAG, "set gauge value: %d", percent);
+ }
+ }
+
+ void EHMTX::draw_gauge()
+ {
+ if (this->display_gauge)
+ {
+ this->display->line(0, 7, 0, 0, esphome::display::COLOR_OFF);
+ this->display->line(1, 7, 1, 0, esphome::display::COLOR_OFF);
+ if (this->gauge_value > 11)
+ {
+ uint8_t height = 7 - (int)(this->gauge_value / 12.5);
+ this->display->line(0, 7, 0, height, this->gauge_color);
+ }
+ }
+ }
+
+ void EHMTX::setup()
+ {
+ ESP_LOGD(TAG, "Setting up services");
+ 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::hold_screen, "hold_screen");
+ register_service(&EHMTX::hide_indicator, "hide_indicator");
+ register_service(&EHMTX::hide_gauge, "hide_gauge");
+ register_service(&EHMTX::hide_alarm, "hide_alarm");
+ register_service(&EHMTX::show_gauge, "show_gauge", {"percent", "r", "g", "b"});
+ register_service(&EHMTX::show_alarm, "show_alarm", {"r", "g", "b"});
+ register_service(&EHMTX::show_indicator, "show_indicator", {"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_weekday_color, "weekday_color", {"r", "g", "b"});
+
+ register_service(&EHMTX::del_screen, "del_screen", {"icon_name","mode"});
+ register_service(&EHMTX::force_screen, "force_screen", {"icon_name","mode"});
+
+ register_service(&EHMTX::fullscreen, "fullscreen", {"icon_name", "lifetime", "screen_time"});
+ register_service(&EHMTX::icon_screen, "icon_screen", {"icon_name", "text", "lifetime", "screen_time", "default_font", "r", "g", "b"});
+ register_service(&EHMTX::text_screen, "text_screen", {"text", "lifetime", "screen_time", "default_font", "r", "g", "b"});
+ register_service(&EHMTX::clock_screen, "clock_screen", {"lifetime", "screen_time", "default_font", "r", "g", "b"});
+ register_service(&EHMTX::blank_screen, "blank_screen", {"lifetime", "screen_time"});
+ register_service(&EHMTX::date_screen, "date_screen", {"lifetime", "screen_time", "default_font", "r", "g", "b"});
+
+ register_service(&EHMTX::set_brightness, "brightness", {"value"});
+
+ this->is_running=true;
+ }
+
+ void EHMTX::show_alarm(int r, int g, int b)
+ {
+ this->alarm_color = Color((uint8_t)r & 248, (uint8_t)g & 252, (uint8_t)b & 248);
+ this->display_alarm = true;
+ ESP_LOGD(TAG, "show alarm color r: %d g: %d b: %d", r, g, b);
+ }
+
+ void EHMTX::hide_alarm()
+ {
+ this->display_alarm = false;
+ ESP_LOGD(TAG, "hide_alarm");
+ }
+
+ void EHMTX::blank_screen(int lifetime, int showtime)
+ {
+ auto scr = this->find_free_queue_element();
+ scr->screen_time = showtime;
+ scr->mode = MODE_BLANK;
+ scr->endtime = this->clock->now().timestamp + lifetime * 60;
+ }
+
+ void EHMTX::update() // called from polling component
+ {
+ }
+
+ 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)
+ {
+ bool force = true;
+ if ((mode == MODE_ICONSCREEN)||(mode == MODE_FULLSCREEN))
+ {
+ if (strcmp(this->queue[i]->icon_name.c_str(), icon_name.c_str()) != 0)
+ {
+ force = false;
+ }
+ if (force)
+ {
+ 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->next_action_time = this->clock->now().timestamp;
+ ESP_LOGW(TAG, "force_screen: icon %s in mode %d", icon_name.c_str(), mode);
+ }
+ }
+ }
+ }
+ }
+ uint8_t EHMTX::find_oldest_queue_element()
+ {
+ uint8_t hit = MAXQUEUE;
+ time_t last_time = this->clock->now().timestamp;
+ for (size_t i = 0; i < MAXQUEUE; i++)
+ {
+ if ((this->queue[i]->endtime > 0) && (this->queue[i]->last_time < last_time))
+ {
+ hit = i;
+ last_time = this->queue[i]->last_time;
+ }
+ }
+ if (hit != MAXQUEUE)
+ {
+ // ESP_LOGD(TAG, "find_oldest_queue_element: oldest screen is: %d", hit);
+ this->queue[hit]->status();
+ }
+ return hit;
+ }
+
+ void EHMTX::remove_expired_queue_element()
+ {
+ time_t ts = this->clock->now().timestamp;
+ std::string infotext;
+ for (size_t i = 0; i < MAXQUEUE; i++)
+ {
+ if ((this->queue[i]->endtime > 0) && (this->queue[i]->endtime < ts))
+ {
+ this->queue[i]->endtime = 0;
+ if ((this->queue[i]->mode == MODE_ICONSCREEN) || (this->queue[i]->mode == MODE_FULLSCREEN))
+ {
+ 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());
+ for (auto *t : on_expired_screen_triggers_)
+ {
+ infotext = "";
+ switch (this->queue[i]->mode)
+ {
+ case MODE_EMPTY:
+ break;
+ case MODE_BLANK:
+ break;
+ case MODE_CLOCK:
+ infotext = "clock";
+ break;
+ case MODE_DATE:
+ infotext = "clock";
+ break;
+ case MODE_FULLSCREEN:
+ infotext = "fullscreen " + this->queue[i]->icon_name;
+ break;
+ case MODE_ICONSCREEN:
+ infotext = this->queue[i]->icon_name.c_str();
+ break;
+ case MODE_TEXT:
+ infotext = "TEXT";
+ break;
+ default:
+ break;
+ }
+ t->process(this->queue[i]->icon_name, infotext);
+ }
+ }
+ this->queue[i]->mode = MODE_EMPTY;
+ }
+ }
+ }
+ void EHMTX::tick()
+ {
+ if (this->is_running)
+ {
+ time_t ts = this->clock->now().timestamp;
+ if (ts > this->next_action_time)
+ {
+ this->remove_expired_queue_element();
+ this->screen_pointer = find_oldest_queue_element();
+ if (this->screen_pointer != MAXQUEUE)
+ {
+ this->queue[this->screen_pointer]->reset_shiftx();
+ 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);
+ }
+ this->next_action_time = this->queue[this->screen_pointer]->last_time;
+ // Todo switch for Triggers
+ if (this->queue[this->screen_pointer]->mode == MODE_CLOCK)
+ {
+ for (auto *t : on_next_clock_triggers_)
+ {
+ t->process();
+ }
+ }
+ else
+ {
+ for (auto *t : on_next_screen_triggers_)
+ {
+ t->process(this->queue[this->screen_pointer]->icon_name, this->queue[this->screen_pointer]->text);
+ }
+ }
+ }
+ else
+ {
+ ESP_LOGW(TAG, "tick: nothing to do. Restarting clock display!");
+ this->clock_screen(24 * 60, this->clock_time, false, C_RED, C_GREEN, C_BLUE);
+ this->date_screen(24 * 60, (int)this->clock_time / 2, false, C_RED, C_GREEN, C_BLUE);
+ this->next_action_time = ts + this->clock_time;
+ }
+ }
+ }
+ else {
+ uint8_t w = ((uint8_t) (32/14) * (this->boot_anim / 14)) % 32;
+ this->display->rectangle(0,1,w,6,Color(120,190,40));
+ this->boot_anim++;
+ }
+ }
+
+ void EHMTX::set_screen_time(uint16_t t)
+ {
+ ESP_LOGD(TAG, "default screen time: %d", t);
+ this->screen_time = t;
+ }
+
+ void EHMTX::skip_screen()
+ {
+ this->next_action_time = this->clock->now().timestamp - 1;
+ }
+
+ void EHMTX::hold_screen()
+ {
+ this->next_action_time += this->hold_time;
+ }
+
+ void EHMTX::get_status()
+ {
+ time_t ts = this->clock->now().timestamp;
+ ESP_LOGI(TAG, "status time: %d.%d.%d %02d:%02d", this->clock->now().day_of_month,
+ this->clock->now().month, this->clock->now().year,
+ this->clock->now().hour, this->clock->now().minute);
+ ESP_LOGI(TAG, "status brightness: %d (0..255)", this->brightness_);
+ ESP_LOGI(TAG, "status date format: %s", this->date_fmt.c_str());
+ ESP_LOGI(TAG, "status time format: %s", this->time_fmt.c_str());
+ ESP_LOGI(TAG, "status text_color: RGB(%d,%d,%d)", this->text_color.r, this->text_color.g, this->text_color.b);
+ ESP_LOGI(TAG, "status alarm_color: RGB(%d,%d,%d)", this->alarm_color.r, this->alarm_color.g, this->alarm_color.b);
+ if (this->display_indicator)
+ {
+ ESP_LOGI(TAG, "status indicator on");
+ }
+ else
+ {
+ ESP_LOGI(TAG, "status indicator off");
+ }
+ if (this->show_display)
+ {
+ ESP_LOGI(TAG, "status display on");
+ }
+ else
+ {
+ ESP_LOGI(TAG, "status display off");
+ }
+
+ this->queue_status();
+ }
+
+ void EHMTX::queue_status()
+ {
+ uint8_t empty = 0;
+ for (uint8_t i = 0; i < MAXQUEUE; i++)
+ {
+ if (this->queue[i]->mode != MODE_EMPTY)
+ this->queue[i]->status();
+ else
+ empty++;
+ }
+ if (empty > 0)
+ ESP_LOGI(TAG, "queue: %d empty slots", empty);
+ }
+
+ void EHMTX::set_default_font(display::Font * font)
+ {
+ this->default_font = font;
+ }
+
+ void EHMTX::set_special_font(display::Font * font)
+ {
+ this->special_font = font;
+ }
+
+ void EHMTX::set_frame_interval(uint16_t fi)
+ {
+ this->frame_interval = fi;
+ }
+
+ void EHMTX::set_scroll_interval(uint16_t si)
+ {
+ this->scroll_interval = si;
+ }
+
+ void EHMTX::del_screen(std::string icon_name,int mode)
+ {
+ for (uint8_t i = 0; i < MAXQUEUE; i++)
+ {
+ if (this->queue[i]->mode == mode)
+ {
+ bool force = true;
+ if ((mode == MODE_ICONSCREEN)||(mode == MODE_FULLSCREEN))
+ {
+ if (strcmp(this->queue[i]->icon_name.c_str(), icon_name.c_str()) != 0)
+ {
+ force = false;
+ }
+ }
+ if (force)
+ {
+ this->queue[i]->mode = MODE_EMPTY;
+ this->queue[i]->endtime = 0;
+ ESP_LOGW(TAG, "del_screen: icon %s in position: %d mode %d", icon_name.c_str(),i, mode);
+ if (i == this->screen_pointer){
+ this->next_action_time= this->clock->now().timestamp;
+ }
+ }
+ }
+ }
+ }
+
+ void EHMTX::icon_screen(std::string iconname, std::string text, int lifetime, int screen_time, bool default_font, int r, int g, int b)
+ {
+ uint8_t icon = this->find_icon(iconname.c_str());
+
+ if (icon >= this->icon_count)
+ {
+ ESP_LOGW(TAG, "icon %d not found => default: 0", icon);
+ icon = 0;
+ }
+ EHMTX_queue *screen = this->find_icon_queue_element(icon);
+
+ int x, y, w, h;
+ if (default_font)
+ {
+ this->display->get_text_bounds(0, 0, text.c_str(), this->default_font, display::TextAlign::LEFT, &x, &y, &w, &h);
+ }
+ else
+ {
+ this->display->get_text_bounds(0, 0, text.c_str(), this->special_font, display::TextAlign::LEFT, &x, &y, &w, &h);
+ }
+ screen->set_text(text, icon, w, lifetime, screen_time);
+ screen->text_color = Color(r, g, b);
+ screen->default_font = default_font;
+ screen->mode = MODE_ICONSCREEN;
+ screen->icon_name = iconname;
+ ESP_LOGD(TAG, "icon_screen icon: %d iconname: %s text: %s lifetime: %d screen_time: %d", icon, iconname.c_str(), text.c_str(), lifetime, screen_time);
+ screen->status();
+ }
+
+ void EHMTX::timer_screen(std::string icon_name, int seconds, int lifetime, int screen_time, bool default_font, int r, int g, int b)
+ {
+ uint8_t icon = this->find_icon(icon_name.c_str());
+
+ if (icon >= this->icon_count)
+ {
+ ESP_LOGW(TAG, "icon %d not found => default: 0", icon);
+ icon = 0;
+ }
+ EHMTX_queue *screen = this->find_icon_queue_element(icon);
+
+ screen->text = "00:00";
+ screen->icon = icon;
+ screen->text_color = Color(r, g, b);
+ screen->default_font = default_font;
+ screen->mode = MODE_TIMER;
+ screen->icon_name = icon_name;
+ screen->screen_time = screen_time;
+ screen->endtime = this->clock->now().timestamp + lifetime * 60;
+ ESP_LOGD(TAG, "TIMER_screen icon: %d icon_ name: %s seconds: %d lifetime: %d screen_time: %d", icon, icon_name.c_str(), seconds, lifetime, screen_time);
+ screen->status();
+ }
+
+ void EHMTX::text_screen(std::string text, int lifetime, int screen_time, bool default_font, int r, int g, int b)
+ {
+ EHMTX_queue *screen = this->find_free_queue_element();
+
+ int x, y, w, h;
+ if (default_font)
+ {
+ this->display->get_text_bounds(0, 0, text.c_str(), this->default_font, display::TextAlign::LEFT, &x, &y, &w, &h);
+ }
+ else
+ {
+ this->display->get_text_bounds(0, 0, text.c_str(), this->special_font, display::TextAlign::LEFT, &x, &y, &w, &h);
+ }
+
+ screen->text = text;
+ screen->pixels_ = w;
+ if (screen->pixels_ < 32)
+ {
+ screen->centerx_ = ceil((32 - screen->pixels_) / 2);
+ }
+
+ screen->shiftx_ = 0;
+ float display_duration = ceil((this->scroll_count * w * this->scroll_interval) / 1000);
+ screen->screen_time = (display_duration > screen_time) ? display_duration : screen_time;
+ ESP_LOGD(TAG, "text_screen text: text: %s pixels %d screen_time: %d lifetime: %d", text.c_str(), w, screen->screen_time, lifetime);
+ screen->endtime = this->clock->now().timestamp + lifetime * 60;
+
+ screen->text_color = Color(r, g, b);
+ screen->default_font = default_font;
+ screen->mode = MODE_TEXT;
+
+ screen->status();
+ }
+
+ void EHMTX::fullscreen(std::string iconname, int lifetime, int screen_time)
+ {
+ uint8_t icon = this->find_icon(iconname.c_str());
+
+ if (icon >= this->icon_count)
+ {
+ ESP_LOGW(TAG, "fullscreen: icon %d not found => default: 0", icon);
+ icon = 0;
+ }
+ EHMTX_queue *screen = this->find_icon_queue_element(icon);
+
+ screen->mode = MODE_FULLSCREEN;
+ screen->icon = icon;
+ screen->icon_name = iconname;
+ screen->screen_time = screen_time;
+ screen->endtime = this->clock->now().timestamp + lifetime * 60;
+ ESP_LOGD(TAG, "fullscreen: icon: %d iconname: %s lifetime: %d screen_time:%d ", icon, iconname.c_str(), lifetime, screen_time);
+ screen->status();
+ }
+
+ void EHMTX::clock_screen(int lifetime, int screen_time, bool default_font, int r, int g, int b)
+ {
+ EHMTX_queue *screen = this->find_free_queue_element();
+
+ screen->text_color = Color(r, g, b);
+ 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;
+ screen->endtime = this->clock->now().timestamp + lifetime * 60;
+ screen->status();
+ }
+
+ void EHMTX::date_screen(int lifetime, int screen_time, bool default_font, int r, int g, int b)
+ {
+ EHMTX_queue *screen = this->find_free_queue_element();
+
+ 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->default_font = default_font;
+ screen->endtime = this->clock->now().timestamp + lifetime * 60;
+ screen->status();
+ }
+
+ EHMTX_queue *EHMTX::find_icon_queue_element(uint8_t icon)
+ {
+ for (size_t i = 0; i < MAXQUEUE; i++)
+ {
+ if ((this->queue[i]->mode == MODE_ICONSCREEN) && (this->queue[i]->icon == icon))
+ {
+ ESP_LOGD(TAG, "free_screen: found by icon");
+ return this->queue[i];
+ }
+ }
+ return this->find_free_queue_element();
+ }
+
+ EHMTX_queue *EHMTX::find_free_queue_element()
+ {
+ time_t ts = this->clock->now().timestamp;
+ for (size_t i = 0; i < MAXQUEUE; i++)
+ {
+ if (this->queue[i]->endtime < ts)
+ {
+ ESP_LOGD(TAG, "free_screen: found by endtime %d", i);
+ return this->queue[i];
+ }
+ }
+ return this->queue[0];
+ }
+
+ void EHMTX::set_show_date(bool b)
+ {
+ this->show_date = b;
+ if (b)
+ {
+ ESP_LOGI(TAG, "show date");
+ }
+ else
+ {
+ ESP_LOGI(TAG, "don't show date");
+ }
+ }
+
+ 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;
+ if (b)
+ {
+ ESP_LOGI(TAG, "show day of week");
+ }
+ else
+ {
+ ESP_LOGI(TAG, "don't show day of week");
+ }
+ }
+
+ void EHMTX::set_week_start(bool b)
+ {
+ this->week_starts_monday = b;
+ if (b)
+ {
+ ESP_LOGI(TAG, "weekstart: monday");
+ }
+ else
+ {
+ ESP_LOGI(TAG, "weekstart: sunday");
+ }
+ }
+
+ void EHMTX::set_brightness(int value)
+ {
+ 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()
+ {
+ return this->brightness_;
+ }
+
+ void EHMTX::set_clock_time(uint16_t t)
+ {
+ this->clock_time = t;
+ }
+
+ void EHMTX::set_hold_time(uint16_t t)
+ {
+ this->hold_time = t;
+ }
+
+ void EHMTX::set_scroll_count(uint8_t c)
+ {
+ this->scroll_count = c;
+ }
+
+ void EHMTX::set_display(addressable_light::AddressableLightDisplay * disp)
+ {
+ this->display = disp;
+ this->show_display = true;
+ }
+
+ void EHMTX::set_clock(time::RealTimeClock * clock)
+ {
+ this->clock = clock;
+ }
+
+ void EHMTX::draw_day_of_week()
+ {
+ if (this->show_day_of_week)
+ {
+ auto dow = this->clock->now().day_of_week - 1; // SUN = 0
+ for (uint8_t i = 0; i <= 6; i++)
+ {
+ if (((!this->week_starts_monday) && (dow == i)) ||
+ ((this->week_starts_monday) && ((dow == (i + 1)) || ((dow == 0 && i == 6)))))
+ {
+ this->display->line(2 + i * 4, 7, i * 4 + 4, 7, this->today_color);
+ }
+ else
+ {
+ this->display->line(2 + i * 4, 7, i * 4 + 4, 7, this->weekday_color);
+ }
+ }
+ }
+ };
+
+ void EHMTX::set_default_font_offset(int8_t y, int8_t x)
+ {
+ this->default_xoffset = x;
+ this->default_yoffset = y;
+ ESP_LOGD(TAG, "set_default_font_offset x: %d y: %d", x, y);
+ }
+
+ void EHMTX::set_special_font_offset(int8_t y, int8_t x)
+ {
+ this->special_xoffset = x;
+ this->special_yoffset = y;
+ ESP_LOGD(TAG, "set_special_font_offset x: %d y: %d", x, y);
+ }
+
+ void EHMTX::dump_config()
+ {
+ ESP_LOGCONFIG(TAG, "EspHoMatriX %s", EHMTX_VERSION);
+ ESP_LOGCONFIG(TAG, "Boot anim: %d", this->boot_anim);
+ ESP_LOGCONFIG(TAG, "Icons: %d of %d", this->icon_count, MAXICONS);
+ ESP_LOGCONFIG(TAG, "Max screens: %d", MAXQUEUE);
+ ESP_LOGCONFIG(TAG, "Date format: %s", this->date_fmt.c_str());
+ ESP_LOGCONFIG(TAG, "Time format: %s", this->time_fmt.c_str());
+ ESP_LOGCONFIG(TAG, "Interval (ms) scroll: %d frame: %d", this->scroll_interval, this->frame_interval);
+ ESP_LOGCONFIG(TAG, "Displaytime (s) clock: %d screen: %d", this->clock_time, this->screen_time);
+ if (this->show_day_of_week)
+ {
+ ESP_LOGCONFIG(TAG, "show day of week");
+ }
+ if (this->show_date)
+ {
+ ESP_LOGCONFIG(TAG, "show date");
+ }
+ if (this->week_starts_monday)
+ {
+ ESP_LOGCONFIG(TAG, "weekstart: monday");
+ }
+ else
+ {
+ ESP_LOGCONFIG(TAG, "weekstart: sunday");
+ }
+ }
+
+ void EHMTX::add_icon(EHMTX_Icon * icon)
+ {
+ this->icons[this->icon_count] = icon;
+ ESP_LOGD(TAG, "add_icon no.: %d name: %s frame_duration: %d ms", this->icon_count, icon->name.c_str(), icon->frame_duration);
+ this->icon_count++;
+ }
+
+ void EHMTX::draw()
+ {
+ if ((this->is_running) && (this->show_display) && (this->screen_pointer != MAXQUEUE))
+ {
+ this->queue[this->screen_pointer]->draw();
+ this->draw_gauge();
+
+ if (this->display_indicator)
+ {
+ this->display->line(31, 5, 29, 7, this->indicator_color);
+ this->display->draw_pixel_at(30, 7, this->indicator_color);
+ this->display->draw_pixel_at(31, 6, this->indicator_color);
+ this->display->draw_pixel_at(31, 7, this->indicator_color);
+ }
+ }
+ }
+
+ void EHMTXNextScreenTrigger::process(std::string iconname, std::string text)
+ {
+ this->trigger(iconname, text);
+ }
+
+ void EHMTXExpiredScreenTrigger::process(std::string iconname, std::string text)
+ {
+ this->trigger(iconname, text);
+ }
+
+ void EHMTXNextClockTrigger::process()
+ {
+ this->trigger();
+ }
+ }
diff --git a/components/ehmtx/EHMTX.h b/components/ehmtx/EHMTX.h
new file mode 100644
index 0000000..156740f
--- /dev/null
+++ b/components/ehmtx/EHMTX.h
@@ -0,0 +1,227 @@
+#ifndef EHMTX_H
+#define EHMTX_H
+#include "esphome.h"
+
+const uint8_t MAXQUEUE = 24;
+const uint8_t C_RED = 240; //default
+const uint8_t C_BLUE = 240;
+const uint8_t C_GREEN = 240;
+const uint8_t CD_RED = 100; // dim
+const uint8_t CD_BLUE = 100;
+const uint8_t CD_GREEN = 100;
+const uint8_t CA_RED = 200; // alarm
+const uint8_t CA_BLUE = 50;
+const uint8_t CA_GREEN = 50;
+
+const uint8_t D_LIFETIME = 5;
+const uint8_t D_SCREEN_TIME = 10;
+
+const uint8_t MAXICONS = 90;
+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.5.0";
+static const char *const TAG = "EHMTX";
+enum show_mode : uint8_t { MODE_EMPTY = 0,MODE_BLANK = 1, MODE_CLOCK = 2, MODE_DATE = 3, MODE_FULLSCREEN = 4, MODE_ICONSCREEN = 5, MODE_TEXT = 6 , MODE_TIMER = 7 };
+
+namespace esphome
+{
+ class EHMTX_queue;
+ class EHMTX_Icon;
+ class EHMTXNextScreenTrigger;
+ class EHMTXExpiredScreenTrigger;
+ class EHMTXNextClockTrigger;
+
+ class EHMTX : public PollingComponent, public api::CustomAPIDevice {
+ protected:
+ float get_setup_priority() const override { return esphome::setup_priority::WIFI; }
+ uint8_t brightness_;
+ uint32_t boot_anim=0;
+ uint8_t screen_pointer;
+ bool week_starts_monday;
+ bool show_day_of_week;
+
+ std::vector