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.

image

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

Create four new Items

Create an Item 'HeatingHours'

image

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 Item MidHeatingHours which can easily be updated with an user interface widget or automatically with a separate Rule.
  • The type of this Item must be Number.

image

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.

image

Create an item 'HeatPumpCompressorControl'

image

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 or HeatingPeaks 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.

image

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 from MidHeatingHours.
  • 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 or HeatingPeaks 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 the HeatPumpCompressorOptimizer 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.

image

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.

image

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.

image

  • 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:

image

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