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

Great that you were able to sort it out! If you don’t want to wait until tomorrow, you can re-run the rule that determines the control values, i.e. this:

dh = require('kolapuuntie/date-helper.js');
wh = require('kolapuuntie/waterheater.js');

start = dh.getMidnight('start');
stop = dh.getMidnight('stop');

wh.determineHours(start, stop, 4);

Change the argument of the last row to be 24 hours instead of 4 hours. This will cause your boiler to turn on on the next full hour.

Hi again

This morning it all worked out as it supposed to, so it all must have been som missmatch in first installation in docker.

But wouldn’t you need a script for turning heatpump on/off? similar to the waterheater hourly script?

Yes, absolutely. Thanks for pointing out that it was missing from the Solution of #13. I have now added this simple snippet there.

Cheers,
Markus

@masipila

Nice solution you have created :slight_smile:

I’m facing this issue when running “Fecth Spot Price”

entsoe.js: Exception parsing spot prices: Cannot read property "period.timeInterval" from undefined

Do you have any idea ??

I just wanted to give a short tipp, if you are using Node-RED I would suggest to look into the awesome PowerSaver Nodes

@Nanna_Agesen it means that you did not receive a valid XML response from the Entso-E API. Most probably because you did not update your personal access token.

I realized this (that you need your personal access token) was not properly documented in my comment marked as the solution.

I have now updated all js files in the comment marked as the solution with better documentation. I also made some changes to the rule actions. Please delete the rule actions you had previously, re-load all js files and try again.

Note for other users such as @Marcus_Carlsson and others who had previously got their setups working: these updated js files are not compatible with your existing rule actions because I harmonized the architecture of the code slightly. If you already have your stuff working, you can keep the js files and rule actions that you already have. There are no new features or anyhting like that, just small improvements to the structure of the code.

I also added some photos from the hardware.

1 Like

@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.

Cheers,
Markus

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?

Hi

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…

Hello

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"

  when

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

then
logInfo("Easee", "eventbased rule started")
if (newDynamicCurrent.state==NULL){
    newDynamicCurrent.postUpdate(6)
}
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
        }
    }
    else
              logInfo("Easee", "Loadbalancing performed but not charging")
    
    }        
end

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).

Hi

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.