Code: Select all
--Frank Energie hourly energy prices
--forum: https://domoticz.com/forum/viewtopic.php?t=38880
--original script by willemD, see https://www.domoticz.com/forum/viewtopic.php?p=296716#p296716
--version 4/4/2023: added check to see if tomorrow's electricity values are already in, starting on line 93
--version 16/4/2023: make URL call daily instead of hourly, put the results in array, added function to calculate lowest price periods
local kwhPrice='kwhPrijs' --device name for hourly kwh price | type: dummy device -> Managed Counter
local kWhAvg='kwhGemiddeld' --device name for average daily price per Kwh | type: dummy device -> custom sensor
local gasPrice='gasPrijs' --device name for hourly kwh price | type: dummy device -> Managed Counter
local ApiEUtoken='yourTokenHere' --API token of user account from entsoe
local UrlStart='https://web-api.tp.entsoe.eu/api?' --the kwh API website
local GASUrlStart='https://api.energyzero.nl/v1/energyprices?' --the gas API website
local DocType='A44' --day ahead prices document type
local PriceRegion='10YNL----------L' --region is set to The Netherlands (adapt to your need as per API documentation)
--Frank Energie settings, feb 2023:
local kwhTax = 0.21 --BTW/VAT; set to 0.21 (= 21%)
local kwhRaise= 2.05 --Frank Energie toeslag inkoopprijs+ (14.6 for all-in price, status 01-2023)
local gasTax=0.21 --BTW/VAT; set to 0.21 (= 21%)
local gasRaise=22 --ct
local header='dzDailyEnergyPrices'
return {
on = {
timer = {
--'at 15:37', -- Timer to get new electricity prices. Adapt timer to your needs. Normally new electricty prices are available after 15:00.
--'every minute',
'every hour'
},
httpResponses = {
'EUDAYpricesToday', -- must match with the callback passed to the openURL command in the code below
'EUDAYpricesTomorrow',
'EZGASprices',
},
devices = {
'triggerhulp', --dummy decive for debug purpose
},
},
logging = {
level = domoticz.LOG_INFO, --remove comment signs to display logs
marker = 'dzDailyEnergyPrices',
},
data = {
kwh = {initial={}}, --kWh prices per hour
},
execute = function(dz, item)
function getKwhPrices (day)
--day is case sensitive and should be: 'Today' or 'Tomorrow'
if day == 'Today' then
offset=0
elseif day== 'Tomorrow' then
offset=24*60*60
else
dz.log("wrong call, unknow day (case sensitive): " .. day, dz.LOG_INFO)
end
local PricePeriodStart=dz.time.dateToDate(dz.time.rawDate,'yyyy-mm-dd', 'yyyymmdd0000', offset )
local PricePeriodEnd=dz.time.dateToDate(dz.time.rawDate,'yyyy-mm-dd', 'yyyymmdd2300', offset )
-- compose the full URL
local EUurl=UrlStart .. 'securityToken=' .. ApiEUtoken .. '&documentType=' .. DocType .. '&in_Domain=' .. PriceRegion .. '&out_Domain=' .. PriceRegion .. '&periodStart=' .. PricePeriodStart .. '&periodEnd=' .. PricePeriodEnd
dz.log("URL : " .. EUurl, dz.LOG_INFO)
-- launch the URL
dz.openURL({
url = EUurl,
method = 'GET',
callback = 'EUDAYprices'..day, -- must match httpResponses above
})
end
function getGasPrices ()
-- section to launch the EnergyZero gas prices API get request (UTC timing)
-- always get current day data
local GasPricePeriodStart=os.date("%Y-%m-%d",os.time()) .. "T00:00:00.000Z" -- this first price is valid from 01:00 CET
local GASPricePeriodEnd=os.date("%Y-%m-%d", os.time()) .. "T23:59:59.999Z"
local usageType=2 -- GAS
-- compose the full URL:
local EZurl=GASUrlStart .. 'fromDate=' .. GasPricePeriodStart .. '&tillDate=' .. GASPricePeriodEnd .. '&interval=4&usageType=' .. usageType .. '&inclBtw=false'
dz.log("URL : " .. EZurl, dz.LOG_INFO)
-- launch the URL
dz.openURL({
url = EZurl,
method = 'GET',
callback = 'EZGASprices', -- must match httpResponses above
})
end
function printPrices ()
dz.log('Overview of kWh prices, per hour',dz.LOG_INFO)
for hour = 0,47 do
if kwh[hour] then dz.log('kWh ['..hour..'] = '..kwh[hour],dz.LOG_INFO) end
end
end
function shiftPrices ()
dz.log('shift prices from 24..47 to 0..23',dz.LOG_INFO)
for hour = 0,23 do
if kwh[hour+24] then
kwh[hour]=kwh[hour+24]
kwh[hour+24]=nil
end
end
end
function updateAverage ()
average=0
for hour = 0,23 do
if kwh[hour] then
if average~=999 then average=average+kwh[hour]/24 end
else
average=999
end
end
dz.devices(kWhAvg).updateCustomSensor(average)
dz.log('Average kWh price is '..average..' ct (999 = error)',dz.LOG_INFO)
end
function cheapHour(duration)
if duration < 1 or duration > 23 then
dz.log('Invalid duration value: '..duration, dz.LOG_ERROR)
return nil
end
local kwhOrder = {}
for hour = 0, 23 do
local sum = 0
local count = 0
-- Compute the sum and count for the specified duration
for i = hour, hour + duration - 1 do
if kwh[i] then
sum = sum + kwh[i]
count = count + 1
end
end
-- Compute the average and order for the specified duration
if count > 0 then
local avg = sum / count
kwhOrder[hour] = 1
for i = 0, 23 do
if kwh[i] then
local sum2 = 0
local count2 = 0
-- Compute the sum and count for the specified duration at i
for j = i, i + duration - 1 do
if kwh[j] then
sum2 = sum2 + kwh[j]
count2 = count2 + 1
end
end
if count2 > 0 and sum2 / count2 < avg then
kwhOrder[hour] = kwhOrder[hour] + 1
end
end
end
else
kwhOrder[hour] = 0
end
end
local lowest = 1
for hour = 1, 23 do
if kwhOrder[hour] < kwhOrder[lowest] then
lowest = hour
end
end
return lowest
end
function getKwhOrderPosition(hour)
if hour < 0 or hour > 23 then
dz.log('Invalid hour value: '..hour, dz.LOG_ERROR)
return nil
end
local kwhOrder = {}
for i = 0, 23 do
if kwh[i] then
local order = 1
for j = 0, 23 do
if kwh[j] and kwh[j] < kwh[i] then
order = order + 1
end
end
kwhOrder[i] = order
else
kwhOrder[i] = 0
end
end
return kwhOrder[hour]
end
--MAIN routine --
-----------------
kwh=dz.data.kwh
if item.isHTTPResponse then
-- response to openURL (HTTP GET) request was received
dz.log(item.trigger..' <-- trigger',dz.LOG_INFO)
if item.trigger=="EUDAYpricesTomorrow" or item.trigger=="EUDAYpricesToday" then
if item.ok then
if item.isXML then -- should be XML
--check to see if tomorrow's electricity values are already in
if item.xml.Acknowledgement_MarketDocument then dz.log("Acknowledgement_MarketDocument bestaat", dz.LOG_INFO) end
if item.xml.Publication_MarketDocument then dz.log("Publication_MarketDocument bestaat", dz.LOG_INFO) end
if
item.xml.Acknowledgement_MarketDocument
then
dz.log('Fout met XML - '..item.xml.Acknowledgement_MarketDocument.Reason.text,dz.LOG_INFO)
else
--dz.log("Reason.code : " .. item.xml.Publication_MarketDocument.Reason.code, dz.LOG_INFO)
for id = 1, 24 do
dz.log(item.trigger..' - debug [id='..id..']',dz.LOG_INFO)
if item.xml.Publication_MarketDocument.TimeSeries[1] then
rawPrice=tonumber(item.xml.Publication_MarketDocument.TimeSeries[1].Period.Point[id]['price.amount']) --after 13:00 hr tommorow xml returns two timeSeries (not one), so check whether to use index number [1]
else
rawPrice=tonumber(item.xml.Publication_MarketDocument.TimeSeries.Period.Point[id]['price.amount'])
end
price=(rawPrice+kwhRaise) * (1+kwhTax) / 10
--local currentDateTime=os.date("%Y-%m-%d 00:00:00",os.time()+0*60*60+id*60*60)
if id-1<10 then hour='0'..id-1 else hour = ''..id-1 end
if item.trigger=="EUDAYpricesTomorrow" then
currentDateTime=dz.time.dateToDate(dz.time.rawDate,'yyyy-mm-dd', 'yyyy-mm-dd', 24*60*60)..' '..hour..":00:00"
-- examples from https://www.domoticz.com/wiki/DzVents:_next_generation_Lua_scripting:
--domoticz.time.dateToDate('31/12/2021 23:31:05','dd/mm/yyyy hh:MM:ss', 'dddd dd mmmm hh:MM:ss', 1 ) -- > 'Friday 31 December 23:31:06'
--domoticz.time.dateToDate('31/12/2021 23:31:05','dd/mm/yyyy hh:MM:ss', 'ddd dd mmm hh:MM:ss', -3600 * 24 - 3 )) -- > 'Thu 30 Dec 23:31:02'
dz.log('adding EL price [id='..(id+24-1)..']: ' .. price..' ['..rawPrice..' raw]' ,dz.LOG_INFO)
kwh[id+24-1]=price
else
currentDateTime=dz.time.dateToDate(dz.time.rawDate,'yyyy-mm-dd', 'yyyy-mm-dd', 0)..' '..hour..":00:00"
dz.log('adding EL price [id='..(id-1)..']: ' .. price..' ['..rawPrice..' raw]' ,dz.LOG_INFO)
kwh[id-1]=price
end
local historystring=price .. ";" .. price
--debug usage local historystring=7 .. ";" .. 7
dz.log("Used command for history update: dz.devices(kwhPrice).updateHistory(\"" .. currentDateTime .. "\",\"" .. historystring .. "\")", dz.LOG_INFO)
dz.devices(kwhPrice).updateHistory(currentDateTime,historystring)
end
--printPrices ()
end
else
dz.log('No XML received', dz.LOG_INFO)
end
end
if item.trigger=="EUDAYpricesToday" then
updateAverage ()
getGasPrices ()
end
elseif item.trigger=="EZGASprices" then
if (item.ok) then
dz.log('gasprices ok', dz.LOG_INFO)
if (item.isJSON) then -- should be JSON
--dz.log('start dumptable', dz.LOG_INFO)
--dz.utils.dumpTable(item.json) -- dumpTable can be used for debugging
--dz.log('end dumptable', dz.LOG_INFO)
--GasDayPrices.reset() -- remove historic prices from previous run
dz.log('Average:'..item.json.average, dz.LOG_INFO)
--waarde=item.json.Prices[1].price
changevalue=item.json.average * 100
if changevalue~=0 then
changevalue=(changevalue + gasRaise) * (1+gasTax)
dz.log('Gas price:'..changevalue, dz.LOG_INFO)
dz.devices(gasPrice).updateCounter(changevalue)
else
dz.log('ERROR: * NO GAS VALUE *', dz.LOG_INFO)
dz.helpers.notify(dz, header,'No gas value, daily price is not updated', -1, 2*3, 60*8,202302121111)
end
else
dz.log('No JSON received', domoticz.LOG_INFO)
end
else
dz.log('There was a problem handling the request. Item not ok', dz.LOG_INFO)
dz.log(item, dz.LOG_INFO)
end
--continue with next set, kwhtoday -> gas -> kwhTomorrow
getKwhPrices ('Tomorrow')
else
dz.log('There was a problem handling the request. Item not ok', dz.LOG_INFO)
dz.log(item, dz.LOG_INFO)
end
else --item is timer or trigger
printPrices ()
--make sure to call these functions after the price array values are filled at 14:00 hr
dz.log('the cheapest hour is: '..cheapHour(1), dz.LOG_INFO)
dz.log('the cheapest five hours in a row start on:'.. cheapHour(5), dz.LOG_INFO)
dz.log('current hour is on place '..getKwhOrderPosition(dz.time.hour).." where place 1 is the cheapest from today's 24 hrs")
if
dz.time.hour == 14 or --make a new URL call every afternoon, after the tomorrow prices are published on Entsoe (at 13:00 hr?)
kwh[dz.time.hour]==nil --No price, after problem or reboot? --> Generate prices
then --get new prices, make URL call
getKwhPrices ('Today') --Tomorrow and gas are started automaticly, after today XML is handled. Order: today (kWh) -> gas -> tomorrow (kWh)
elseif dz.time.hour == 0 then
shiftPrices ()
updateAverage ()
end
end
dz.devices(kwhPrice).updateCounter(kwh[dz.time.hour]) --update device for hourly energy price
dz.data.kwh=kwh
end
}