Persisting Norwegian electricity price

My humble start was a simple rule that fetched the electricity price and updated an item every hour from a very simple to use open API:

With the introduction of “Time Series Support for Forecasts and Historical Values” in version 4.1 (already over a year ago!) possibilities suddenly increased. Most examples I found still utilized more elaborate persistence services like InfluxDB. Luckily I discovered the InMemory persistence (released already in version 4.0 as far as I can tell). This service simply installs from the “Add-on Store” and requires no external server/software.

Required installations from the “Add-on Store”:

  • JavaScript Scripting
  • InMemory Persistence

I have been struggling with the actual item value not being automatically updated as expected with the strategy = forecast. Thus suspecting my persistence\inmemory.persist might not be perfect:

Strategies {
}

Items {
  gPersistenceForecast* : strategy = forecast
}

items\Forecast.items configuration:

Group  gForecastInmemory  "Inmemory forecast stragegy"  <settings>
Number:EnergyPrice  Spotpris  "Spotpris [%.2f kr/kWh]"  <price>  (gForecastInmemory)

MainUI chart page configuration:

config:
  chartType: day
  label: Spotpris
slots:
  grid:
    - component: oh-chart-grid
      config:
        includeLabels: true
  xAxis:
    - component: oh-category-axis
      config:
        categoryType: day
        gridIndex: 0
        name: Time
  yAxis:
    - component: oh-value-axis
      config:
        gridIndex: 0
        name: NOK/kWh
  legend:
    - component: oh-chart-legend
      config:
        bottom: 3
        type: scroll
  tooltip:
    - component: oh-chart-tooltip
      config:
        confine: true
        smartFormatter: true
  series:
    - component: oh-aggregate-series
      config:
        aggregationFunction: last
        dimension1: hour
        gridIndex: 0
        item: Spotpris
        name: Spotpris
        offsetAmount: 0
        offsetUnit: hour
        service: inmemory
        type: line
        xAxisIndex: 0
        yAxisIndex: 0

Lastly automation\js\Hvakosterstrommen.no.js fetches electricity prices every hour (if needed). Valid area codes are:
NO1 = Oslo / Øst-Norge
NO2 = Kristiansand / Sør-Norge
NO3 = Trondheim / Midt-Norge
NO4 = Tromsø / Nord-Norge
NO5 = Bergen / Vest-Norge

const area = "NO2"
const item_name = "Spotpris";
const serviceId = "inmemory";

const zeroPad = (num, places) => String(num).padStart(places, '0')


function HvakosterstrommenHttpGet(req_date) {
  // https://www.hvakosterstrommen.no/strompris-api
  const request = "https://www.hvakosterstrommen.no/api/v1/prices/" + req_date.getFullYear().toString() + "/" + zeroPad(req_date.getMonth() + 1, 2) + "-" + zeroPad(req_date.getDate(), 2) + "_" + area + ".json";
  const response = actions.HTTP.sendHttpGetRequest( request );
  console.info( "httpResponse = " + response.substring(0, 128) );
  
  var status = "";
  if ( response.includes("<title>Error 404") ) {
    status = "Error 404: " + request
  } else {
    try {
      obj = JSON.parse(response);
    } catch (e) {
      status = e.message;
    }
  }
  if ( status ) {
    console.warn( status );
  } else {
    json_start = new Date( obj[0].time_start );
    json_end = new Date( obj[ Object.keys(obj).length - 1 ].time_end);
    if (  json_start.getTime() <= req_date.getTime() && json_end.getTime() >= req_date.getTime()  ) {
      return response;
    }
  }
  return "UNDEF";
}


rules.JSRule({
  name: "Hvakosterstrommen.no HttpGet and persist forecast",
  triggers: [ triggers.SystemStartlevelTrigger(100),
              triggers.GenericCronTrigger("3 59 * * * *")     // At 3 seconds past the minute, at 59 minutes past the hour
            ],
  execute: (event) => {
    const price_item = items.getItem(item_name);
    console.debug( "price_item = " + price_item );
    
    const now = new Date();
    begin = new Date( now );
    begin.setHours(0,0,0,0);
    if ( !price_item.persistence.nextUpdate(serviceId) ) begin.setDate( begin.getDate() - 7 );  //Requesting historic prices when database appears empty
    end = new Date( now );
    end.setHours(23,59,0,0);
    if ( now.getHours() > 13 ) end.setDate( end.getDate() + 1 );              //Expecting tomorrows prices after 13:00
    
    console.info( "Expecting prices between " + begin.toLocaleDateString() + " and " + end.toLocaleDateString() );
    
    while( begin < end ) {
      const end2 = new Date( begin );
      end2.setHours(23,59,0,0);
      states = price_item.persistence.getAllStatesBetween(begin, end2, serviceId);
      var states_length = 0;
      if ( states ) states_length = states.length;                            //.length only works if states != NULL
      if ( states_length < 24 ) {
        console.info( "Fetching prices for " + begin.toLocaleDateString() );
        const jsonStr = HvakosterstrommenHttpGet( begin );
        if ( jsonStr != "UNDEF" ) {
          const obj = JSON.parse( jsonStr );                                  //Parsing received JSON string
          var timeSeries = new items.TimeSeries('REPLACE');                   //Setting up a TimeSeries that will overwrite eventual existing data
          for ( var i = 0; i < obj.length; i++ ) {                            //Looping through received price array
            time_start = new Date( obj[i].time_start );
            timeSeries.add( time_start, obj[i].NOK_per_kWh.toString() );
          }
          console.debug( timeSeries );
          price_item.persistence.persist(timeSeries, serviceId);
          console.info( timeSeries.size + " elements persisted" );
        } else {
          console.info( "HttpGet did not return a valid response" );
        }
      } else {
        console.info( begin.toLocaleDateString() + " already contains " + states_length + " elements" );
      }
      begin.setDate( begin.getDate() + 1 );
    }
  }
});

1 Like