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

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

Create two new Items

Create an Item 'BoilerHours'

image

Create an item 'BoilerControl'

image

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 the AllForecastItems 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.

image

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 have TotalPrice 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 select prices-received as the Channel that triggers the Rule as illustrated in the screenshot below.

image

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.

image

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);