Control a water heater and ground source heat pump based on cheap hours of spot priced electricity

@masipila great got it working - I had the wrong bidding zone defined, found the correct in the documentation you added :slight_smile:

@Nanna_Agesen great to hear that you got it working! The more people schedule their consumption outside the demand peaks, the better it is for the climate when there is less need to burn fossiles for reserve power :heart:

1 Like

@masipila this has a huge potential, charging cars, washing machines, tumble dryers and dishwasher :slight_smile:

  • Based on the average temperature for the day, it will determine how many hours the heat pump compressor must be allowed to run. When it’s -20 celsius, 22 hours will be required (I can avoid 2 most expensive hours). When it’s +20 celsius, 2 hours will be required for heating the hot water. This heat curve is configurable in the code, modify directly there based on your experiments.

I have a cooler in our server room, if I invert the math in your code to this

When it’s +20 degree I’ll need 22 hours and - 20 I’ll need 2 hours (example might need adjustment to Denmark)

Would you say that will work, or maby just skip the outside temperature?

Anyhow I’m going to slice it so I can keep a acceptable temperature all day.

I don’t think that inverting the heating logic will be an optimal solution for your cooling.

Do you have a temperature sensor in your room that you’re able to log? It should be quite easy to plot the temperatures and see how quickly the cooler can bring the temperature down and how quickly it will rise again. Then make some educated guesses, try it out and iterate until you feel that you have found an optimal approach.

Hi all

I have now set up all of original function in my docker environment and it seems to work flawless. I just intergrated it with my two Balboa spa controllers that now onle have permission to heat the same hours that waterheater scripts calculates.

I’m not good at coding at all but I’want to be and these scripts where in my opinion very good.

I ended up with a couple of questions.

  • Do I have to make som adjustments for Timezon Europe/Stockholm GMT +1 in wintertime and GMT+2 in summertime?

  • For weatherforecast I planning to use SMHI Binding and may have difficulties to do so, or secondary use my own MIN temperature as a forecast. I’m not sure how to make this happend but I will try to figure out how to write SMHI binding for writing forecast to influx, any input will be appreciated.

  • How do one adjust amounts of hours in waterhetaer script incase my hot tubs needs extra hours for keeping warm.

  • For integration with my Easee (binding under development works really nice) wallbox I probably need a temperature independent script where I just set how many hours It should be possible to charge, you can’t really change the time so much of when you need to charge the car. Today I just use Easee integration for loadbalancing our main fuse, in Sweden a typical mainfuse for a house is 16,20 or 25A and we have 25A.

I’m not 100% sure but I don’t think so. Entso API seems to quite weirdly take the input parameters in CEST (without explicitly declaring the timezone) so I assume they will automagically treat the input parameters as CET during the winter time. So that should be fine.

Entso API respects the ISO8601 standard in their responses and they return the data in UTC, which is the standard behavior for APIs. The spot prices are also saved to InfluxDB in UTC. So as long as your computer that runs OpenHab is treating the daylight saving correctly, the scripts should be fine without modifications.

Minor remark, you should refer to UTC and not GMT when talking about timezone offsets. CET is UTC+1 and CEST is UTC+2. The UK uses GMT only during winter time (GMT = UTC+0), their time zone during the summer is BST (UTC+1).

wh.determineHours() in the script action takes three arguments. The start midnight, the stop midnight, and the number of hours to search between these two moments.

I’m not familiar with that binding and what it does. If you need support with that, please open a thread of your own for that. You can link to this comment, but please don’t pollute this thread with that.

However, as a general guidance, this is how the nibe script works. Please do read the code comments in nibe.js, they are written in plain English.

If we start from the bottom of what your nibe script action does:

nibe.determineHours() takes the number of needed hours as one of the arguments, just like the water heater script does.

Because the needed heating is not static like it is for hot water heating, I wanted to automate the calculation of the hours. The number of hours is calculated with

n = nibe.calculateNumberOfHours(t);

Where t is the average temperature. So if you are able to use that binding to save the avg temperature (or coldest, or however you want your house to be heated), you should be able to read that value and pass it as t.

