DIY Boiler Solar Energy Storage

The idea:
I recently bought a small photovoltaics system, which can output up to 800W. This does not sound like much (and it isn’t) but still, 50% of the yield gets fed into the grid and is wasted, because I don’t even get any kind of compensation for it.
The biggest single consumer in the house is a hot water boiler with 2500W - the problem now is, when it turns on, it turns on completely and draws 2500W for a couple of minutes (or longer - depending on the temperature of the water). This is obviously not ideal, because that way I use 100% of the produced solar-power for a few minutes but waste 75% of it the rest of the time (disclaimer: the values are representative, not measured)
So the optimal solution would be, to only deliver excess power to the boiler.

The how:
There are off the shelf solutions for this problem. The drawback of them is, firstly they are expensive - you don’t only need a special heat-rod (which alone is ~750€), but also the appropriate controllers/electronics. And secondly, I have a regular wall mounted 50l boiler which doesn’t even have an opening to add another heater.

So DIY it is: From what I’ve figured out, these “special” heat rods are simple thyristor-dimmers. And where are thyristor-dimmers usually used?! That’s right: stage lighting. And what are heating rods? that’s right: lightbulbs, that don’t emmit light (ohmic consumers). And what does most stage equipment have? that’s right: the ability to be controlled via DMX

The Components:
First of all you need a boiler - a dumb-boiler would be ideal. One that does not have any electronics, but a simple bi-metal switch. Then I got one of these Dimmers. Also you need an ESP32 board, a MAX485 RS-485 TTL to RS485 Converter and a female XLR plug. Quite cucial for optimal power distribution is a way to measure the current total power comsumption of your house. I use a Shelly 3EM for that. A bit of wiring, and that’s it for a basic setup.
You might also want a way to measure the boiler’s temperature, so you can give it more power if it is too cold - on days without sun for example, or just to have it at a decent temperature at all times a DS18B20 (+ 4.7 kΩ resistor) should do the trick

The Setup:
In my specific case I had to convert my boiler, because it’s a rather weird contraption and also had electronics and a display - I didn’t want these things, so I replaced the electronics with two shelly 1…
When altering a boiler keep in mind Mythbusters 2007 E20 (and others) - it’s definitely not not dangerous. Never disable the thermal fuses and/or overpreassure valves.
Anyways - I’m not going into that for now and assume it’s a “dumb-boiler” that is used.

The actual basic conversion is quite simple and straight forward: follow this guide to control the dimmer. It’s a lot more simple than that long thread suggests at first glance. You simply connect the MAX485 to the ESP32 like explained here - but you don’t need a resistor - it’s already on the board. Also I drive everything with 5V - it’s just more convenient to simply use a USB charger HOWEVER - don’t power the MAX485 through the ESP32 - mine wouldn’t boot like that, because it draws too much power (I guess).

Here’s my ESPHome YAML:

esphome:
  name: boiler-dmx-controller

esp32:
  board: esp32dev
  framework:
    type: arduino

external_components:
  - source: github://andyboeh/esphome-dmx512

dallas:
  - pin: GPIO16
    update_interval: 10s

substitutions:
  device_name: "Boiler DMX Controller"

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_pass
  - ssid: !secret wifi_ssid_EZ
    password: !secret wifi_pass

  ap:
    ssid: "${device_name} wifi"
    password: "NdnbtPAtF6sv6F"

captive_portal:


uart:
  id: uart_bus
  baud_rate: 250000
  tx_pin: GPIO5
  stop_bits: 2

dmx512:
  id: dmx
  uart_id: uart_bus
#  enable_pin: 33
  periodic_update: true
  tx_pin: GPIO5
  uart_num: 1

logger:

api:
  password: !secret api_ota_pass

ota:
  password: !secret api_ota_pass

text_sensor:
  - platform: wifi_info
    ip_address:
      name: ${device_name} IP

sensor:

  - platform: uptime
    name: ${device_name} Uptime

  - platform: wifi_signal
    name: ${device_name} WiFi Signal
    update_interval: 60s

  - platform: dallas
    address: 0xe74c1ak648e76f84
    name: "${device_name} Boiler Temperature"
    accuracy_decimals: 2

output:
  - platform: dmx512
    channel: 1   #<--- make sure it's the same channel as on the dimmer
    universe: dmx
    id: dmx_1
#    min_power: 0.40
    max_power: 0.75 #<--- you might want to set this, because the dimmer can only deliver 10A. when everything is set up, use a power meter to slowly determine an appropriate value

