Spot Price Optimizer: Advanced algorhitms to optimize heating, charging of electric vehicles, water boilers and more

Hi all

I managed to troubleshoot this myself, my issue was that I have several persitence services and influxdb is not the default, so I had to specify which persitance service I wanted to use influxdb. Below is my modified funstion for reading prices and save controlpoints.

// Read prices from the database, optimize and save the control points.
var delayedFunction = function() {
  console.log(priceItem);
  var prices = priceItem.persistence.getAllStatesBetween(start,end,"influxdb");
  optimizer.setPrices(prices);
  optimizer.allowPeriod(hours);
  optimizer.blockAllRemaining();
  var timeseries = optimizer.getControlPoints();
  controlItem.persistence.persist(timeseries,"influxdb");
  console.log(prices);
};

However it seems like I have the same issue with the heatingperiod-optimizer, script doesn’t return any prices and I cant figure out where to define persitence service for items or why I need to define that. I have tried to set influxdb as default service but that didn’t help and I have a persitence group AllforcastItems only enabled in influx db service as forecast strategy.

Is there any ideas of why I get this trouble and where to change it?

I’m on a training camp at the moment preparing for the World Championships which start at the end of this month so I have very limited possibility to debug this at the moment.

Based on what you are describing, I believe this starts to boil down here, on line 57 of the HeatinPeriodOptimizer.

Could you please check if this assumption is correct? If it is, I can modify the module (after the Worlds) so that we could pass an optional input parameter for explicitely defining which persistence service to use.

Markus

Hi

Yes it seems to me like it exits at row 61. Prices and temeratures are in influxdb database and I did get genric-optimizer to work.

Good luck in the training camp.

This is to inform all community members who are still using the 3.x version, that I just updated the end-of-life date from 31 July to 10 June 2025.

Nordpool is expected to switch to 15 minute price resolution on 11 June 2025. The internal logic of version 3.x is using 15 minute resolution and the entsoe.js which fetches the prices from Entso-E API should be able to fetch prices with that resolution if the structure of their API response will be what I expect it will be. But there’s no way to be sure before we see it. But in plain English, I won’t update the 3.x version to be compatible with the new API responses if it breaks.

I highly recommend everybody to plan their upgrade to 4.x well in advance before 11 June.

Markus

I started the migration to version 4 as we have a couple of days off during Easter. One change I noticed is that the scripts used to force car charging on and off are refering to influx.js. Are there updates for these scripts somewhere?

Hi,

Are you referring to this page on the wiki or are there some other pages that still contain old scripts?

I will be able to update this page tomorrow Sunday (local time in Ontario, Canada) which is the night between Sunday and Monday in Europe.

Yes, this is it.

I had a few minutes already today and updated the docs:

Cheers,
Markus

1 Like

The first line comment in the “OFF script” is wrong, you may want to correct this for clarity:

// Read the hour that should be forced ON.
var item = items.getItem("HeatPumpCompressorForceOff");
var datetime = time.toZDT(item.state);
console.log(datetime);

Thanks, fixed

I noticed the script for the control point cloner failsafe was also missing an example for 4.x. I also remembered that @ruxu pointed this documentation gap already earlier but I forgot to update the docs then even though the functionality was there.

Anyway, the control point cloner example is now available at

Markus

Now that I updated the failsafe control point cloner documentation, I realized that the example controller scripts for version 4.x have not had the same failsafe mechanism that we had with 3.x.

I’m talking about the controller Rules that run every 15 minutes and toggle the devices ON or OFF based on the control point values.

In version 3.x we queried the most recent control point value directly from the influx database. If the database was not available, our influx.js returned value 1 so that the device would be turned ON as a failsafe.

Version 4.x accesses the database via openHAB persistence layer, but the documented example did not have any handling for a situation where the database would be unavailable. The result of this would be that the state of the device would remain unchanged, meaning that heating might not go ON at the night.

The wiki docs have now been updated to have proper error handling. If you are controlling a critical device such as heating or a water boiler, I would highly recommend to update your controller Rules and have the failsafe configured so that the device will be turned ON if the database is unavailable.

Cheers,
Markus

1 Like

I have been commissioning Version 4 for a couple of weeks time permitting. Most features work again, but I have trouble with the car charging scripts I cannot solve. Seems like the “Block all remaining” only blocks within the optimizing window and leaves the rest of the control points in an undefined state, that Openhab for some reason interprets as state 1. This results in the charger being on during these undefined states, too. Updated to OH 4.3.5 in hopes it would solve the problem, but no.

