LED Matrix with ESPHome & HomeAssistant
When I'm in the workshop, I usually wear my hearing protection all the time. That's good for my ears, but it drives Kaddi nuts when she wants to talk to me. Because I will neither hear her shout, nor will I notice my phone buzzing in my pockets…
So of course I had to find a solution. I needed some kind of visual alarm and maybe a way to get a short message.
Electronics
I decided to use a flashing light for the alarm and a LED pixel matrix for the message part. Connect both to an ESP32, add a few more components and voila…
Most of the stuff I already had. I just needed to buy the light and the LED Matrix.
Wiring it all up was relatively straight forward. I connected the all the ground and Vcc connections on the back of the matrix with some beefier speaker wires, so that all LEDs would get enough juice.
To power the ESP32 I had planned to simply provide 5V to the Vin
pin. Just like I usually do with my ESP8266s. However it just didn't want to work. After a lot of googling, I finally found the answer: on some boards the WiFi won't be enabled when powering from Vin
. Only when powered via USB, the WiFi will work. What a pain.
So I cut up one of my many microUSB cables and soldered to that. Problem solved.
Everything else was simple. The MOSFET is used to turn on and off the 12V flash light. Since this uses up the only 3.3v pin, the arcade button is using the internal pull up and is wired to ground and a GPIO.
Software
The idea is to have a way to set a text in HomeAssistant and that will make the box flash and scroll the message on the display. To do so a text input helper was needed first in HomeAssistant.
input_text: led_matrix_text: name: Message initial: "" icon: "mdi:chat"
Next an ESPHome config is needed. The code published by Richard Nauber got me quickly started, but I made a couple of adjustments.
- led_matrix.yaml
# LED Matrix substitutions: devicename: esp32-03 xscrollpadding: "4" # in pix esphome: name: $devicename platform: ESP32 board: esp32dev # Enable logging logger: # Enable Home Assistant API api: web_server: port: 80 ota: password: !secret ota_password wifi: ssid: "W00t" password: !secret wifi_password # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "${devicename} Fallback Hotspot" password: !secret ap_password captive_portal: ########## Setup ####################### script: - id: start mode: restart then: - switch.turn_on: led_matrix_flash - delay: 60s - script.execute: stop - id: stop then: - script.stop: start - switch.turn_off: led_matrix_flash - homeassistant.service: service: input_text.set_value data: value: "" entity_id: input_text.led_matrix_text text_sensor: - platform: homeassistant name: "Matrix Text" entity_id: input_text.led_matrix_text id: led_matrix_text internal: true on_value: - if: condition: text_sensor.state: id: led_matrix_text state: "" else: - script.execute: start font: - id: tinyfont file: "ttf/lexis.ttf" size: 8 glyphs: '''äöüß!"%()+,-_.:*=°?~#0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz' light: - platform: fastled_clockless chipset: WS2812B pin: GPIO4 num_leds: 256 rgb_order: GRB name: "led_matrix" id: led_matrix_light default_transition_length: 0s color_correct: [50%, 50%, 50%] internal: true restore_mode: ALWAYS_ON display: - platform: addressable_light id: led_matrix_display addressable_light_id: led_matrix_light width: 32 height: 8 pixel_mapper: |- if (x % 2 == 0) { return (x * 8) + y; } return (x * 8) + (7 - y); rotation: 0° update_interval: 200ms lambda: |- static int16_t xpos = it.get_width(); const char * text = id(led_matrix_text).state.c_str(); auto color = Color(0, 0, 255); int x_start, y_start; int width, height; it.get_text_bounds( 0, 0, text, id(tinyfont), TextAlign::TOP_LEFT, &x_start, &y_start, &width, &height ); if(xpos < -1 * (width + $xscrollpadding)) { xpos = it.get_width(); } if(width <= it.get_width()) { xpos = 0; } it.print( xpos, 0, id(tinyfont), color, TextAlign::TOP_LEFT, text ); xpos--; switch: - platform: gpio pin: GPIO23 name: "LED Matrix Flash" id: led_matrix_flash internal: true restore_mode: ALWAYS_OFF binary_sensor: - platform: gpio pin: number: GPIO13 inverted: true mode: input: true pullup: true name: "LED Matrix Confirm" id: led_matrix_confirm internal: true on_press: then: - script.execute: stop
My start and stop scripts control the flashing light and will stop the whole thing automatically after one minute. The stop script is also triggered by the button, so I can end the madness early.
I adjusted the scrolling code a bit so that words always scroll in from the right instead of weirdly starting at the left. If the message is short enough to be shown in full, no scrolling is done.
I'm using the Dogica font by Rob Mocci. It's an 8×8 pixel font, so it's perfect for the matrix display. To make the font available to ESPHome, I copied the font files to config/esphome/ttf/
via SSH.
Update: the Lexis font by Damian Vila is a 8×6 pixel font that is even better, since characters are less wide.
Hardware
With the software out of the way, it was time to put everything into a nice box.
I 3D printed this grid model off Thingiverse to have a nice separation between the pixels.
For a diffuser, Kaddi had the great idea to cut up an IKEA Trofast storage container. The bandsaw made quick work of it. Strips of the same plastic were also used to keep the matrix in place later on.
The front got roughly cut out of plywood on my scroll saw before a final cut and a chamfer was made on the router table. Use all the tools!
In the bottom I added two magnets. A old piece of metal got screwed on top of my homemade air filter and the magnets are used to fix the box up there. The power supply I mounted at the back of the box with some adhesive velcro.