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

You should never need to use the Java types with your Javascript rules. You should use the openHAB Javascript Library instead.

Here’s a bit more elegant way to achieve this:

// Define the items here
var temperatureItem = items.getItem('FMI_Weather_Forecast_Temperature');
var windSpeedItem   = items.getItem('FMI_Weather_Forecast_Wind_Speed');
var windChillItem   = items.getItem('windChillTemperature');

// Define the time range
var start = time.toZDT('00:00').plusDays(1);
var end = start.plusDays(1);

// Function for calculating windchill compensated temperature for given temperature and windspeed
var getWindChillTemperature = function(temp, wind_kmh) {
  var windchill = temp;
  if (temp < 10 && wind_kmh > 4.8) {
    windchill = 13.12 + 0.6215 * temp - 11.37 * (wind_kmh ** 0.16) + 0.3965 * temp * (wind_kmh ** 0.16);
  }
  console.debug("Temperature: " + temp + ", windspeed: " + wind_kmh + ", windchill compensated temperature: " + windchill);
  return windchill.toFixed(2);
};

// Initialize an empty timeseries for the windchill compensated temperatures.
var windChillSeries = new items.TimeSeries('REPLACE');

// Get all temperature forecast states
var tempForecast = temperatureItem.persistence.getAllStatesBetween(start, end);

// Loop through all temperature forecast states.
for (var i = 0; i < tempForecast.length; i++) {
  var timestamp = tempForecast[i].timestamp;
  var temp      = tempForecast[i].numericState;
  
  // Get the windspeed forecast for the same timestamp
  var wind = windSpeedItem.persistence.persistedState(timestamp).numericState;
  
  // Calculate windchill compensated temperature
  var windChill = getWindChillTemperature(temp, wind);
  
  // Add the new value to our timeseries
  windChillSeries.add(timestamp, windChill);
}
// Print debug message for our timeseries
console.debug(windChillSeries);

// Persist the timeseries to our Item.
windChillItem.persistence.persist(windChillSeries);

1 Like

It seem slike my scripts keeps mesing with me, the heating period optimizer receives no prices from database. If I go back to the generic optimizer and specify influxdb as persitence service then it works. But I can’t figure out how to pass the ,“influxdb” to each item in the heating period optimizer.