light:

  - platform: monochromatic
    name: "${device_name} Dimmer"
    output: dmx_1
    id: dimmer_01
    default_transition_length: 1s
    gamma_correct: 0
    

Now you should be able to control the dimmer via ESPHome - test it with a light bulb (NO LED or other energy saving bulbs - it has to be a good old filament bulb) and make sure the manual slider on the dimmer is set to 0 - otherwise it overrides the DMX signal.

Now we have to find a way to tell the dimmer how much power it should give to the boiler.
I went the Node-Red way and have made a simple flow that reads the total power consumption of the house and if it is below zero (which means power get’s fed into the grid) turns up the dimmer one step. And if the total power consumption is over 40W it goes down one step. I also added different percentage steps, depending on how much excess power there is (this value can flucutate fast and drastically if you turn on/off devices in your home)
Additionally I have added a dummy-thermostat so I can set a minimum temperature for the boiler, which it tries to get to under all circumstances (that’s what the DS18B20 is for) - it just controls an input_boolean, Node red can work with
Here’s my flow:


of course you should’t disable the first node - saw my mistake after I took the screenshot

and here the code:

[{"id":"ab4cb0742f777d75","type":"trigger","z":"37d6892f50d80d96","name":"","op1":"","op2":"check","op1type":"nul","op2type":"str","duration":"5","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":230,"y":1580,"wires":[["cf9132730574bee5"]]},{"id":"cf9132730574bee5","type":"api-current-state","z":"37d6892f50d80d96","name":"Total Wattt House","server":"938bbb0d.d39038","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.shelly_3em_total_power","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":410,"y":1620,"wires":[["ab4cb0742f777d75","ae63cd85d799e401"]]},{"id":"a5963674c0db6d6f","type":"server-state-changed","z":"37d6892f50d80d96","name":"Total Watt House","server":"938bbb0d.d39038","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.shelly_3em_total_power","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"num","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":140,"y":1680,"wires":[["ab4cb0742f777d75","ae63cd85d799e401"]]},{"id":"ae63cd85d799e401","type":"api-current-state","z":"37d6892f50d80d96","name":"Boiler too cold?","server":"938bbb0d.d39038","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.boiler_climate_dummy","state_type":"str","blockInputOverrides":true,"outputProperties":[],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":440,"y":1760,"wires":[["97a89b61d54cce19"],["8095276a47c81476"]]},{"id":"97a89b61d54cce19","type":"delay","z":"37d6892f50d80d96","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":1,"x":630,"y":1700,"wires":[["49825630d88e78f4"]]},{"id":"8095276a47c81476","type":"switch","z":"37d6892f50d80d96","name":"","property":"payload","propertyType":"msg","rules":[{"t":"lte","v":"-150","vt":"num"},{"t":"lte","v":"-50","vt":"num"},{"t":"lte","v":"-1","vt":"num"},{"t":"gte","v":"750","vt":"num"},{"t":"gte","v":"500","vt":"num"},{"t":"gte","v":"200","vt":"num"},{"t":"gte","v":"150","vt":"num"},{"t":"gte","v":"40","vt":"num"},{"t":"else"}],"checkall":"false","repair":false,"outputs":9,"x":950,"y":1780,"wires":[["d3d9e7e77ed85dfd"],["412cdf5be245a302"],["2aee9936649440d9"],["772c67e66f3b68c2"],["9d650f4f4e4c7190"],["8df2920dffe00f17"],["29c563a2e7f783c8"],["60128712d20d3016"],[]]},{"id":"772c67e66f3b68c2","type":"api-current-state","z":"37d6892f50d80d96","name":"Dimmer >50%","server":"938bbb0d.d39038","version":3,"outputs":2,"halt_if":"50","halt_if_type":"num","halt_if_compare":"gt","entity_id":"sensor.boiler_dimmer_zustand","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1260,"y":1840,"wires":[["08663637889827c3"],["9d650f4f4e4c7190"]]},{"id":"9d650f4f4e4c7190","type":"api-current-state","z":"37d6892f50d80d96","name":"Dimmer >20%","server":"938bbb0d.d39038","version":3,"outputs":2,"halt_if":"20","halt_if_type":"num","halt_if_compare":"gt","entity_id":"sensor.boiler_dimmer_zustand","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1260,"y":1900,"wires":[["4e3d2a6665392e64"],["8df2920dffe00f17"]]},{"id":"8df2920dffe00f17","type":"api-current-state","z":"37d6892f50d80d96","name":"Dimmer >5%","server":"938bbb0d.d39038","version":3,"outputs":2,"halt_if":"5","halt_if_type":"num","halt_if_compare":"gt","entity_id":"sensor.boiler_dimmer_zustand","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1250,"y":1960,"wires":[["0c609a3ba2e6e6f1"],["29c563a2e7f783c8"]]},{"id":"29c563a2e7f783c8","type":"api-current-state","z":"37d6892f50d80d96","name":"Dimmer >3%","server":"938bbb0d.d39038","version":3,"outputs":2,"halt_if":"3","halt_if_type":"num","halt_if_compare":"gt","entity_id":"sensor.boiler_dimmer_zustand","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1250,"y":2020,"wires":[["1e0fc71dc4a79c71"],["60128712d20d3016"]]},{"id":"60128712d20d3016","type":"api-current-state","z":"37d6892f50d80d96","name":"Dimmer >2%","server":"938bbb0d.d39038","version":3,"outputs":2,"halt_if":"2","halt_if_type":"num","halt_if_compare":"gt","entity_id":"sensor.boiler_dimmer_zustand","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1250,"y":2080,"wires":[["2998d908fe8b5576"],["33b4b4431d11473e"]]},{"id":"49825630d88e78f4","type":"api-call-service","z":"37d6892f50d80d96","name":"Dimmer set 100%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_pct\": \"100\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":910,"y":1620,"wires":[[]]},{"id":"d3d9e7e77ed85dfd","type":"api-call-service","z":"37d6892f50d80d96","name":"+5%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"+5\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":1600,"wires":[[]]},{"id":"412cdf5be245a302","type":"api-call-service","z":"37d6892f50d80d96","name":"+3%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"+3\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":1660,"wires":[[]]},{"id":"2aee9936649440d9","type":"api-call-service","z":"37d6892f50d80d96","name":"+1%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"+1\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":1720,"wires":[[]]},{"id":"08663637889827c3","type":"api-call-service","z":"37d6892f50d80d96","name":"-49%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"-49\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":1840,"wires":[[]]},{"id":"4e3d2a6665392e64","type":"api-call-service","z":"37d6892f50d80d96","name":"-19%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"-19\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":1900,"wires":[[]]},{"id":"0c609a3ba2e6e6f1","type":"api-call-service","z":"37d6892f50d80d96","name":"-4%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"-4\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":1960,"wires":[[]]},{"id":"1e0fc71dc4a79c71","type":"api-call-service","z":"37d6892f50d80d96","name":"-2%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"-2\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":2020,"wires":[[]]},{"id":"2998d908fe8b5576","type":"api-call-service","z":"37d6892f50d80d96","name":"-1%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_step_pct\": \"-1\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1510,"y":2080,"wires":[[]]},{"id":"33b4b4431d11473e","type":"api-current-state","z":"37d6892f50d80d96","name":"temp < 50","server":"938bbb0d.d39038","version":3,"outputs":2,"halt_if":"55","halt_if_type":"num","halt_if_compare":"lte","entity_id":"sensor.boiler_unten_durchschnittstemperatur","state_type":"num","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1380,"y":2140,"wires":[["629e6cc208f4d569"],["2998d908fe8b5576"]]},{"id":"629e6cc208f4d569","type":"api-call-service","z":"37d6892f50d80d96","name":"set 2%","server":"938bbb0d.d39038","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":["light.boiler_steuerung_dimmer"],"data":"{\"brightness_pct\": \"2\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1550,"y":2140,"wires":[[]]},{"id":"938bbb0d.d39038","type":"server","name":"Home Assistant","version":4,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m"}]

