Skip to content

Latest commit

 

History

History
921 lines (692 loc) · 28.5 KB

README.md

File metadata and controls

921 lines (692 loc) · 28.5 KB

EspHoMaTriX (ehmtx)

A simple DIY status display, build with a flexible 8x32 RGB LED panel implemented with esphome.io

Introduction

There are some "RGB-matrix" status displays/clocks out there, the commercial one from Lametric and some very good d.i.y.-alternatives.

  • LaMetric commercial ~ 199€
  • Ulanzi TC001 commercial ~ 50€
  • Awtrix (project has been discontinued after more than 4 years now in August 2022)
  • PixelIt (project is under active development)

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. Yet the LDR and battery sensor are not perfectly supported. For another use of the hardware see PixelIT_Ulanzi firmware.

See this German tutorial video with information on setting up your display RGB-LED Status Display für Home Assistant mit ESPHome | ESPHoMaTrix.

See this nice article about EsphoMaTrix on a Ulanzi TC001 from blakadder.

See this english discussions: Share your projects ESPHOME

Or in german: Showroom

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 (boring, no sound but subtitles).

Features

Based a on a 8x32 RGB flexible matrix it displays a clock, the date and up to 16 other screens provided by Home Assistant. Each screen (value/text) can be associated with a 8x8 bit RGB icon or gif animation (see installation). The values/text can be updated or deleted from the display queue. Each screen has a lifetime, if not refreshed in its lifetime it will disappear.

You can use the ehmtx32.yaml as sample for an ESP32. As mentioned you have to edit to your needs. So check font, icons, board and the GPIO pins for your display.

The file ehmtx32.yaml uses the functions ehmtx provides, the sample file ehmtx8266.yaml uses actions where possible. You have to adapt the yaml to your hardware, since there are other RGB LED Displays possible.

Installation

Matrix Types

There are some different matrices-tapes on the market, to adapt them to EspHoMaTriX you have to find the proper pixelmapper. Here are the most common types for flexible 8x32 matrices:

Type 1

under the display tag specify this pixelmapper:

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:

display:
  - platform: addressable_light
    .....
    pixel_mapper: |-
      if (y % 2 == 0) {
        return (y * 32) + x;
      }
      return (y * 32) + (31 - x);
    .....

Font

Download a small "pixel" TTF-font, i use "monobit.ttf". You can modify this font with FontForge 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, details on alternative fonts are here.

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 are automagically scaled to 8x8 on compile-time. This doesn't work well for gif's, you will have to resize them beforehand!

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 80 icons possible.

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-small.png      

The id of the icons is used later to configure the screens to display. So you should name them wisely.

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 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. 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.

Parameters

  • 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.
  • file (Exlusive, filename): a local filename
  • url (Exclusive, url): an URL to download the icon

Icon preview helper

You can create a file with all icons and names as reference. The file is named like the yaml with the extension ".html" and places in esphome config directory.

emhtx:
  html: true

Example result:

<HTML><STYLE> img { height: 40px; width: 40px; background: black;}</STYLE><BODY>
error: <img src="_icons/error fatal.gif" alt="error">&nbsp;
leia: <img src="_icons/princess leia.gif" alt="leia">&nbsp;
</BODY></HTML>

Show all icons on your matrix

This code shows all icons once on boot up, depending on the amount of your icons it can take a while to see them all.

esphome:
  ....
  on_boot:
    priority: -100
    # ...
    then:
      - lambda: !lambda |-
          id(rgb8x32)->show_all_icons();

Here you can show all of your icons via a service call:

api:
  services:
    - service: icons
      then:
        lambda: |-
          id(rgb8x32)->show_all_icons();

esphome custom component

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.

external_components:
   - source:
       type: local
       path: components # e.g. /config/esphome/components

use from repo direct

Use the github repo as component. Esphome refreshes the external components "only" once a day, perhaps you have to refresh it manually.

external_components:
  - source:
      type: git
      url: https://github.com/lubeda/EsphoMaTrix

YAML configuration

Example ehmtx: configuration

ehmtx:
  id: rgb8x32 # needed to reference the components in services and actions etc.
  show_clock: 6 
  show_screen: 8
  duration: 5
  html: true
  display8x32: ehmtxdisplay
  time: EHMTX_clock
  font_id: EHMTX_font
  icons: 
    - file: sample.png  # use your icons/animations here
      id: boot 
    - file: celsius.png
      id: temp 
    - file: garage door.gif
      id: garage

Configuration variables:

id (Required, ID): Manually specify the ID used for code generation and in service definitions.

show_clock (Optional, seconds): duration to display the clock after this time the date is display until next "show_screen". If show_date is false show_clock is false and the clock will be display as long as a normal screen! Setting show_clock to 0 will not show the clock or date, if there are no screens the display will be blank until the next screen is sent.

You can set this value during runtime e.g. for a night mode

