An LED display for Home Assistant Part 2

      No Comments on An LED display for Home Assistant Part 2

The integration of my display with Home Assistant was prompted by my experiences with Tasmota, an alternate firmware for off the shelf ESP8266 and ESP32 -based devices. Essentially it allows the manufacturer supplied firmware for everything from lightbulbs to curtain motors to be replaced with an open source equivalent, often with Tasmota providing additional functionality on top of what the manufacturer supplied firmware offers.

In my case the specific device I wanted to flash with Tasmota was a Sonos NSPanel (Amazon). These are wall mountable light switches with integrated displays. I had in mind to build a desktop display with simple buttons for controlling some devices in my home; lights in the lounge where the device was to be located, the heating etc. In the end I was quite pleased with what I created with the NSPanel, Tasmota and a 3D print:

But as it turned out I ended up not really using the device I’d created. It was cumbersome to operate when I could just grab my phone out of my pocket, and it chewed up a small amount of electricity doing nothing particularly useful.

I learned a lot setting it up with my HA install. The really interesting part is that Tasmota communicates with the controlling platform, which in my case was Home Assistant, via the MQTT protocol which runs atop of TCP (with optional SSL) over Wi-Fi. MQTT is a protocol for the control and monitoring of devices on a network; it is lightweight, low-bandwidth, and simple enough to implement in low-end microcontrollers. It is an example of a publish-subscribe protocol; the device registers with a “broker” and can both publish data and subscribe to data sources, in both cases the data is presented at a particular “topic”, which is a string like:

homeassistant/sensor/ical_our_house_event_0/friendly_name

The protocol does not mandate a format for the data available at the topic. It could be a simple text string, or something more complex like a JSON blob. The broker I chose, which is a popular choice amongst HA users, was mosquitto. Naturally Home Assistant has a full range of integrations for talking to devices via MQTT after registering with the broker.

So the next step was to get my Matrix Display (as I decided it was called) prototype on my wireless LAN and see what MQTT client code was available for FreeRTOS.

Both things ended up being fairly easy, at least from the point of view of getting started with them; there were of course niggles along the road!  FreeRTOS can integrate the lwIP (Light weight IP) TCP/IP stack. FreeRTOS also has its own MQTT implementation, but since the examples that come with the Pi Pico SDK use one that is included with lwIP itself, I went for that approach instead, using this tutorial as a starting point.

Subscribing to data feeds is much simpler then publishing data so I looked at that first.

This involved creating another FreeRTOS task to handle all the networking and MQTT related activities. I was up to three tasks at this point: one for drawing into the framebuffer, one for driving the HUB75 pins on the LED matrix, and now one for the MQTT side. Naturally this task would have to feed data to the animation task, for instance the weather state.

The first problem I had was reliability in creating the Wi-Fi connection. Occasionally it would succeed first time, but usually it failed. I adjusted my code for performing the connection, which was originally based on the standard Pi Pico W sample code. Eventually I ended up with the following:

So it will loop indefinitely trying to connect. One critical detail is that the CYW43340 (PDF) Wi-Fi IC needs to be deinitialised and then initialised again when the connection is retried. It is not sufficient to simply loop calling cyw43_arch_wifi_connect_blocking(). In my experience, and on my network, the connection will generally fail the first time, but succeed the second time.

After making headway with obtaining a reliable Wi-Fi connection, the next step was to establish an MQTT level connection. This was basically a cut and paste from the tutorial, linked above.

A key part of the code and mechanism for getting data into the Pico W was the formatting of the data. This is probably what I spent most of my time on, in fact.

Initially the Matrix Display MQTT implementation performed direct subscriptions to the entities containing the Home Assistant data I was interested in. For instance, for weather data the code subscribed to the entities present at:

homeassistant/weather/openweathermap/#

The hash character is a wildcard, so all entities directly under this path were subscribed to, with different leaf topics for the forecast and the current state. The interesting data is the weather forecast, which is structured as a JSON blob containing fields like the temperature, chance of rain etc, for different times of day. I opted to use the cJSON library to parse the JSON.

Whilst this approach worked, I ran into a problem with the sheer size of data being packed into the JSON structures by Home Assistant. In particular, after updating Home Assistant new fields were being sent across MQTT that I had no intention of displaying. For instance, I was not interested in the forecast data for the days ahead, only the data for the next few hours. Yet the Pico W still had to receive all this data and parse it, using up memory the RP2040 did not really have.

A variant of this problem also reared its head with the frequency of updates. I wanted display to show the album name and other fields for the song currently playing on the media player in the room the display was in, but Home Assistant likes to present this information continually as the song is played, creating unnecessary MQTT messages for the RP2040 to process. Whilst neither problem would be a challenge for a full on PC, or even something like a Raspberry Pi, it became a problem for the small RP2040 present on the Pi Pico W board, with memory exhaustion (crashes) and general slowness.

The solution to these problems was actually fairly simple: use a Home Assistant Automation to send the data instead of getting the Pico W to subscribe to the entities. As well as limiting the load on the RP2040, this also has the advantage that the configuration of where the display should obtain the data is performed in the Home Assistant user interface instead of being hard-coded into the display firmware. There is a downside, though. It is small in the scheme of things: it requires knowledge of Home Assistant templates.

