3.x ‐ Usage example: Heating period optimizer - masipila/openhab-spot-price-optimizer GitHub Wiki
Important note
- These instructions apply only to version 3.x of openHAB Spot Price Optimizer.
- See separate wiki page for version 4.x of openHAB Spot Price Optimizer.
Overview
This documentation page gives an example how to use the HeatingPeriodOptimizer class of the openhab-spot-price-optimizer to optimize the heating of a house. This is an advanced and highly configurable optimization algorithm introduced in the version 3.0.0.
Optimizing the heating of the house has multiple objectives, which need to be balanced:
- Optimizing the heating to the cheapest time of the day
- Ensuring that the house won't cool too much during the day
- Additionally, you might want to minimize the number of compressor starts if you have an on/off ground source heat pump.
Conceptual description of the optimization steps
Let's use 12 January 2024 as an example day. In the figure below, the blue area represents total electricity price (spot price + tariff) and the green line presents the temperature in the weather forecast. The yellow bars represent the result of the heating optimization. As you can see, there is quite steep temperature drop from -4 to -18 degrees during the day.
Step 1: Calculation of the heating needs
The algorithm first divides the day into heating periods and calculates the heating needs for each period based on a heating curve, which defines how many hours the heating needs to be ON on different temperatures. Because are buildings are different, the optimization parameters are highly configurable. In this example, we divide the day into 4 x 6h heating periods and use a heating curve defined by two points:
- When the average temperature of the day is -25, heating needs to be ON for 24 hours per day
- When the average temperature of the day is 13, heating needs to be ON for 0 hours per day.
The algorithm considers 50% of each period's heating need to be flexible and the rest non-flexible. The idea here is to ensure that each heating period will have at least some minimum amount of heating, the non-flexible part. The flexibility is configurable in two ways:
- The default flexibility, 50% in this example, is configurable between 0% and 100%.
- There is a threshold in hours for warmer times of the year after which the whole heating need will be considered flexible.
On this example days, the average temperatures and heating needs for the 4 x 6h periods are as follows:
- Period 1, 00:00 - 06:00: Avg temperature -9.75, heating need 3.59 hours
- Period 2, 06:00 - 12:00: Avg temperature -5.92, heating need 2.99 hours
- Period 3, 12:00 - 18:00: Avg temperature -5.33, heating need 2.89 hours
- Period 4, 18:00 - 00:00: Avg temperature -11.78, heating need 3.91 hours
Step 2: Analyzing big temperature drops
As we can see, there is a big temperature drop in the evening as the average temperature drops to -11.78 degrees. What is not visible in the picture is that it's getting even colder on 13 January, the average temperature between 00:00 - 06:00 is -16.83 degrees.
To ensure enough heating, the algorithm compares the average temperatures between the heating periods and when it detects a temperature drop of 2 degrees (configurable), the heating need and flexibility for each period is adjusted.
- If there is a temperature drop between three consecutive heating periods (A to B is dropping by at least 2 degrees and B to C is dropping at least 2 degrees), the flexibility is set to 0 for all three affected heating periods A, B and C. Furthermore, the heating need of period A is updated to the original heating need of B, and heating need of period B is updated to the original value of heating need C.
- If there is a temperature drop between just two consecutive heating periods, then the flexibility is set to 0 for both heating periods.
After these adjustments, the heating needs for our example day are as follows:
- Period 1, 00:00 - 06:00: avg temperature -9.75, heating need 3.59, flexibility: 0.5
- Period 2, 06:00 - 12:00: avg temperature -5.92, heating need 2.99, flexibility: 0.5
- Period 3, 12:00 - 18:00: avg temperature -5.33, heating need 3.91, flexibility: 0 (3.91 hours is the original need of the next period)
- Period 4, 18:00 - 00:00: avg temperature -11.78, heating need 4.71, flexibility: 0 (4.71 hours is the original need of the next period after midnight)
In order to be able to the temperature drop analysis for the first and last heating periods of the day, the algorithm checks also the last heating period of yesterday and the next 2 heating periods of tomorrow. The latter point assumes that the weather forecast contains enough data to the future.
Step 3: Allocation of the non-flexible heating need
The algorithm then allocates the non-flexible heating needs for each heating period in 15 minute increments, by finding the cheapest 15 minute slots. The heating need is rounded up to the closest 15 minute.
Period 1: allow 2h in slots of 15 mins Period 2: allow 1h 30 min in slots of 15 mins Period 3: allow 4h in slots of 15 mins Period 4: allow 4h 45 min in slots of 15 mins
Step 4: Allocation of the flexible need
After the non-flexible heating needs have been allocated, the algorithm allocates the flexible heating need.
On this example day the remaining, flexible heating need is 3h 30 minutes. The algorithm searches the cheapest 15 minute slots from the whole remaining day and allows heating for them.
After the non-flexible and flexible heating needs have been allocated, the remaining 15 minute slots are blocked.
Step 5: Minimizing the compressor starts
As mentioned in the beginning of the page, one of the optimization objectives is to minimize the compressor starts of traditional ON/OFF ground source heat pumps. According to some sources, the compressors tend to break after 100 000 starts, so it is not desired to have it running it short 15 minute periods, then stop for 15 minutes, only to start again for 15 minutes.
On this example day, there is only one short gap at 19:45 as illustrated in the figure below.
The algorithm has a couple of more configuration parameters:
- Threshold for minimum heating time: I use 0.5 hours as the minimum heating time. If there is a shorter heating time than this, the algorithm will shift this heating time either left or right, depending which is cheaper. The minimum heating time threshold is configurable and can be omitted if short heating times is not a concern.
- Threshold for short gaps: I use 1 hour as this threshold. If there is a heating gap which is 1 hour or shorter, the heating periods around this gap will be merged.
- For both shifts (avoid short heating, avoid short break), there is a configurable price limit which prevents the merging from happening if the price difference is higher than the limit. The purpose of this limit is to prevent merging happening if there is for example a 1h break in the heating for the highest price peak of the day.
Create an item 'HeatPumpCompressorControl'
- The yellow bars in the example above are the control points for when the heat pump compressor should be on. These control points are calculated by the script below and stored to the Influx database as
HeatPumpCompressorControl
. So let's create an Item with this name so that we can display this in the Chart. - The type of this Item must be Number.
- See an example of control point visualization chart that renders control points with spot prices
Rule script for using the Heating Period Optimizer
- Go to edit the previously created
FetchSpotPrices
Rule and add a new Action as illustrated in the picture below.
- Select ECMAScript as the scripting language (ECMAScript 262 Edition 11)
- Copy-paste the following script as the Script Action
- All you need to touch in the code is to update the required and optional optimization parameters.
This Rule does not need any helper Items. Of course nothing stops you from creating Items for the configuration parameters, but that is left as an exercise to you.
// Load modules. Database connection parameters must be defined in config.js.
var { Influx } = require('openhab-spot-price-optimizer/influx.js');
var { GenericOptimizer } = require('openhab-spot-price-optimizer/generic-optimizer.js');
var { HeatingCalculator } = require('openhab-spot-price-optimizer/heating-calculator.js');
var { HeatingPeriodOptimizer } = require('openhab-spot-price-optimizer/heating-period-optimizer.js');
// Create services.
var influx = new Influx();
var genericOptimizer = new GenericOptimizer();
var heatingCalculator = new HeatingCalculator();
// Required optimization parameters
var parameters = {};
parameters.priceItem = "TotalPrice"; // Name of the price item for optimizing. Example: "SpotPrice" or "TotalPrice".
parameters.forecastItem = "FMIForecastTemperature"; // Name of the weather forecast item. Example: "FMIForecastTemperature".
parameters.controlItem = "HeatPumpCompressorControl"; // Name of the control point item. Example: "HeatPumpCompressorControl".
parameters.numberOfPeriods = 4; // Number of heating periods in a day. Example: 4
parameters.heatCurve = [ // Heat curve:
{temperature : -25, hours: 24}, // - When average temperature is -25°C or colder, heat 24 hours per day
{temperature : 13, hours: 0} // - When average temperature is +13°C or warmer, heat 0 hours per day
];
// Optional parameters below. You can remove these if you don't want to use these advanced features.
parameters.dropThreshold = 2; // Threshold for significant temperature drops. Remove to disable.
parameters.flexDefault = 0.5; // Default amount of heating need which is considered flexbile. 0 means 0% and 1 means 100%. Default: 0.
parameters.flexThreshold = 1; // Threshold (in hours) to consider heating need 100% flexible. Example: 1. Default 0.
parameters.shortThreshold = 0.5; // Minimun duration for heating. Shorter periods will be merged together. Remove to disable.
parameters.gapThreshold = 1; // Merge two heating periods together if the gap between them is less than this, in hours. Example: 1. Default 0.
parameters.shiftPriceLimit = 2; // Allow heating period shifting only if the price difference is less than this. Example: 2. Default 0.
// Actual optimization happens here. Do not modify anything below this line unless you know what you are doing.
// If the script is called after 14.00, optimize tomorrow. Otherwise optimize today.
var start = time.toZDT('00:00');
if (time.toZDT().isBetweenTimes('14:00', '23:59')) {
start = start.plusDays(1);
}
var end = start.plusDays(1);
// Optimize the heating and save the control poitns to the database.
var heatingPeriodOptimizer = new HeatingPeriodOptimizer(influx, genericOptimizer, heatingCalculator, start, end, parameters);
heatingPeriodOptimizer.optimize();
var points = heatingPeriodOptimizer.getControlPoints();
influx.writePoints(parameters.controlItem, points);
Create a Rule 'HeatPumpCompressorController' to toggle the compressor ON and OFF
- This rule will run every 15 minutes and turn the compressor ON or OFF based on the current control point
- If the compressor is currently OFF and the current control point is 1, the compressor will be turned ON and vice versa.
Inline script action for the rule
// Load modules. Database connection parameters must be defined in config.js.
var { Influx } = require('openhab-spot-price-optimizer/influx.js');
// Create services.
var influx = new Influx();
// Read the control value for the current hour from the database.
var control = influx.getCurrentControl('HeatPumpCompressorControl');
// HeatPumpCompressor: Send the commands if state change is needed.
var HeatPumpCompressor = items.getItem("HeatPumpCompressor");
if (HeatPumpCompressor.state == "ON" && control == 0) {
console.log("HeatPumpCompressor: Send OFF")
HeatPumpCompressor.sendCommand('OFF');
}
else if (HeatPumpCompressor.state == "OFF" && control == 1) {
console.log("HeatPumpCompressor: Send ON")
HeatPumpCompressor.sendCommand('ON');
}
else {
console.log("HeatPumpCompressor: No state change needed")
}
Analyzing the optimization results
Using the logs to understand the results
As this documentation page illustrates, the optimization algorithm is quite advanced and has multiple steps. Therefore it is not always obvious at first glance why the heating is ON or OFF for some individual time slot. Below is an example output of the logs for the same day which is discussed in this documentation. Note that some timestamps are on local time zone and some timestamps are on UTC. The letter Z means that the timestamp is on UTC.
heating-period-optimizer.js: Starting heating period optimizer...
-----------------------------------------------------------------
generic-optimizer.js: price window 2024-01-11T22:00Z - 2024-01-12T22:00Z (PT24H)
heating-period-optimizer.js: Calculating heating need for the heating periods.
heating-period.js: 2024-01-11T18:00+02:00[SYSTEM]: temperature -9.18, heating hours 3.50, flexibility: 0.5
heating-period.js: 2024-01-12T00:00+02:00[SYSTEM]: temperature -9.75, heating hours 3.59, flexibility: 0.5
heating-period.js: 2024-01-12T06:00+02:00[SYSTEM]: temperature -5.92, heating hours 2.99, flexibility: 0.5
heating-period.js: 2024-01-12T12:00+02:00[SYSTEM]: temperature -5.33, heating hours 2.89, flexibility: 0.5
heating-period.js: 2024-01-12T18:00+02:00[SYSTEM]: temperature -11.78, heating hours 3.91, flexibility: 0.5
heating-period.js: 2024-01-13T00:00+02:00[SYSTEM]: temperature -16.83, heating hours 4.71, flexibility: 0.5
heating-period.js: 2024-01-13T06:00+02:00[SYSTEM]: temperature -11.33, heating hours 3.84, flexibility: 0.5
heating-period-optimizer.js: Checking for significant temperature drops...
heating-period-optimizer.js: Big temperature drop detected after period starting at 2024-01-12T12:00+02:00[SYSTEM], adjusting heating hours and flexibility of this and next two periods.
heating-period-optimizer.js: Temperature drop detected after period starting at 2024-01-12T18:00+02:00[SYSTEM], adjusting flexibility of this and next period.
heating-period-optimizer.js: Heating periods after temperature drop compensations:
heating-period.js: 2024-01-11T18:00+02:00[SYSTEM]: temperature -9.18, heating hours 3.50, flexibility: 0.5
heating-period.js: 2024-01-12T00:00+02:00[SYSTEM]: temperature -9.75, heating hours 3.59, flexibility: 0.5
heating-period.js: 2024-01-12T06:00+02:00[SYSTEM]: temperature -5.92, heating hours 2.99, flexibility: 0.5
heating-period.js: 2024-01-12T12:00+02:00[SYSTEM]: temperature -5.33, heating hours 3.91, flexibility: 0
heating-period.js: 2024-01-12T18:00+02:00[SYSTEM]: temperature -11.78, heating hours 4.71, flexibility: 0
heating-period.js: 2024-01-13T00:00+02:00[SYSTEM]: temperature -16.83, heating hours 4.71, flexibility: 0
heating-period.js: 2024-01-13T06:00+02:00[SYSTEM]: temperature -11.33, heating hours 3.84, flexibility: 0.5
heating-period-optimizer.js: Optimizing the non-flexible heating need.
generic-optimizer.js: allow PT2H in slots of PT15M between 2024-01-12T00:00+02:00[SYSTEM] and 2024-01-12T07:00+02:00[SYSTEM]
generic-optimizer.js: allow PT1H30M in slots of PT15M between 2024-01-12T05:00+02:00[SYSTEM] and 2024-01-12T13:00+02:00[SYSTEM]
generic-optimizer.js: allow PT4H in slots of PT15M between 2024-01-12T11:00+02:00[SYSTEM] and 2024-01-12T19:00+02:00[SYSTEM]
generic-optimizer.js: allow PT4H45M in slots of PT15M between 2024-01-12T17:00+02:00[SYSTEM] and 2024-01-13T00:00+02:00[SYSTEM]
heating-period-optimizer.js: Optimizing the flexible heating need.
generic-optimizer.js: allow PT3H30M in slots of PT15M between 2024-01-12T00:00+02:00[SYSTEM] and 2024-01-13T00:00+02:00[SYSTEM]
generic-optimizer.js: block all remaining...
heating-period-optimizer.js: Checking if there are too short heating periods...
heating-period-optimizer.js: Checking if there are short gaps...
heating-period-optimizer.js: PT15M gap starting at 2024-01-12T17:45Z
heating-period-optimizer.js: Shift heating period to the right.
heating-period-optimizer.js: Checking if there are short gaps...
influx.js: Preparing to write 96 points to the database for HeatPumpCompressorControl
Further debugging and analyzing tips
If you want to analyze the algorithm behavior deeper, the second step after log analysis is to disable the short periods and gap shifting. The easiest way to do this is to set the shiftPriceLimit
parameter to 0, which prevents the shifting from taking place.
Discussion about the algorithm
Discussion about this algorithm is more than welcome on this thread at the openHAB community forum.
However, I would appreciate if you would show some effort before asking questions like "why is this heating on during this expensive hour" by analyzing the logs before asking the question. It is possible that the algorithm has bugs, but the code is well isolated and there is covered by 134 automated test cases at the time this wiki page is written.