Here is a plot of the control item CarChargingControl that illustrates the problem:
image

CarChargingControl item:
image

CarChargingOptimizer script:

// 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('CarChargingControl'); 

// Read how many hours are needed from the CarChargingHours item.
var item = items.getItem("CarChargingHours");
var hours = parseFloat(item.numericState);

// Define the optimization window here, this example optimizes tomorrow.
var start = time.toZDT('20:00');
var end = time.toZDT('02:00').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);  
// Block all remaining 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);

Car charging control script:

// Define your item names here.
var controlItem = items.getItem("CarChargingControl");
var powerItem  = items.getItem("CarChargingPower");

// 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("Leaf: Send OFF");
  powerItem.sendCommand("OFF");
}
else if (controlPoint && powerItem.state == "OFF" && controlPoint.numericState == 1) {
  console.log("Leaf: Send ON");
  powerItem.sendCommand("ON");
}
else if (controlPoint) {
  console.log("Leaf: No state change needed");
}

// Failsafe: if persistence service is unavailable, turn the device ON.
else {
  console.warn("leaf: Persistence service returned NULL. Failsafe ON!");
  powerItem.sendCommand("ON");
}

Control view
image

Any ideas for this behavior?

Hi

My script doesn’t behave that way, could it be your failsafe else statement in the end that confuses things. How will the script perform without that?

I had an issue with version 4 script, what seems to be related to having multiple persistence services so I had to define influxdb as persitence service for my items in script, otherwise script wouldn’t find any spotprices.

/Marcus

1 Like

Thank you for the feedback. I did some further twinkering during the weekend and the script below seems to work as it should. In short, I separated the spot price time interval from the optimisation interval. I commented out the lines that I have changed, so it should be fairly easy to see how I did it.

// 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('CarChargingControl'); 

// Read how many hours are needed from the CarChargingHours item.
var item = items.getItem("CarChargingHours");
var hours = parseFloat(item.numericState);

//NEW 4.5.2025 define total spotprice data length
var spotstart = time.toZDT('00:00');
var spotend = spotstart.plusDays(2);

// Define the optimization window here,
var start = time.toZDT('20:00');
var end = time.toZDT('04:00').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); // Commented out 4.5.2025
  var prices = priceItem.persistence.getAllStatesBetween(spotstart, spotend); // New 4.5.2025  
  optimizer.setPrices(prices);
  optimizer.allowPeriod(hours, start, end); // New 4.5.2025
//  optimizer.allowPeriod(hours); //Commented out 4.5.2025
  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);

Apparently the wind chill correction is not yet available for Version 4 @masipila ?

Is there anyone but me that have the issue with defining persistence service in heating optimization script?

/Marcus

Persistence you should not be defining in the script but in OH config.

Hi Markus,

can you explain what to change? I think I might be experiencing a related issue. I am trying to define the X most expensive hours in order to feed back energy to the grid from my battery. In order to do this I now have a time series that has the 24-X least expensive hours and then inverse the time series. However this fails on the " controlItem.persistence.persist(timeseries);" line, without a clear error, only a warning. Is there another way to add this “updated” timeseries tot the control item?

Thanks!

Full rule below:

// 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('MEB_Discharge_Control'); 

// Read how many hours are needed from the BoilerHours item.
var period =12.0;
var item = items.getItem("MEB_DischargingHours");
var hours = period - parseFloat(item.numericState);
//var hours = parseFloat(item.numericState);

// Define the optimization window here, this example optimizes tomorrow.
var start = time.toZDT('12:00').plusDays(-1);
var end = start.plusHours(period);
// Define the delay (seconds) to ensure prices have been saved first.
var delay = 10;
//sleep(10000); // 10000 milliseconds = 10 seconds

// 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.allowIndividualHours(hours);
  optimizer.blockAllRemaining();
  var timeseries = optimizer.getControlPoints();
  console.log(timeseries)
  timeseries.states.forEach((s, i, a) => a[i][1] = (s[1] == 0) ? 0 : 1);
  console.log(timeseries);
  
  controlItem.persistence.persist(timeseries);
  //controlItem.persist(timeseries);
};
// Create a timer that calls delayedFunction after the delay.
actions.ScriptExecution.createTimer(time.toZDT().plusSeconds(delay), delayedFunction);

Link the original question how to “invert” the time serieslink to timeseries