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

Is the Entso-E service down? My system stopped getting spot price values yesterday. This is my log:

2023-04-15 07:08:11.500 [INFO ] [nhab.automation.script.ui.00b077135e] - entsoe.js: Making an API call to Entso-E API...
2023-04-15 07:08:11.959 [INFO ] [nhab.automation.script.ui.00b077135e] - entsoe.js: transforming XML to JSON and parsing prices...
2023-04-15 07:08:12.144 [ERROR] [nhab.automation.script.ui.00b077135e] - entsoe.js: Exception parsing spot prices: Cannot read property "period.timeInterval" from undefined
2023-04-15 07:08:12.150 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Preparing to write 0 points to the database for spot_price
2023-04-15 07:08:12.156 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Points successfully saved for measurement spot_price
2023-04-15 07:08:27.899 [INFO ] [nhab.automation.script.ui.00b077135e] - waterheater.js: Starting to calculate on/off hours for the waterheater...4
2023-04-15 07:08:27.902 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Reading spot prices from the database...
2023-04-15 07:08:27.969 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Preparing to write 1 points to the database for waterheater_control
2023-04-15 07:08:27.993 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Points successfully saved for measurement waterheater_control
2023-04-15 07:08:42.855 [INFO ] [nhab.automation.script.ui.00b077135e] - nibe.js: Calculating forecasted average temperature...
2023-04-15 07:08:42.921 [INFO ] [nhab.automation.script.ui.00b077135e] - nibe.js: average temperature: 2.391666666666666
2023-04-15 07:08:42.924 [INFO ] [nhab.automation.script.ui.00b077135e] - nibe.js: Calculating number of ON hours for Nibe...
2023-04-15 07:08:42.930 [INFO ] [nhab.automation.script.ui.00b077135e] - nibe.js: Number of needed hours: 5.3121212121212125
2023-04-15 07:08:42.933 [INFO ] [nhab.automation.script.ui.00b077135e] - control-point-optimizer-slicing.js: Searching for cheapest hours...
2023-04-15 07:08:42.936 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Reading spot prices from the database...
2023-04-15 07:08:42.984 [ERROR] [nhab.automation.script.ui.00b077135e] - control-point-optimizer-slicing.js: Not enough spot prices! Expected 24 but found 1
2023-04-15 07:08:42.988 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Preparing to write 0 points to the database for nibe_control
2023-04-15 07:08:42.991 [INFO ] [nhab.automation.script.ui.00b077135e] - influx.js: Points successfully saved for measurement nibe_control

Or has my system got corrupted?

Entso-E is not completely down, but have not been able to publish today’s prices. See the link below and observe that prices are there for Apr 14 but not for Apr 15.

If you’re using the scripts unmodified, all hours are allowed and your real world devices should be operating on their own logic.

Markus

https://transparency.entsoe.eu/transmission-domain/r2/dayAheadPrices/show?name=&defaultValue=false&viewType=GRAPH&areaType=BZN&atch=false&dateTime.dateTime=14.04.2023+00:00|CET|DAY&biddingZone.values=CTY|10YFI-1--------U!BZN|10YFI-1--------U&resolution.values=PT15M&resolution.values=PT30M&resolution.values=PT60M&dateTime.timezone=CET_CEST&dateTime.timezone_input=CET+(UTC+1)+/+CEST+(UTC+2)

1 Like

@masipila. Entso-E is not your product or responsibility and you still help! Thanks a lot!

Hello. I know you @masipila from the Fission portal. I promised to try OpenHAB and your scripts. What can I say, work like the famous train toilet, thanks a lot for your effort and for sharing these with others !!! I have semi-reserving electric heating and night/day electricity rates in transmission, so I modified your scripts to take into account cheaper transmission rate between 22:00…07:00. I use a Shelly pro 2 relay for controls and a tiny PC and Rocky Linux instead of Raspberry.

2 Likes

Glad to hear that it works well for you!

Now that I have been running my own version of this software concept, I noticed it’s somewhat suboptimal at times.
Say I don’t heat during the n most expensive hours, and one of those n is at 23:00 (which is not uncommon) so I don’t heat by then.
But now what if next day prices are overall higher than today’s … I will start heating at 0:00 because that’s among 24 - n cheapest of the next day but that hour it is more expensive than it was the hour before so reversing those would have been the cheaper choice in total.

Ok we don’t obtain prices more than 1 day in advance, but by 13:00 or so they are there.
I now came to think that instead of doing the calculation on a static 0:00-23:00 basis, we should better be using a rolling window of the next 24 hours (or as few or as many as we have prices available).
If looking to cover a fixed period of 24h, we might need to estimate prices, say it’s 12:00 and we haven’t received next day price info yet, we would need to generate a value to calculate with for the hours 13 (= 00:00 next day) to 24.

