4.x ‐ Usage example: Generic Optimizer ‐ heating domestic hot water during the cheapest hours of the day - masipila/openhab-spot-price-optimizer GitHub Wiki
Important note
- These instructions apply only to version 4.x of openHAB Spot Price Optimizer.
- See separate wiki page for version 3.x of openHAB Spot Price Optimizer.
Overview
This page gives an example how to use the GenericOptimizer
class of the openhab-spot-price-optimizer
module.
As the name suggests, the GenericOptimizer
algorithms are generic and can be used for many different use cases such as water boilers or charging electric vehicles. This page shows how to find given number of cheapest hours of the day to heat the domestic hot water with a water boiler that can be controlled with openHAB. Further examples can be found on separate documentation page.
WARNING: Do not try to optimize the heating of domestic hot water too agressively.
- The boiler should always have enough hours to reach the thermostate max temperature.
- Legionella bacteria reproduces in temperatures between 20 - 45 ° celcius.
- In Finland, there is a law that the water temperature in the boiler must always be at least 55 ° celcius to ensure that legionella bacteria will die.
Pre-requisites
- The boiler can be controlled with an openHAB Item.
- The rest of this tutorial assumes you can control your boiler with an item called
BoilerPower
. - See example how to control boiler via openHAB
- The rest of this tutorial assumes you can control your boiler with an item called
- Spot prices are available in your database
Create two new Items
Create an Item 'BoilerHours'
- In order to optimize the heating of domestic hot water, our optimizing script needs to know how many hours the boiler needs to be ON to reach its thermostate max temperature.
- We don't want to hard code this number to our script, so let's create an Item
BoilerHours
which we can easily update with an user interface widget. - 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
Create an item 'BoilerControl'
- The rule below optimizes when the boiler should heat water and writes
BoilerControl
control points timeseries to our database. - The type of this Item must be Number
- See an example of control point visualization chart
Configure the persistence strategy of your control item
- The
GenericOptimizer
will prepare a timeseries of control points for the Item you created in the previous step. - The persistence strategy of this Item needs to be configured to be
forecast
, and only this, because you will have timestamps in the future. - In other words, the control item MUST NOT be persisted for example using
everyChange
,everyUpdate
,restoreOnStartup
or any other persistence policy. If you have a persistence policy for one of these strategies, you MUST exclude the control item from that strategy. - The recommended approach is to use an Item Group called
AllForecastItems
and configure the persistence policies for the whole group. If you follow this recommendation, all you need to do is to add the control Item to theAllForecastItems
group. - Instructions for creating the group and configuring the the persistence policy for the whole group is documented on the EntsoE instruction page.
Create a Rule 'BoilerOptimizer' to calculate the control points
- This rule will create control points for each 15 minute period of the day
- Control point value 1 means the power supply will be ON during and value 0 means that power supply will be OFF.
- This Rule will be triggered whenever the Item
BoilerHours
changes. - We will also configure this Rule to be run after the spot prices have been fetched, see below.
Version 4.x: Inline script action for the rule
Important This script works only on version 4.x of openHAB Spot Price Optimizer. See separate instructions for version 3.x of openHAB Spot Price Optimizer.
- The example below optimizes against
SpotPrice
Item. If you haveTotalPrice
and want to optimize against that, update it here. - The example reads how many hours the boiler needs to be ON from the
BoilerHours
item - The optimization window is defined to be tomorrow (from midnight to midnight)
- This example has a 60 seconds delay before the actual optimization is done. The purpose of this delay is to ensure that the prices have been saved to your database before the optimization is executed. This is needed because the rule gets invoked immediately after the
prices-received
channel fires and the process for persisting the prices to your database might not be completed yet at that moment. - This example optimization finds the cheapest consecutive period for heating the water and blocks the rest of the day. If you want to force the heating ON between a given timeframe, see further examples on the GenericOptimization documentation page.
// Load module and create service.
var { GenericOptimizer } = require('openhab-spot-price-optimizer/generic-optimizer.js');
var optimizer = new GenericOptimizer();
// Define price item and control item here.
var priceItem = items.getItem('SpotPrice');
var controlItem = items.getItem('BoilerControl');
// Read how many hours are needed from the BoilerHours item.
var item = items.getItem("BoilerHours");
var hours = parseFloat(item.numericState);
// Define the optimization window here, this example optimizes tomorrow.
var start = time.toZDT('00:00').plusDays(1);
var end = start.plusDays(1);
// Define the delay (seconds) to ensure prices have been saved first.
var delay = 60;
// Read prices from the database, optimize and save the control points.
var delayedFunction = function() {
var prices = priceItem.persistence.getAllStatesBetween(start, end);
optimizer.setPrices(prices);
optimizer.allowPeriod(hours);
optimizer.blockAllRemaining();
var timeseries = optimizer.getControlPoints();
controlItem.persistence.persist(timeseries);
};
// Create a timer that calls delayedFucntion after the delay.
actions.ScriptExecution.createTimer(time.toZDT().plusSeconds(delay), delayedFunction);
List of all optimization algorithms provided by GenericOptimizer
The GenericOptimizer
optimizing class provides several optimization methods for different use cases. If you want to for example charge an electric vehicle so that you want to find the cheapest period between 21.00 and 06.00, that can easily be achieved. See more usage examples on a separate wiki page.
Invoke this Rule also after the spot prices have been fetched
- The rule was defined to be run every time after the item
BoilerHours
changes. But what if this value is kept unchanged day after a day, what triggers this Rule to be executed? - The solution differs between versions 3.x and 4.x. of openHAB Spot Price Optimizer.
In version 4.x, the solution depends which Binding you use to fetch spot prices. The Entso-E Binding has a Channel which is triggered immediately after the Binding has fetched new prices. (If you're using some other Binding, you can also use time based trigger to run the rule for example at 15:00.)
- Edit the
BoilerOptimizer
Rule which you just created above - Add a second trigger and select
Thing event
- Select the
Day-ahead prices
Thing and selectprices-received
as the Channel that triggers the Rule as illustrated in the screenshot below.
Create a Rule 'BoilerController' to toggle the boiler ON and OFF
- This rule will run every 15 minutes and turn the boiler ON or OFF based on the current control point
- If the boiler is currently OFF and the current control point is 1, the boiler will be turned ON and vice versa.
- This example script has a failsafe mechanism: If the persistence service (database) is unavailable, the device will be turned ON.
Version 4.x: Inline script action for the rule
// Define your item names here.
var controlItem = items.getItem("BoilerControl");
var powerItem = items.getItem("BoilerPower");
// Get the latest control point from the persistence service.
var controlPoint = controlItem.persistence.previousState();
// Send the commands if state change is needed.
if (controlPoint && powerItem.state == "ON" && controlPoint.numericState == 0) {
console.log("Send OFF");
powerItem.sendCommand("OFF");
}
else if (controlPoint && powerItem.state == "OFF" && controlPoint.numericState == 1) {
console.log("Send ON");
powerItem.sendCommand("ON");
}
else if (controlPoint) {
console.log("No state change needed");
}
// Failsafe: if persistence service is unavailable, turn the device ON.
else {
console.warn("Persistence service returned NULL. Failsafe ON!");
powerItem.sendCommand("ON");
}
4.x: Script for deleting control points
When developing your solution and experimenting, you might want to delete control points from your database. The script below can be used for that.
// Define control item here
var controlItem = items.getItem('BoilerControl');
// Define time range to be today.
var start = time.toZDT('00:00');
var stop = start.plusDays(1);
controlItem.persistence.removeAllStatesBetween(start, stop);