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

@timo12357 Your implementation looks correct. You are now using the optional start constraint and end constraint parameters as documented at GenericOptimizer API documentation · masipila/openhab-spot-price-optimizer Wiki · GitHub

1 Like

There are a couple of ways how you can do this:

Perhaps the easiest approach is to use [blockInPieces](https://github.com/masipila/openhab-spot-price-optimizer/wiki/GenericOptimizer-API-documentation#usage-example-with-start-and-end-constraints-3) (or blockPeriod or blockHours) method, which will search the most expensive slots for you. It will return a time series so that the expensive periods are 0 and all others are 1, so this is inverted compared to what you need.

It will be your choice how you want to invert them back (either you just build your logic so that 0 means that you want to sell back to the grid or you invert the values before persisting them with a method of your choice).

Additional hint 1: You most probably want to use the optional startConstraint and endConstratint arguments so that you are chasing for the most expensive times during the day time.

Additional hint 2: plusDays(-1) can be written more intuitively as minusDays(1).

You most probably will love @rlkoshak 's eye-poppingly excellent examples at Working with Date Times in JS Scripting (ECMAScript 11)

Cheers,
Markus

Hi

I have tested to have influxdb as default persitence service, optimization script fails if I do not add “influxdb” as persitence service for controlItem.persitence.persist(timeseries,“influxdb”) as the third line from below in snippet. If influxdb is specified as persitence servie there te script ends up with no spotprices to compute. And I figured out that one for generic optimizer but can’t figure out how to define wich persitence service to use in the heater optimzations script.

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

@mackemot can you share how do your peristence settings look like? There must be something different in your setup compared to the other users, it would help us to understand what this difference is.

Hi I will give it a try, I’ve tried booth Influxdb as default service and as my usual configuration JDBC as default. In generic optimizer I figured out that if I define influxdb in the persistence querys it returns data. I don’t know if it because the other persitence services collects all items states without beeing a part of a specific group. Like the rrd4j service has items * in that service for easy restore on startup.

influxdb persitence strategys defined in ui

configurations:
  - items:
      - AllForecastedItem*
    strategies:
      - forecast
    filters: []
  - items:
      - all*
      - "!AllForecastedItem*"
    strategies:
      - everyChange
      - everyUpdate
      - restoreOnStartup
    filters: []
  - items:
      - gridload
      - gridloadmax
      - gridloadmin
    strategies:
      - everyChange
      - everyminute
    filters: []
cronStrategies:
  - name: everyminute
    cronExpression: 0 * * * * ? *
defaultStrategies:
  - forecast
thresholdFilters: []
timeFilters: []
equalsFilters: []
includeFilters: []

influx db configuration

rrd4j strategys

configurations:
  - items:
      - "*"
    strategies:
      - restoreOnStartup
      - everyChange
      - everyMinute
    filters: []
cronStrategies:
  - name: everyMinute
    cronExpression: 0 * * * * ?
defaultStrategies:
  - restoreOnStartup
  - everyChange
  - everyMinute
thresholdFilters: []
timeFilters: []
equalsFilters: []
includeFilters: []

/Marcus

So JDBC is your default persistence service, which most probably explains why you need to explicitly define that you want to deal with influxdb.

What bothers me a bit is that you mentioned that your spot prices are in influxdb. How do they get persisted over there and not in JDBC, which is your default? You’re using the Entso-E Binding for spot price retrieval, right?

Cheers,
Markus

My guess is that because I have influxd persistence strategys configured for forecasted items, the data will be stored i influxdb de default service is just for fallback if no service is configured.

/Marcus

How to trigger a rule based upon a control item? The heat pump control has this control script that fires the action based upon the control item every 15 minutes. One could assume that is possible to use the Openhab item triggers “change” and/or “update” for this purpose. Apparently I have not managed to get the right numerical format. Should it be from 0 to 1, 0 to 1.0, 0.0 to 1.0 or something else?

Okay so the situation is as follows, if I understand correctly.

  1. You use JDBC as the default persistence service

  2. However, you have configured your Spot Price item persistence so that it uses influxdb persistence instead of your default JDBC.

This explains your observation with this optimizer that you need to explicitly define that you want to read from / write to influxdb. By default openHAB uses your primary persistence service, which is JDBC, and you don’t have the prices available there.

By quick thinking, I see three options here:

  1. Continue using JDBC as the default persistence service, but make sure that you write also your spot prices there. This ways spot price optimizer should find the prices from there and it would also write the optimization results there. (I haven’t tested openHAB Spot Price Optimizer with other persistence services than influxdb but the spot price optimizer version 4 does not know which persistence service is being used)

  2. Switch using influxdb as the default persistence service. I don’t know the impacts of this to your other use cases, though.

  3. It would also be conceptually possible that I would make a small change to the Heating Period Optimizer so that it would accept yet another configuration parameter (the persistence service) and if defined, it would pass that to the calls where it reads data from persistence. This would require also documentation efforts. My concern with this is that it might cause confusion among the readers and for new users there is already now a lot to digest.

Could you please elaborate your rationale and logic behind your different persistence services i.e. what do you do with JDBC, what do you with influxdb etc?

Hi

Thanks for your reply, influxdb is just used for the timeseries and related items states, mostly heating, ev charger and pool heating, all the energy optimizations stuff. JDBC is just for me to store everychange in a database for easy access when needed, sometimes one do not know what may be needed in the future.

I did try to change default persitence service once befor but that didn’t help, maybe that change requires a restart so I will test that again.

I try to set influxdb as default persitence service tonight or tomorrow and recap the result here.

/Marcus

Just tested to set influxdb as default persistence service and made a reboot of the whole system, it seems like it work, but didn’t do lasttime maybe the reboot is neccessary when chaning default persistence service.

I will try to run my system like this for a while, if this doesn’t turn out to be good then I’ll be glad to receive some instructions about where to change stuff in .js file to pass persistence service as a parameter.

/Marcus

Glad to hear that this works.

I don’t know the exact details for this particular scenario but openHAB does have different internal caching mechanisms.

For example, if we update the openHAB Spot Price Optimizer to a new version, all Rules need to be re-saved so that openHAB will use the new .js files instead of the old, cached versions of them.

Most probably this is related to something similar and your reboot cleared these caches.

Markus

I am using the triggers to start my dish washer and washing machine, so no need to turn them off, only start at the right time, this setting now works.

1 Like

Any plans to bring the wind chill calculation to version 4?

No. The responsibility split in version 4.x is such that

  • Bindings are responsible for fetching the data that is needed in the optimizations (spot prices, weather forecasts) and persisting as TimeSeries
  • openHAB Spot Price Optimizer is responsible for the optimization calculations

If you really need the windchill factor (my own experiences in our own house were inconclusive whether this makes sense or not), you can always write your own Rule which reads the weather forecast from your Items, does the windchill calculations and then persists this as a TimeSeries to your own Item (which is separate from what the weather binding uses).

Then, you would simply say in the openHAB Spot Price Optimizer parameters that you use this Item instead of the one that the weather binding users.

Cheers,
Markus

I suspect your stone house has better wind insulation properties than our wooden house. My experiences with the wind chill script were good. Together with the solar compensation it worked fine. So, I asked ChatGPT for help. ChaGPT claims, that it is not possible to update item values in the future via a script, only Bindings can do it. It ensists that the only way to solve this is to create 48 new items, one for each forecasted hour. This can not be true, can it?

Try to prompt something like this. I’m pretty confident that ChatGPT can help you to write the Rule with these instructions.

Happy coding,
Markus

—clips—

I’m using openHAB Spot Price Optimizer to optimize the heating of our house. You can find the repository at GitHub - masipila/openhab-spot-price-optimizer: Openhab Spot Price Optimizer modules help you to optimize energy consumption to the cheapest hours of the day.

As you can read from the project wiki, the Heating Period Optimizer uses weather forecast and electricity spot prices to schedule the heating for optimal times. You can read about this concept on this page:

The previous version of this solution (version 3.x) had fundamental difference to the current 4.x version. This difference is explained on this page: Installation instructions · masipila/openhab-spot-price-optimizer Wiki · GitHub

As explained in the documentation page I just linked above, the version 4.x. does NOT fetch the the weather forecast like version 3.x did. Version 4.x. relies that openHAB Bindings have persisted the weather forecasting data to the database.

I’m using the FMI Binding to fetch the weather forecast. The temperature forecast can be found from Item XXXX and the windspeed forecast can be found from Item YYYY.

I also have a third Item ZZZZ where I want to have a windchill compensated temperature.

Help me to write an openHAB Rule which does this:

  1. I can run this Rule every 6 hours

  2. The Rule has an inline Script Action written with Ecmascript. This Rule reads the forecasted temperature and wind speed from items XXXX and YYYY for a given time range.

  3. The Rule calculates the windchill compensated temperature for the same time range.

  4. The Rule persists the windchill compensated temperature data to the Item ZZZZ.

This way I can use the Item ZZZZ as an input for my heating optimizations instead of XXXX.

Given a temperature and wind speed, the windchill compensated temperature can be calculated with this formula:

      // https://en.wikipedia.org/wiki/Wind_chill#North_American_and_United_Kingdom_wind_chill_index
      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);
      }