And you’re done! There’s not much more to it

The Pros:
For me this has helped me to get my self consumed solar energy level from 50-60% to around 99%

As for cost-efficiency, the dimmer + ESP32 + MAX485 + DS18B20 were around 70€ - considering that I store between 1 and 2 kWh of solar power in the boiler each day, this means I break even after a year (at current energy prices - which will go up quite significantly by the end of the year). Quite a good deal I would say, even if I am wel laware, that the efficiency will be a lot worse in the winter … the only thing I don’t know is how long the dimmer will work … I drive it mostly at low settings, 10-50% usually and have a fan built in to keep it cool, but it’s definitely not meant to be on 24/7.

The Cons:
The dimmer humms quite audibly … it doesn’t matter in my case, but you should consider it.

The Afterthought:
Hot water is just energy stored as heat - I know how much water there is and I know the temperature, therefore I know how much energy is stored in my boiler - so I made two template sensor and two utility meters (one for the energy fed into the boiler and one for the energy used) and implemented it as a “battery” into the energy dashboard - it’s quite interesting to see how much energy a shower consumes in a short amount of time


This was one long shower in the early afternoon :sweat_smile:

it’s 50l, so 1°K difference is 58Wh energy difference
Here’s the code for the template sensors

 sensor:
 - platform: template
    sensors:

      boiler_energy_stored:
        unit_of_measurement: "kWh"
        icon_template: mdi:lightning-bolt
        value_template: >
            {% set value1 = states('sensor.boiler_temp') %}
              {{  (((value1|float ) * (0.058|float)) |round(4, default="not available") ) }}

      boiler_energy_stored_inverted:
        unit_of_measurement: "kWh"
        icon_template: mdi:lightning-bolt
        value_template: >
            {% set value1 = states('sensor.boiler_temp') %}
              {{  (((value1|float ) * (-0.058|float)) |round(4, default="not available") ) }}