wdyt?

1 Like

I am not an algorithmic expert, concept which seem close to what you describe is a “sliding window” (in context of time series) and “fuzzy logic”. First because calculations you make need to be accurate according to information which is available at the execution time (time window), which is in continuous move. Second term because there is a whole set of “but” conditions which make calculation far from 0 or 1 which make decision be somewhere between 0 and 1. :wink:

Sliding window it is. But frankly I don’t understand what you mean by fuzzy logic in this context.
Provided I have the pricing data, I can determine at any time which n of the next 24 hours are the most expensive ones and if <now> is one of them then act accordingly.
Or generalised, to compensate for the fact that window size changes because new prices are obtained in 24h chunks only so that I don’t always know the complete next 24 hours, if I have values for the next k hours, I can select the (n + k)/24 most expensive ones and still determine if <now> is one of them.

At the next tick of clock with pricing data for next slot let say 24th hour since now you can find a new minimum. Also by next tick of clock a weather forecast might bring low temperatures earlier than cheapest time is, hence you need to seek for optimum. If you know that you will not keep comfort cause cheap time is too far you need to pick up a mid-ground to keep place warm, but at the time you reach mid-ground now is moved ahead and minimum might be again further than expected.

Looking at the problem as 24 slots for each hour there will be always a minimum within it with cheapest energy, so you always have true/false. Yet, if you reach situation where you have 2 or 3 continuous hours in a row with the same spot price, which one is best? Will it be always first or last? It depends on the context and other conditions which you can clearly score.

I’m not sure how likely an ever-falling price time series is that could result in too many ‘off’ hours so we cool down ever waiting for things to become even cheaper and cheaper (like deflation in money theory).
To play it safe I think we need to track in how many of the past 24h we already stopped heating and use that as a “but” condition in your terms that overrides if the sliding window’s computation concludes we should not heat now because we see falling prices ahead so it would be cheaper to wait even more. But as said not sure if that’s worth checking/preventing.

I recognize the phenomenom. It’s somewhat related to the cold winter days here in the North where we could have cheaper hours coming in the evening but to prevent too big cooling, we need to allow some heating during the day. And when this is needed, it makes sense to find the local minimum from the daytime.

So this comes back to the fact that the optimization problem has multiple objectives: comfort and price optimization.

Coming back to the concrete topics and questions.

We know as a fact that Nordpool publishes the day-ahead prices at 12.45 CET/CEST. We also know that the data is usually available at API endpoints like Entso-E at 13.15 CET/CEST. Thus, the price fetching is typically scheduled to occur in the afternoon. The calculations can conceptually be modified somewhat trivially so that it does the optimizations from “now” until the end of the price window.

For the “prevent too much cooling” dilemma, this can be addressed in different ways.

One is of course to use a less intrusive control strategy where the device can work with its own internal logic which is usually based on feedback loop from temperature sensors. This optimizes the comfort in favor of price.

A second approach could be to define a number which defines the max not-allowed heating period and then find local minimums from prices.

A third one is almost the same i.e. the slicing concept of my solution that we have discussed already many times in this thread.

I’m sure combinations of these exist. If for example we can make a safe assumption that we have reliable indoor temperature measurements, then this “let’s kick in the prevention of too much cooling” logic could be wired to the current indoor temperature, current outdoor temperature, weather forecast (temperature and cloudiness being significant factors, wind speed to some extent) and spot prices. It’s a balance between complexity and the optimization objectives.

We should not assume there’s indoor temp measurements available for the average user
If someone has it they can of course additionally deploy it to complement whatever we do in the algorithm but in many environments it just isn’t there and we need to provide a solution that works for everyone.
So your #1 isn’t applicable. Not sure how your #3 slicing proposal would solve this so I’m with your #2.
I wonder what’s the best ‘but’ condition: do we need the user to define a maximum not-allowed period or can we auto-derive that from the slice size or from the ‘n’ of my example which actually isn’t a period but the number of hours we’re allowed to “not-heat” ?

I agree with you that the assumption that the indoor temperature is (reliably) available is not safe. For example, I have a DS18B20 sensor in our living room but it’s only “for info” purposes. I would most definitely not use it for control purposes.

In the slicing concept: Let’s assume that the number of needed heating hours is known, let’s use 12 as an example.