# sample for ulanzi tc001
binary_sensor:
  - platform: gpio
    pin:
      number: $left_button_pin
      inverted: true
    on_press:
      - logger.log: "Clock on"
      - lambda:
          id(rgb8x32)->set_show_clock(6);
    name: "clock on"
  - platform: gpio
    pin: 
      number: $right_button_pin
      inverted: true
    name: "Clock off"
    on_press:
      - logger.log: "clock off"
      - lambda:
          id(rgb8x32)->set_show_clock(0);

clock_interval (Optional, seconds): show the clock at least each x seconds, (default=60)

show_screen (Optional, seconds): duration to display a screen or a clock/date sequence, a long text will be scrolled at least two times

timing

duration (Optional, minutes): lifetime of a screen in minutes (default=5). If not updates a screen will be removed after duration minutes

hold_time (Optional, seconds): extends the display time of the current screen in seconds (default=20)

date_format (Optional, string): formats the date display with strftime syntax, defaults "%d.%m." (use "%m.%d." for the US)

time_format (Optional, string): formats the date display with strftime syntax, 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

xoffset (Optional, pixel): xoffset the text is aligned BASELINE_LEFT, the left defaults to 1

display8x32 (required, ID): ID of the addressable display

dayofweek (Optional, bool): draw the day indicator on the bottom of the screen. Disable e.g. if you want larger fonts, defaults to true.

show_date (Optional, bool): if true, show the date for show_screen - show_clock seconds otherwise only shows the clock for show_screen seconds, defaults to true.

time (required, ID): ID of the time component. The display shows !t! until the time source is valid.

font (required, ID): ID of the font component

week_start_monday (optional, bool): default Monday is first day of week, false => Sunday

scroll_intervall (Optional, ms): the interval in ms to scroll the text (default=80), should be a multiple of the update_interval from the display (default: 16ms)

anim_intervall (Optional, ms): the interval in ms to display the next anim frame (default = 192), should be a multiple of the update_interval from the display (default = 16)

html (Optional, boolean): If true generate the html (filename.yaml.html) file to show all included icons. (default = false)

Usage without Home Assistant

You can add screens locally and display data directly from any local sensor. See this sample:

sensor:
  - platform: bh1750
    id: sensorlx
    ...
    on_value:
      then:
        lambda: |-
          char text[30];
          sprintf(text,"Light: %2.1f lx", id(sensorlx).state);
           id(rgb8x32)->add_screen("sun", text, 5, false); // 5 Minutes, no alarm

Take care that the char text[30]; has enough space to store the formated text.

Local trigger

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 log
ehmtx:
  ....
  on_next_screen:
    lambda: |-
        ESP_LOGD("TriggerTest","Iconname: %s",x.c_str());
        ESP_LOGI("TriggerTest","Text: %s",y.c_str());
Change the text color like crazy
ehmtx:
  ....
  on_next_screen:
    lambda: |-
      id(rgb8x32)->set_text_color(rand() % 255, rand() % 255, rand() % 255);
Send an event to Home Assistant

To send data back to home assistant you can use events.

ehmtx:
  ....
  on_next_screen:
    - homeassistant.event:
      event: esphome.next_screen
      data_template:
          iconname: !lambda "return x.c_str();"
          text: !lambda "return y.c_str();"

on_next_clock

The trigger on_next_clock is triggered every time a new clock display circle starts. See the examples:

Change Clock colors like crazy for each clock circle
ehmtx:
  ....
  on_next_clock:
    lambda: |-
      id(rgb8x32)->set_clock_color(rand() % 255, rand() % 255, rand() % 255);
      id(rgb8x32)->set_weekday_color(rand() % 255, rand() % 255, rand() % 255);
      id(rgb8x32)->set_today_color(rand() % 255, rand() % 255, rand() % 255);

Actions

For local automations you can use actions. This is the normal way of automations. The id(rgb8x32)-> style will also work.

Show date

You can dynamically enable or disable the display of the date see parameter show_date.

    - ehmtx.show.date:
        id: rgb8x32
        flag: !lambda return true;
Show day of week

You can dynamically enable or disable the display of the day of week, see parameter day_of_week.

    - ehmtx.show.dayofweek:
        id: rgb8x32
        flag: !lambda return true;
Force screen

Force the selected 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.

    - ehmtx.force.screen:
        id: rgb8x32
        icon_name: !lambda return icon_name;
Set (text/alarm/clock/weekday/today) color action

Sets the color of the select element

You have to use use id of your ehmtx component, e.g. rgb8x32

     - ehmtx.***.color:
        id: rgb8x32
        red: !lambda return r;
        green: !lambda return g;
        blue: !lambda return b;

valid elements:

  • ehmtx.text.color:
  • ehmtx.alarm.color:
  • ehmtx.clock.color:
  • ehmtx.weekday.color:
  • ehmtx.today.color:
  • red, green, blue: the color components (0..255) (default = 80)

Example