15:06:54.578 [INFO ] [openhab.event.ChannelTriggeredEvent  ] - entsoe:day-ahead:c52272b366:prices-received triggered
15:06:54.578 [INFO ] [penhab.core.model.script.ENTSO-E Rule] - ENTSO-E channel triggered, new spot prices available
15:06:54.580 [INFO ] [enhab.automation.script.ui.8707c8de85] - energySpotPrice (Type=NumberItem, State=0.031467162311565856 SEK/kWh, Label=Current Spot Price, Category=price, Groups=[AllForecastedItem])
15:06:54.580 [INFO ] [enhab.automation.script.ui.8707c8de85] - PoolHeatingControlPoint (Type=NumberItem, State=0.0, Label=Controlpints pool, Category=null, Groups=[AllForecastedItem])
15:06:54.587 [INFO ] [enhab.automation.script.ui.8707c8de85] - 2025-05-30T00:00+02:00[Europe/Stockholm]
15:06:54.587 [INFO ] [enhab.automation.script.ui.8707c8de85] - 2025-05-31T00:00+02:00[Europe/Stockholm]
15:06:54.601 [INFO ] [nhab.event.ItemTimeSeriesUpdatedEvent] - Item 'energySpotPrice' updated timeseries [Entry[timestamp=2025-05-28T22:00:00Z, state=0.37444834321617637 SEK/kWh], Entry[timestamp=2025-05-28T23:00:00Z, state=0.3755371723619053 SEK/kWh], Entry[timestamp=2025-05-29T00:00:00Z, state=0.37216180201014565 SEK/kWh], Entry[timestamp=2025-05-29T01:00:00Z, state=0.36443111507547027 SEK/kWh], Entry[timestamp=2025-05-29T02:00:00Z, state=0.3670443050252197 SEK/kWh], Entry[timestamp=2025-05-29T03:00:00Z, state=0.36900419748753177 SEK/kWh], Entry[timestamp=2025-05-29T04:00:00Z, state=0.3629067542714498 SEK/kWh], Entry[timestamp=2025-05-29T05:00:00Z, state=0.3193535884422929 SEK/kWh], Entry[timestamp=2025-05-29T06:00:00Z, state=0.34690096582923463 SEK/kWh], Entry[timestamp=2025-05-29T07:00:00Z, state=0.2981214201005789 SEK/kWh], Entry[timestamp=2025-05-29T08:00:00Z, state=0.200017914070403 SEK/kWh], Entry[timestamp=2025-05-29T09:00:00Z, state=0.02504307035176521 SEK/kWh], Entry[timestamp=2025-05-29T10:00:00Z, state=0.019381158793974815 SEK/kWh], Entry[timestamp=2025-05-29T11:00:00Z, state=0.025696367839202566 SEK/kWh], Entry[timestamp=2025-05-29T12:00:00Z, state=0.0224298804020158 SEK/kWh], Entry[timestamp=2025-05-29T13:00:00Z, state=0.031467162311565856 SEK/kWh], Entry[timestamp=2025-05-29T14:00:00Z, state=0.05792571055277866 SEK/kWh], Entry[timestamp=2025-05-29T15:00:00Z, state=0.22473433567844955 SEK/kWh], Entry[timestamp=2025-05-29T16:00:00Z, state=0.32272895879405256 SEK/kWh], Entry[timestamp=2025-05-29T17:00:00Z, state=0.35190957989958765 SEK/kWh], Entry[timestamp=2025-05-29T18:00:00Z, state=0.3682420170855215 SEK/kWh], Entry[timestamp=2025-05-29T19:00:00Z, state=0.3581159060302425 SEK/kWh], Entry[timestamp=2025-05-29T20:00:00Z, state=0.34363447839204786 SEK/kWh], Entry[timestamp=2025-05-29T21:00:00Z, state=0.2984480688442976 SEK/kWh], Entry[timestamp=2025-05-29T22:00:00Z, state=0.2565281467337341 SEK/kWh], Entry[timestamp=2025-05-29T23:00:00Z, state=0.1553759190955172 SEK/kWh], Entry[timestamp=2025-05-30T00:00:00Z, state=0.1598401185930058 SEK/kWh], Entry[timestamp=2025-05-30T01:00:00Z, state=0.1529804949749136 SEK/kWh], Entry[timestamp=2025-05-30T02:00:00Z, state=0.1079029683417362 SEK/kWh], Entry[timestamp=2025-05-30T03:00:00Z, state=0.1426366180904888 SEK/kWh], Entry[timestamp=2025-05-30T04:00:00Z, state=0.3190269396985742 SEK/kWh], Entry[timestamp=2025-05-30T05:00:00Z, state=0.3633422859297414 SEK/kWh], Entry[timestamp=2025-05-30T06:00:00Z, state=0.37183515326642697 SEK/kWh], Entry[timestamp=2025-05-30T07:00:00Z, state=0.24270001658297677 SEK/kWh], Entry[timestamp=2025-05-30T08:00:00Z, state=0.04649300452262498 SEK/kWh], Entry[timestamp=2025-05-30T09:00:00Z, state=0.014808076381913343 SEK/kWh], Entry[timestamp=2025-05-30T10:00:00Z, state=0.006641857788946426 SEK/kWh], Entry[timestamp=2025-05-30T11:00:00Z, state=0.0025043070351765213 SEK/kWh], Entry[timestamp=2025-05-30T12:00:00Z, state=-0.011977120603018145 SEK/kWh], Entry[timestamp=2025-05-30T13:00:00Z, state=-0.0021776582914578447 SEK/kWh], Entry[timestamp=2025-05-30T14:00:00Z, state=0.019381158793974815 SEK/kWh], Entry[timestamp=2025-05-30T15:00:00Z, state=0.1358858773869695 SEK/kWh], Entry[timestamp=2025-05-30T16:00:00Z, state=0.21373716130658743 SEK/kWh], Entry[timestamp=2025-05-30T17:00:00Z, state=0.26687202361815887 SEK/kWh], Entry[timestamp=2025-05-30T18:00:00Z, state=0.4257321959800086 SEK/kWh], Entry[timestamp=2025-05-30T19:00:00Z, state=0.3290441678392803 SEK/kWh], Entry[timestamp=2025-05-30T20:00:00Z, state=0.27623595427142755 SEK/kWh], Entry[timestamp=2025-05-30T21:00:00Z, state=0.09701467688444697 SEK/kWh]]
15:06:54.687 [INFO ] [enhab.automation.script.ui.28d2984034] - heating-period-optimizer.js: Starting heating period optimizer...
15:06:54.688 [INFO ] [enhab.automation.script.ui.28d2984034] - -----------------------------------------------------------------
15:06:54.702 [ERROR] [enhab.automation.script.ui.28d2984034] - heating-period-optimizer.js: Prices not available, aborting optimization!
15:06:54.713 [ERROR] [enhab.automation.script.ui.28d2984034] - generic-optimizer.js: Not enough prices for optimizations!
15:06:54.714 [ERROR] [enhab.automation.script.ui.28d2984034] - heating-period-optimizer.js: Optimization aborted, see previous errors.
15:06:54.715 [ERROR] [enhab.automation.script.ui.28d2984034] - generic-optimizer.js: Aborting optimization, see previous errors!