If the day is split into 2 x 12h parts and both parts are guaranteed to have 40% of the heating time, that would mean that both 12h parts have at least 5 hours, if we round up. In the worst case, the first part would have 7 h in the beginning and the latter would have 5 in the end. Meaning that the max cooling period on a total 24h window would be 12h. So not that much of a benefit.

If the 24 hour window is split into 3 x 8 hour parts and each part is guaranteed 30% of the heating time, it would mean that each part has 4 hours if we round up. That would mean that in the worst case, the first part has all 4 hours in the beginning and the second part would have them at the end. Meaning that the max cooling period is 8h.

If the day is split into 4 x 6 hour parts and 20% of heating time is guaranteed for each part, it would be 2 or 3 hours per part depending how we do the rounding. Let’s say that 2h would be guaranteed for each part. Max cooling period in the worst case is again 8h if the first part has the 2 hours in the beginning and the next one has them at the end.

The question between the approaches 2-3 is that do we use absolute number of max cooling hours in the algorithm or do we use relative / percentage values. The goal and result is the same, in other words to guarantee / force a more even distribution of the heating time.

@mstormi some Nibe models have a feature called Smart Price Adaption. It’s documentation is next to non-existing and my pump does not have this feature so I haven’t been able to test in practice how it works. According to the Finnish forum posts (which are very biased to enthusiasts and do not represents average users) it’s not very aggressive in terms of cost optimization. According to some users it’s not for example scheduling the heating of domestic hot water to the cheapest hours which is kinda “interesting”.

Anyway, one thing that we might find inspiring is that it has a settings where the user can choose how much weight is given to cost optimization vs. comfort. This kind of slider could be translated to this “even distribution of heating time” / finding local minimums within the price window.

I just checked my electricity bill 1-31.3.2023

Consumption: 1201,96 kWh
Average spot price paid: 7,26 c/kWh
Energy bill total: 90,81€ including tax and 0,22c/kWh margin

At the same time our local energy company was providing one of the cheapest fixed energy prices in the country at 16 c/kWh. 1201,96 kWh x 0,16€/kWh = 192,31€

Total savings in March 101,5 €! I think this is proof enough that the code works.

Thank you for the hours you have put in developing the code, publishing it and answering our questions here in the forum, @masipila!

7.26 c/kWh as an average is quite nice achievement. The market average on March was 8.16 so you were 0.9 c/kWh below it i.e. -11% compared to market average.

Without active scheduling you would have most probably been at least +25% because daytime prices are most of the time above monthly average.

Our March was -2.4 c/kWh (-27%) compared to market average. Because of electric vehicle our consumption was a bit higher than yours, about 1720 kWh.

Now that OL3 is finally up and running the prices are not jumping that much anymore. In April so far we are -1.8 c/kWh and the average so far is 6.85. So percentage wise still on the same ballpark i.e. between 25-30%

For non-Finns: OL3 is the notorious 1600 MW nuclear power plant which finally started commercial production last weekend, only 14 years delayed compared to the original schedule. According to Wikipedia, it’s the third most expensive building ever built in the World.

Including passive solar heat.
The average temperature here in the arctics has for the past two weeks been between +5 to +10 C with clear skies and a lot of sunshine. Our house has big windows to the south and west that let in a lot of sunshine and provide additional heating. Today the weather changed, we are down to 0 C and 100% overcast. As a results the straight heating curve provided by the code no longer serves it purpose right but the house cools down. I have manually changed the curve few times when this happens, but I think the influence of passive solar heating could be automated, too.

Reading the windchill code I understand it takes the temperature forecast and wind forecast and calculates a windchill corrected temperature value for each hour of the temperature forecast. The original temperature forecast is then replaced hour by hour with the corrected one. Heating hours are calculated based upon the average of these corrected temperature forecast hours. Correct @masipila ?

To include the passive solar heat a couple of factors should be considered:

  1. Time of the year. Up here in the north the sun does not provide significant heat from beginning of November to end of February. During this time passive solar heating should not be included in the heating hours.

  2. Time of day. Solar heat is typically available only if the sun is above the horizon ;-). This could be approximated e.g. by taking the passive solar effect into account only between 11:00 and 18:00 local time. The local sunrise and set times can also be downloaded from a internet source such as Sunrise and Sunset Calculator and if a more exact calculation is desired.

  3. Cloudiness. The FMI forecast gives a hourly cloudiness percentage that can be used to calculate average cloudiness over the time period when the sun is up. This can be used to compensate for a weather situation like the one we have right now.

I am not quite sure how the algorithm should work. Should it recalculate the hourly forecast temperatures like the windchill algorithm seems to do or should it just add and subtract from the heating hours calculated. Opinions?

