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

@jlikonen You can use the normal OpenHab Graphs to plot them. There is only one gotcha:

Since we bypass the OpenHab persistence layer when writing the values to our database, OpenHab does not know that there is a measure called “spot_price”. However, if you create a dummy item called “spot_price”, then the OpenHab graph engine will fetch the data from the database like it does for all other Items.

I have graph like this:

@jlikonen See comment #152

@masipila Ok thanks for the hints.

Entso-e is down for Finland, again.
Estonian Elering is an alternative live and open source for day-ahead prices.

Anyone interested in coding this as a backup connection for the scripts?

It was down for sweden as well but came back to life the next day. It’s unfortunate but kind of expected.

I have changed the script so my script defaults to 2 instead of default to 1, so then I can have a script that gathers data from anotherplace if there was one.

I developed a new version of the optimizing algorithm which might be of interest or inspiration for the others so I thought to share this one as well.

In our household, the heating optimization is a multiobjective optimization problem with three partially contradicting objectives:

  1. Cost optimization i.e. utilizing the cheapest hours available for heating

  2. Comfort: It’s not nice if the indoor temperature drops too much because all the heating hours occurred in the night and then the no heating hours occur during the afternoon / evening

  3. Our ground source heat pump is a traditional on/off pump and according to people on the heat pump forums, the compressor lifecycle is around 100 000 compressor starts. So from this point of view, it is uncool if the compressor goes on-off-on-off-on-off every other hour just because the spot prices were a fraction lower.

So I came up with the following concept, which seems to work very nicely in our house, see the picture below:

On this example day, 12 hours of heating were needed. The logic for determining this number of required heating hours is the same as explained in comment #13, no changes to that.

The fact that 12 hours of heating is needed means that 12 hours will need to be blocked.

This new algorithm starts from the most expensive hours and blocks, not from the cheapest hours. The number of expensive, to-be-blocked hours is fist divided into two, i.e. there will be a 2 x 6 hour periods when the heating will be blocked.

  • The algorithm first searches the most expensive 6 hour consecutive period and blocks that.
  • The previous and next hour just before this block period will be marked as allowed.
  • Then, the algorithm finds the second most expensive 6 hour period from those hours that are not yet blocked nor allowed.
  • The fact that the previous and next hour around the first block period were marked as “allowed” guarantees that these two block periods are not consecutive i.e. there will always be at least one heating hour between these two blocked periods. This helps to the comfort objective.

The algorithm is here (remove the .txt file extension so that the file name becomes peak-period-blocker.js)
peak-period-blocker.js.txt (6.8 KB)

Usage in your Rule action scripts, assuming you have an Item called HeatingHours which contains a number of needed heating hours (updated via another Rule or adjusted manually)

dh = require('kolapuuntie/date-helper.js');
influx = require('kolapuuntie/influx.js');
optimizer = require('kolapuuntie/peak-period-blocker.js');

// Read desired amount of heating hours
item = items.getItem("HeatingHours");
hours = Math.round(item.state);

// Read spot prices from InfluxDB
start = dh.getMidnight('start');
stop = dh.getMidnight('stop');
prices = influx.getPrices(start, stop);

// Optimize heating hours
points = optimizer.getControlValues(hours);

// Write control values to InfluxDB
influx.writePoints('nibe_control', points);

The results are so far very promising, here’s a temperature graph from our living room. The green line is indoor temperature and yellow line is outdoor temperature. The pink area indicates when heating has been on.

You can see that previously the indoor temperature started to decline quite steeply if the heating was blocked for too long (red arrows). Now with this new algorithm there is always one shorter heating period in the middle of the day between the morning & evening price peak and the indoor temperature is much more stable. I can most probably even adjust the heating hour calculation so that we can survive with less heating hours, which will mean reduced total energy consumption.


My next small project will be to enhance the algorithm that calculates the number of required heating hours so that it can cope with days like tomorrow, Dec 30th:

The outdoor temperature drops from -4 to -15 during the day. I have now added manually more allowed heating hours both to the first half of the day to proactively “charge” heat to our concrete floor before the outdoor temperature drops too much and I have also added more heating hours towards the evening.

The idea that I’m playing in my mind goes something like this:

  • Currently the “calculate the number of required heating hours” is based only on tomorrow’s average temperature and the result is X hours, which is stored to an openHab Item “HeatingHours” which the actual optimizer script reads as illustrated in the previous post.
  • It’s conceptually quite easy to add more logic to this “calculate the number of required heating hours” script so that “if the latter half of the day is significantly colder than the first half of the day, add a couple of heating hours to the result.” I just need to play with this slightly to figure out what “significantly colder” would mean in practice.
  • This does not yet guarantee that the additional hours are allocated to the colder half of the day, but it will help for sure.