For the rest of the questions I’ll come back later. It’s getting late and I need to go to bed now.


Can you elaborate this use case a bit more? What exactly do you want to achieve? I understand that it’s related to charging the electric car, but what exactly is it that you want to optimize?


I beleive that I first of all need to make a new control unktion similar to the one for your waterheater but with a longer period of on. But thanks to your previois answers that will probably be within my knowledge now.

Im sorry for using wrong name for timezones, I schould have known better.

Thanks once again for all help.

No worries at all, the remark of UTC vs. GMT was just to prevent misunderstandings. Timezone handling is a pain in the rear, but it’s significantly less painful when the systems (including the code that we ourselves write) respect the ISO 8601 standard. :slight_smile:

I would really be interested how you are planning to optimize your car charging. We are still waiting our electric car, so I’m all ears. I can imagine that there are many different aspects to be taken into account in addition to just the spot prices, including the spot prices, how many hours you need for charging, when do you need the car to be charged, is the fuse a limiting factor i.e. are you able to charge with full power at the same time when other big loads are on and so on. So if you could elaborate on these real-world needs, I can then try to reflect what these existing scripts are already capable of…


I will try to elaborate my use case, here in Sweden we typically have a 6,20 or 25A main fuse per phase (3phases). From the first of september I will have a floating electrical price from previous where we had a fixed price 0,47SEK/kWh, within the upcoming 12 months we will also get a maxpower fee, if we could load or 25A fuses to just 20A the fee will become lower.

So in somewhat we will probably not want to max the fuses. My main concern upuntil recently hav been to protect mainfuse from overloading, but soon having floating electricalprice and this summer we also bought two bath tubs that also need electricity, and our EV car a Kia that chrages via Easee wallbox that ships with a simcard and wifi so one could connect internet in two way and they have a good api for integrating. I have one rule that handles charger when no car is connected and one rule that handles loadbalancing (this rules is pasted below).

I can’t figure out how to connect to Kia webapi rumors telling there’s an api, so it’s really hard to get som automatic reading of how many hours the car will need charging and my wife will not have to set the cars battery percent or similar somewhere it should just start and load typically during the late evenings or nights now it starts when powerconsumption in the house is setteled for a period of 30 minutes, the it increases as much the powerconsumption will allow I did set a spare buffer of 5A if I remember correctly.

Just to give you an idea of which rule I have I paste the loadbalancing rule below.

var lastIncTimer = now
var lastDecTimer = now
var loadInc = 6
var loadDec = 6
rule "EaseeBox Charging event based"


  Time cron "45 */1 * * * ?"  or Item gridloadmax changed  //"0 */1 * * * ?"
  //Item gridload changed //powergridCurrentConsumptionL1 changed or Item powergridCurrentConsumptionL2 changed or Item powergridCurrentConsumptionL3 changed

