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

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”

Hi

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); 
date.setHours(0); 
date.setMinutes(0); 
date.setSeconds(0); 
date.setMilliseconds(0);

//console.log(date);

pointsHp = [];
pointsWh = [];

createPointsArrayDataHp();
createPointsArrayDataWh();

// 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);
  pointsHp.push(obj);
  
  }
}

// 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);
  pointsWh.push(obj);
  
  }
}

// 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!

Cheers,
Markus

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:

Ta-daa, I now had the time to publish the new version as a npm package. Fully updated documentation and usage instructions are documented at:

Some notes for the users of the earlier solution:

  • The basic concept is still the same i.e. there are control point Item for each device and then a separate Item which actually toggles the device ON/OFF (or to any other state)
  • The documentation instructions of the new version harmonize the naming conventions for these Items. If (most probably when) you want to keep using your existing data, pay attention to the Item names. For example, the instructions in the wiki use the name SpotPrice whereas it was spot_price in the earlier versions of the instructions. You can use the new solution, just replace SpotPrice with spot_price (or whatever is your existing item name.
  • The new solution is completely re-written to use js-joda date and time handling, there is 0 places left that use the shitty vanilla javascript date and time functions. If you want to tweak the scripts to work for your use cases, here are two excellent resources: JavaScript Scripting - Automation | openHAB and Working with Date Times in JS Scripting (ECMAScript 11)
  • The earlier “heating slice” concept is no longer available, I replaced it with PeakPeriodOptimizer which is simpler and is working much better at least in our house.

I will update the comment #13 (marked as solution) so that I’ll remove the files and rule snippets and link to the wiki documentation instead.

Happy optimizations to everybody and many thanks for the cheers so far!

4 Likes

I handle it so that even though the solution uses 15 minute resolutions, I still find cheapest periods (or block the most expensive periods) of desired lengths.

For example for heating the domestic hot water, I have an Item which I can use to define how many hours will be needed (it’s usually 2, I change it to a higher value if there is a lot of bathing or sauna party or something like that). Then I use optimizer.allowPeriod(N) where N is the number of the Item and block the rest. This will find the cheapest consecutive 2 hour period if the Item is set to 2.

There is also an algorithm optimizer.allowIndividualHours(N) which can be used, it will find N individual 1 hour periods which do not have to be consecutive.

For heating the house, I use the PeakPeriodOptimizer. This concept is fully documented here:

Cheers,
Markus

2 Likes

Great work, as usual, @masipila. As I have a well working system based upon your previous code I am very hesitant to migrate to the new system. What drawbacks and benefits are associated with migrating?

@timo12357 : Nothing of course forces you to change a working setup that you have.

There are a couple of benefits in the new version. The approach is now much more generic than in the first evolution. In the first version, the approach was quite tightly coupled to the water boiler and the ground source heat pump, starting from the name of the javascript library files. Now the approach is much more generic.

The second thing is that the 15 min resolution means that for example the heating optimization does not have to be full hours. For example for today, my solution is heating the house for 8,5 hours, not 8 or 9.

The third thing is that now the concepts are implemented using Duration objects. If the 15 min resolution kicks in next year in the day ahead market, then you really need to have this, at least I don’t want to toggle the heat pump on and off every 15 minutes.

If you want to try this, you have two main approaches: You can either set up a test environment or you can build the new Rules in parallel to your existing Rules.

For the test environment, I use Docker.

Here’s my Dockerfile to build the ohab image which is based on the official openhab:4.1.1 image so that it includes nodejs and npm

FROM openhab/openhab:4.1.1

# install node.js and npm
RUN set -eux; \
  apt-get update; \
  apt-get install -y --no-install-recommends nodejs; \
  apt-get install -y --no-install-recommends npm;

Here’s my docker-compose.yml:

version: '2.2'

services:
  openhab:
    image: "ohab:latest"
    restart: always
    ports:
      - "8080:8080"
      - "8443:8443"
    volumes:
      - "./openhab_addons:/openhab/addons"
      - "./openhab_conf:/openhab/conf"
      - "./openhab_userdata:/openhab/userdata"
    environment:
      CRYPTO_POLICY: "unlimited"
      EXTRA_JAVA_OPTS: "-Duser.timezone=Europe/Helsinki"
      OPENHAB_HTTP_PORT: "8080"
      OPENHAB_HTTPS_PORT: "8443"
  influxdb:
    image: influxdb:latest
    container_name: influxdb
    ports:
      - 8086:8086
    volumes:
      - ./influxdb:/var/lib/influxdb
    restart: always

For the parallel implementation in your PROD environment:

  • Make sure that you make a backup of your node_modules directory, which contains your current scripts
  • Then install the new openhab-spot-price-optimizer. Restore your old scripts so that your current scripts continue to work.
  • You can then copy-paste a new Script which uses the new openhab-spot-price-optimizer scripts to test how it works and then fine-tune that as much as you want.
1 Like

Just found this super cool service that has a machine learning model to predict Finnish spot prices with an incredible accuracy…

https://sahkovatkain.web.app/

I was thinking to experiment with this so that we could bring these forecasts to the same influxdb and use these predictions in our optimizations. Let’s see if I find time for this and if I can figure out any sensible use for it…

2 Likes

I’m really interested in this project! I’m also (eventually) going to have a heat pump, and would like to do some clever controlling.

However, when reading up online, most people advice against disabling the heat pump, claiming it will use a lot more energy for the same heat. Presumably because the pump will use direct heating when turned on again, to reach the desired level fast.

Do you have any thoughts on that? Are some heat pumps prepared for this, and others not?

My experiences do not support such claims. Having said this, our house is new, very well insulated and we have 200 m2 of 10cm thick concrete floor which reserves the heat extremely well. The walls are made of 37.5 cm thick siporex which also has great capacity to reserve heat.

I have disabled the direct heating in Nibe menus. It only kicks in in the ultimate fallback scenario where the outgoing water temperature is under 17 degrees. This is the last line of a failsafe defence which prevents the house from freezing if everything else has failed.

Too much cooling needs to be prevented though, because it takes time for a 200 m2 house to heat. That’s why I developer the PeakPeriodOptimizer algorithm which guarantees configurable amount of heating period between the price peaks. Also the number of peaks to block is configurable so it should be fit for purpose also for houses which does not reserve as much heat as our house.

There are two kinds of ground source heat pumps. Traditional on/off pumps where the compressor is always on or off. And inverter pumps, where the pump is always on but it adjusts the speed. I only have experience with on/off pumps, but the same optimization concept can be applied with a modern inverter pump. You could configure it for example so that when the prices are high, you adjust the target temperature down, and when the prices are low, you adjust the target temperature up.

I would assume that all modern ground source heat pumps are also SG Ready. SG is a vendor independent industry standard which has 4 modes. How the pump behaves in each of these 4 modes are usually configurable, for example so that when you set the pump for example to “now it’s cheap”, the pump will adjust the target temperature higher than usual.

Cheers,
Markus

Oh, and forgot to mention one more thing. Our heat pump continues to circulate the water in the underfloor pipes even when the compressor is blocked. So it will continue to heat the house for quite some time after openHab blocks the compressor.

Do I interpret this correctly that you are building a new house? If we would be building a house now, the only thing I would do differently is to have same thousands of liters water buffer where the heat pump would store energy and the underfloor heating would then consume the heat from this buffer. That way you could really heat this buffer on the absolute cheapest times of the day. These kind of buffers are quite common here in Finland, it’s called “puskurivaraaja” in Finnish if you want to Google it.

If my memory serves me well @ruxu or @timo12357 has this kind of thing?

Markus

Yes I do. Our heatpump is a Nibe 1245 R8 ground source heat pump that has only on and off switching for the compressor. Works great, and never heard of it using the direct electricity for heating. The pump power is so great that it is a lot faster than direct electricity.

One recommendation if you are building a new house with underfloor heating using circulating water. Invest in a thicker floor, about 20 cm. This will give you a thermal storage that will last for roughly 24 hours. Typically, when desigining water based under floor heating, construction engineers in Finland design about 8 cm floor depth. This is not an optimal thermal storage for a system such as the one @masipila has designed as it expects heating every few hours.

Thanks for all the info! I will definitely look out for “SG ready” then. If even the standard supports blocking the power, there cannot be any principal issue with it.

Probably the warnings refer to turning the power of the entire heat pump off.

It’s not a new house, but it currently has direct electricity heating. Unfortunately there are regular radiators rather than floor heating, so the heat storage mass is not comparable to yours. I have thought about a large water accumulation tank, and from your diskussion it sounds that would be the way to go.

Yeah, you kinda need something that can reserve the thermal energy if you are planning to skip the heating for based on the spot prices. If you have just radiators heating the air, it will cool down quite quickly if you don’t have a tank that can preserve the energy.

If you’re about to have a ground source heat pump, an additional hint. After the depth has been calculated be an engineer, make it a bit deeper than needed. If you ever need to change the pump and the new pump is more powerful than the original, the dwell can freeze if it’s not deep enough for the new pump. A colleague of mine is suffering from this and as a result, the new pump needs to run in shorter intervals than would be ideal.

As an example, our dwell was calculated to 150 meters but we paid a bit extra to make it 170m. According to the logs, it has never gone to minus degrees even though it has sometimes been running constantly for a 24 hour period when it was freezing cold…

Cheers,
Markus

Hi @masipila ,

i started using your script this weekend. Thank you very much for your efforts!
Unfortunately i get an error that i can’t get fixed.
I’m not using your influxdb implementation. I fetch the future data using the OH REST API. I will post my rulescript as soon as everything works.

Here is the log:

As you can see the window is from 00:00 2024-03-05T00:00Z until 2024-03-05T23:00Z
The script tries to set a controlpoint at 2024-03-05 22:00. Of course there is no point available.
Any idea how to get this fixed? Or is this more of an warning thatn an error?

Here is my points datafield:

Regards,
Carlo

Hi,

can you elaborate a little bit more on your context.

  • What are you trying to achieve?
  • What parts of the setup you have been able to complete? (e.g. fetching of the spot prices is confirmed to be working)
  • What is the part that you are stuck with?

The second screenshot looks very weird as there is a mixture of thigs there.

  • epoch timestamps (time) do not belong here, where did they come from?
  • states do not belong here, where did they come from?

Dear Markus,

first of all: I want to control my heatpumpt with your script. The script is running for a few days now and it looks quite good at all.

As i told before I’m fetching the future prices using OH REST API. I’m NOT using your implementation for influxDB. The OH REST API returns an array with value and time in epoch timestamp. I’m than conferting this Epoch timestamp to an timedate zoned format like you are using in your scrips.

The array i postet is what i get back from the peak-period-optimizer using optimizer.getControlPoints().
This looks like it returns the same array you send to the script using optimizer.setPrices(x) but adding the control values.

Here is the idea of my script. I’m leaving some code blank where the REST API stuff is done as i need to clean this up.

PeakPeriodOptimizer = require('openhab-spot-price-optimizer/peak-period-optimizer.js');
optimizer = new PeakPeriodOptimizer.PeakPeriodOptimizer('PT1H');
start = time.toZDT('00:00');
start = start.minusHours(1); // I added this caus my future-time is only set until 23:00 
if (time.toZDT().isBetweenTimes('14:00', '23:59')) {
  start = start.plusDays(1);  
}
stop = start.plusDays(1);
//--- HERE COMES MY REST API SCRIPT FOR FUTURE PRICES ---
optimizer.setPrices(prices);

//getpreviousControlPoints
previousDayStart = start.minusDays(1);
previousDayStop = start;
//--- HERE COMES MY REST API SCRIPT FOR PREVIOUS CONTROL POINTS ---
optimizer.setPreviousControlPoints(prevctrlpoints);

optimizer.setPreviousControlPoints(prevctrlpoints);

var heatingHours = Math.round(items.WP_HEATING_NEEDED.numericState);
var midHours = Math.round(items.WP_MIN_TIME_BETWEEN_HEATING.numericState);
var peaks = Math.round(items.WP_PeaksTOBlock.numericState);

optimizer.setOptimizationParameters(heatingHours, midHours, peaks);
optimizer.blockPeaks();
optimizer.allowAllRemaining();
points = optimizer.getControlPoints(); //<- This throws generic-optimizer.js error that it can't set controlpoint at time x (eg. 2024-03-04T22:00:00Z)

//console.error(JSON.stringify(points)); //<- This is what you have seen in my second screenshot before

Even if it throws the error it looks like the values get normally set:

Blue is Energyprice
Green is when Heatpump is allowed
Red is the frequecy output for the heatpump
the other values are only some feedbacks for me to see how good everything works.

One more information: I’m using a 1000 liter water stoarage to store the energy between price-peaks.

As you can see it looks like everything works great. The only weired thing is, that the generic-optimizer.js tries to set a control-value outside of the selected time-range what throws the error in Screenshot 1 on my post before.

Regards,
Carlo