In our heating system using Markus control system we have a 1 900L storage tank for the underfloor heating circuit. As we heat the storage tank only during the cheapest hours between 0 to 8 we have also noticed some needs for fine/tuning of the system.

What we have implemented so far is that we have altered the function that calculates the needed heating hours based on the average temperature to a logarithmic function that tries to take into account that when the temperature difference between the house and outside temperatures increases the heat loss increases.

As this still doesn’t work perfectly under all conditions we have added a panel slider to manually decrease or increase the calculated heating hours between -5…+5 hours.

This works reasonably well but two cases still need fine/tuning,

  1. If the storage tank drops very low to about 25C before next heating the calculated hours are not enough to heat the storage tank to a temperature sufficient for the next day. This is probably due to the fact that the floor concrete has cooled down so it needs some reheating energy.
  2. If the outside temperature is very cold during the heating period so much heating energy is used during the heating period that the calculated hours are not enough to heat the storage tank to a temperature sufficient for the next day.

Fine-tuning these two issues requires access to temperature measurement values on our solar heating system using CAN transfer of the values and some additional script coding.

When I have time from my other coding projects I must look into these issues. So far I am very pleased how the system works as we don’t have any issues with the temperature comfort in the evenings. Our room temperatures doesn’t drop more than about 0,5C below the set value in the evening during very cold days.

Could you please elaborate on this logarithmic function that you are currently using?



First of all I must apologise for the shortcircuit in my brains - I meant to say exponential function not logarithmic.

I just tinkered to create an exponential function for the running hours based on the temperature and used this weird function that I tested in Excel to create the settings