the utility meters I configured through the UI - make sure to keep net-consumption disabled, so they only recognise positive changes

Only issue I still have with this is, that the temperature tends to fluctuate - and every change in temperature, even if insignificant, gets seen as a change in stored energy - so it’s not super accurate, unless there is a big change

The End

6 Likes

Even though I do speak German, I would appreciate if we kept this conversation in English, so anyone can read along and contribute - or, at least I will answer in English for that reason :wink:

To your question:
I completely forgot to take pictures while building it, but here’s a quick and ugly schematic of how it’s all wired up:

Usually boilers have a hollow rod in them, where a temperature fuse is pushed in - you can use the same opening to put the temperature sensor in too.

And a close up of the ESP32 and MAX485


red is 5V, pink is 3,3v. black is GND and green, blue and yellow are data

I completely took appart the Dimmer, removed the outlet and added a housing in its place to put all electronics in, so there’s not much to see from the outside any more.
Here a picture of the dimmer’s internals. It’s really well built and easy to work with.

There’s one cable (with 5 cables in it) that delivers mains power to the dimmer (and ESP) and feed the dimmed power back into the boiler. (in the final setup I don’t have a temperature sensor integrated there, but get the temperature readings from the modified boiler)
But here’s what it looks like for me in the end:


and yes - I’m also a bit unconfortable with how close the plug is to the water inlet of the boiler (even though it’s completely tight of course) … I will place the plug way up higher some time, but to do that I’ll have to pull a new power cable from the fuse box to there … which I was too lazy to do now :sweat_smile:

2 Likes

irrelevant, because the comment I answered to got deleted

Unfortunately, the cable comes out of the wall just below the boiler, so it needs to be replaced with a longer one to put the outlet anywhere else. Initially there was no outlet, but the cable went straight into the boiler - that’s why it’s so short…

irrelevant, because the comment I answered to got deleted

For now, I just screwed the first junction-box I could find at home to the dimmer - it’s not pretty, but does the job xD
Inside the dimmer, there’s not enough space to put a power supply, the ESP32 and MAX485 - at least not enough to make sure it doesn’t come in contact with anything it shouldn’t. Also airflow is a thing to consider, so it shouldn’t get too crammed…

But yes - that grey box does look quite out of place, and I’ll replace it some time xD

irrelevant, because the comment I answered to got deleted

I don’t like that it’s not insulated - but yes of course that could work. Right now I use an old iPhone charger, which fits nicely and I don’t have to worry about insulation :wink:
Also, I’m not sure if 450mA will be sufficient for an ESP32, the MAX485 and a small 5V fan…

@Farnsworth wow, I like your guide, the most thorough DIY walkthrough I found.

I’m ordering the materials and implementing this system in the next 2 weeks, will post here my results.
I have a 10 kW solar panel system with a water boiler of 300L and a heating rod of 2kW.

I have all inverter stats dumped into HA and a shelly 1pm that does on/off logic based on grid_export stats. During summer it’s fine, but fall/winter it’s not sufficient as I have 2 EVs and heating the house with electricity so I want to dump smaller excess power to hot water boiler.

Glad you like it - yeah, I was looking for something like this for ages and pulled together lots of different informations and ideas - thought a nice, compact writeup could come in handy for someone.

I’m still thrilled by how well it works. Still get round 99% self consumed solar energy - not a Wh gets wasted.

Yes so am I, really thrilled to turn the most “dumb” electric boiler I could find into something really useful :slight_smile: . I also insulated the boiler on top of its own insulation to keep heat losses at an absolute minimum.