As I am no coder I am not able to write the code needed, even with the help of ChatGPT, to solve this, so help is needed. But before that, I would like to hear your opinions on my ideas of including passive solar heating in the code.

Almost correct, except that I store both the “raw” temperature forecast as “fmi_forecast_temperature” and in addition to this, I store “fmi_forecast_WindChillTemp”. So everything you wrote is correctly understood except that I don’t “replace” the temperature forecast, I store both. And then, in the scripts that calculate how much heating is needed, I use “fmi_forecast_WindChillTemp”, not “fmi_forecast_temperature”.

You might want to consider de-coupling the “calculate the number of needed heating hours” into a separate rule. The output of this would then go to a separate Item, called for example “number of heating hours”. You can expose this Item in the user interface like I do, like this:

Basically what happens here is that I have a rule “Calculate Heating Hours” that runs every day at 14.20. The result of this calculation is saved to Item called “HeatingHours”. I have a separate Page called “Ohjausparametrit” (control parameters in Finnish) where I can manually adjust the number of heating hours if I’m not happy with the automatically calculated number for whatever reason. I implemented this in the winter time because there were a couple of days when the FMI weather forecast provided by their API was like 10 degrees off from what their meteorologists were saying on TV, on their website and their mobile app.

On this “Ohjausparametrit” page, I also have similar Item for selecting how many hours the water heater needs (it’s usually set to 2 and I change it only if I have a reason for it). And I have an Item for selecting how many hours our electric vehicle needs to be charged. We update this number manually with this stepper UI widget and it’s quite handy since 10% increase of battery charge level corresponds almost exactly 1 hour of charging time. So if we need to charge the car from 50% to 80%, we would check that the value of this “CarChargingHours” is 3.

The point here is that when this “calculate heating hours” is a Rule of its own, you can make it as sophisticated as you want. For example, if it’s March - October, apply this logic and otherwise, apply this logic.

Finally, you should apply a logic that WHEN the value of HeatingHours changed, THEN trigger the other rule that optimizes your heating hour control points.

openHab is your best friend because most of things that you need have already been solved by somebody else. See the Astro Binding, Astro - Bindings | openHAB

TLDR;

  • You want to have a separate Rule that calculates the number of needed heating hours.
  • This Rule updates the Item called HeatingHours
  • When HeatingHours changes, (re-) trigger the heating optimization rule and feed the current value of HeatingHours as the input to the heating optimization.
1 Like

Hello

Would this mean to splice out this part of nibe.js, like so?

**  GNU nano 5.4                            /etc/openhab/automation/js/node_modules/sienitie18/nibe.js                              >
/**
 * Javascript module for Nibe helper methods.
 *
 * Copyright (c) 2022 Markus Sipilä.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * Exports.
 */
module.exports = {
    calculateNumberOfHours: calculateNumberOfHours
};
/**
 * Calculates number of needed hours for given average temperature.
 *
 * @param float temperature
 *   Average temperateure for the day.
 *
 * @return float
 *   Number of hours the heat pump should be allowed to run.
 */
function calculateNumberOfHours(temperature) {
    console.log('nibe.js: Calculating number of ON hours for Nibe...');

    // Early exit if temperature is null.
    if (temperature == null) {
        console.warn('nibe.js: No temperature given! Number of needed hours defaulted to 24!');
        return 24;
    }

    // Calculate curve based on two constant points.
    // y = kx + b
    // x = temperature, y = number of needed hours.
    const p1 = {
        x : -27,
        y : 16
    };
    const p2 = {
        x: 2,
        y: 6
    }
    const k = (p1.y-p2.y) / (p1.x-p2.x);
    const b = p2.y - (k * p2.x);
    console.debug('nibe.js: y = ' + k + 'x + ' + b);

    let y = k * temperature + b;
    if (temperature < p1.x) {
        y = p1.y;
    }
    if (temperature > p2.x) {
        y = p2.y;
    }
    console.log('nibe.js: Number of needed hours: ' + y);
    return y;
}

You don’t need to modify the javascript files. You currently have the Rule where you

  1. determine the number of heating hours and
  2. pass that number to the method that calculates the ‘nibe_control’ control points for your heat pump.

First, you need to create a new Item called for example HeatingHours.

Once you have this, create a new Rule called for example DetermineHeatingHours. This rule will have nothing to do with nibe_control. All it does, is to calculate the number of needed heating hours and save that result to the item HeatingHours.

Then, you want to modify your current rule that writes the nibe_control points so that this script no longer calculates the number of heating hours on the fly. Instead, it reads the number from your Item called HeatingHours and passes that value to the method that generates the nibe_control points.