I combined a similar degreeday calculation with another script to do gas classification, i.e. split my gas usage in gas used for heating, for hot water and for cooking. Initially the script was developed for my DSMR 4.0 meter, that reports gas usage per hour. Now I have adapted it for my DSMR 5.0 meter. It still relies on my OTGW to report when heating and/or hot water is switched on, measures gas during those seconds, and adds the quantity to the correct type of gas usage. After midnight the "heating gas per degreeday" is calculated in a 3rd script.
The gas_classification script is below. Sorry, not cleaned up after the switch from DSMR4 to DSMR5.
Code: Select all
--[[
This scrips classifies gas usage every hour into 3 different categories
This script first tracks the seconds during each hour that the hot water or central heating has been active.
If either hot water or central heating was active across the hour boundary then the seconds are split into before- and after-the-hour values,
because the P1 meter provides gas usage exactly before- and after-the-hour, even though the actual value of the previous hour is only
received a few minutes after the hour (between 02 and 04 minutes after the hour).
When the gas usage is received, the gas usage is split into the right categories using the known usage seconds of the past hour.
Any events between the hour and the gas usage update are tracked for the new hour.
Previous version of this script also included degreeday calculations, but these have now been removed because it did not work well. This is now in a separate script.
Known limitations:
- cooking gas is not measured separately so will also be included in heating or hot water gas, unless both were not active
- if both hot water and central heating were active, a theoretical split is made based on past known gas flow rate for hot water
(and this avg flow rate is updated if hot water usage has been longer than 3 minutes)
- gas value is known approx. 4 minutes after the hour from the P1 meter, it is the value for the previous hour. This requires some special handling.
- opentherm gateway updates the switch states only every 30 seconds.
- the script is especially made for a version 4 Smart Meter that provides a new gas value only once per hour
Note the script can be triggered in very quick succession if two switches are update almost simultaneously by the Opentherm Gateway
No problem has been observed as result of this.
The script has not been extensively tested during the heating season yet. Only 3 days so far ....
Note: don't use virtual GAS devices to track the types of gas usage. Use virtual COUNTER devices instead and update those manually (with edit device) to type GAS with divider 1000 once created.
This is because resets of GAS counters (not only counterToday but also counter) have been observed after midnight, (update 04/10/2022 : this problem seems to be solved in latest beta version)
]]--
local idxGasP1=2 -- idx of P1 meter Gas usage
local idxCentralHeatingActive=50 -- idx of CH-enabled from Opentherm Gateway (because CH-active is actually CH-pump-active and therefore not the one to use)
local idxHotWaterActive=57 -- idx of HW-active from Opentherm Gateway
local idxFlame=58 -- idx of Flame On from Opentherm Gateway (used for small corrections when hot water usage is only a few seconds)
local idxCentralHeatingGas=87 -- 3 virtual devices for tracking type of GAS usage
local idxCookingGas=88
local idxHotWaterGas=89
local idxAvgGasFlowRateHW=81 -- average hot water flow rate
return {
on = {
devices = {
idxHotWaterActive, -- hw switch
idxCentralHeatingActive, -- ch switch
idxGasP1 -- GAS P1 update
},
timer = {
'every 5 minutes' -- to split the seconds into past and future hour for any devices active at xx:00
},
},
data = {
-- persistent variables, values kept from one script run to the other, also after system reset
-- two variables for tracking how many seconds either hot water or central heating was switched ON during the hour
hotwaterseconds = { initial = 0 },
centralheatingseconds = { initial = 0 },
-- a variable to check not only an update but an actual increase of the gas meter
previousgascounter = { initial = 0 },
-- variables to help splitting total seconds into before and after the hour boundary
previoushourhotwaterseconds = { initial = 0 },
newhourhotwaterseconds = { initial = 0 },
previoushourcentralheatingseconds = { initial = 0 },
newhourcentralheatingseconds = { initial = 0 },
previoushourprocessed = { initial = 'True' },
firstgasupdateoftheday = { initial = 'False' }
},
logging = {
level = domoticz.LOG_ERROR,
marker = 'gas usage classification',
},
execute = function(domoticz, item)
if item.isDevice then -- triggered by one of the devices, not the timer
if item.idx==idxHotWaterActive then -- hot water switched on/off
if item.state == 'On' then
domoticz.log('Hot Water was switched On', domoticz.LOG_INFO)
-- on rare occasions Flame On is detected shortly before Hot Water is On
--if (domoticz.devices(idxFlame).state=='On' and domoticz.devices(idxCentralHeatingActive).state=="Off") then
-- domoticz.log('Should add extra ' .. domoticz.devices(idxFlame).lastUpdate.secondsAgo .. 'hot water seconds because of earlier flame activation', domoticz.LOG_INFO)
-- if domoticz.data.previoushourprocessed == 'True' then
-- domoticz.log('Adding seconds to current hr', domoticz.LOG_INFO)
-- domoticz.data.hotwaterseconds = domoticz.data.hotwaterseconds + domoticz.devices(idxFlame).lastUpdate.secondsAgo
-- else
-- domoticz.log('Saving seconds for next hr', domoticz.LOG_INFO)
-- domoticz.data.newhourhotwaterseconds = domoticz.data.newhourhotwaterseconds + domoticz.devices(idxFlame).lastUpdate.secondsAgo
-- end
--end
else
domoticz.log('Hot Water was switched Off', domoticz.LOG_INFO)
domoticz.log('previoushourprocessed' .. domoticz.data.previoushourprocessed )
if domoticz.data.previoushourprocessed == 'True' then
domoticz.log('Adding seconds to current hr', domoticz.LOG_INFO)
domoticz.data.hotwaterseconds = domoticz.data.hotwaterseconds + item.lastUpdate.secondsAgo - domoticz.data.previoushourhotwaterseconds
else
domoticz.log('Saving seconds for next hr', domoticz.LOG_INFO)
domoticz.data.newhourhotwaterseconds = domoticz.data.newhourhotwaterseconds + item.lastUpdate.secondsAgo - domoticz.data.previoushourhotwaterseconds
end
domoticz.log('Current time hot water ' .. item.lastUpdate.secondsAgo - domoticz.data.previoushourhotwaterseconds .. ' seconds', domoticz.LOG_INFO)
domoticz.log('Total time hot water ' .. domoticz.data.hotwaterseconds .. ' seconds', domoticz.LOG_INFO)
domoticz.data.previoushourhotwaterseconds = 0
end
end
if item.idx==idxCentralHeatingActive then
if item.state == 'On' then
domoticz.log('Central Heating was switched On', domoticz.LOG_INFO)
else
domoticz.log('Central Heating was switched Off', domoticz.LOG_INFO)
if domoticz.data.previoushourprocessed == 'True' then
domoticz.log('Adding seconds to current hr', domoticz.LOG_INFO)
domoticz.data.centralheatingseconds = domoticz.data.centralheatingseconds + item.lastUpdate.secondsAgo - domoticz.data.previoushourcentralheatingseconds
else
domoticz.log('Saving seconds for next hr', domoticz.LOG_INFO)
domoticz.data.newhourcentralheatingseconds = domoticz.data.newhourcentralheatingseconds + item.lastUpdate.secondsAgo - domoticz.data.previoushourcentralheatingseconds
end
domoticz.log('Current time central heating ' .. item.lastUpdate.secondsAgo - domoticz.data.previoushourcentralheatingseconds .. ' seconds', domoticz.LOG_INFO)
domoticz.log('Total time central heating ' .. domoticz.data.centralheatingseconds .. ' seconds', domoticz.LOG_INFO)
domoticz.data.previoushourcentralheatingseconds = 0
end
end
if item.idx==idxGasP1 then
local currentGas = domoticz.devices(idxGasP1).counter
local increasegas = 0
local currentValue=0
--if domoticz.data.previousgascounter > 200 then
-- domoticz.data.previousgascounter=102.284
--end
domoticz.log('Triggered by GASP1 with value ' .. currentGas, domoticz.LOG_INFO)
domoticz.log('while previous GASP1 value ' .. domoticz.data.previousgascounter, domoticz.LOG_INFO)
domoticz.log('Device ' .. item.name .. ' was updated', domoticz.LOG_INFO)
domoticz.log('hotwaterseconds ' .. domoticz.data.hotwaterseconds .. ' ', domoticz.LOG_INFO)
domoticz.log('previous hr hotwaterseconds ' .. domoticz.data.previoushourhotwaterseconds .. ' ', domoticz.LOG_INFO)
domoticz.log('centralheatingseconds ' .. domoticz.data.centralheatingseconds .. ' ', domoticz.LOG_INFO)
domoticz.log('previous hr centralheatingseconds ' .. domoticz.data.previoushourcentralheatingseconds .. ' ', domoticz.LOG_INFO)
domoticz.log('Hot Water Gas counter value ' .. domoticz.devices(idxHotWaterGas).counter .. ' ', domoticz.LOG_INFO)
domoticz.log('Hot Water Gas counterToday value ' .. domoticz.devices(idxHotWaterGas).counterToday .. ' ', domoticz.LOG_INFO)
domoticz.log('Central Heating Gas counter ' .. domoticz.devices(idxCentralHeatingGas).counter .. ' ', domoticz.LOG_INFO)
domoticz.log('Cooking Gas counter ' .. domoticz.devices(idxCookingGas).counter .. ' ', domoticz.LOG_INFO)
--if domoticz.devices(idxHotWaterGas).counter==0 then
-- domoticz.notify('Error: hot water gas counter was reset to zero',domoticz.PRIORITY_NORMAL)
--end
if currentGas>domoticz.data.previousgascounter then -- not only an update but a real increase. This occurs max every hour, approx 4 minutes past the hour
-- The increase is the real increase of the previous hour. The P1 meter does a real split exactly at the hour.
increasegas = domoticz.utils.round(currentGas - domoticz.data.previousgascounter,3)
domoticz.log('Current value of Device ' .. item.name .. currentGas .. 'm3', domoticz.LOG_INFO)
domoticz.log('Previousgascounter ' .. domoticz.data.previousgascounter .. 'm3', domoticz.LOG_INFO)
domoticz.log('Value of Device ' .. item.name .. ' has increased by ' .. increasegas .. 'm3', domoticz.LOG_INFO)
if domoticz.data.hotwaterseconds>0 and domoticz.data.centralheatingseconds==0 then
domoticz.log('Value of Device ' .. item.name .. ' has increased as result of hot water (+ possibly cooking) usage', domoticz.LOG_INFO)
currentValue=domoticz.devices(idxHotWaterGas).counter
domoticz.devices(idxHotWaterGas).updateCounter(increasegas*1000+currentValue*1000) -- update value must be in dm3
-- also update the avg gas flow rate for hot water, provided minimum 3 minute flow
-- apply factor 1000 for accuracy/decimals
if domoticz.data.hotwaterseconds>=180 then
local flowrate=increasegas/domoticz.data.hotwaterseconds*1000
local pastflowrate=domoticz.devices(idxAvgGasFlowRateHW).sensorValue
if flowrate<=1.5 * pastflowrate then -- ignore too high value, as result of inclusion of cooking gas
domoticz.devices(idxAvgGasFlowRateHW).updateCustomSensor(domoticz.utils.round((pastflowrate*9+flowrate)/10,3)) -- new value weights only 10% on the total average
domoticz.log('flow rate past hour ' .. flowrate .. ' ', domoticz.LOG_INFO)
domoticz.log('flow rate pas avg ' .. pastflowrate .. ' ', domoticz.LOG_INFO)
end
end
elseif domoticz.data.hotwaterseconds==0 and domoticz.data.centralheatingseconds>0 then
domoticz.log('Value of Device ' .. item.name .. ' has increased as result of central heating (+ possibly cooking) usage', domoticz.LOG_INFO)
currentValue=domoticz.devices(idxCentralHeatingGas).counter
domoticz.devices(idxCentralHeatingGas).updateCounter(increasegas*1000+currentValue*1000) -- update value must be in dm3
elseif domoticz.data.hotwaterseconds==0 and domoticz.data.centralheatingseconds==0 then
domoticz.log('Value of Device ' .. item.name .. ' has increased as result of cooking usage', domoticz.LOG_INFO)
currentValue=domoticz.devices(idxCookingGas).counter
domoticz.devices(idxCookingGas).updateCounter(increasegas*1000+currentValue*1000) -- update value must be in dm3
elseif domoticz.data.hotwaterseconds>0 and domoticz.data.centralheatingseconds>0 then
domoticz.log('Value of Device ' .. item.name .. ' has increased as result of combined usage', domoticz.LOG_INFO)
-- make a theoretical split between hot water and central heating, ignore possible cooking usage
-- local hotwatergasestimate = domoticz.data.hotwaterseconds * 0.02/60 -- if not good avg calculation is available then 0.02 m3 per minute was a good average based on past observations
local hotwatergasestimate = domoticz.devices(idxAvgGasFlowRateHW).sensorValue * domoticz.data.hotwaterseconds / 1000 -- assume now a good avg is available, so use it.
if hotwatergasestimate> increasegas then
hotwatergasestimate=increasegas
increasegas = 0
else
increasegas = increasegas - hotwatergasestimate
end
currentValue=domoticz.devices(idxCentralHeatingGas).counter
domoticz.devices(idxCentralHeatingGas).updateCounter(increasegas*1000+currentValue*1000) -- update value must be in dm3
currentValue=domoticz.devices(idxHotWaterGas).counter
domoticz.devices(idxHotWaterGas).updateCounter(hotwatergasestimate*1000+currentValue*1000) -- update value must be in dm3
end
domoticz.data.hotwaterseconds=domoticz.data.newhourhotwaterseconds
domoticz.data.centralheatingseconds=domoticz.data.newhourcentralheatingseconds
domoticz.data.newhourhotwaterseconds=0
domoticz.data.newhourcentralheatingseconds=0
domoticz.data.previousgascounter=currentGas
domoticz.data.previoushourprocessed='True'
else
domoticz.log('Value of Device ' .. item.name .. ' has stayed the same', domoticz.LOG_INFO)
end
end
else -- triggered by the timer, not one of the devices
-- the hourly timer is used to split the seconds for any switches than are ON across the hour limit
if domoticz.devices(idxHotWaterActive).state == 'On' then
-- calculate as-if switched off/on at the hour
domoticz.log('As-if Hot Water was switched Off', domoticz.LOG_INFO)
domoticz.data.previoushourhotwaterseconds=domoticz.devices(idxHotWaterActive).lastUpdate.secondsAgo
-- the above value is kept for later correction when the OFF switch occurs and then reset
domoticz.data.hotwaterseconds = domoticz.data.hotwaterseconds + domoticz.data.previoushourhotwaterseconds
domoticz.log('Extra before-hour hot water ' .. domoticz.data.previoushourhotwaterseconds .. ' seconds', domoticz.LOG_INFO)
domoticz.log('Total time hot water ' .. domoticz.data.hotwaterseconds .. ' seconds', domoticz.LOG_INFO)
end
if domoticz.devices(idxCentralHeatingActive).state == 'On' then
-- calculate as-if switched off/on at the hour
domoticz.log('As-if Central Heating was switched Off', domoticz.LOG_INFO)
domoticz.data.previoushourcentralheatingseconds=domoticz.devices(idxCentralHeatingActive).lastUpdate.secondsAgo
-- the above value is kept for later correction when the OFF switch occurs and then reset
domoticz.data.centralheatingseconds = domoticz.data.centralheatingseconds + domoticz.data.previoushourcentralheatingseconds
domoticz.log('Extra before-hour central heating ' .. domoticz.data.previoushourcentralheatingseconds .. ' seconds', domoticz.LOG_INFO)
domoticz.log('Total time central heating ' .. domoticz.data.centralheatingseconds .. ' seconds', domoticz.LOG_INFO)
end
-- previoushourprocessed is used to ensure the value of hotwaterseconds is kept until the gas increase of the previous hour has been processed
if domoticz.data.hotwaterseconds==0 and domoticz.data.centralheatingseconds==0 then
domoticz.data.previoushourprocessed='True'
else
if domoticz.data.previoushourprocessed=='True' then -- only carry forward the hotwaterseconds one single hour, not more.
domoticz.data.previoushourprocessed='False'
else
domoticz.data.previoushourprocessed='True'
end
end
--if domoticz.time.hour==0 then
-- below updates were added because of a domoticz bug, which is apparently solved in latest beta as per 04/10/2022
local currentcountervalue=0
currentcountervalue=domoticz.devices(idxCentralHeatingGas).counter
domoticz.log('Updating CH counter to existing value ' .. currentcountervalue .. ' ', domoticz.LOG_INFO)
domoticz.devices(idxCentralHeatingGas).updateCounter(currentcountervalue*1000) -- update with 0 increase to prevent loosing real updates later
currentcountervalue=domoticz.devices(idxHotWaterGas).counter
domoticz.log('Updating HW counter to existing value ' .. currentcountervalue .. ' ', domoticz.LOG_INFO)
domoticz.devices(idxHotWaterGas).updateCounter(currentcountervalue*1000) -- update with 0 increase to prevent loosing real updates later
currentcountervalue=domoticz.devices(idxCookingGas).counter
domoticz.log('Updating C counter to existing value ' .. currentcountervalue .. ' ', domoticz.LOG_INFO)
domoticz.devices(idxCookingGas).updateCounter(currentcountervalue*1000) -- update with 0 increase to prevent loosing real updates later
--end
end
end
}