I’ve added “influxdb” in the heating optimizer, and may have goten it to work better for me, stupid me to not remember to re save the rules when editing .js scripts.

// Read prices, validate them and pass them to GenericOptimizer.
      const pricePoints = this.priceItem.persistence.countBetween(this.start, this.end,"influxdb");

If I set heating periods to 1 i get my Pool won’t need to watch out for temperaturedrops and add more heating periods. If I run with default 4 heating periods script does not throws any error. But wth heating periods set to 1 heating optimzation script goes for four days forecast. 2025-05-29 to 2025-06-01, and thers no data present yet for 2025-06-01.

generic-optimizer.js: price window 2025-05-29T22:00Z - 2025-05-30T22:00Z (PT24H)
15:35:31.493 [INFO ] [enhab.automation.script.ui.28d2984034] - heating-period-optimizer.js: Calculating heating need for the heating periods.
15:35:31.531 [INFO ] [enhab.automation.script.ui.28d2984034] - heating-period.js: 2025-05-29T00:00+02:00[Europe/Stockholm]: temperature 13.30, heating hours 2.00, flexibility: 1
15:35:31.570 [INFO ] [enhab.automation.script.ui.28d2984034] - heating-period.js: 2025-05-30T00:00+02:00[Europe/Stockholm]: temperature 12.21, heating hours 2.07, flexibility: 1
15:35:31.610 [INFO ] [enhab.automation.script.ui.28d2984034] - heating-period.js: 2025-05-31T00:00+02:00[Europe/Stockholm]: temperature 13.95, heating hours 2.00, flexibility: 1
15:35:31.622 [ERROR] [enhab.automation.script.ui.28d2984034] - heating-calculator.js: Not enough forecast data available!

/Marcus

Hi again

Is there a typoo in the file heating-period.js line 141 console logger says heating-calculator.js

/**
   * Validates that forecast data exists for this period.
   */
  validateForecast() {
    const points = this.forecastItem.persistence.countBetween(this.start, this.end);
    if (points < this.duration.toHours()) {
      console.error('heating-calculator.js: Not enough forecast data available!');
      return false;
    }
    return true;
  }

maybe it should be like this

validateForecast() {
    const points = this.forecastItem.persistence.countBetween(this.start, this.end,"influxdb");
    if (points < this.duration.toHours()) {
      console.error('heating-period.js: Not enough forecast data available!');
      return false;
    }
    return true;
  }

/Marcus

Yes, thanks for reporting this. I’ll try to remember to fix this at some point.

I’m a bit confused about your other comment above the typo report. Do I interpret it correctly that you managed to get it working eventually when you edited the heating-period-optimizer.js and re-saved the Rule?

I thought you previously mentioned that you got this working by making InfluxDB as your primary persistence service? Did you ran into some issues with this?

Markus

Hi

Yes I keep running into issues, it works sometimes when I manipulate script or rules but then it fails because it can’t read enough prices from database. But I will keep going on to make it stable even with my setup where it really seems like there is something that’s keeps messing things up.

Today I discoverd that I unfortunatley had set delay in rule script to zero, so I corrected that and we will see what happends this afternoon. I could atelast manually manipulate rule script to get todays values and that worked out well. I have no idea when I did change delay parameter so just need to start over again, before upgrade to version 4 I had other conditions for controlpoint calculators.

/Marcus

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.