If you select one of these options, it will be stored in your web browser and automatically applied to other pages on this website.

Showing spot electricity prices on a Ulanzi clock using ESPHome

Knowing the price helps keep our electricity use at a sustainable level. This approach is extremely flexible and doesn't require MQTT.

Close-up photograph of a white plastic Ulanzi clock, with a black screen made of a 8x32 LED matrix. On the left of the matrix is a shape made of yellow pixels that resembles a power plug and on the right the number 16.2 in red.

Why I did it

Where we live, the price of electricity changes every hour. There's an auction between different producers every day. Based on the auction the "exchange price" each hour for the next day is known from around 2PM.

We could pay more for a guaranteed, fixed price, but we chose not to. Our usage is usually quite low and we can adjust our plans, like what time we make dinner, if the price is high. Paying, and watching, the hourly price is a good way to keep our usage at a sustainable level for the supply.

Earlier, we had the price shown in the Home Assistant app, but it's not always easy to remember to check it. I wanted to try making it visible when we are in the kitchen and living space, where most of the energy is consumed.

In this project, I got the price to pop up on the LED panel of a Ulanzi clock, using Home Assistant and ESPHome. If the price is low, it behaves like a clock and only shows the time. If the price exceeds a pre-defined limit, the electricity price appears.

Ingredients

  • Ulanzi TC001 Smart Pixel Clock
  • ESPHome - I have it running as an add-on in Home Assistant OS, but any method should work

Method

Get the price into Home Assistant

I did this a while ago. The starting point was installing HACS. HACS enables the easy installation and updating of custom components. The website has a good step-by-step guide to getting started with HACS.

With HACS installed, I could install the Nordpool custom component. The component is then configured from the user interface. The electricity contract includes a breakdown of the margin, fees and VAT rate. A few months ago the VAT rate changed. It wasn't easy to update these options then, so I recommend not using the built-in VAT calculation. Instead, I now use Home Assistant in "number helpers" for the margin and VAT rate. Then the "additional fees" value will be something like:

{{ (current_price * states('input_number.electricity_vat_rate') | float(0)) + states('input_number.electricity_margin_cents') | float(0) }}

I ended up with a Home Assistant sensor showing the current price. It has attributes for hourly prices and other statistics, but they're not used here.

Flash the clock

The clock contains an ESP32-based processor. I can connect to it with a USB-C cable to write firmware to it. This replaces the stock firmware the clock comes with, but unlocks a great deal of power.

I use ESPHome to compile and install this firmware. The ESPHome website explains how to get started.

My Home Assistant instance doesn't have an SSL certificate on the local network, so the easiest way to get started was using ESPHome Web. This writes an empty ESPHome image onto the device. After this, I could set your wi-fi credentials, then "adopt" the empty device into the local ESPHome. Once the wi-fi credentials are set, further updates happen over wi-fi.

When I flashed the image, the clock made a continuous beep. This is annoying, but it doesn't seem possible to stop it until I was able to write my own configuration. Muffling the noise with some tape helped, as did moving the clock to another room.

Write the code

I'm not going to explain every intricacy of using ESPHome here. I suggest starting with the ESPHome documentation if you're new to it and want to copy this.

The first few lines written were to stop the constant beeping. Adding this to the configuration file in ESPHome, then recompiling and flashing the software helped:

# Fix the beeping
output:
  - platform: gpio
    pin:
      number: GPIO15
      ignore_strapping_warning: true
    id: buzzer_pin

Next, it was time to think about the architecture. I looked at ready-to-install solutions, like ehmtx. I decided not to go with this, as it was restrictive, and included features I didn't need. Their code did help me understand things like pin numbers, though.

First, I added the "light" component, which has 256 addressable LEDs. This would allow me to write the display next.

light:
  - platform: neopixelbus
    id: ulanzi_leds
    type: GRB
    internal: true
    variant: WS2812
    pin: GPIO32
    num_leds: 256
    color_correct: [30%, 30%, 30%]
    gamma_correct: 2.0
    restore_mode: ALWAYS_OFF

Then, adding a display component, which references the light, allowed me to write what appears on the pages.

display:
  - platform: addressable_light
    id: ulanzi_display
    addressable_light_id: ulanzi_leds
    width: 32
    height: 8
    pixel_mapper: |-
      if (y % 2 == 0) {
        return (y * 32) + x;
      }
      return (y * 32) + (31 - x);
    rotation:update_interval: 16ms
    auto_clear_enabled: true