Additional instructions:

The start and end times of the range in our Rule will be defined as follows:
var start = time.toZDT(‘00:00’).plusDays(1);
var end = start.plusDays(1);

To read the temperatures and wind speeds from persistence, use getAllStatesBetween method as documented here:

The Rule should create a timeSeries and add points to it. This timeSeries can can be persisted using ItemPersistence - Documentation

So like this:
items.MyItem.persistence.persist(timeSeries);

Here is an example rule that writes future spot prices in to the inmemory persistance database.

https://community.openhab.org/t/persisting-norwegian-electricity-price/161925

There are also examples here:

ChatGPT failed to produce a working code but DeepSeek succeeded. The following code seems to work in my OpenHAB.

(function(event) {
    var ZonedDateTime = Java.type('java.time.ZonedDateTime');
    var TimeSeries = Java.type('org.openhab.core.types.TimeSeries');
    var PersistenceExtensions = Java.type('org.openhab.core.persistence.extensions.PersistenceExtensions');
    var DecimalType = Java.type('org.openhab.core.library.types.DecimalType');

    // Define time range for tomorrow
    var start = time.toZDT('00:00').plusDays(1);
    var end = start.plusDays(1);
    
    // Get forecasts using InfluxDB persistence
    var tempForecast = PersistenceExtensions.getAllStatesBetween(items.FMIForecastTemperature, start, end, 'influxdb');
    var windForecast = PersistenceExtensions.getAllStatesBetween(items.FMIForecastWindSpeedMS, start, end, 'influxdb');

    // Create map for wind speeds using actual timestamps
    var windMap = new java.util.HashMap();
    for (var i = 0; i < windForecast.size(); i++) {
        var entry = windForecast.get(i);
        windMap.put(entry.getTimestamp().toString(), parseFloat(entry.getState().toString()));
    }

    // Create windchill series matching temperature forecast timestamps
    var windchillSeries = new TimeSeries(TimeSeries.Policy.REPLACE);

    for (var i = 0; i < tempForecast.size(); i++) {
        var tempEntry = tempForecast.get(i);
        var timestamp = tempEntry.getTimestamp();
        var temp = parseFloat(tempEntry.getState().toString());
        
        // Get wind speed for EXACT same timestamp
        var windSpeedMps = windMap.get(timestamp.toString());

        if (windSpeedMps === null || isNaN(windSpeedMps)) {
            console.warn('Skipping ' + timestamp + ' - missing wind data');
            continue;
        }

        // Calculate windchill
        var windKmh = windSpeedMps * 3.6;
        var windchill = temp;

        if (temp < 15 && windKmh > 4.8) {
            windchill = 13.12 + 
                      0.6215 * temp - 
                      11.37 * Math.pow(windKmh, 0.16) + 
                      0.3965 * temp * Math.pow(windKmh, 0.16);
            windchill = Math.round(windchill * 10) / 10;
        }

        windchillSeries.add(timestamp, new DecimalType(windchill));
    }

    // Persist only if we have data
    if (windchillSeries.size() > 0) {
        PersistenceExtensions.persist(items.FMIForecastWindChillTemp, windchillSeries, 'influxdb');
        console.info('Persisted ' + windchillSeries.size() + ' windchill values');
    } else {
        console.error('No windchill values calculated - check forecast data!');
    }
})(event);