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