OK so, here’s a screen shot of my energy stats. Yellow (sun) is injection. Red (bad) is power consumption, and blue (water) is for the moment an electric kettle. When I have the electric boiler installed, I’ll hook it up to this working “proof of concept”.

I adapted the flow a bit to the boiler I use (2400W). I have added points to the switch for every single % value it could have up to 50%. So for example if the automation sees >=816W of injection (34% of 2400W), it will increase the dimmer with 34%. In home assistant, I also added a sensor with the influxdb platform which updates the sensor every second rather than every 30 seconds. This makes the whole setup even more accurate.


- platform: influxdb
  api_version: 1
  ssl: false
  host: influxdb.mydomain.org
  port: 8086
  database: "elektriciteit"
  scan_interval: 1
  queries:
    - name: "current electricity usage"
      measurement: '"P1 values"'
      field: "CURRENT_ELECTRICITY_USAGE"
      where: 'time > now() - 10s'
      unit_of_measurement: "kW"
      group_function: last
    - name: "current electricity delivery"
      measurement: '"P1 values"'
      field: "CURRENT_ELECTRICITY_DELIVERY"     
      where: 'time > now() - 10s'
      unit_of_measurement: "kW"
      group_function: last

I think this might make it, faster and more accurate. Except, the last bit of the flow I don’t know yet how to do. This flow is just for debugging purposes since it will only activate on a click.

In this example, it will query an influxdb server where kW figures are kept in the measurement “P1 values”. You have to set the max power consumption of your boiler (2.4kW in my case) and the function node will according to the power injection/usage calculate how much it needs to dim up and down depending on the query result. It passes on that integer to the call service node with {{payload}}. I also added a switch since if you’re injecting 0.7kW, power draw from the grid generally should be 0kW. I noticed I there was a “race” condition, where the dim level to go up and down was both sent, so it would send +33% AND 0%. If the 0% came last, it would not adjust the dim level. So hence the switch. If the calculated dim adjust percentage is 0%, then do nothing.

The advantages I see in this is that it “should” always be spot on in one step (this I have not tested yet). And in my experience, it’s also much faster since it queries my influxdb database directly.

The possible disadvantage is that you need an influxdb server for this to work faster. But you can easily change the input node if you don’t mind it being slower.