esphome:
  name: $devicename
  on_boot:
    priority: -100
    then: 
      - ehmtx.text.color:
          id: rgb8x32
          red: !lambda return 200;
          blue: !lambda return 170;
      - ehmtx.today.color:
          id: rgb8x32
          red: !lambda return 10;
          green: !lambda return 250;
      - ehmtx.clock.color:
          id: rgb8x32
          red: !lambda return 50;
          green: !lambda return 150;
          blue: !lambda return 230;
      - ehmtx.weekday.color:
          id: rgb8x32
          red: !lambda return 250;
          green: !lambda return 50;
          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

     - 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
     - ehmtx.indicator.off:
            id: rgb8x32
Add screen to loop
        - ehmtx.add.screen:
            id: rgb8x32
            text: !lambda return text;
            icon_name: !lambda return icon_name;
            duration: 7
            alarm: false

parameters:

  • id (required, ID): ID of the ehmtx component
  • text (required, string): the text to display
  • icon_name (required, string): the name of the icon to display
  • duration (optional, int): the lifetime of the screen in minutes (default=5)
  • alarm (optional, bool): if alarm set true (default = false)

Hardware/Wi-Fi

Adapt all other data in the yaml to your needs, I use GPIO04/GPIO16 (esp8266/ESP32) as port for the display.

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 for the default services, but you can add your own.

Use the light component

To use the light component add the sample lambdason_turn_on and on_turn_off to the light component.

Example

light:
  - platform: neopixelbus
    id: ehmtx_light
    ....
    on_turn_on:
      lambda: |-
         id(rgb8x32)->set_enabled(false);
    on_turn_off:
       lambda: |-
         id(rgb8x32)->set_enabled(true);

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).

Example

api:
  services:
    - service: alarm
      variables:
        icon_name: string
        text: string
      then:
        lambda: |-
          id(rgb8x32)->add_screen(icon_name, text, 7, true); // 7 Minutes alarm=true

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:

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);

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!

You can update the text on the fly. If the screen is displayed and you change the text for the icon, it will start a new lifetime (see duration) with the new text.

parameters:

  • icon_name: The number of the predefined icons (see installation)
  • text: The text to be displayed

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.

parameters:

  • icon: The number of the predefined icons (see installation)
  • text: The text to be displayed
  • duration: The lifetime in minutes

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.

parameters:

  • icon_name: The name of the predefined icon id (see installation)
  • text: The text to be displayed

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)

Service indicator_on

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

Service indicator_off

removes the indicator

Service display_on / display_off

turns the display on or off

There's an easier way in using a switch component:

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

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();

Service status

This service displays the running queue and a list of icons in the logs

[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

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.

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

Display precision after home assistant 2023.3.0

See templating 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

  - 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

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_*
  - service: esphome.ulanzi_screen
    data:
      icon_name: weather_{{ trigger.to_state.state }}
      text: >-
        {{ states("sensor.external_actual_temperature") }}°C

or another sample automation for the trashcan type

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_*
  - 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")}}
      duration: 120
    service: esphome.ulanzi_screen
mode: single

Prerequisites: This works since 2023.3.1 thanx to @andrew-codechimp fpr the new del_screen

Integrate in Home Assistant UI

Add entities to Home Assistant UI for interactive control of your display

Brightness

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

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();

Force screen

With the select component you can select, from a dropdown, which screen to show next. As with the force service if the chosen screen/icon isn't active nothing will happen. The state of the select componenten doesn't reflect the actual display because it is published only all 30s. You should also consider to not record this state in your history.

ehmtx:
  id: rgb8x32
  ...
  ehmtxselect: ehmtx_screens #id of your select component
  ...
  
select:
  - platform: ehmtx
    id: ehmtx_screens
    name: "ehmtx screens"

With the select-component you can use a script like this to show all icons. The integrated script editor in home assistant doesn't support it, so you have to add it with e.g. the vs-code addon

alias: EHMTX show all icons
sequence:
  - repeat:
      for_each: '{{ states.select.ehmtx8266_screens.attributes.options }}'
      sequence:
        service: esphome.ehmtx8266_screen
        data:
          icon_name: '{{ repeat.item }}'
          text: '{{ repeat.item }}'
mode: single
icon: mdi:led-strip

Buzzer, sound, buttons and 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.

Example: automatic brightness control with an bh1570 sensor

sensor:
  - platform: bh1570
    # ...
    on_value:
      then:
         lambda: |-  
            if (x > 200)
            {
               id(rgb8x32)->set_brightness(50);
            } else {
               id(rgb8x32)->set_brightness(250);
            }

Notifier Custom Component

There is an optional notifier custom component you can install with HACS. It is comparable to the _screen service but more streamlined.

Breaking changes

  • 2022.6.1 removed image types only rgb565 is valid!
  • 2023.2.0 removed awtrix icon awtrixid support

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.

Disclaimer

THE SOFTWARE IS PROVIDED "AS IS", use at your own risk!

Thanks

  • blakadder for his contribution (cleanup README.md,fixed sample)
  • andrew-codechimp for his contribution (display on/off & del_screen "*" & show_clock with 0)
  • jd1 for his contributions
  • aptonline for his work on the ulanzi hardware
  • wsbtak for the work on the ulanzi hardware
  • ofirsnb for his contributions

Special thanks to all sponsors