I wrote pages using ESPHome's display engine. This was a big challenge, as the resolution is only 8x32 pixels. I went for MatrixChunky6 and MatrixChunky8, MIT-licensed fonts I found on GitHub. It's just a case of copying the BDF version of the font into Home Assistant's config/esphome/ directory, and adding some more YAML:

font:
  - file: "fonts/MatrixChunky6.bdf"
    id: chunky6
    glyphs: "0123456789."
  - file: "fonts/MatrixChunky8.bdf"
    id: chunky8
    glyphs: "0123456789."

My wife quickly created an 8x8 pixel icon. I tried using the Material Design icons, which are already integrated into Home Assistant and ESPHome, but these all looked terrible at this size. My wife's icon was designed at this size, so looked pretty good:

Again, it was just a case of uploading the file (the original was a PNG) and adding a bit more to the YAML:

image:
  - file: 'custom_icons/plug.png'
    id: plug_icon
    resize: 10x8
    type: BINARY

Setting type: BINARY here was nice, as it allowed me to re-color the image later.

Now it was time to write the actual views. This used ESPHome's "pages" concept. One screen only includes the time, and another only includes the electricity price. The following code is added so that pages is a key under the existing display item.

    pages:
      - id: current_time
        lambda: |-
            auto pale_yellow = Color(255, 255, 127);
            it.strftime(16, 0, id(chunky8), pale_yellow, TextAlign::TOP_CENTER, "%H.%M", id(ha_time).now());
      - id: electricity_price
        lambda: |-
            auto red = Color(255, 0, 0);
            auto yellow = Color(255, 255, 0);
            it.image(0, 0, id(plug_icon), yellow);
            it.printf(32, 2, id(chunky6), red, TextAlign::TOP_RIGHT, "%.1f", id(current_electricity_price).state);

You might have noticed the references to ha_time and current_electricity_price. These need to be fetched from the Home Assistant instance on the local network:

time:
  - platform: homeassistant
    id: ha_time

sensor:
  - platform: homeassistant
    name: "Electricity price"
    entity_id: sensor.nordpool_kwh_fi_eur_3_10_0
    id: current_electricity_price

The entity_id he is copied from Home Assistant, so yours will probably look different.

Next, I added a switch. This switch allows me to swap from only displaying the time to displaying both the time and electricity price. The key detail about this switch is that it's exposed to Home Assistant. This means I can toggle it or write automations to toggle it automatically.

switch:
  - platform: template
    name: Show electricity price
    id: electricity_price_enabled
    optimistic: True
    restore_mode: RESTORE_DEFAULT_OFF
    entity_category: config
    icon: mdi:currency-eur

Then, I used this switch value to set the on_page_change options of the display component I created earlier.

    on_page_change:
      - to: electricity_price
        then:
          - if:
              condition:
                switch.is_off: electricity_price_enabled
              then:
                - display.page.show_next: ulanzi_display
                - component.update: ulanzi_display

This just tells ESPHome that when the page changes to the electricity price, if the "show electricity price" switch is off, then go to the next page. At this point, there's only one other page, but writing it this way means that it's possible to add more pages in the future.

I also set the view to go to the next page automatically every 15 seconds. If the switch is off, then when it goes onto the electricity_price page, it will immediately call display.page.show_next, meaning that we only see the clock (and not even a moment of the other page).

interval:
  - interval: 15s
    then:
      - display.page.show_next: ulanzi_display
      - component.update: ulanzi_display

Finally, I wrote the Home Assistant automation to turn this switch on when the price is high. I set a custom threshold as a fixed value.

alias: "Living Room Clock: Show electricity price based on value"
triggers:
  - trigger: numeric_state
    entity_id:
      - sensor.nordpool_kwh_fi_eur_3_10_0
    above: 8
conditions: []
actions:
  - action: switch.turn_on
    target:
      entity_id: switch.esphome_web_2d6341_show_electricity_price
mode: single

I also needed the inverse - to make the electricity price disappear when the price dropped low enough.

alias: "Living Room Clock: Hide electricity price when normal"
description: ""
triggers:
  - trigger: numeric_state
    entity_id:
      - sensor.nordpool_kwh_fi_eur_3_10_0
    below: 8
conditions: []
actions:
  - action: switch.turn_off
    metadata: {}
    data: {}
    target:
      entity_id: switch.esphome_web_2d7734_show_electricity_price
mode: single

Now the clock, in a highly visible spot at home, shows the electricity price whenever it is over 8 cents.

A close-up of the clock, again showing a pixelated plug icon and a red price of 16.2