logInfo("Easee", "eventbased rule started")
if (newDynamicCurrent.state==NULL){
var old_dynamic_current = easeeMaxCurrent
var loadDec = (old_dynamic_current.state as Number) -2
 //var loadDecx2 = (newDynamicCurrent.state) as Number -4
var loadInc = (old_dynamic_current.state as Number) +2
  if (availableCurrentCriticalTimestamp.state == NULL) {
  availableCurrentCriticalTimestamp.postUpdate(new DateTimeType())  
  if(availableCurrentLowTimestamp.state == NULL){
    availableCurrentLowTimestamp.postUpdate(new DateTimeType())
  if(availableCurrentMedTimestamp.state == NULL){
    availableCurrentMedTimestamp.postUpdate(new DateTimeType())

  if(availableCurrentHighTimestamp.state == NULL){
    availableCurrentHighTimestamp.postUpdate(new DateTimeType())
  if(currentOverloadTimestamp.state == NULL){
    currentOverloadTimestamp.postUpdate(new DateTimeType())

 val timestampCritical = (availableCurrentCriticalTimestamp.state as DateTimeType).getZonedDateTime()
 val timestampLow = (availableCurrentLowTimestamp.state as DateTimeType).getZonedDateTime()
 val timestampMed = (availableCurrentMedTimestamp.state as DateTimeType).getZonedDateTime()
 val timestampHigh = (availableCurrentHighTimestamp.state as DateTimeType).getZonedDateTime()

  // increase dynamic charging power
  var stringLoadInc = (loadInc + ";" + loadInc + ";" + loadInc)
  var stringLoadDec = (loadDec + ";" + loadDec + ";" + loadDec)
  logInfo("Easee", "Easee loadInc 3 phase: " + stringLoadInc)
  logInfo("Easee", "Easee loadDec 3 phase: " + stringLoadDec )
  //Easee_DynamicCircuitCurrentsPhase123.sendCommand ("10;10;10")
  //Easee_DynamicCircuitCurrentsPhase123.sendCommand (loadInc + ";" + loadInc + ";" + loadInc)
            if (EaseeBox_chargerOpMode.state as Number == 3){
             logInfo("EV charger", "Loadbalancing started")
             logInfo("EV charger", "Increase step 2 Timestamp high: "+timestampHigh+" Timestamp med: "+timestampMed+ "Last inc timer: " +lastIncTimer)
            if (availableCurrentHighProxy.state ==ON && easeeMaxCurrent.state >= loadDec && now.minusMinutes(5).isAfter(lastIncTimer) || availableCurrentMedProxy.state ==ON && easeeMaxCurrent.state >= loadDec  && now.minusMinutes(5).isAfter(lastIncTimer)) { //(powergridCurrentConsumptionL1.state <=0 && powergridCurrentConsumptionL1.state >=-5 || powergridCurrentConsumptionL2.state <=0 && powergridCurrentConsumptionL2.state >=-5 || powergridCurrentConsumptionL3.state <=0 && powergridCurrentConsumptionL3.state >=-5){
             logInfo("EV charger", "Increase step 2 Timestamp high: "+timestampHigh+" Timestamp med: "+timestampMed+ "Last inc timer: " +lastIncTimer)
                 if (now.minusMinutes(30).isAfter(timestampHigh) || now.minusMinutes(30).isAfter(timestampMed)){
                //if (availableCurrent.state >=8.4 && easeeMaxCurrent.state >= loadDec && now.minusMinutes(5).isAfter(lastIncTimer) || availableCurrent.state >=8.4 && easeeMaxCurrent.state >= loadDec  && lastIncTimer == NULL) { //(powergridCurrentConsumptionL1.state <=0 && powergridCurrentConsumptionL1.state >=-5 || powergridCurrentConsumptionL2.state <=0 && powergridCurrentConsumptionL2.state >=-5 || powergridCurrentConsumptionL3.state <=0 && powergridCurrentConsumptionL3.state >=-5){
                logInfo("EV charger", "Increase step 3")
               if (newDynamicCurrent.state >=0 && newDynamicCurrent.state <=14){
                Easee_DynamicCircuitCurrentsPhase123.sendCommand (loadInc + ";" + loadInc + ";" + loadInc)
                //EaseeBox_dynamicChargingCurrentL1.sendCommand (loadInc)
                //EaseeBox_dynamicChargingCurrentL2.sendCommand (loadInc)
                //EaseeBox_dynamicChargingCurrentL3.sendCommand (loadInc)             
                logInfo("Easee", "Tillgänglig ström ökad till: "+loadInc+" Amp")
                postUpdate(newDynamicCurrent,loadInc +2)
                lastIncTimer = now

if (availableCurrentLowProxy.state ==ON && now.minusSeconds(90).isAfter(lastDecTimer) || availableCurrentCriticalProxy.state ==ON && now.minusSeconds(90).isAfter(lastDecTimer)){  // if (powergridCurrentConsumptionL1.state <=-6 || powergridCurrentConsumptionL2.state <=-6 || powergridCurrentConsumptionL3.state <=-6){
  if (now.minusSeconds(60).isAfter(timestampCritical) || now.minusMinutes(10).isAfter(timestampLow)){
      logInfo ("EV charger", "decend step 2")
       //if (availableCurrent.state <3.5 && now.minusSeconds(90).isAfter(lastDecTimer) || availableCurrent.state <3.5 && lastDecTimer == NULL){  // if (powergridCurrentConsumptionL1.state <=-6 || powergridCurrentConsumptionL2.state <=-6 || powergridCurrentConsumptionL3.state <=-6){
        logInfo ("EV charger", "decend step 3")
        if (newDynamicCurrent.state <=18 && newDynamicCurrent.state >=2){
              //easeeRequestBody = '{"dynamicCircuitCurrentP1":'+loadDec+',"dynamicCircuitCurrentP2":'+loadDec+',"dynamicCircuitCurrentP3":'+loadDec+'}'
            logInfo("Easee", "Tillgänglig ström sänkt till: "+loadDec+" Amp")
           Easee_DynamicCircuitCurrentsPhase123.sendCommand (loadDec + ";" + loadDec + ";" + loadDec)
            //EaseeBox_dynamicChargingCurrentL1.sendCommand (loadDec)
            //EaseeBox_dynamicChargingCurrentL2.sendCommand (loadDec)
            //EaseeBox_dynamicChargingCurrentL3.sendCommand (loadDec)
              postUpdate(newDynamicCurrent,loadDec -2)
              lastDecTimer = now
              logInfo("Easee", "Loadbalancing performed but not charging")

Thanks for the additional info! So if I try to summarize with my own words:

Your loads are:

  • Charging the car
  • Heating your house with a ground source heat pump
  • Heating the water in your boiler / waterheater
  • Heating the bath tubs
  • All other loads such as dish washers, washing machine, everything else

Your objectives are:

  • You must avoid overloading the fuse above 25A
  • You prefer to keep the fuse a bit below 20A
  • You want to optimize the power consumption of all loads to the cheapest hours

To plan this, you need to first plan which loads can be “on” at the same time.

  • Does your power company have a web portal or something like that where you could see what kind of power consumption you have on different hours?
  • If I were in your shoes, I would first see how much power your house consumes when all the big loads are off. As an example in our house, the air conditioning, fridge, freezer etc. consume about 0.4 kW during the summer. Note that the AC machine uses more power during the winter so that it does not freeze.

After you have seen the baseline, you can calculate the current I = P/U for different load combinations, assuming that your car charger, heat pump, water heater and bath tub heater all use all 3 phases equally.

  • When you know what loads can run at the same time, you can plan a strategy in which order you want to schedule the big loads.
  • Once you have a strategy for the schedules, then you can think of how to implement that strategy in openHab and Easee

This sort of control needs quite some planning and it’s even harder to implement it in a generic fashion.
Some big loads you can shut off (like a SG ready charge signal, handing local control to the heat pump), some you cannot (say the oven), but for most of them, it depends on the situation, like an EV. The EMS usually will not know if it can interrupt charging (like today you REALLY need the EV to charge because you need it for a tour while normally you’d just want it to be charged to some 80% throughout the night).
A washing machine you may interrupt depending on time that has passed since start and on personal need when you need it to finish.
Loads are also dynamic in nature: a washing machine or dish washer for example takes ~2kW for some minutes only. Plus all the loads you don’t know and cannot control, like the kitchen devices. That’s really difficult to forecast/schedule.

Also keep in mind it is very challenging to determine a ‘scheduling algorithm’ that also meets people’s life needs. What if your load is ‘optimized’ to 19A and you need to do the cooking ?
If you feel cold or hot or the EV isn’t charged or the washing ain’t done when you expected it to, users (in particular other persons on your household) will be annoyed quickly and start demanding changes from you or manually override things, thwarting your nice optimizations.

Ultimately, the best advice is to really not overengineer things.
(I know since I’m providing a commercial energy management system based on openHAB, see e.g. Looking for energy management system testers and have experimented with that a lot).


Yes your quite right just that I wille leave smaller loads up to enduser to determine buy their opinion (not much can beat the human brain)

I will only take automatic control over big consumer such as

  • EV charger (easee wallbox via yet unoficcial binding)

  • Heatpump 3phase boiler I will not laborate with the compressor unit due to excessive shutdown and start may shorten compressor liftetime) This is controlled via a Fibaro Z wave smart implant just closing a circuit in the heatpump aux panel.

  • Bathtubs one swimspa unit with two balboa controllers and 1phase heater element each integreated via balboa wifi module and python scripts, where I can set temprange to low and make sure tha temperauture can drop safley even in winter time.

Objectives will be
A variable for allowed peakpower from my mainfuses wich I have in a powergridload that based on Aeontec zwave energymeter 3phases. That one meassuers all consumers in our house.

Extra control items for cheapest daytime eg 06:00-17:59 and one for cheapeast night time 18:00-05:59 And I assume car charging will mostly take place in late evenings and nights, and hot tub may be heated during day hours.

But as @mstormi points out generall solution will fail and to specific solutions will not be robust because life happends, so in shortly I rather pay for electricity than having a debate of wy can’t we do any laundry now one might end up doing all the stuff in the household for savings.

I’ve had this working with charger and bat tubs now uses @masipila waterheater script so my tubs just runs a couple of hours aday during this warm period, in the winter times there will be different times leading to, probably manual heat tubs a little more when price are really low and not heat everyday, if no bathing is to be done winter bathing require som planning of other stuff. Or maybe just heat bathtubs if electrical price is underX sek kWh but only if temperature is safe above freezingpoint.


I would just say that I totally agree with you thoughts about general solutions and even more the to specific solution. All this stuff will probably be hard to commercialize without having to make alot of adjustment. One need to find that familys do’s and don’ts so life can keepon without to much interruption. I really would have preferd to have type 4 charging so I could read battery status of the car via charger, but I don’t think I can and Kia doesn’t provide an open API but there is reverse engineering beeing done.

In my oppinion really fun that we are 3x Marcus in this thread now.

@mstormi I second everything you wrote.

For the sake of clarification:

  • I was not suggesting that the dish washer or washing machine would be controlled by openHab.
  • I mentioned them because they are significant in terms of spot priced electricity contracts and they create consumption peaks which is good to take into account if the fuse soft 20A and hard 25 limit are of concern.
  • Most dish washers have a timer so that you can make it start say at 02:00 when the electricity is the cheapest on that day.
  • Our washing machine also has a timer which can be used to tell the washing machine when you want the program to be ready. We use this regularly to schedule the laundry so that the program finishes at say 06:30 when we wake up.

My experiences so far is that the openHab-control of the compressor is significantly reducing the number of compressor starts. I have two AUX inputs in the Nibe F1226 heat pump that I can configure in the Nibe menus. I have configured them as follows:

  • Aux 1: Prevent compressor start
  • Aux 2: Do not heat tap water

With the combination of these, I can allow the compressor to start for house heating purposes but not for hot tap water purpose. Or deny both.

Here’s the strategy for my heat pump & tap water heating:

  • Our total hot tap water capacity is 180 + 300 liters. This capacity is sufficient for 2 to 3 days, so I can easily heat the hot water on the most cheapest hours. This applies to both Nibe integrated 180 l tank (Aux 2) and the external 300 l water heater. I use the water heater script for both of these.
  • This alone reduces the number of compressor starts significantly because the tap water will be heated at one go, once per night. If I take a shower in the morning, the compressor won’t start to heat hot tap water until next night.

Aux 1 for house heating
This will optimize the hours when the house will be heated. Also here my assumption is (to be proven by data when the heating season starts), that the compressor run-times will be longer but there will be less starts.

Yes they are good candidates to postpone for optimization.
But devil’s in the details. Did you know that a common laundry run consumption is like
20W for 15 mins - 2000W for 20 mins - 50W for some 2 hours ?
The 20 mins vary based on laundry temperature (how long it takes to heat the water).

Chances are high you don’t hit the sweet spot.
Long story short, my opinion is it isn’t worth trying (unless you are VERY attentively watching your consumption and are willing to check and adapt over and over).

Unless you have a battery. That’s a gamechanger particularly w.r.t. maximum amperage but it’s
also easing scheduling a lot and just like with a hybrid car (which has a very small battery only) you can substantial savings just because your scheduling doesn’t have to exactly match your consumption (which is a predicted one hence subject to forecast failures anyway).
You don’t need a largish battery.

Thing with these is, you have to manually rework all of your scheduling when spot prices change, what if you don’t notice in time ?
You have to be “always on” for changes. For a consumer product, that would be inacceptable, and even for an enthusiast with his very own installation, having to keep up is annoying.
Ans what about living need dynamics, like if 02:00 isn’t acceptable because you urgently need some specific clothing. And what if your laundry is done at 6:30 but you forget about it when it’s done?
As I said, devil’s in the details.

Funny enough, I am nonetheless offering this with my EMS. I provide EMS-side presettable “must be finished by XXX”. You load and start the machine and EMS auto-shuts it off when there’s a better time. But you really need the ability to override start at any time. I am providing an Alexa-linked item for that. And I am sending a ‘laundry is done’ notification to the user’s phone, even with reminders when they do not confirm it (ever forgot your still wet laundry for a whole day ? Ugh …).
Am I contradicting myself (overengineering) ?
Let me know what you think about ‘applicability’ of that.

P.S.: don’t waste your money on smart home controllability of your white goods if they charge extra for it. Device-driven optimization is inferior to EMS (household wide) driven and will even interfere with that.
(and P.P.S.: did you know how many watts white goods are drawing in timer mode ? It’s more efficient to shut it off. Plus that works with any white good.)

I’m actively watching the spot prices on daily basis, because my electricity price depends on them, see comment #27.

On this week, prices have been jumping like crazy. On Wed and Thu nights this week there were several hours when the spot prices were practically free (cheapest hours were 0.006 c/kWh) but on Monday, the spot price was peaking at 107 c/kWh. That’s a 17833 x price difference.

If my weighted average (consumption on a given hour vs. spot price of that hour) is below the month’s average spot price, I get the delta as a discount, see details from the same comment #27. At the moment, I’m 6.7 c/kWh below the average in the month of August, which means that I’m paying 14.8 - 6.7 c/kWh if I can maintain the -6.7 delta until the end of the month.

If you look at the graph below, you can see that there are typically 1-5 very cheap hours every night. Green line = spot prices, scale on right axis. Blue bars are our consumption, left axis.

I can easily hit the dish washer’s water heating to a cheap hour using the “delayed start” feature of the dishwasher. Even if the eco-program is 3,5 hours, the water heating happens during the first hour. This has nothing to do with openHab, I simply use the control panel of the dish washer and program it to start at the desired time.

What comes to the laundry machine, we usually use it when the electricity is cheap between 04-07 so that the machine finishes the program around the same time (or a bit before) when wake up so that we can hang the clothes immediately. Again, this has nothing to do with openHab, our washing machine also has a “delayed start” functionality in the control panel. This isn’t exact science either, but if there are 4 cheap hours I can easily hit one of them.

As mentioned, these have nothing to do with openHab, we simply use the “delayed start” functionality of the dish washer / waching machine. We always check the next days spot prices when starting them, it has become a habit. And of course we wash the dishes / clothes during the day if we need to.

And no, I haven’t checked how much the machines consume on the couple of hours in the evening (after I go to bed and between the time they start with the “delayed start”), but that’s insignificant because the benefits outweight the small consumption for sure.


Sure thing.
What I wanted to promote actually IS to have openhab control your machines.
Get a 25€ Shelly plug. Advantages are reliability and, worth even more, peace of mind:
you don’t have to watch the spot prices. OH always knows when spot prices changes. It does not forget to delay nor err in point of time for an optimum start. Plus it reminds you to remove the laundry. For me that’s well worth some 50€ all by itself.

Sorry I was somehow (wrongly) assuming everybody has a PV system (in Germany most do but in turn few have variable tariffs) but now realize this is not a prerequisite for this thread.
Then again if you have PV, that in turn is another dimension of optimization and control complexity because while weather/yield can be forecasted, sub-minute excess power isn’t predictable.
You really benefit from a battery a lot in that scenario in terms of ‘smoothening’ consumption.