[
    {
        "id": "a05dc41aee61ce82",
        "type": "tab",
        "label": "Adjust dim level electric boiler",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "a3f67f25cfe58d7a",
        "type": "influxdb in",
        "z": "a05dc41aee61ce82",
        "influxdb": "8286ecedfacf173d",
        "name": "USAGE",
        "query": "SELECT last(\"CURRENT_ELECTRICITY_USAGE\") FROM \"P1 values\" WHERE time > now() - 10s",
        "rawOutput": false,
        "precision": "",
        "retentionPolicy": "",
        "org": "organisation",
        "x": 620,
        "y": 520,
        "wires": [
            [
                "9febb6b0e579076f"
            ]
        ]
    },
    {
        "id": "d41af18bb4b83630",
        "type": "inject",
        "z": "a05dc41aee61ce82",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "2.5",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 490,
        "y": 520,
        "wires": [
            [
                "a3f67f25cfe58d7a"
            ]
        ]
    },
    {
        "id": "2d693dc35464ecd6",
        "type": "influxdb in",
        "z": "a05dc41aee61ce82",
        "influxdb": "8286ecedfacf173d",
        "name": "DELIVERY",
        "query": "SELECT last(\"CURRENT_ELECTRICITY_DELIVERY\") FROM \"P1 values\" WHERE time > now() - 10s",
        "rawOutput": false,
        "precision": "",
        "retentionPolicy": "",
        "org": "organisation",
        "x": 630,
        "y": 640,
        "wires": [
            [
                "ec330c13a5ca0521"
            ]
        ]
    },
    {
        "id": "1b5f2f72c88de9d4",
        "type": "inject",
        "z": "a05dc41aee61ce82",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "2.5",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 490,
        "y": 640,
        "wires": [
            [
                "2d693dc35464ecd6"
            ]
        ]
    },
    {
        "id": "9febb6b0e579076f",
        "type": "function",
        "z": "a05dc41aee61ce82",
        "name": "DimDownPrecentage",
        "func": "var kwmax = flow.get('kwmax') || 2.4;\nvar dimmeradjustdown = flow.get('dimmeradjustdown') || 0;\nmsg.payload = parseFloat((msg.payload[0].last / kwmax) * -100).toFixed(0);\ndimmeradjustdown = msg.payload\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 820,
        "y": 520,
        "wires": [
            [
                "a1387187cf92685e",
                "3451561e2354d68d"
            ]
        ]
    },
    {
        "id": "ec330c13a5ca0521",
        "type": "function",
        "z": "a05dc41aee61ce82",
        "name": "DimUpPercentage",
        "func": "var kwmax = flow.get('kwmax') || 2.4;\nvar dimmeradjustup = flow.get('dimmeradjustup') || 0;\nmsg.payload = parseFloat((msg.payload[0].last / kwmax) * 100).toFixed(0);\ndimmeradjustup = msg.payload\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 810,
        "y": 640,
        "wires": [
            [
                "a1387187cf92685e",
                "d6e42e86e6a6f164"
            ]
        ]
    },
    {
        "id": "a1387187cf92685e",
        "type": "debug",
        "z": "a05dc41aee61ce82",
        "name": "debug 1",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1220,
        "y": 580,
        "wires": []
    },
    {
        "id": "e70ae6207f22dcf3",
        "type": "api-call-service",
        "z": "a05dc41aee61ce82",
        "name": "Dim Down",
        "server": "27f5a4e48e0f0ce9",
        "version": 5,
        "debugenabled": false,
        "domain": "light",
        "service": "turn_on",
        "areaId": [],
        "deviceId": [],
        "entityId": [
            "light.boiler_dmx_controller_dimmer"
        ],
        "data": "{\"brightness_step_pct\": \"{{payload}}\"}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "x": 1230,
        "y": 520,
        "wires": [
            []
        ]
    },
    {
        "id": "cffd2b1341015174",
        "type": "api-call-service",
        "z": "a05dc41aee61ce82",
        "name": "Dim Up",
        "server": "27f5a4e48e0f0ce9",
        "version": 5,
        "debugenabled": false,
        "domain": "light",
        "service": "turn_on",
        "areaId": [],
        "deviceId": [],
        "entityId": [
            "light.boiler_dmx_controller_dimmer"
        ],
        "data": "{\"brightness_step_pct\": \"{{payload}}\"}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "x": 1220,
        "y": 640,
        "wires": [
            []
        ]
    },
    {
        "id": "3451561e2354d68d",
        "type": "switch",
        "z": "a05dc41aee61ce82",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lt",
                "v": "0",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 1070,
        "y": 520,
        "wires": [
            [
                "e70ae6207f22dcf3"
            ]
        ]
    },
    {
        "id": "d6e42e86e6a6f164",
        "type": "switch",
        "z": "a05dc41aee61ce82",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "gt",
                "v": "0",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 1070,
        "y": 640,
        "wires": [
            [
                "cffd2b1341015174"
            ]
        ]
    },
    {
        "id": "8286ecedfacf173d",
        "type": "influxdb",
        "hostname": "influxdb.example.org",
        "port": "8086",
        "protocol": "http",
        "database": "elektriciteit",
        "name": "influxdb.example.org",
        "usetls": false,
        "tls": "",
        "influxdbVersion": "1.x",
        "url": "http://localhost:8086",
        "rejectUnauthorized": true
    },
    {
        "id": "27f5a4e48e0f0ce9",
        "type": "server",
        "name": "HomeAssistant",
        "version": 5,
        "addon": false,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "y|yes|true|on|home|open",
        "connectionDelay": true,
        "cacheJson": true,
        "heartbeat": false,
        "heartbeatInterval": "30",
        "areaSelector": "friendlyName",
        "deviceSelector": "friendlyName",
        "entitySelector": "friendlyName",
        "statusSeparator": ": ",
        "statusYear": "hidden",
        "statusMonth": "short",
        "statusDay": "numeric",
        "statusHourCycle": "default",
        "statusTimeFormat": "h:m",
        "enableGlobalContextStore": false
    }
]

This is what my flow looks like now: :slight_smile:

Blue: power of boiler, yellow: power to grid, red:power from grid. Here you see in the beginning of the curve, I had set the repetition interval too low, so the flow was adjusting again before the new power consumption figures were updated, hence the flapping. At 15:02 I adjusted the repetition interval (to 2.5s) and you see it’s much smoother. Later stuff in the house started to switch on/off. It can’t pick up everything, but it can pick up a lot :slight_smile:

That looks really cool - unfortunately I have given up on InfluxDB many times already, so I can’t try it for myself.
But I do kind of like the idea to calculate the power directly instead of increasing/decreasing by steps.