Here is a screenshot of a section of that automation. The automation follows the common pattern of having multiple triggers, each with a Trigger ID (Reddit). Within the Action block a “Choose” tree routes the Trigger ID to the actions to perform. In this case, there are a total of four different triggers for the media player handling, and they each call the same action. First up, the triggers:

So there are distinct Trigger IDs for: player plays a new track, player turned on or off,  player started and player paused. Each trigger runs the same action.

Now the action which, in many ways, is simpler then all the triggers:

So you can see that it does an MQTT publish to the display. Inside the MQTT message is a simple JSON dictionary containing the state (paused, playing etc), the artist name, album name and track name.

There are three other (core) topics that receive Home Assistant state:

  1. Weather information, containing both the current state and basic forecast information for the next 9 hours, in 3 hour increments. It also contains the current temperature obtained from various (currently 9) temperature sensors scattered about the house. Instead of sending this topic when the information changes, it is simply sent every 5 minutes.
  2. The three next calendar appointments; just the start date/time and the summary is sent.
  3. The state of a motion sensor in the porch.

This information is received by the MQTT task and used in various ways. For the weather and calendar information it is bundled up into messages and sent to the animation task, where it will be rendered into a “page” to display when it cycles around. This is covered in a little more detail below. For the porch state an icon overlay will show in the top left corner of the display which indicates motion in the porch. The icon is simply the word “PORCH” inverted in a yellow box, rendered as small as possible to be legible. Though the original idea was to use this to show when humans were at the front door of the house, it’s ended up being more useful for knowing that the cat wants to come in at night!

Another MQTT topic is provided to show notifications. I was particularly pleased with this functionality. They make use of the “grey scale” font capability provided by the framebuffer described in the previous post:

The notification message scrolls across the display after the whole display flashes white. One thing I have yet to do is integrate these notifications into Home Assistant’s own notification mechanism. Instead they must be generated via a MQTT publish service call. I have neatly overcome this problem by wrapping all my notification methods, including sending messages to the phone app, in a script so it is not a big issue for me.

These notifications are used for various things including upcoming calendar appointments and to show when the weather forecast predicts that it will rain.

Behind the notification you can see another use of the display, which is important to our household: information on the expected time of busses at a nearby bus stop.

The last thing I want to cover in this post is what the different screens (or pages) that the Matrix Display shows  look like and how they capture their data, just for some context.

First up, the time and date. This is obtained through a Real Time Clock IC, specifically a DS3231 (PDF) I2C RTC. This functionality is contained in yet another task, which periodically posts the time and date to the animation task. It is also possible to set the time and date via a MQTT message, though this is not very friendly: the format of the data is the same as the data block used by the RTC IC, though at least it needs to be sent in ASCII format.

Unlike a more complete OS, FreeRTOS does not seem to have native support for holding the system time and date. And whilst I could use NTP to fetch the time from the network, this would be more work than simply using a small piece of hardware to hold the time and date.

Note that the time is displayed using the original IBM PC font, and the date is shown using my custom compact font.

By default the clock shows for the longest amount of time compared to the other pages; about 10 seconds.

Next, the temperatures obtained from various sensors scattered around the house. These are returned in the “weather” MQTT message. The display scrolls vertically through each sensor before moving onto the next page.

Not the most interesting page! I need to do something about this one. It shows the current weather and temperature.

The weather forecast shows the hour, projected temperature and type of weather expected. The temperature is periodically swapped with the chance of rain. I use OpenWeatherMap for my weather information, but any weather integration supported by Home Assistant should be adaptable.

Next, a view of the upcoming three calendar appointments in the linked calendar. There are several approaches for integrating a calendar with Home Assistant, but the one I use consists of a “custom component” hosted on github. It creates entities for each upcoming appointment with attributes for the starting date/time and the summary. One oddity, perhaps, is how the appointment time is massaged for display. It is the Pico W code which turns a start time like:

2024-07-04 09:15:00+01:00

Into what is displayed:

07-04 09:15

The summary of the calendar appointment is rendered for display using the print_wrapped_string() API in the framebuffer module. Provided the summaries are kept fairly short this actually works quite well.

It’s not easy to see what’s going on here, but if music is playing (or paused) on the configured media player, then a scrolling message shows the track name, artist name and album name. It scrolls across quickly enough not to linger for too long, but slowly enough to be read easily. Obviously if the media player is idle this page is skipped.

Lastly, the aforementioned bus information page.

This information does not originate at the Home Assistant installation, but instead is obtained from the local bus company’s website, parsed, and presented to the Pico W using a Go script. It would be nice to implement this as a Home Assistant integration so that bus information could be shown on the dashboard or perhaps used in notifications, but that would involve hacking on HA python code. Instead I wrote a completely self contained persistent Go script.

Unfortunately the bus company does not have any public APIs exposing nice JSON blobs. Instead human-facing HTML pages must be parsed. Obviously Go is well equipped for dealing with downloading from HTTP servers and parsing the resultant HTML pages, walking DOMs etc. But what is more surprising is there is also a very tidy and nice MQTT library.

Looking at the screenshot above I see I have a small bug to fix: “1 min” is not shortened to “1m” like it is for “25 mins” etc.

This project is spread over a number of github repositories, but the starting point is the one for the Matrix Display firmware code.

In the next and final part in this series I will go over the why and how I had to complicate the driving of the HUB75 display with an FPGA, the rest of the hardware – everything from 3D printed plastic to CNC’d wood was used – and smaller details around the firmware’s functionality…

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.