Usage example: PeakPeriodOptimizer ‐ Optimizing heating of the house - masipila/openhab-spot-price-optimizer GitHub Wiki
PeakPeriodOptimizer is deprecated and will not receive further updates. The algorithm is kept for backwards compatibility reasons but all users are encouraged to migrate to Heating Period Optimizer which is much more advanced than Peak Period Optimizer.
This documentation page gives an example how to use the PeakPeriodOptimizer
class of the openhab-spot-price-optimizer
to optimize the heating of a house.
The picture above illustrates how the heating of a house is optimized so that the morning and evening spot price peaks are avoided. In order to optimize the heating of a house, we first tell the optimizing algorithm how many heating hours are needed. The yellow bars in the picture above represent the 14 hours when heating is allowed to be ON.
Optimizing the heating has also other objectives than just finding the cheapest hours of the day because the house may cool down too much if there are too many hours without heating. The idea of the PeakPeriodOptimizer
algorithm is to block the most expensive price peaks and allow the rest. In the example above:
- 14 hours of heating is needed
- That means that 24 - 14 = 10 hours can be blocked
- The 10 hours are divided into two periods, 5 hours each.
- The algorithm searches two most expensive 5 hours price peaks and blocks them.
The minimum number of hours between the block periods is configurable with an Item MidHeatingHours
. The number of periods to block is configurable with an Item HeatingPeaks
.
Pre-requisites
- The heating system can be controlled with an openHAB Item.
- The rest of this tutorial assumes you can control your heat pump with an item called
HeatPumpCompressor
. - See an example how to control a ground source heat pump via openHAB
- The rest of this tutorial assumes you can control your heat pump with an item called
- Fetching of spot prices is working
Create four new Items
Create an Item 'HeatingHours'
- In order to optimize the heating, our optimizing script needs to know how many hours the house needs to be heated. In the example above, there are 14 heating hours.
- We don't want to hard code this number to our script, so let's create an Item
HeatingHours
which can easily be updated with an user interface widget or automatically with a separate Rule. - See a separate documentation page which shows how this Item can be updated based on the forecasted temperature.
- The type of this Item must be Number.
- See an example of a Control parameters page which shows how this value can be easily changed manually
Create an Item 'MidHeatingHours'
- In the example above, there are three heating hours between the two 5 hour blocks
- The algorithm can guarantee a minimum amount of heating hours between the two blocked price peaks. This minimum amount of hours between the two price peaks is called
MidHeatingHours
. We don't want to hard code this number to our script so let's also create an ItemMidHeatingHours
which can easily be updated with an user interface widget or automatically with a separate Rule. - The type of this Item must be Number.
Create an Item 'HeatingPeaks'
- In the example above, two most expensive peaks are blocked.
- The number of expensive peaks the optimizing algorithm will search and block is configurable.
- Create a new Item
HeatingPeaks
which can easily be updated with an user interface widget or automatically with a separate Rule.
Create an item 'HeatPumpCompressorControl'
- The green bars in the example above are the control points for when the heating 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
Create a Rule 'HeatPumpCompressorOptimizer' to calculate the control points
- This rule will create the control points for when then heating should be ON and when it should be OFF
- Control point value 1 means the heating will be ON and value 0 means that heating will be OFF during that period.
- This Rule will be triggered whenever the Items
HeatingHours
,MidHeatingHours
orHeatingPeaks
change. - We will also modify the
FetchSpotPrices
rule so that this rule will be invoked right after the spot prices have been fetched, see below.
Inline script action for the rule
- The following rule first reads the
SpotPrice
values from midnight to midnight - It then reads how long the heating needs to be ON from the
HeatingHours
and what is the minimum heating duration between the OFF periods fromMidHeatingHours
. - It then calculates the control points with the concept described above.
- Finally, the control points will be saved to InfluxDB as
HeatPumpCompressorControl
.
// Load modules. Database connection parameters must be defined in config.js.
Influx = require('openhab-spot-price-optimizer/influx.js');
PeakPeriodOptimizer = require('openhab-spot-price-optimizer/peak-period-optimizer.js');
// Create objects.
influx = new Influx.Influx();
optimizer = new PeakPeriodOptimizer.PeakPeriodOptimizer('PT15M');
//If the script is called after 14.00, optimize tomorrow. Otherwise optimize today.
start = time.toZDT('00:00');
if (time.toZDT().isBetweenTimes('14:00', '23:59')) {
start = start.plusDays(1);
}
stop = start.plusDays(1);
// Read spot prices from InfluxDB and pass them for the optimizer.
prices = influx.getPoints('SpotPrice', start, stop);
optimizer.setPrices(prices);
// Read the control points of the previous day and pass them for the optimizer.
previousDayStart = start.minusDays(1);
previousDayStop = start;
previousControlPoints = influx.getPoints('HeatPumpCompressorControl', previousDayStart, previousDayStop);
optimizer.setPreviousControlPoints(previousControlPoints);
// Read desired amount of heating hours from the HeatingHours item.
heatingItem = items.getItem("HeatingHours");
heatingHours = heatingItem.state;
// Read the minimum amount of hours between the blocked periods from the MidHeatingHours item.
midItem = items.getItem("MidHeatingHours");
midHeatingHours = midItem.state;
// Read the number of peaks to be blocked.
peaksItem = items.getItem("HeatingPeaks");
peaks = Math.round(peaksItem.state);
// Pass the optimization parameters to the optimizer.
optimizer.setOptimizationParameters(heatingHours, midHeatingHours, peaks);
// Optimize the heating hours: Block the peaks and allow remaining. Save results to the database.
optimizer.blockPeaks();
optimizer.allowAllRemaining();
points = optimizer.getControlPoints();
influx.writePoints('HeatPumpCompressorControl', points);
Invoke this Rule also after the spot prices have been fetched
- The rule was defined to be run every time after the item
HeatingHours
,MidHeatingHours
orHeatingPeaks
changes. But what if these values remaing unchanged day after a day? - The solution is to modify the previously created
FetchSpotPrices
Rule so that we execute theHeatPumpCompressorOptimizer
Rule as an additional action immediately after the spot prices have been fetched. - Go to edit the previously created
FetchSpotPrices
Rule and add the action as illustrated in the picture below.
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.
Influx = require('openhab-spot-price-optimizer/influx.js');
// Create objects.
influx = new Influx.Influx();
// Read the control value for the current hour from the database.
control = influx.getCurrentControl('HeatPumpCompressorControl');
// HeatPumpCompressor: Send the commands if state change is needed.
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")
}
Additional considerations about the Peak Period Optimizer
The first thing the optimization algorithm will do is to check if the previous day ended with sufficient amount of heating hours. This is to handle situations where the previous day ends with a long period of blocked hours, the new day would be starting with a long period of blocked hours and as a result, the house would be cooling too much. The algorithm ensures that there is at least MidHeatingHours
hours allowed when the day changes, regardless of the spot prices at the beginning of the day.
After this, the algorithm checks if the optimization is mathematically possible with the given optimization parameters. If the HeatingHours
would be 8, it would mean 16 hours would be blocked. If MidHeatingHours
would be 4, the two block periods require 8 + 4 + 8 = 20 hours. There are still 4 ON hours required and this is just enough, but with slightly different optimization parameters the ON and OFF hours might not fit to the available hours. If that would be the case, the optimization algorithm will abort the optimization (and write an error to logs).
IMPORTANT: It is worth noting that the Peak Period Optimizer performs well when the number of heating hours is relatively big. If you only need to find, say 8 hours like in the picture below, you might get results which at first glance do not make any sense. Let's have a look at the situation in the screenshot below.
- Number of heating hours is 8. That means 16 hours will be blocked.
- Number of peaks to block is 2. That means that there will be 2 x 8 hour peaks to be blocked.
- MidHeatingHours is 1 hour in this example.
The algorithm first finds the most expensive 8 hour period which still makes it possible to allocate the second 8 hour period.
- This period starts at 9:00 and ends at 17:00 (A in the screenshot above)
- The algorithm then forces the surrounding hours ON (B in the screenshot above)
Now the algorithm needs to find the second most expensive 8 hour period. Intuitively it would make sense to block evening (C in the screenshot) but there period starting at 19:00 is too short for an 8 hour block. Thus, the algorithm blocks the period 00:00-08:00 (D) which is the most expensive available 8 hour period. Finally, all remaining hours will be allowed, which means that period C will be allowed.
If needed heating hours would be larger, for example 14 hours, the algorithm would result in much better result:
- The heating hours is 14, which means 10 hours can be blocked.
- This means 2 x 5 h OFF periods.
- The most expensive 5h period starts at 08:00 and ends at 13:00 (A)
- The hours surrounding this peak are allowed (B)
- The second most expensive 5h period starts at 16:00 and ends at 21:00 (C)
- All remaining hours allowed
Conclusion: The Peak Period Optimizer works well if the peak periods that will be blocked are relatively short. The longer the OFF peaks are, the less freedom this algorithm will have. When temperatures are still relatively high in the fall and when they rise again towards the spring and the number of heating hours is smaller, you might be better off by using the Generic Optimizer instead of the Peak Period Optimizer.
I personally combine the use of PeakPeriodOptimizer and GenericOptimizer so that if it is cold and at least 10 hours of heating is needed, I use PeakPeriodOptimizer. When it's warmer than this, GenericOptimizer is automatically used instead to simply allow cheapest periods (even if they would happen to be consecutive). The script action looks like this:
// Load modules. Database connection parameters must be defined in config.js.
Influx = require('openhab-spot-price-optimizer/influx.js');
PeakPeriodOptimizer = require('openhab-spot-price-optimizer/peak-period-optimizer.js');
// Create objects.
influx = new Influx.Influx();
optimizer = new PeakPeriodOptimizer.PeakPeriodOptimizer('PT15M');
//If the script is called after 14.00, optimize tomorrow. Otherwise optimize today.
start = time.toZDT('00:00');
if (time.toZDT().isBetweenTimes('14:00', '23:59')) {
start = start.plusDays(1);
}
stop = start.plusDays(1);
// Read spot prices from InfluxDB and pass them for the optimizer.
prices = influx.getPoints('SpotPrice', start, stop);
optimizer.setPrices(prices);
// Read the control points of the previous day and pass them for the optimizer.
previousDayStart = start.minusDays(1);
previousDayStop = start;
previousControlPoints = influx.getPoints('HeatPumpCompressorControl', previousDayStart, previousDayStop);
optimizer.setPreviousControlPoints(previousControlPoints);
// Read desired amount of heating hours from the HeatingHours item.
heatingItem = items.getItem("HeatingHours");
heatingHours = heatingItem.state;
// Read the minimum amount of hours between the blocked periods from the MidHeatingHours item.
midItem = items.getItem("MidHeatingHours");
midHeatingHours = midItem.state;
// Define how many peaks you want to block.
peaksItem = items.getItem("HeatingPeaks");
peaks = Math.round(peaksItem.state);
// If at least 10 heating hours are needed, use PeakPeriodOptimizer
if (heatingHours >= 10) {
optimizer.setOptimizationParameters(heatingHours, midHeatingHours, peaks);
optimizer.blockPeaks();
optimizer.allowAllRemaining();
points = optimizer.getControlPoints();
influx.writePoints('nibe_control', points);
}
// Otherwise simply allow N cheapest periods. The same 'HeatingPeaks' Item is used to define the number of cheap periods.
else {
for (i=0; i < peaks; i++) {
optimizer.allowPeriod(heatingHours/peaks);
}
optimizer.blockAllRemaining();
points = optimizer.getControlPoints();
influx.writePoints('HeatPumpCompressorControl', points);
}