I'm using Tibber as electricity supplier (dynamic pricing) and wanted to use the actual price level for some other scripts. Like "if the price is very high, suspend the water boiler for a while" or similar when the price is (very) low.
The Enever plugin provides the daily and actual electricity prices and you can use that to calculate an abstracted level.
However, the Tibber API readily provides an abstracted price indication like 'very cheap', 'cheap', 'normal', 'expensive', and 'very expensive'. It is based on a moving 3 days average.
This is a script that obtains that 'price alert' every hour from Tibber and stores it in a Domoticz Alert Sensor. A script can use that for controlling other stuff.
There are a lot of comments in the script providing probably an overkill of information, but as I'm new to scripting/dzVents it helps me keep track.
Likewise, if you set logging to 'LOG_DEBUG' in the script, there will be a lot of info pushed to the Domoticz log. Any mistakes or 'un'-logic, etc.: comments welcome
Code: Select all
-- get the Current relative E-Price level from Tibber API
-- 240805.4
--[[
Description:
Script to retrieve the current 'price level' of the Tibber electricity price in terms of 'VERY_CHEAP',
'CHEAP', 'NORMAL', 'EXPENSIIVE', or 'VERY_EXPENSIVE'.
This information is obtained using a http call to the Tibber API with a GraphQL query in its body.
The script processes the reply to set a Domoticz 'Alert Sensor' accordingly
Tibber calculates these 'levels' using an average price based on a rolling price average of three days.
The levels are defined by Tibber as follows:
VERY_CHEAP Price is smaller or equal to 60 % compared to average price.
CHEAP Price is greater than 60 % and smaller or equal to 90 % compared to average price.
NORMAL Price is greater than 90 % and smaller than 115 % compared to average price.
EXPENSIVE Price is greater or equal to 115 % and smaller than 140 % compared to average price.
VERY_EXPENSIVE Price is greater or equal to 140 % compared to average price.
This script runs every hour (as the electricity price varies per hour).
To ensure you obtain the level from the Tibber site for the coming hour, I suggest to run the script one
minute after the hour - just to be sure. You can run it more often, but there will be no changes during the hour.
Installation and requirements:
1. You need a Tibber account and API key:
For experiments, consider using the Tibber 'demo' API access key mentioned also below.
For your own account, use your personal API access key. To obtain this key:
You need to have an account with Tibber (username and password)
Go to https://developer.tibber.com/settings/accesstoken and sign in
Follow the steps to obtain the Access Token and paste it in the local variable
'AccesTokenTibber' below
2. You need to create a Domoticz virtual ('dummy') sensor of the type 'Alert Sensor':
Go to Domoticz / Setup / Hardware > Click ‘Create virtual sensors’ in the Dummy device
Give it a name: “Alert Electricity price”, Sensor type: Alert, Click OK
Go to Setup / Devices and locate this new Dummy Device, note down its idx
You need to enter the idx with 'priceAlertIdx' below.
Go to the Utility tab and locate the new device and click Edit:
Description: (for example): “device to show the current prive level in a relative way, based on information obtained from the Tibber API”
Click Update
Note: The Tibber API has a rate limit of 100 requests in 5 minutes per IP address intended, to protect the API.
--]]
-- define local variables/constants
local logMarker = 'E-price alert v4' -- same as the title to make it clear in the Domoticz log
local priceAlertIdx = 931 -- the idx of the Alert Sensor you created in Domoticz
local endpointTibberAPI = 'https://api.tibber.com/v1-beta/gql'
local accessTokenTibber = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' -- this is my own Tibber API token
-- local accessTokenTibber = '5K4MVS-OjfWhK_4yrjOlFe1F6kJXPVf7eQYggo8ebAE' -- use the Tibber demo API token during testing
return {
on = {
timer = {
'at *:01' -- one minute after every hour (one extra min to ensure Tibber is all set)
-- 'every 2 minutes' -- just for testing
},
httpResponses = {
'lvlResponse' -- this must match with the below callback passed to the openURL command
}
},
logging = {
level = domoticz.LOG_INFO, -- use LOG_DEBUG if you want more logging, LOG_INFO as default is OK
marker = logMarker
},
execute = function(dz, item)
-- show some information for debugging
dz.log('Idx of alert device = ' .. priceAlertIdx, dz.LOG_DEBUG)
dz.log('Tibber url endpoint = ' .. endpointTibberAPI, dz.LOG_DEBUG)
-- main body of script
if (item.isTimer) then -- script triggered by timer, so need to send http request:
dz.log('script triggered by isTimer; sending http request ...', dz.LOG_DEBUG) -- show what triggered the script
dz.openURL({
url = endpointTibberAPI,
method = 'POST',
callback = 'lvlResponse', -- see httpResponses section above.
headers = { -- a table or string (‘[‘ and ‘]’ are used to denote table indices)
['Content-Type'] = 'application/json',
['Authorization'] = 'Bearer ' .. accessTokenTibber
},
postData = '{"query": "{viewer{homes{currentSubscription{priceInfo{current{startsAt level total}}}}}}"}' --"the" request to Tibber
})
end
if (item.isHTTPResponse) then -- script triggered by http call response, so process the response:
dz.log('Script triggered by isHTTPResponse; processing the http response ...', dz.LOG_DEBUG) -- show what triggered the script
dz.log('RAW response data "item.data" was ' .. item.data, dz.LOG_DEBUG) -- add the RAW reply (a string) to the log
dz.log('http response status = ' .. item.statusCode, dz.LOG_DEBUG) -- add http response status code to the log
if (item.ok) then -- statusCode == 2xx, call was successful and repy not nil
priceNow = item.json.data.viewer.homes[1].currentSubscription.priceInfo.current -- shortcut to make other parts of script more readable
if (item.isJSON) then -- it typically will be json, there is also no 'else' for that reason
startsAt = priceNow.startsAt
level = priceNow.level
total = priceNow.total -- fyi: 'total' is of type 'number' (not string)
dz.log('StartsAt : ' .. startsAt, dz.LOG_DEBUG)
dz.log('Level : ' .. level, dz.LOG_DEBUG)
dz.log('Total price : ' .. total, dz.LOG_DEBUG)
-- update the Domoticz dummy Alert sensor device
if level == "VERY_CHEAP" then
alertNum = 1
alertText ='1 Very Cheap (€ ' .. total .. ')'
elseif level == "CHEAP" then
alertNum = 1
alertText ='2 Cheap (€ ' .. total .. ')'
elseif level == "NORMAL" then
alertNum = 2
alertText ='3 Normal (€ ' .. total .. ')'
elseif level == "EXPENSIVE" then
alertNum = 3
alertText ='4 Expensive (€ ' .. total .. ')'
elseif level == "VERY_EXPENSIVE" then
alertNum = 4
alertText ='5 Very Expensive (€ ' .. total .. ')'
else -- reply from Tibber contained something else
alertNum = 0
alertText ='0 Err: Undefined alert level received: ' .. level
end
dz.devices(priceAlertIdx).updateAlertSensor(alertNum, alertText)
dz.log('Alert sensor ' .. priceAlertIdx .. ' set to color: ' .. alertNum .. ', text: "' .. alertText .. '"', dz.LOG_INFO)
end
else -- something wrong in the json converted reply
dz.log('There was a problem handling the request, "item.ok" was ' .. tostring(item.ok), dz.LOG_ERROR) -- true when the request was successful (statusCode in range of 200-299)
dz.log('Status response "item.statusCode / statusText" is: ' .. tostring(item.statusCode) .. ' / '.. tostring(item.statusText) .. ' (should be 2xx),', dz.LOG_ERROR)
dz.log('and/or response data-element "current" is ' .. tostring(priceNow) .. ' (should not be nil)', dz.LOG_ERROR)
-- notify user via message on Alert sensor UI. Disadvantage: cannot see timestamp anymore when the error happened first
alertNum = 0
alertText ='0 Error, response "item.statusCode / statusText" is: ' .. tostring(item.statusCode) .. ' / '.. tostring(item.statusText) .. ' (see log),'
dz.devices(priceAlertIdx).updateAlertSensor(alertNum, alertText)
end
end
end
}