Disclaimer: I’m super happy with below project and I’m sharing it as someone else might find it use-full and I also want to give back. In some cases I don’t understand in detail why things work like they do as I’ve stolen most bits and pieces from other to get this to work.
I recently switched to a dynamic energy contract with electricity based on hourly prices and gas on daily prices.
In the end you cannot really influence the price but you can influence -in some degree- your energy consumption.
Unfortunately I do not have that many high consumers that I can delay, and even worse, most of those, like my dishwasher, dryer and washing machine I can’t even delay remotely (e.g. do not have a possibility to integrate with Homeassistant).
My machines can do this manually (by pressing some buttons) but I (nor my wife) do not want to open the phone app everything to check when the price is the cheapest.
I’ve created a few sensors in HASS to find the cheapest hours in a window of 6,12 and 14 hours and display them on a low power ESP32 with a small OLED display which I have placed near the dishwasher & in the attic near the washer & dryer.
If you want to re-create you will need the following:
- ESP32 (I have used the ESP 32 Devkit v1, like this)
- SSD1306 Oled display (like this)
- 3d printed case that fit the ESP & display snugly. (Like this)
- USB charger with micro USB cable
- Install the ESPHome integration
- Create the sensors in HASS
Build the device:
The SSD1306 has 4 wires. If you use the config below & the same devices then GND & 3.3V got the 3.3V and GND pins on ESP. SCL display to D15 ESP, SDA displat to D4 on ESP. Pins can be changed but don’t for get to change the config. If you want the case to fit you will need to cut the pins and solder the wires.
Adding the ESP to HASS
Add the ESP to ESPhome like any other ESP device. If you do not know how check out for instance this video or search for ESPhome on youtube. Trust me, its easy.
After you have added the device to ESPhome (don’t forget to also enable it under settings, devices & services, esphome) paste below code under the “captive portal:” section:
Config for ESP code (copy below captive portal)
font:
- file: "gfonts://Roboto"
id: roboto
size: 30
- file: "gfonts://Roboto"
id: roboto_2
size: 25
- file: "gfonts://Roboto"
id: roboto_3
size: 20
i2c:
sda: GPIO4
scl: GPIO15
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
reset_pin: GPIO16
address: 0x3C
lambda: |-
it.print(0, 0, id(roboto_2), "Delay (H):");
it.print(0, 35, id(roboto_3), id(near).state.c_str());
it.print(25, 35, id(roboto_3), "/");
it.print(40, 30, id(roboto_2), id(message).state.c_str());
it.print(70, 35, id(roboto_3), "/");
it.print(85, 35, id(roboto_3), id(cheapest).state.c_str());
text_sensor:
- platform: homeassistant
id: cheapest
name: "Display Message"
entity_id: sensor.hours_low_coming_far
- platform: homeassistant
id: message
name: "Display Message"
entity_id: sensor.hours_low_coming
- platform: homeassistant
id: near
name: "Display Message"
entity_id: sensor.hours_low_coming_short
Add the sensors to HASS:
As you can see there are 3 sensor in above config. In my case I use Zonneplan as energy provider so your sensors to determine the cheapest price will look different if you do not use Zonneplan. Within zonneplan there is a datetime
attribute and a price attribute
you will see them referenced in the code. You will need to play around with these if your attributes of you energy provide look different. Also the sensor is optimized for a 2 hout slot. Please note that I’m not the author of this sensor, I was helped by others who actually seem to know what they are doing:
You will need to create them in HomeAssistant.
Code for the 3 sensors that calculate the time for cheapest 2 hours in a range of 6,12 and 24h
- platform: template
sensors:
time_low_coming_short:
friendly_name: Time lowest price short
unique_id: time_low_coming_short
device_class: timestamp
value_template: >
{% set dtnow = now().isoformat()[0:26]~"Z" %}
{% set dtend = (now()+timedelta(hours=6)).isoformat()[0:26]~"Z" %}
{% set fclist = state_attr('sensor.zonneplan_current_electricity_tariff','forcast')
|selectattr('datetime','>=',dtnow)|selectattr('datetime','<=',dtend)|list %}
{% set plist = fclist|map(attribute='price')|list %}
{% set ns = namespace(fc2=[]) %}
{%- for fc in fclist[:-1] -%}
{%- set ns.fc2 = ns.fc2 + [{'datetime':fc['datetime'],'price':(plist[loop.index0] + plist[loop.index0+1])/2}] -%}
{%- endfor -%}
{% set pmin = ns.fc2|map(attribute='price')|list|min %}
{{ (ns.fc2|selectattr('price','eq',pmin)|first)['datetime'] }}
- platform: template
sensors:
time_low_coming:
friendly_name: Time lowest price
unique_id: time_low_coming
device_class: timestamp
value_template: >
{% set dtnow = now().isoformat()[0:26]~"Z" %}
{% set dtend = (now()+timedelta(hours=12)).isoformat()[0:26]~"Z" %}
{% set fclist = state_attr('sensor.zonneplan_current_electricity_tariff','forcast')
|selectattr('datetime','>=',dtnow)|selectattr('datetime','<=',dtend)|list %}
{% set plist = fclist|map(attribute='price')|list %}
{% set ns = namespace(fc2=[]) %}
{%- for fc in fclist[:-1] -%}
{%- set ns.fc2 = ns.fc2 + [{'datetime':fc['datetime'],'price':(plist[loop.index0] + plist[loop.index0+1])/2}] -%}
{%- endfor -%}
{% set pmin = ns.fc2|map(attribute='price')|list|min %}
{{ (ns.fc2|selectattr('price','eq',pmin)|first)['datetime'] }}
- platform: template
sensors:
time_low_coming_far:
friendly_name: Time lowest price far
unique_id: time_low_coming_far
device_class: timestamp
value_template: >
{% set dtnow = now().isoformat()[0:26]~"Z" %}
{% set dtend = (now()+timedelta(hours=24)).isoformat()[0:26]~"Z" %}
{% set fclist = state_attr('sensor.zonneplan_current_electricity_tariff','forcast')
|selectattr('datetime','>=',dtnow)|selectattr('datetime','<=',dtend)|list %}
{% set plist = fclist|map(attribute='price')|list %}
{% set ns = namespace(fc2=[]) %}
{%- for fc in fclist[:-1] -%}
{%- set ns.fc2 = ns.fc2 + [{'datetime':fc['datetime'],'price':(plist[loop.index0] + plist[loop.index0+1])/2}] -%}
{%- endfor -%}
{% set pmin = ns.fc2|map(attribute='price')|list|min %}
{{ (ns.fc2|selectattr('price','eq',pmin)|first)['datetime'] }}
Above sensors give you the time. However you want the number of hours so we need to create those also.
Code for # hours used by ESPhome
- platform: template
sensors:
hours_low_coming:
friendly_name: Hours lowest price
unique_id: hours_low_coming
device_class: duration
unit_of_measurement: "H"
value_template: >
{% set n = as_timestamp(now().isoformat()[0:26]~"Z") %}
{% set x = as_timestamp( states('sensor.time_low_coming'), default=0 ) %}
{% set t = (x-n) |timestamp_custom('%H') %}
{{ int(t) }}
- platform: template
sensors:
hours_low_coming_short:
friendly_name: Hours lowest price short
unique_id: hours_low_coming_short
device_class: duration
unit_of_measurement: "H"
value_template: >
{% set n = as_timestamp(now().isoformat()[0:26]~"Z") %}
{% set x = as_timestamp( states('sensor.time_low_coming_short'), default=0 ) %}
{% set t = (x-n) |timestamp_custom('%H') %}
{{ int(t) }}
- platform: template
sensors:
hours_low_coming_far:
friendly_name: Hours lowest price far
unique_id: hours_low_coming_far
device_class: duration
unit_of_measurement: "H"
value_template: >
{% set n = as_timestamp(now().isoformat()[0:26]~"Z") %}
{% set x = as_timestamp( states('sensor.time_low_coming_far'), default=0 ) %}
{% set t = (x-n) |timestamp_custom('%H') %}
{{ int(t) }}
Don’t forget to re-start HASS after adding the sensors and check in developer tools>states of they have values. If they do and your ESP is working you should see something like the picture above.
First number is # hours in 6 hours slot, second (larger number) 12H slot and the 3e number is with 24H.
Have fun!