y = (k * temperature + b) **((90-temperature/79) - 3

This creates the following values compared to the original values

There is no mathematical logic in the exponentiation function - it just creates a slightly exponential running hours set. It is still not optimal so I am thinking of replacing it in the script with a switch - case statement for every temperature between -20 and +20 degrees. Then it is possible to set the running hours for every temperature to a value that is based on empirical findings.

In this exponential approach I have not been able to find a formula where the running hours at -5C would be 5 hours and at -10C 7 hours and the value at -20C below 12 hours.

I refactored most of the javascript libraries this week so that they are better decoupled from each other and so that the configurations such as influxdb connection parameters are not hard coded in influx.js and so on. I also added some functionalities like the peak period blocked thingy.

Does anyone have experience on maintaining npm modules? I guess this solution has been sufficiently stable and used by a decent amount of folks so that we could promote it to be installable with npm…

Before publishing this though, would really appreciate somebody with more node / npm experience to chime in, I have a couple of questions since javascript is like my fourth language, not first, second or even third…


1 Like

@ ruxu and @ masipila, this is very interesting project. My approach for controlling our Nibe S1255 is somewhat different as I’m using Ouman Ouflex unit for calculating the cheapest hours. Basically you give the number of required heating hours manually and then Ouflex places the hourly prices from min to max and defines the price limit. When this price limit is exceeded then the compressor will be stopped. I don’t need to give the number of heating slices. I’m also using the AUX inputs for controlling Nibe. This approach is completely independent from openHAB.

I have been using Ouflex only for some days now so I don’t have enough experience how to determine the required heating hours (especially now when it’s pretty cold). Now I’m wondering how @ ruxu has defined the required heating hours. I think that the heating hours depend very much on your house, i.e. e.g. how well it’s insulated. I’m not a programmer so I don’t know that much about Java.

Hi I migrated to the new kind of script today and i works out of the box, however it’s hard for me to find good settings for our old house and cold weather. Or house is built in 1870 and we have somewath of a winter now maybe -10 to -20degrees, and our hose starts to feel cold after 2-3 hours without heating. is there any ideas of how I can manipulate this to be able to set a maximum hours of prevented heating to 3-4 hours in a block?

Are you able to describe a bit closely what kind of end result you would like to achieve with concrete examples e.g. by using an example day’s price graph and drawing the desires outcome on top of that graph?

I mean are you looking to have more than 2 blocked price peaks per day, which will make them shorter? Or to guarantee that there is always more than 1 hour between the blocked periods?

I’m looking for a comination of a parameter to set maximum hours blocked in series and also a parameter for minimum heating between blocked periods, and I think that it’s already there it’s just that the variable for how the spot price will jump up and down, spotprice doesn’t curve doesn’t adjust to our demand for heating and having comfort. If the house gets chilled of it takes some time to get temperature back up.

1 Like

I was actually thinking about similar thing and there are a couple of different things to consider.

One very easy thing is to add a parameter which defines how many hours there must always be between blocked periods. The peak-period-blocker I shared on Dec 29 does not have this as a configurable parameter but uses implicitly 1. I have already developed the algorithm further for my own purposes (I’m planning to publish it as a npm package hopefully in the near future) and it’s trivial to make this configurable so that there is an Item that can be updated either manually or automatically (based on the weather forecast, for example).

Another question is the number of block periods to begin with. I’m not sure if this kind of peak blocking approach is ideal if the temperature drops are fast and more than two (but shorter) block periods are needed. It might be that a better approach in those kind of scenarios would be to use the concept of smart grid modes like @mstormi has been talking a lot where we don’t override the internal logic completely but more give suggestions to the device that “now it’s cheaper than on average - please heat more” or “now it’s more expensive than on average - don’t heat that much unless it’s necessary”


Good thoughts, in some ways it comes down to what your devices can do. My heatpump can’t control set temperature via network I need to set a curve on the display and maybe adjust the curve with the dial knob. My heatpump will allways priritize heating water and then heat the radiators. I really think your soultion saves money even though my parameters decrease total cost for electricity but some days increase consumption.

I haven’t really gotten to it to arrange a temperature for my location hence fmi and SMHI provides data in different ways. One could offcourse make a delta T of todays temperature and tomorrows forecasted average temperature and add running hours on forehand. But our house take long time to heat and cools down pretty fast. If one would like to use temperature forecast as a variable then it would be great if one could use first known forecast after spotprice is uppdated and due to entsoe downtime handle lack of day ahead prices or forecast in a rule.

I really appreciate the work you put into it I couldn’t have done something like this myself.

Here has been some comments on have to handle situations when Entso-E occasionally is down and you can’t get the spot prices to create your control points for the heat pump and water heater. So far when noticed I have handled these situations by manually loading control points. But if I would be away from home it could be a critical situation in the winter.

To cope with the situation automatically I made a rule that runs every day at 13:00 before the Entso-E spot prices are fenced. This rule creates ON control points for the heat pump and waterheter for the next day at times where they usually are allowed to be ON.

If the Entso-E spot price is fetched the control points are created normally and the “rescue” control points are overwritten.

Here is to code if someone should need this type of solution.

influx = require('/etc/openhab/scripts/kotikolo/influx.js');

// Set the date to todays date and time 00:00:00
date = new Date(); 
date.setDate(date.getDate() + 1); 


pointsHp = [];
pointsWh = [];


// Write the actual control data to the points array for the heatpump (control value = 1 between 01 - 07)
function createPointsArrayDataHp() {
for (let h = -2; h < 22; h++) {
  startString = '{"datetime": ';
  if (h < -1) {
    endString = ', "value": 0}';
  } else if (h > -2 && h < 5) {
    endString = ', "value": 1}';
  } else  if ((h >= 5 && h < 21)) {
    endString = ', "value": 0}';
  } else {
    endString = ', "value": 0}';
  newHour = 2 + h;
  d = new Date(date.setHours(newHour)).toISOString().replace(".000Z", "Z");
  newString = startString + '"' + d + '"' + endString;
  data = newString;
  eval('var obj='+newString);

// Write the control points to InfluxDB
influx.writePoints('heatpump_control', pointsHp);

// Write the actual control data to the points array for the water (control value = 1 between 03 - 05)
function createPointsArrayDataWh() {
for (let h = -2; h < 22; h++) {
  startString = '{"datetime": ';
  if (h < -1) {
    endString = ', "value": 0}';
  } else if (h > 0 && h < 3) {
    endString = ', "value": 1}';
  } else  if ((h >= 3 && h < 21)) {
    endString = ', "value": 0}';
  } else {
    endString = ', "value": 0}';
  newHour = 2 + h;
  d = new Date(date.setHours(newHour)).toISOString().replace(".000Z", "Z");
  newString = startString + '"' + d + '"' + endString;
  data = newString;
  eval('var obj='+newString);

// Write the control points to InfluxDB
influx.writePoints('waterheater_control', pointsWh);

I actually just implemented a control point cloner which copies today’s controls for the next day. It works so that I run the rule at 23.00 and if tomorrow’s 12.00 does not have a control point, then it clones today’s controls for tomorrow.

I’ll do so further testing this week and will then publish the whole optimer. It has many other major changes like changing to 15 min resolution instead of 60 min resolution (the day ahead market is supposed to moved to 15 min resolution next year).

If somebody wants to “test drive” the new instructions which have been completely re-written, send me a message!


1 Like

What a co-incidence, just when I got my ”control point rescue rule” ready it seems that Entso-E is down. So now I got it tested in a real situation.

Nice to hear that you are making a solution for the same problem to be part of the complete package.

I have also been thinking about when the 15min resolution change hits us. How are you approaching the issue? That is obviously clear that you can’t run a heatpump in 15 min cycles. Are you going to sum four 15 min cycles to a average one hour price or do you have some more sophisticated solution in mind?

Anyway thumbs up for your dedication to this project Markus! :+1: