Dutch local 'Stookwijzer'
Posted: Friday 22 March 2024 19:10
I am willing to share my project in this topic which describes the setup I use for a local alert.
This is a different approach than the RIVM stookalert, which applies to the entire province.
Here it works with local information, with which the advice is calculated in the script.
All 6 items below are needed for it to work:
1. Custom sensor that holds the current local LKI value (Air quality index) based on your coordinates. I have set the axis-label is set to 'LKI'.
2. LKI script that will update the LKI sensor. The script runs every hour. Update of the LKI sensor will trigger the stoke-alert script to run.
You have to enter the device idx of the LKI sensor. Note that for logging, I make use of a function in global_data. You may use or modify this or comment it out.
3. Global_data set up where the LKI script stores the calculated avarage LKI for the coming hours in global_data, which will be picked up by the stoke-alert script. I give my entire 'global_data' script. You may or may not want to use the logging function. If not, you have also to modify corresponding lines in the scripts that call this function. Save this script as 'global_data', or take the used variables 'avgnextlkiValues' and 'avgnextlkiHours' in your existing script.
4. Buienradar plugin (or another method with which you can get the current local windspeed). Like the LKI, Buienradar is based on your coordinates.
5. A stoke-alert script that combines the LKI, the stored global_data, the windspeed and winddirection and gives a calculated advice with some additional information. The calculation used is described on https://iplo.nl/thema/lucht/houtstook-s ... d2bfe71123
Or course you have to enter the idx numbers of the used devices.
6. An alert device, which holds the result. Levels are described in the script.
At the moment I am not satisfied with the orange alert, which comes up when an LKI >= 5, because the orange warning remains even when a strong wind is blowing. In that case I ignore the warning. So it may require some personal adjustment. At a low wind speed it always corresponds to my personal feeling.
This is a different approach than the RIVM stookalert, which applies to the entire province.
Here it works with local information, with which the advice is calculated in the script.
All 6 items below are needed for it to work:
1. Custom sensor that holds the current local LKI value (Air quality index) based on your coordinates. I have set the axis-label is set to 'LKI'.
2. LKI script that will update the LKI sensor. The script runs every hour. Update of the LKI sensor will trigger the stoke-alert script to run.
You have to enter the device idx of the LKI sensor. Note that for logging, I make use of a function in global_data. You may use or modify this or comment it out.
Code: Select all
-- 05-01-2023: Script created by Jan peppink, https://ict.peppink.nl
-- Get data from https://api.luchtmeetnet.nl for LKI every hour for new values
-- Example url: url = 'https://api.luchtmeetnet.nl/open_api/concentrations?formula=lki&longitude=' .. long .. '&latitude=' .. lat .. '&start=' .. requestTime .. '&end=' .. requestTime
-- Use of Starttime and Endtime makes no difference
return {
on = {
timer = {
'at *:02', -- (LKI info online is updated at the hour)
},
httpResponses = {
'lki' -- matches callback string below
},
},
logging = {
level = domoticz.LOG_INFO,
marker = 'LKI-',
},
execute = function(domoticz, triggeredItem)
-- Set Local environment=================
-- loging level 0 = NO logging, 1 = INFO, 2 = DEBUG, 3 = ERROR or 4 = FORCE
local debug_level = 0
--local _u = domoticz.utils
local _h = domoticz.helpers -- Holds the global functions
local _d = domoticz.globalData -- Holds the global data
-- Get location coordinates
local lat = domoticz.settings.location.latitude
local long = domoticz.settings.location.longitude
-- currenTime = RawDateTime = 2022-12-06 19:50:00
local currentTime = tostring(domoticz.time.rawDateTime)
-- requestTime in IS08601 representation formatted amd for the current (whole) hour e.g. 2022-12-06T19:00:00Z
local requestTime = domoticz.time.dateToDate( currentTime, 'yyyy-mm-dd hh:MM:ss', 'yyyy-mm-ddThh:00:00Z', 0 )
-- resultTime comes back in json result e.g. 2022-12-06T19:00:00+00:00
local resultTime = domoticz.time.dateToDate( currentTime, 'yyyy-mm-dd hh:MM:ss', 'yyyy-mm-ddThh:00:00+00:00', 0 )
local nextresultTime = domoticz.time.dateToDate( currentTime, 'yyyy-mm-dd hh:MM:ss', 'yyyy-mm-ddThh:00:00+00:00', 3600 )
local lki_idx = n --idx of the custom sensor 'Luchtkwaliteitsindex'
local lkiValue = domoticz.devices(lki_idx).sensorValue -- Gets last stored value
local totallkiValues = 0 -- Will hold a count of the total of the values we get.
local intloopCounter = 0 -- Will hold a count of the the number of the values we get.
-- Now start to do something ============
-- Remark: Starttime and endtime does not seem to do much.
if (triggeredItem.isTimer) then
-- Log what we do.
_h.logthis( domoticz, debug_level, 'https://api.luchtmeetnet.nl/open_api/concentrations?formula=lki&longitude=' .. long .. '&latitude=' .. lat .. '&start=' .. requestTime .. '&end=' .. requestTime )
-- Get the data.
domoticz.openURL({
url = 'https://api.luchtmeetnet.nl/open_api/concentrations?formula=lki&longitude=' .. long .. '&latitude=' .. lat .. '&start=' .. requestTime .. '&end=' .. requestTime,
method = 'GET',
callback = 'lki'
})
end
if (triggeredItem.isHTTPResponse) then
-- Check the response and process the data
if (triggeredItem.ok and triggeredItem.isJSON) then
_h.logthis( domoticz, debug_level, 'Item and JSON - OK' )
-- We have some result. Store in table.
local result_table = triggeredItem.json
if type(result_table) == "table" then
_h.logthis( domoticz, debug_level, 'resulttable: type = ' .. type(result_table) )
local data_table = result_table['data']
if type(data_table) == "table" then
_h.logthis( domoticz, debug_level, 'datatable: type = ' .. type(data_table) )
local tc = #data_table
for i = 1, tc do
-- Loop through the table. The data we get is:
-- "timestamp_measured": "2022-12-06T14:00:00+00:00"
-- formula": "LKI"
-- "value": 4.44999980926514
if data_table[i].timestamp_measured == resultTime then
_h.logthis( domoticz, debug_level, 'Current Recordtime: ' .. data_table[i].timestamp_measured )
--
lkiValue = math.ceil( tonumber( data_table[i].value ) )
_h.logthis( domoticz, debug_level, 'Current Recordvalue: ' .. lkiValue )
end
-- The table can also contain values from past hours. Only count future hours
if data_table[i].timestamp_measured > resultTime then
totallkiValues = totallkiValues + tonumber( data_table[i].value )
intloopCounter = intloopCounter + 1
end
end
-- Update device with lkiValue with new value (or the old-current).
domoticz.devices(lki_idx).updateCustomSensor(lkiValue)
-- Calculate and store the average value for future hours in global_data.
--NB. This will be used in script d-StookwijzerLokaal
_d.avgnextlkiValues = math.ceil( totallkiValues / intloopCounter )
-- Keep track of the number of hours that is received.
_d.avgnextlkiHours = intloopCounter
---
_h.logthis( domoticz, debug_level, 'Totaal lki values = ' .. totallkiValues .. '; Number of records = ' .. _d.avgnextlkiHours .. '; avg = ' .. _d.avgnextlkiValues )
else
_h.logthis( domoticz, debug_level, 'No datatable found' )
end
else
_h.logthis( domoticz, debug_level, 'No resulttable found' )
end
else
_h.logthis( domoticz, debug_level, 'Item or JSON - NOT OK' )
end
end
end
}
Code: Select all
-- 05-01-2023: Script created by Jan peppink, https://ict.peppink.nl
-- This scripts holds all the globally persistent variables and helper functions
-- See the documentation in the wiki
-- NOTE: THERE CAN BE ONLY ONE global_data SCRIPT in your Domoticz install.
return {
-- Global persistent data
data = {
-- Used in t-Alarmeringen for Alarmeringen.nl.
lastAlarmNotification = {},
-- Used in t-Luchtkwaliteitindex + d-StookwijzerLokaal.
avgnextlkiValues = { initial = 0 },
avgnextlkiHours = { initial = 0 },
},
-- Global helper functions
helpers = {
logthis = function( domoticz, debug_level, log_string )
-- Call this with domoticz.helpers.logthis( 'string to log'[, log_level])
-- The function gives the option to log or not to log under certain conditions.
-- You then have only to set debug_level once in a script to change the logging level (or skip logging).
debug_level = debug_level or 0
if debug_level ~= 0 then
if debug_level == 1 then
log_level = domoticz.LOG_INFO
elseif debug_level == 2 then
log_level = domoticz.LOG_DEBUG
elseif debug_level == 3 then
log_level = domoticz.LOG_ERROR
elseif debug_level == 4
then log_level = domoticz.LOG_FORCE
else
return
end
-- Function. Creates a logging entry in the Domoticz log but respects the log level settings.
-- domoticz.LOG_INFO, domoticz.LOG_DEBUG, domoticz.LOG_ERROR or domoticz.LOG_FORCE
-- In Domoticz settings you can set the log level for dzVents.
-- For optional log_level the default is LOG_INFO
if log_string ~= "" then
-- domoticz.log(message, [level])
domoticz.log( log_string, log_level)
else
return
end
end
end
}
}
5. A stoke-alert script that combines the LKI, the stored global_data, the windspeed and winddirection and gives a calculated advice with some additional information. The calculation used is described on https://iplo.nl/thema/lucht/houtstook-s ... d2bfe71123
Or course you have to enter the idx numbers of the used devices.
Code: Select all
--[[
- 05-01-2023: Script created by Jan peppink, https://ict.peppink.nl
- Based on information at: https://iplo.nl/thema/lucht/houtstook-stookwijzer-stookalert/#h80dfe1e2-a855-424b-81b4-cdd2bfe71123
- Result: Update 'Lokale Stookwijzer', when 'Luchtkwaliteitsindex' is changed.
- Gives a treee line result with:
-- Alertcolor, Calculated code (in accordance with the definition on https://iplo.nl), current LKI.
-- Winddescription (Winddirection) windspeed in m/s, bft and km/h.
-- LKI expectation (average for number of coming hours that has been received.
- NB. Expectation counts down when online information is not updatesd.
- (remains future hours stored in file, until we pass the end of it)
- Depends on LKI device (= local Air Quality Index based on the coordinates).
- Depends on Buienradar 'BR-Wind' device. (Local windspeed based on Buienradar with given coordinates).
- Depends on stored average LKI for coming hours stored in global_data.
- Setting alertLevel by constant: domoticz.ALERTLEVEL_GREY, ALERTLEVEL_GREEN, ALERTLEVEL_YELLOW, ALERTLEVEL_ORANGE, ALERTLEVEL_RED
- Getting alertLevel by number: 0=gray, 1=green, 2=yellow, 3=orange, 4=red
]]
--Set the device that triggers this script first.
local lki_idx = n -- idx of your 'Luchtkwaliteitsindex' device
return {
on = {
devices = {
lki_idx, -- idx of your 'Luchtkwaliteitsindex' device
},
},
logging = {
level = domoticz.LOG_INFO,
marker = "LokaleStookwijzer-"
},
execute = function( domoticz )
-- Set Local environment=================
-- loging level 0 = NO logging, 1 = INFO, 2 = DEBUG, 3 = ERROR or 4 = FORCE
local debug_level = 0
--local _u = domoticz.utils
local _h = domoticz.helpers -- Holds the global functions
local _d = domoticz.globalData -- Holds the global data
local lkiValue = domoticz.devices(lki_idx).sensorValue
local alertIdx = n -- 'Lokale Stookwijzer'
local lkiText = '\nLKI verwachting komende ' .. _d.avgnextlkiHours .. ' uur is gemiddeld ' .. _d.avgnextlkiValues
local windSpeedIdx = n -- Set to the idx of the (Buienradar) windspeed sensor.
-- This one does not exist when Buienradar is off. SO default wind info to ...
local windText = 'Lokale wind niet beschikbaar.'
if domoticz.devices(windSpeedIdx) ~= nil then
-- Buienradar WindSpeed does exist. Get the info.
local windSpeedMs = domoticz.devices(windSpeedIdx).speedMs -- Gets the current value
local windDirection = domoticz.devices(windSpeedIdx).directionString -- just for information.
-- Translate abbreviation into Dutch abbreviation. E(ast) becomes O(ost) S(outh) becomes Z(uid).
windDirection = string.gsub(windDirection, "E", "O",2)
windDirection = string.gsub(windDirection, "S", "Z",2)
-- Cannot remembeer from where I got these naming definitions.
if windSpeedMs >= 0 and windSpeedMs <= 0.2 then windText = '\nWindstil (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 0 bft; < 1 km/u' end
if windSpeedMs >= 0.3 and windSpeedMs <= 1.5 then windText = '\nZeer zwakke wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 1 bft; 1-5km/u' end
if windSpeedMs >= 1.6 and windSpeedMs <= 3.3 then windText = '\nZwakke wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 2 bft; 6-11km/u' end
if windSpeedMs >= 3.4 and windSpeedMs <= 5.4 then windText = '\nZeer matige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 3 bft; 12-19km/u' end
if windSpeedMs >= 5.5 and windSpeedMs <= 7.9 then windText = '\nMatige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 4 bft; 20-28km/u' end
if windSpeedMs >= 8.0 and windSpeedMs <= 10.7 then windText = '\nVrij krachtige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 5 bft; 29-38km/u' end
if windSpeedMs >= 10.8 and windSpeedMs <= 13.8 then windText = '\nKrachtige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 6 bft; 39-49km/u' end
if windSpeedMs >= 13.9 and windSpeedMs <= 17.1 then windText = '\nHarde wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 7 bft; 50-61km/u' end
if windSpeedMs >= 17.2 and windSpeedMs <= 20.7 then windText = '\nStormachtige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 8 bft; 62-74km/u' end
if windSpeedMs >= 20.8 and windSpeedMs <= 24.4 then windText = '\nStorm (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 9 bft; 75-88km/u' end
if windSpeedMs >= 24.5 and windSpeedMs <= 28.4 then windText = '\nZware storm (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 10 bft; 89-102km/u' end
if windSpeedMs >= 28.5 and windSpeedMs <= 32.6 then windText = '\nZeer zware storm (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s = 11 bft; 103-117km/u' end
if windSpeedMs > 32.6 then windText = '\nOrkaan (' .. windDirection .. ') ' .. windSpeedMs .. ' m/s = 12 bft; >117km/u' end
-- Now start to do something ============
--domoticz.log('Winds = ' .. windSpeedMs .. ' m/s and lkiValue = ' .. lkiValue)
if _d.avgnextlkiHours == 0 and _d.avgnextlkiValues == 0 then
--Code = Geel; No LKI data for current hour received.
alertText = 'Niet beschikbaar!! Laatste LKI was ' .. lkiValue .. windText .. lkiText
alertLevel = domoticz.ALERTLEVEL_YELLOW
-- Update with new alertLevel and alertText.
domoticz.devices(alertIdx).updateAlertSensor(alertLevel, alertText)
elseif lkiValue <= 4 and windSpeedMs > 2 then
--Code = Blauw; You can light the fireplace, but take the neighbors into account.
--LKI <= 4 and windsspeed > 2m/sec.
alertText = 'Code blauw LKI: ' .. lkiValue .. windText .. lkiText
alertLevel = domoticz.ALERTLEVEL_GREEN
-- Update with new alertLevel and alertText.
domoticz.devices(alertIdx).updateAlertSensor(alertLevel, alertText)
elseif lkiValue >= 5 and lkiValue <=7 and windSpeedMs > 2 then
--Code = Oranje; It is better not to burn wood now.
--LKI >= 5 and <= 7 and windspeed > 2m/sec.
alertText = 'Code Oranje. LKI: ' .. lkiValue .. windText .. lkiText
alertLevel = domoticz.ALERTLEVEL_ORANGE
-- Update with new alertLevel and alertText.
domoticz.devices(alertIdx).updateAlertSensor(alertLevel, alertText)
elseif lkiValue >= 8 or windSpeedMs <= 2 then
--Code = Rood; Do not burn wood.
--LKI >= 8 and/or the windspeed is low (<= 2m/sec).
alertText = 'Code Rood. LKI: ' .. lkiValue .. windText .. lkiText
alertLevel = domoticz.ALERTLEVEL_RED
-- Update with new alertLevel and alertText.
domoticz.devices(alertIdx).updateAlertSensor(alertLevel, alertText)
end
else
alertLevel = domoticz.ALERTLEVEL_YELLOW
alertText = 'LKI: ' .. lkiValue .. " " .. windText .. lkiText
domoticz.devices(alertIdx).updateAlertSensor(alertLevel, alertText)
end
end
}
At the moment I am not satisfied with the orange alert, which comes up when an LKI >= 5, because the orange warning remains even when a strong wind is blowing. In that case I ignore the warning. So it may require some personal adjustment. At a low wind speed it always corresponds to my personal feeling.