short script to obtain Tibber abstracted prices

Moderator: leecollings

Post Reply
User avatar
Domoberry
Posts: 122
Joined: Tuesday 30 May 2017 19:00
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Contact:

short script to obtain Tibber abstracted prices

Post by Domoberry »

Perhaps this setup can be useful for someone.
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.
Example Alert Tibber 5 Very Expensive 4532.jpg
Example Alert Tibber 5 Very Expensive 4532.jpg (20.28 KiB) Viewed 2074 times
Example Alert Tibber 1 Very Cheap 1443.png
Example Alert Tibber 1 Very Cheap 1443.png (11.75 KiB) Viewed 2074 times
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
}
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests