Dutch local 'Stookwijzer'

In this subforum you can show projects you have made, or you are busy with. Please create your own topic.

Moderator: leecollings

janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

At the end of the day, I have changed the character used for the colored bar and show the amount of hours chosen.
I think that 24 hour is the most usefull. More hours is not only more unreliable, but also ugly when the line breaks.
Also a little bit rearranged the code.
I think I will keep this for a while and see how it works.
LocaleStookwijzer-JanPep-10.png
LocaleStookwijzer-JanPep-10.png (14.45 KiB) Viewed 1920 times
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

I have made some huge changes and made the whole setup even more configurable.
It can be set up the way you want. I will show you a few examples of combinations you can make here.

Major changes.
1. You can choose to show the full windtext or shortened (only direction meter/second and then added to the first line.
2. You can choose to show the forcast value AT the time of setpoint or the average UNTIL the time of setpoint.
3. Dynamic setopint calculates until midnight, but now you can override this by triggering the script by a change in setpoint to look further ahead.
4. I have further developed the idea of ​​the footer. I believe that the insight per hour says more about the development over time than just the average over a period. To save space the footer with colored blocks shows 1 hour in a block up to 24 hours.
Up to 48 hours the average per 2 hours in a block and up to 72 hours the average of 3 hours in a block.
5. You can choose to show or omit the forecast text or footer or both as desired.
6. With useSetpointForFooter you will get the same number of hours in the footer as sets in the forcast with the dynamic or fixed setpoint.
7. Maximum number of hours you can get is 72 hours over 3 days starting at 00:00 hr. So the maximum you can use is 72 minus the hours that passed already today. The script adjusts the number of wanted hours to what it can use.

Here a few examples of these combinations.
windTextLong = true
useForecastText = true
useDynamicSetpoint = true
useSetpointFor = 'value' = AT
footerHours = 48 (average 2 per 'block')
useSetpointForFooter = false
LocaleStookwijzer-JanPep-12.png
LocaleStookwijzer-JanPep-12.png (12.77 KiB) Viewed 1569 times
windTextLong = true
useForecastText = true
useDynamicSetpoint = true
useSetpointFor = 'average'
footerHours = 24
useSetpointForFooter = false
LocaleStookwijzer-JanPep-13.png
LocaleStookwijzer-JanPep-13.png (12.47 KiB) Viewed 1569 times
windTextLong = false
useForecastText = false
footerHours = 24
useSetpointForFooter = false
LocaleStookwijzer-JanPep-14.png
LocaleStookwijzer-JanPep-14.png (9.44 KiB) Viewed 1569 times
Last edited by janpep on Wednesday 26 February 2025 9:02, edited 3 times in total.
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

I forgot to mention that I also added an option to daily clear the log of the alert when the script runs for the first time that day.
I think it does not make much sense to keep history. Also because it is growing because of all that color html stuff.
I already had a separate script to clear the log of several alert devices. Now I have build it in the script itself.
So firstt time it runs, it clears the logged data of yesterday. (You can turn it on or off as you like).
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
User avatar
waltervl
Posts: 5766
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Dutch local 'Stookwijzer'

Post by waltervl »

Log should be cleaned up automatically based on the number of days in the Lights/switches log setting. If you have it for a large number of days and you have a lot of updates then indeed this can become a large part of your database.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

That is the reason to give the option to clear it more frequent (on daily basis). With the colored footer in html becomes a very large string that will be saved every time the script runs.
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

In its smallest form, just for now, it is not too bad in size, but much less informative because it lacks all context.
It is then only a snapshot of the current time.
And that is the beauty of the footer.
LocaleStookwijzer-JanPep-15.png
LocaleStookwijzer-JanPep-15.png (8.26 KiB) Viewed 1560 times
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
User avatar
waltervl
Posts: 5766
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Dutch local 'Stookwijzer'

Post by waltervl »

So script runs 4x per day I presume? Or more? Seems not to give a lot of logging when running 4x per day.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

In my case it runs every hour. With dynamic setpoint that gives the text prediction until midnight and the colored footer for 24 hours ahead.
Now that you mention it, I could of course change the schedule so that it only runs from morning until midnight. When I am asleep I don't look at it anyway :-)
I can also call it manually at any time I wish by changing the setpoint.
(The other RIVM script runs only 4 times a day, because it does not give more information.)
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

Never used this before in combination. I will see if this works.

Code: Select all

    'at *:02 between 8:00 and 23:04',   -- (LKI info online is updated at the hour)
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

Also I did not uinderstand the much lower LKI in the new RIVM output.
But on https://iplo.nl/thema/lucht/houtstook-stookwijzer/ where the new criteria are mentioned it becomes clear that they use 'LKI-PM2.5'.
I have to understand this as the LKI value, that comes exclusively from the PM2.5 concentration!
Confusing that they keep calling this LKI. because earlier the LKI was based on ozone and PM10 and PM2.5 and nitrogen_dioxide, where the highest concentration thereof determines the LKI value.
See See https://www.rivm.nl/bibliotheek/rap ... 4-0050.pdf (§6.4, Table 7).
I think I can consider this as a clear explanation for the difference noted.

Where before I almost always saw an LKI of 4 or 5 and occasionally 3, I see it now almost all the time at 1.

I thought I will give it a try.
For who thinks it is better to use the new criteria I added the new definitions in the script.
You can choose between them for your own location by setting

Code: Select all

local useNewRIVMcriteria = true     -- Use the new criteria found on https://iplo.nl/thema/lucht/houtstook-stookwijzer/
                                    -- and https://www.rivm.nl/nieuws/nieuwe-criteria-stookwijzer-op-basis-van-overlast
The calculation that is used then:
Green (actually yellow): windspeed >= 3.4 and LKI-PM2,5 <= 3
DarkYellow for PEPS override mode when LKI-PM2,5 is 4,5 or 6 (actually orange), but Windspeed configurable at acceptable >= 5.5
Orange: Windspeed >= 3.4 and LKI-PM2,5 is 4,5 or 6
Red: Windspeed <= 3.3 or LKI-PM2,5 >= 7

And see: Now I also have a low LKI (pm2.5)
Attachments
LocaleStookwijzer-JanPep-16.png
LocaleStookwijzer-JanPep-16.png (22.82 KiB) Viewed 1478 times
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
GJvdP
Posts: 20
Joined: Thursday 30 November 2017 0:33
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Arnhem, the Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by GJvdP »

I cannot find the script that belongs to the "' Lokale Stookwijzer"
Did you publish that already or is it a wor in progress?
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

GJvdP wrote: Sunday 18 May 2025 23:17 I cannot find the script that belongs to the "' Lokale Stookwijzer"
Did you publish that already or is it a wor in progress?
It is published here with several updates. I can imagine that it can become a bit confusing in time. I will post the latest version tomorrow.
Nevertheless, it is useful to read through the developments in the entire topic.
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

janpep wrote: Sunday 18 May 2025 23:24 It is published here with several updates. I can imagine that it can become a bit confusing in time.
Ah, now I see that I have never published the latest version of the script with the colored footer et cetera.

My experience: I have been using the new definition for a while now, as mentioned earlier. The outcome matches the "RIVM stookalert" very well.
The new definition does give an alert much more often/faster, because a higher wind speed is required!
I mostly use the "dynamic setpoint" calculated until midnight for the forecast and look ahead 24 hours for the colored footer. That gives more detail (per hour) of the development over time and can look further ahead if desired.

This afternoon I will try to list and summarize everything that is needed. Then you will have everything together.
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
User avatar
waltervl
Posts: 5766
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Dutch local 'Stookwijzer'

Post by waltervl »

GJvdP wrote: Sunday 18 May 2025 23:17 I cannot find the script that belongs to the "' Lokale Stookwijzer"
Did you publish that already or is it a wor in progress?
There is also this more simple script by janpep: viewtopic.php?t=43183
The script is in the first post.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

Due to the maximum allowed length of the text, which I exceed by three scripts, I will split it up into 4 posts:
1. This Introduction.
2. dt-OpenMeteo script
3. global_data script.
4. d-StookwijzerLokaal script

As Waltervl mentioned, the other script for 'RIVM stookalert' is much more simple.
- It needs just one source and one script to get the data and one device to show the result.
- It has also more limitations. It shows the 'current' LKI and windspeed. That means that it is already data from the past at the moment you look at it. :-)
- Compared to how it was in the past (once per day for entire province) it now also gives a forecast and for your own location, but only a few points in time.

Now for the Dutch local 'Stookwijzer'.
Indeed it is huge! But nevertheless surprisingly fast.
And it is certainly much more complex, but also with many more options, more information and much more configurable to your liking.

In short:
1. In the alert device it gives the information for the current hour with the data it is based on (Windspeed and LKI).
2. Optional. It gives you (with alert code color) a forecast for a configurable amount of hours in the future (1 - 72), where you can select to show the absolute expected value at that hour or the calculated average from the moment the script runs until that (setpoint) hour. Also you can have a setpoint to change the wanted number of hours in future at any moment and next to that you can calculate the setpoint dynamically until midnight.
3. Optional. It gives you a footer, with colored blocks per hour for the number of hours that is given. In case of > 24 hour, it combines the average of 2 or 3 hours to one block.
Also you can choose to use the old calculation or the new RIVM calculation.

First script is dt-OpenMeteo
This holds some of the configuration options set and it gets and stores the needed data.
It runs on device change of the setpoint device and/or on time once per hour. And because I never look at it during the night, I give the script a night break. It is set at *:02 between 8:00 and 23:04.
Results of the calculations that have been done for the current time are stored in devices that are needed. (Wind, LKI and LKIpm2.5)
LocaleStookwijzer-JanPep-17.PNG
LocaleStookwijzer-JanPep-17.PNG (57.88 KiB) Viewed 220 times
Second script is global_data
Result for the forecast must be picked up by the other (d-StookwijzerLokaal) script, but cannot be stored in the devices mentioned. Therefore this is stored in persistent data in global_data script.
(More information about this global_data script in the wiki of dzVents.)

Third script is d-StookwijzerLokaal
Also here some configuration options:
- to use an override (configurable less strict on needed wind for code Orange)
- to display windspeed information (long or short)
- to cleanup once per day
It gets the information from the devices and the persistent data, calculates the html colors and then updates the device with the resulting information.
LocaleStookwijzer-JanPep-18.PNG
LocaleStookwijzer-JanPep-18.PNG (42.27 KiB) Viewed 220 times
For more specific and detailed information in the history of the scripts.


Some extra notes before you start:
- I have set all device_idx to 99999. Change it for your devices.
- Fill in your IP address and port number, when you want to use the daily cleanup.
- All other settings I have left as I use it at the moment.
- For the global_data script, I only show those parts that are used from both other scripts. Note how you add this to existing data if you are already using this global_data script.
a. the persistent data
b. the helper function that is used by me in multiple scripts.
Last edited by janpep on Monday 19 May 2025 19:25, edited 4 times in total.
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

dt-OpenMeteo

Code: Select all

-- 26-03-2024: Script created by Jan Peppink, https://ict.peppink.nl
-- Get data from https://open-meteo.com/
-- Free and without api key for non-commercial use and less than 10.000 daily API calls per day. (See other terms of use on their website).
-- Created URLs are visible in log when debug_level is set.
-- Script is triggered every hour on timer setting and also on change of the setpoint device (wanted #hours).
-- Setpoint can be set between 1 and 72 hour.
-- When you set useDynamicSetpoint = true, the forecast is calculated until 00:00 hour and the setpoint dynamically counts down.
-- In ths case manually changing the Setpoint has no effect and will be overwritten next round.
-- Gets data in two rounds with multiple callbacks, because there is a separate api for weater(wind) and for airquality.
-- 1. Gets the current and forecast ozone, pm10, pm2_5, nitrogen_dioxide data for current location
--    Calculates LKI for current time and average LKI for the forecastHours.
--    Saves LKI value in LKI device and average in global_data
-- 2. Gets the current wind data and forecast wind speed for current location.
--    Saves wind values in wind device and average in global_data.
--    Updates setpoint device with current value, when useDynamicSetpoint = true.
-- Works with _d.forecastHours, _d.avgforecastLKI and _d.avgforecastwindspeed from global_data
-- Added option to useDynamicSetpoint until midnight. This overrules manually set Setpoint and also ignores device trigger.
-- 07-04-2024: Changed forecastHours calculation. Fix for 23 hr. when useDynamicSetpoint = true to set hoursLimit = 1.
-- 10-04-2024: Fixed bug that caused a loop when triggered on device and useDynamicSetpoint == false.
-- 25-05-2024 Removed function 'getWindDirectionString'. Instead call _h.getDirectionfromDegree in global_data
-- 29-05-2024 Directly make use of dz.log instead of the 'logthis' function in global_data.
-- 13-02-2025 Changed use of the setpoint device with the use of Dynamic setpoint.
--  A. To use only the setpoint device, set useDynamicSetpoint to false
--  B. When useDynamicSetpoint = true, it calculates remaining hours until end of the day running by timer, BUT
--  C. Now can also directly be triggered when changing the setpoint(, while next hour continues with the dynamic setting).
-- Added useSetpointFor variable with:
--  Set to 'average' for calculation of average LKI, Windspeed and advice code UNTIL the setpoint time. (as it was before)
--  Set to 'value' to show the LKI, Windspeed and advice code AT the setpoint time. Makes use of _d.sa_forecastSetpointTable in global data.
-- 15-02-2025 Added optional colored footer showing forecast code color. (useColoredFooter = true or false)
--  footerHours defines how many hours you want to see in the footer. Min. = 1, Max = 72 ( disabled by 0). suggested 24.
--  The desired number of hours in the future (for setopint or footer) will be automatically adjusted when not available.
--  The maximum you can get is 72 minus the hours that have already passed today, where 00:00 hr. is the first.
--  Added _d.forecastColorTable (in global_data) used in d-StookwijzerLokaal.
--  Huge changes in the loops to take care for these settings.
--  Many variables renamed to better fit with the drastic changes.
--  Added useSetpointForFooter overrules footerHours and use the setpoint also for the footer. Same (once) when triggerd by the setpoint change manually. 
--  Take care with LOG_DEBUG. complete tables are logged multiple times to keep track of the data changes.
-- I have shortened the lki calculation, which is frequently performed.
-- Changed the timer schedule not to run at night.

--- Your settings ----------------------------------------------------------------
-- First set the used device index numbers and variables you might want to change.
local meteolki_idx = 99999	-- idx of your custom sensor 'Luchtkwaliteitsindex'.
local meteolki2_5_idx = 99999	-- idx of your custom sensor 'Luchtkwaliteitsindex fijnstof'
local meteowind_idx = 99999	-- idx of your custom meteowind sensor
local sa_setpoint_idx = 9999	-- idx of your custom setpoint device.

local useDynamicSetpoint = true -- True = Limit forecast until 00:00 hour and use setpoint when changing. False = Use Setpoint only.
local useSetpointFor = 'average'    -- set to 'average' or 'value'
local useColoredFooter = true   -- false = Off; true = On. To show colored footer for next footerHours hours.
local footerHours = 24       -- (Coded here) How many hours forecast in footer.(showing <=24 = value per hour, 25-48 = avg per 2 hour, 49-72 = avg per 3 hour.)
local useSetpointForFooter = false   -- Overrule footerHours and use the setpoint also for the footer.

local useNewRIVMcriteria = true     -- Use the new criteria found on https://iplo.nl/thema/lucht/houtstook-stookwijzer/
                                    -- and https://www.rivm.nl/nieuws/nieuwe-criteria-stookwijzer-op-basis-van-overlast

----------------------------------------------------------------------------------
return {
	on = {
		timer = { 
			--'every 2 minutes',    -- Only used for testing.
            'at *:02 between 8:00 and 23:04',   -- (LKI info online is updated at the hour)
        },
		devices = {
			sa_setpoint_idx,	-- idx of your setpoint for wanted forecastHours
		},
		httpResponses = {
            'meteoair',       -- matches callback string below
            'meteowind'       -- matches callback string below
        },
	},
	logging = {
	    -- Level can be domoticz.LOG_INFO, domoicz.LOG_MODULE_EXEC_INFO, domoticz.LOG_DEBUG, domoticz.LOG_ERROR or domoticz.LOG_FORCE
		-- Level can be domoticz.LOG_INFO, domoticz.LOG_STATUS, domoticz.LOG_ERROR or domoticz.LOG_DEBUG
		level = domoticz.LOG_STATUS,
		--level = domoticz.LOG_DEBUG,
		marker = 'OpenMeteo-',
	},
	execute = function( dz, triggeredItem )
	    
        -- Set Local environment=================
        local _u = dz.utils       -- Holds subset of handy utilities.
        local _h = dz.helpers     -- Holds the global functions.
        local _d = dz.globalData  -- Holds the global data.

        -- Get location coordinates
        local lat = dz.settings.location.latitude
        local long = dz.settings.location.longitude

        -- currenTime = rawDateTime = e.g. 2022-12-06 19:50:00
        local currentTime = tostring( dz.time.rawDateTime )
        -- resultTime comes back in json result e.g. "2024-03-26T00:00"
        local resultTime = dz.time.dateToDate( currentTime, 'yyyy-mm-dd hh:MM:ss', 'yyyy-mm-ddThh:00', 0 )
        -- nextHour is the first hour after the currentTime.
        local nextHour = dz.time.dateToDate( currentTime, 'yyyy-mm-dd hh:MM:ss', 'hh', 3600 )
        dz.log( 'currentTime = ' .. currentTime .. ' and nextHour = ' .. nextHour, dz.LOG_DEBUG )

        -- For air and Wind data
        local pm10 = 0
        local pm2_5 = 0
        local nitrogen_dioxide = 0
        local ozone = 0
        local lki = 0
        local lki2_5 = 0
        local totallkiValues = 0    -- Will hold a count of the total of the values we get.
        local loopCounter = 0    -- Will hold a count of the the number of the values we get.

        local meteowindSpeed = dz.devices( meteowind_idx ).speedMs  -- Gets last stored value.
        local totalWindspeedValues = 0    -- Will hold a count of the total of the values we get.

        -- Set this to global_data. It is used in d-StookwijzerLokaal
        _d.sa_useNewRIVMcriteria = useNewRIVMcriteria

        -- Use the setpoint value as the default for number of hours in future we want
        -- to calculate average until, or the value at that time.
        local setpointHours = _u.round( dz.devices( sa_setpoint_idx ).setPoint, 0 )
        dz.log( 'Wanted setpointHours = ' .. setpointHours, dz.LOG_DEBUG )
        -- Therefore we set hoursLimit to the setpointHours for the average calculation.
        local hoursLimit = setpointHours 

        if useSetpointForFooter == true or dz.devices(sa_setpoint_idx).lastUpdate.minutesAgo < 1 then
            --Overrule footerHours with the setpointHours
            footerHours = setpointHours            
        end

        -- When useDynamicSetpoint == true, we use that, EXCEPT when the setpoint is changed manually.
        if useDynamicSetpoint == true and dz.devices(sa_setpoint_idx).lastUpdate.minutesAgo > 1 then
            -- Calculate remaining hous untill (including) 00:00 o'clock, which is next day.
            if dz.time.dateToDate( currentTime, 'yyyy-mm-dd hh:MM:ss', 'hh', 0 ) == '23' then
                -- Exeption! nexthour '00' does not fit with forecastDays calculation below.
                -- Cannot add '00' hr. Therefore set nextHour to 24, which is 1 hour from now.
                nextHour = 24
                hoursLimit = 1
                dz.log( 'For dynamic setpoint calculation corrected nextHour to ' .. nextHour .. ' and set hoursLimit to '  ..hoursLimit, dz.LOG_DEBUG )
            else
                hoursLimit = _u.round( 25 - nextHour, 0 )
            end
        end

        -- Check how many days we need to ask for. Using the max number of hours.
        dz.log( 'Wanted footerHours = ' .. footerHours, dz.LOG_DEBUG )

        -- Start getting the MAX number of hours we need for value at setpoint, average until setpoint or footertable.
        local maxHours = math.max( hoursLimit, setpointHours, footerHours )

        -- The result of query in number of days also contains the past hours of today.
        -- Therefore we add them into the totalHours (= + nextHour -1 )
        local totalHours = maxHours + nextHour
        dz.log( 'Wanted totalHours to calculate days from = ' .. maxHours .. '+' .. nextHour .. '=' .. totalHours, dz.LOG_DEBUG )
        
        -- Get number of days we need to query
        local forecastDays = 0
        if totalHours >= 48 then
            forecastDays = 3
        elseif totalHours  >= 24 and totalHours < 48 then
            forecastDays = 2
        elseif totalHours < 24 then
            forecastDays = 1
        end
        dz.log( 'forecastDays to retreive is set to  ' .. forecastDays, dz.LOG_DEBUG )


        -- Local Functions go here =============
        local function calculatedLKI( ozone, pm10, pm2_5, nitrogen_dioxide )
            --To calculate the LKI index. See https://www.rivm.nl/bibliotheek/rapporten/2014-0050.pdf (§6.4, Table 7)
            -- The component that falls into the highest category determines the value of the index.
            --INDEX             1	    2	    3	    4	    5	    6	    7	    8	    9	    10
            --ozone	            0-15	15-30	30-40	40-60	60-80	80-100	100-140	140-180	180-200 >200
            --pm10              0-10	10–20	20–30	30-45	45-60	60-75	75-100	100-125	125-150 >150
            --pm2_5             0-10	10-15	15-20	20-30	30-40	40-50	50-70	70-90	90-100  >100
            --nitrogen_dioxide	0-10	10-20	20-30	30-45	45-60	60-75	75-100	100-125	125-150 >150 
            lki = 0
            lki2_5 = 0
            dz.log( '_d.sa_useNewRIVMcriteria is set to  ' .. tostring( _d.sa_useNewRIVMcriteria ), dz.LOG_DEBUG )                
            --if _d.sa_useNewRIVMcriteria == true then
                if ( pm2_5 > 0 and pm2_5 <= 10 ) then lki2_5 = 1 end
                if ( pm2_5 > 10 and pm2_5 <= 15 ) then lki2_5 = 2 end
                if ( pm2_5 > 15 and pm2_5 <= 20 ) then lki2_5 = 3 end
                if ( pm2_5 > 20 and pm2_5 <= 30 ) then lki2_5 = 4 end
                if ( pm2_5 > 30 and pm2_5 <= 40 ) then lki2_5 = 5 end
                if ( pm2_5 > 40 and pm2_5 <= 50 ) then lki2_5 = 6 end
                if ( pm2_5 > 50 and pm2_5 <= 70 ) then lki2_5 = 7 end
                if ( pm2_5 > 70 and pm2_5 <= 90 ) then lki2_5 = 8 end
                if ( pm2_5 > 90 and pm2_5 <= 100 ) then lki2_5 = 9 end
                if ( pm2_5 > 100 ) then lki2_5 = 10 end
                --return lki2_5
            --else
                if ( ozone > 0 and ozone <=15 ) or ( pm10 > 0 and pm10 <= 10 ) or ( pm2_5 > 0 and pm2_5 <= 10 ) or ( nitrogen_dioxide  > 0 and nitrogen_dioxide <= 10 ) then lki = 1 end
                if ( ozone > 15 and ozone <= 30 ) or ( pm10 > 10 and pm10 <= 20 ) or ( pm2_5 > 10 and pm2_5 <= 15 ) or ( nitrogen_dioxide > 10 and nitrogen_dioxide <= 20 ) then lki = 2 end
                if ( ozone > 30 and ozone <= 40 ) or ( pm10 > 20 and pm10 <= 30 ) or ( pm2_5 > 15 and pm2_5 <= 20 ) or ( nitrogen_dioxide > 20 and nitrogen_dioxide <= 30 ) then lki = 3 end
                if ( ozone > 40 and ozone <= 60 ) or ( pm10 > 30 and pm10 <= 45 ) or ( pm2_5 > 20 and pm2_5 <= 30 ) or ( nitrogen_dioxide > 30 and nitrogen_dioxide <= 45 ) then lki = 4 end
                if ( ozone > 60 and ozone <= 80 ) or ( pm10 > 45 and pm10 <= 60 ) or ( pm2_5 > 30 and pm2_5 <= 40 ) or ( nitrogen_dioxide > 45 and nitrogen_dioxide <= 60 ) then lki = 5 end
                if ( ozone > 80 and ozone <= 100 ) or ( pm10 > 60 and pm10 <= 75 ) or ( pm2_5 > 40 and pm2_5 <= 50 ) or ( nitrogen_dioxide > 60 and nitrogen_dioxide <= 75 ) then lki = 6 end
                if ( ozone > 100 and ozone <= 140 ) or ( pm10 > 75 and pm10 <= 100 ) or ( pm2_5 > 50 and pm2_5 <= 70 ) or ( nitrogen_dioxide > 75 and nitrogen_dioxide <= 100 ) then lki = 7 end
                if ( ozone > 140 and ozone <= 180 ) or ( pm10 > 100 and pm10 <= 125 ) or ( pm2_5 > 70 and pm2_5 <= 90 ) or ( nitrogen_dioxide > 100 and nitrogen_dioxide <= 125 ) then lki = 8 end
                if ( ozone > 180 and ozone <= 200 ) or ( pm10 > 125 and pm10 <= 150 ) or ( pm2_5 > 90 and pm2_5 <= 100 ) or ( nitrogen_dioxide > 125 and nitrogen_dioxide <= 150 ) then lki = 9 end
                if ( ozone > 200 ) or ( pm10 > 150 ) or ( pm2_5 > 100 ) or ( nitrogen_dioxide > 150 ) then lki = 10 end
                --return lki
            --end
            return lki, lki2_5
        end

        -- Now start to do something ===========================
        -- Get the data for air quality.
		if ( triggeredItem.isTimer or triggeredItem.isDevice ) then
            -- Retrieve the data
            dz.openURL({
                url = 'https://air-quality-api.open-meteo.com/v1/air-quality?latitude=' .. lat .. '&longitude=' .. long .. '&current=pm10,pm2_5,nitrogen_dioxide,ozone&hourly=pm10,pm2_5,nitrogen_dioxide,ozone&timezone=auto&forecast_days=' .. forecastDays,
                method = 'GET',
                callback = 'meteoair'
            })
		end	
    
        -- Process the obtained data.
        if ( triggeredItem.isHTTPResponse ) then
            -- For AIR ------------------------------------------
            if triggeredItem.trigger == 'meteoair' then
                -- Check the response and process the data.
                if ( triggeredItem.ok and triggeredItem.isJSON ) then
                    dz.log( 'Item and JSON - OK', dz.LOG_DEBUG )
                    -- We have some result. Store in table.
                    local result_table = triggeredItem.json
                    if type( result_table ) == "table" then
                        dz.log( 'result_table: type = ' .. type( result_table ), dz.LOG_DEBUG )
                        dz.log( 'Number of records in result_table.hourly.time = ' .. #result_table.hourly.time, dz.LOG_DEBUG )
                        
                        -- Always clear the forecastSetpointTable before we start filling it.
                        _d.sa_forecastSetpointTable = {
                            LKI = 0,
                            windSpeed = 0,
                            alertText = ''
                        }

                        -- Always clear the forecastTable before we start filling it.
                        _d.sa_forecastTable = {}
                        
                        --Build the table with wanted number of rows we need and what we do have.
                        -- The max we can have in te future is 72 records minus the hours that has passed already today.
                        -- This is (#rows in table - nextHour) for the day in table starts at 00:00)
                        
                        -- Now loop trhough the hourly table for the lki
                        local tc = #result_table.hourly.time
                        for i = 1, tc do
                            -- Get LKI for the current hour and store into the LKI device.
                            if loopCounter < 1 and result_table.hourly.time[i] == resultTime then
                                -- This is for the current hour in the result_table that starts at 00:00.
                                pm10 = result_table.hourly.pm10[i]
                                pm2_5 = result_table.hourly.pm2_5[i]
                                nitrogen_dioxide = result_table.hourly.nitrogen_dioxide[i]
                                ozone = result_table.hourly.ozone[i]
                                -- Value for the this hour.
                                --lki = calculatedLKI( ozone, pm10, pm2_5, nitrogen_dioxide )
                                lki, lki2_5 = calculatedLKI( ozone, pm10, pm2_5, nitrogen_dioxide )
                                if _d.sa_useNewRIVMcriteria == true then
                                    dz.log( 'CurrentTime Record = ' .. loopCounter .. ' - time - value: ' .. result_table.hourly.time[i] .. ' - LKI2.5 = ' ..lki2_5  .. '; pm2_5 = ' ..  pm2_5, dz.LOG_DEBUG )
                                else
                                    dz.log( 'CurrentTime Record = ' .. loopCounter .. ' - time - value: ' .. result_table.hourly.time[i] .. ' - LKI = ' ..lki  .. '; pm10 = ' ..  pm10 .. '; pm2_5 = ' ..  pm2_5 .. '; nitrogen_dioxide = ' ..  nitrogen_dioxide ..'; ozone = ' ..  ozone, dz.LOG_DEBUG )
                                end
                                -- Update sensor with current LKI value
                                dz.log( 'Update LKI device ' .. dz.devices( meteolki_idx ).name .. '.', dz.LOG_DEBUG )
                                dz.devices( meteolki_idx ).updateCustomSensor( lki )
                                dz.log( 'Update LKI PM2.5 device ' .. dz.devices( meteolki2_5_idx ).name .. '.', dz.LOG_DEBUG )
                                dz.devices( meteolki2_5_idx ).updateCustomSensor( lki2_5 )
                                
                                loopCounter = loopCounter + 1
                            end
                            
                            -- Now fill forecastTables
                            if result_table.hourly.time[i] > resultTime and loopCounter <= maxHours then
                                -- This is for the forecast, staqrting at one hour from now.
                                pm10 = result_table.hourly.pm10[i]
                                pm2_5 = result_table.hourly.pm2_5[i]
                                nitrogen_dioxide = result_table.hourly.nitrogen_dioxide[i]
                                ozone = result_table.hourly.ozone[i]
                                -- Value for the this hour.
                                --lki = calculatedLKI( ozone, pm10, pm2_5, nitrogen_dioxide )
                                lki, lki2_5 = calculatedLKI( ozone, pm10, pm2_5, nitrogen_dioxide )
                                if _d.sa_useNewRIVMcriteria == true then
                                    dz.log( 'Record = ' .. loopCounter .. ' - time: ' .. result_table.hourly.time[i] .. ' - LKI2.5 = ' .. lki2_5 .. '; pm2_5 = ' ..  pm2_5, dz.LOG_DEBUG )
                                    -- Now we are using the new criteria, FROM NOW ON we use lki2_5 for lki
                                    lki = lki2_5
                                else
                                    dz.log( 'Record = ' .. loopCounter .. ' - time: ' .. result_table.hourly.time[i] .. ' - LKI = ' ..lki  .. '; pm10 = ' ..  pm10 .. '; pm2_5 = ' ..  pm2_5 .. '; nitrogen_dioxide = ' ..  nitrogen_dioxide ..'; ozone = ' ..  ozone, dz.LOG_DEBUG )
                                end

                                -- When useSetpointFor == 'value', update _d.forecastSetpointTable with lki.
                                if useSetpointFor == 'value' and loopCounter == setpointHours then
                                    _d.sa_forecastSetpointTable.LKI = lki
                                end
                                
                                -- Update forcastTable with the calculated lki
                                local newRecord = {
					                    LKI = lki,
                                        windSpeed = 0,
                                        inFooter = false,
                                        alertColor = ''
				                        }
                                table.insert( _d.sa_forecastTable, newRecord )
                                
                                --_Mark the wanted number of records to show up in footer.
                                if useColoredFooter == true and loopCounter <= footerHours then
                                    -- Use for Footer
                                    _d.sa_forecastTable[loopCounter].inFooter = true
                                end
                                
                                -- To calculate for average stop adding when (dynamic) setpoint is reached. (maxHours can be more for footer or value at setpoint.)
                                if loopCounter <= hoursLimit then
                                    -- Update totallkiValues with current LKI value
                                    totallkiValues =  totallkiValues + lki
                                end
                                
                                loopCounter = loopCounter + 1
                            end
                        end
                        
                        -- Adjust the limit when the limit is greater than the number of records retreived,
                        if #_d.sa_forecastTable < hoursLimit then hoursLimit = #_d.sa_forecastTable end
                        
                        -- Calculate and store the average value for future hours in global_data.
                        --NB. This will be used in script d-StookwijzerLokaal
                        _d.sa_avgForecastLKI = _u.round( totallkiValues / hoursLimit, 0 )
                        dz.log( 'Found average LKI = ' .. _d.sa_avgForecastLKI .. ' in ' .. hoursLimit .. ' records.', dz.LOG_DEBUG )
                        
                        -- Keep track of the number of hours that is received and used.
                        _d.sa_forecastHours = hoursLimit

                    else
                        dz.log( 'No result_table found', dz.LOG_ERROR )
                    end
    			else
                    dz.log( 'Item or JSON - NOT OK', dz.LOG_ERROR )
    			end

                -- Get the data for Wind.
                -- Retrieve the data
                dz.openURL({
                    url = 'https://api.open-meteo.com/v1/forecast?latitude=' .. lat .. '&longitude=' .. long .. '&current=temperature_2m,apparent_temperature,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=wind_speed_10m&wind_speed_unit=ms&timezone=auto&forecast_days=' .. forecastDays,
                    method = 'GET',
				    callback = 'meteowind'
                })
            end -- meteoair

            --====================================================
            -- For WIND -----------------------------------------
            if triggeredItem.trigger == 'meteowind' then
                -- Check the response and process the data.
                if ( triggeredItem.ok and triggeredItem.isJSON ) then
                    dz.log( 'Item and JSON - OK', dz.LOG_DEBUG )
                    -- We have some result. Store in table.
                    local result_table = triggeredItem.json
                    if type( result_table ) == "table" then
                        dz.log( 'result_table: type = ' .. type( result_table ), dz.LOG_DEBUG )
                        -- Get the current values
                        --"temperature_2m": 10.2,
                        --"apparent_temperature": 6.2,
                        --"wind_speed_10m": 4.71,
                        --"wind_direction_10m": 197,
                        --"wind_gusts_10m": 16.5
                        local temperature = result_table.current.temperature_2m
                        local chill = result_table.current.apparent_temperature
                        local windSpeedMs = _u.round( result_table.current.wind_speed_10m, 1 )
                        local windDirection = result_table.current.wind_direction_10m
                        local windDirectionString = _h.getDirectionfromDegree( windDirection )
                        local windGust = result_table.current.wind_gusts_10m
                        dz.log( 'Current values - temperature = ' ..  temperature .. '; chill = ' ..  chill .. '; windSpeedMs = ' ..  windSpeedMs ..'; windDirection = ' ..  windDirection .. '; windDirectionString = ' ..  windDirectionString ..'; windGust = ' ..  windGust, dz.LOG_DEBUG )

                        -- LOG current windSpeedMs with the last windSpeedMs from Weerlive.
                        local weerliveWindSpeed = dz.devices( 1026 ).speedMs  -- Gets last stored value from WeerliveWind sensor.
                        _h.AppendStringToFile( dz, '/home/janpep/domoticz/scripts/WindLog.txt', currentTime .. '\t' .. weerliveWindSpeed .. '\t' .. windSpeedMs .. '\n' )

                        loopCounter = 1
                        local tc = #result_table.hourly.time
                            --dz.log( 'forecasthours = ' .. _d.sa_forecastHours, dz.LOG_DEBUG )
                            for i = 1, tc do
                                if result_table.hourly.time[i] > resultTime and loopCounter <= maxHours then
                                    
                                    -- Value for the this hour.
                                    meteowindSpeed = result_table.hourly.wind_speed_10m[i]
                                    dz.log( 'Record = ' .. loopCounter .. ' - time: ' .. result_table.hourly.time[i] .. ' - Wind = ' .. meteowindSpeed, dz.LOG_DEBUG )

                                    -- When useSetpointFor == 'value', update _d.forecastSetpointTable with lki.
                                    if useSetpointFor == 'value' and loopCounter == setpointHours then
                                        _d.sa_forecastSetpointTable.windSpeed = meteowindSpeed
                                    end
                                    
                                    -- Update forcastTable with the windspeed.
                                    _d.sa_forecastTable[loopCounter].windSpeed = meteowindSpeed
                                    --dz.log( 'forecastTable has LKI = ' .. _d.sa_forecastTable[loopCounter].LKI .. ' and wind = ' .. _d.sa_forecastTable[loopCounter].windSpeed , dz.LOG_DEBUG )
                                
                                    -- To calculate for average stop adding when (dynamic) setpoint is reached. (maxHours can be more for footer or value at setpoint.)
                                    if loopCounter <= _d.sa_forecastHours then
                                        totalWindspeedValues = totalWindspeedValues + meteowindSpeed
                                    end
                                    
                                    loopCounter = loopCounter + 1
                                end
                            end

                        -- Calculate and store the average value for future hours in global_data.
                        --NB. This will be used in script d-StookwijzerLokaal.
                        _d.sa_avgForecastWindspeed = _u.round( totalWindspeedValues / _d.sa_forecastHours, 1 )
                        dz.log( 'Found average windspeed = ' .. _d.sa_avgForecastWindspeed .. ' in ' .. _d.sa_forecastHours .. ' records.', dz.LOG_DEBUG )
                        
                        -- Now update our meteowinddevice, which immediately triggers d-StookwijzerLokaal script to produce the advice.
                        dz.log( 'Update ' .. dz.devices( meteowind_idx ).name  .. '.', dz.LOG_DEBUG )
                        dz.devices( meteowind_idx ).updateWind( windDirection, windDirectionString, windSpeedMs, windGust, temperature, chill )
                    else
                        dz.log( 'No result_table found', dz.LOG_ERROR )
                    end
    			else
                    dz.log( 'Item or JSON - NOT OK', dz.LOG_ERROR )
    			end
            end
        end
	end
}
-- That's All --------------------------------------------------
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

global_data

Code: Select all

-- 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-OpenMeteo, d-StookwijzerLokaal.
		sa_avgForecastLKI = { initial = 0 },
		sa_avgForecastWindspeed = { initial = 0 },
		sa_forecastTable =  { initial = {} },
		sa_forecastSetpointTable =  { initial = {} },
		sa_forecastHours = { initial = 0 },
		sa_useNewRIVMcriteria = { initial = true },                 
	},

	------------------------------------------
	-- Global helper functions
	helpers = {
		------------------------
		-- Used in dt-OpenMeteo, t-Weerlive, t-Airplanes,
		getDirectionfromDegree = function( degrees )
			-- To calculate the degrees.
			local directionString = ''
			-- When string is not in Enlish, the icon does not appear!
			if degrees >= 0 and degrees < 11.25 then directionString = 'N' 
			elseif degrees >= 11.25 and degrees < 33.75 then directionString = 'NNE'
			elseif degrees >= 33.75 and degrees < 56.25 then directionString = 'NE'
			elseif degrees >= 56.25 and degrees < 78.75 then directionString = 'ENE'
			elseif degrees >= 78.75 and degrees < 101.25 then directionString = 'E'
			elseif degrees >= 101.25 and degrees < 123.75 then directionString = 'ESE'
			elseif degrees >= 123.75 and degrees < 146.25 then directionString = 'SE'
			elseif degrees >= 146.25 and degrees < 168.75 then directionString = 'SSE'
			elseif degrees >= 168.75 and degrees < 191.25 then directionString = 'S'
			elseif degrees >= 191.25 and degrees < 213.75 then directionString = 'SSW'
			elseif degrees >= 213.75 and degrees < 236.25 then directionString = 'SW'
			elseif degrees >= 236.25 and degrees < 258.75 then directionString = 'WSW'
			elseif degrees >= 258.75 and degrees < 281.25 then directionString = 'W'
			elseif degrees >= 281.25 and degrees < 303.75 then directionString = 'WNW'
			elseif degrees >= 303.75 and degrees < 326.25 then directionString = 'NW'
			elseif degrees >= 326.25 and degrees < 348.75  then directionString = 'NNW'
			elseif degrees >= 348.75 and degrees <= 360 then directionString = 'N' end
			return directionString
		end,
	}
}
-- That's All --------------------------------------------------
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
janpep
Posts: 268
Joined: Thursday 14 March 2024 10:11
Target OS: Linux
Domoticz version: 2025.1
Location: Netherlands
Contact:

Re: Dutch local 'Stookwijzer'

Post by janpep »

d-StookwijzerLokaal

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
-- 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 hours counts down when online information is not updatesd.
--     (remains future hours stored online, until we pass the end of it)
-- Depends on LKI device (= local Air Quality Index based on the coordinates).
-- Depends on wind device. (Local windspeed based on given coordinates).
-- Depends on stored average LKI for coming hours stored in global_data.
-- Getting alertLevel by number: 0=gray, 1=green, 2=yellow, 3=orange, 4=red
-- Setting alertLevel by constant:  domoticz.ALERTLEVEL_GREY, ALERTLEVEL_GREEN, ALERTLEVEL_YELLOW, ALERTLEVEL_ORANGE, ALERTLEVEL_RED
-- 25-03-2024 added two variables to override toggle if and when you want to override Orange warning definition. 
--  This is reasonable when LKI is 5, 6 or 7 and windspeed is acceptable.
--  Override is toggled by setting local pepsOverridemode = true (False = off)
--  Acceptable windspeed can be set by local acceptableWindspeed = number
--  I call it 'Code blauw-PLUS' - The alertcolor remains green.
-- 26-03-2024 Improved advice based on windspeed forecast.
--  Depends on stored average Windspeed for coming hours stored in global_data.
--  Calculates forecast advice based on avg LKI and avg wind speed in coming hours.
-- 08-04-2024 Changed color definition. See: https://iplo.nl/thema/lucht/houtstook-de-stookwijzer-en-het-stookalert/
--  Colors and lineheight are set in variables.
-- 29-05-2024 Directly make use of dz.log instead of the 'logthis' function in global_data.
-- 29-05-2024 Replaced \n by <br> for html string, to let following lines also appear in the log.
-- 13-02-2025 Changed the colors and alert text. It did not look nice. Also did not like the code text.
--  You can set both background and foreground color in variables as you wish.
--  I translate code yellow to green (Geen stookalert). With yellow they avoid green as a Clearly allowed. :-)
--  If pepsOverridemode = true (Lichte waarschuwing stookalert), then darker yellow (not yet orange). Then alert icon is still green.
--  Code orange (Waaarschuwing stookalert.)
--  Code red (Stookalert!).
-- A lot of changes are based on changes in dt-OpenMeteo.
--  Added check on _d.sa_forecastSetpointTable to show forecast value AT the setpoint time in stead of average UNTIL setpoint time.
--  Added code to show the colored footer. Detects number of hours that are present in table.
--  0-24 shows one value per hour, 25-48 shows average per 2 hours. 49-72 wh9ows average per 3 hours.
--  Calculates the colorcode for each individual time (or averaged period).
--  Many variables renamed to better fit with the drastic changes.
--  Take care with LOG_DEBUG. complete tables are logged multiple times to keep track of the data changes.
--  Added optional clearHistoryLog to clear the log of sa_alert_idx (needs also IP address and portnumber.


--- Your settings ----------------------------------------------------------------
-- First set the used device index numbers and variables you might want to change.
local meteowind_idx = 99999   --idx of your custom meteowind sensor, which triggers this script.
local meteolki_idx = 99999         -- idx of your 'Luchtkwaliteitsindex' device
local meteolki2_5_idx = 99999        -- idx of your custom sensor 'Luchtkwaliteitsindex fijnstof'
local sa_alert_idx = 99999        -- idx of the Local Stookwijzer sensor to store the advice.
-- Set overrule for situation where LKI between 5 and 6, but windspeed > acceptable windspeed
local pepsOverridemode = true		-- true to enable the override. False to turn if off.
local acceptableWindspeed = 5.5	-- Adjust the limit for the wind speed to be used in override mode.
local windTextLong = true       -- False only Wind direction and m/s next to LKI.
local useForecastText = true       -- False Skip forcast LKI and Wind (average until or value at with increase or decrease signs)

-- Settings for clearing the log (first time it runt on the day).
local clearHistoryLog = true
local dz_ip = 'yourIPaddress'
local dz_port = <yourpornumber>

----------------------------------------------------------------------------------
return {
	on = {
		devices = {
			meteowind_idx,  -- Update of meteowind, after update of LKI, triggers this script to run.
		},
	},
    data = {
        -- Persistant data to use in the next run of this script.
        saCleaningDate = { initial = '' },
    },
	logging = {
		-- Level can be domoticz.LOG_INFO, domoticz.LOG_STATUS, domoticz.LOG_ERROR or domoticz.LOG_DEBUG
		level = domoticz.LOG_STATUS,
		--level = domoticz.LOG_DEBUG,
		marker = "LokaleStookwijzer-"
	},
	execute = function( dz, triggeredItem )
		-- Set Local environment=================
        local _u = dz.utils     -- Holds subset of handy utilities.
		--local _h = dz.helpers         -- Holds the global functions
		local _d = dz.globalData      -- Holds the global data

        local numFooterRecords = 0
        
        local lkiValue = 0
		-- Get current LKI value
        if _d.sa_useNewRIVMcriteria == true then
		    lkiValue = dz.devices( meteolki2_5_idx ).sensorValue
	    else
	        lkiValue = dz.devices( meteolki_idx ).sensorValue
        end

        -- Use some color variables
        local htmlAdviceColor = '#000000;'  -- Default = Black
        local htmlForecastColor = '#000000;' -- Default = Black\
        local cWhite = '#FFFFFF;'
        local cMagenta = '#ff00ff;'
        local cDarkGray = '#808080;'
        local cLightGray = '#d8d8d8;'
        local cGreen = '#008000;'
        local cBlue = '#0000FF;'
        local cYellow = '#ffff00;'
        local cDarkYellow = '#ffdb00;'
        local cOrange = '#ffa500;'
        local cRed = '#ff0000;'	
        local cTheme = '#F1F5FA;' -- Default theme background color
        local htmlDarkBackgroundColor = cDarkGray
        local htmlLightBackgroundColor = cTheme
        
        --Adjust lineheight
        local lineHeight = 1.1

        local convertedFooterTable = {}
        
        -- Local Functions go here =============
        local function getSetpointTimeAfterHours( hours )
            -- Get the current hour and add 10 hours
            local currentHour = os.date("%H")
            local targetHour = (tonumber(currentHour) + tonumber( hours ) ) % 24
            -- Format the target hour as hh:mm
            local targetTime = string.format( "%02d:%02d", targetHour, 0 )
            return targetTime
        end

        local function getFooterFieldCount()
            --How many fields for Footer
            local count = 0
            for _, row in ipairs( _d.sa_forecastTable ) do
                if row.inFooter == true then
                    count = count + 1
                end
            end            
            return count
        end
        
        local function updateconvertedFooterTable()
	        for i = 1, #convertedFooterTable do
                local lki = convertedFooterTable[i].LKI
                local wind = convertedFooterTable[i].windSpeed
                if _d.sa_useNewRIVMcriteria == true then
                    if lki < 1 and wind < 1 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cDarkGray  .. ';">0</span>'
                        elseif lki <= 3 and wind >= 3.4 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cGreen  .. ';">&#8803;</span>'
                        elseif pepsOverridemode == true and lki >= 4 and lki <= 6 and wind >= acceptableWindspeed then convertedFooterTable[i].alertColor = '<span style="color: ' .. cDarkYellow  .. ';">&#8803;</span>'
                        elseif lki >= 4 and lki <= 6 and wind >= 3.4 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cOrange  .. ';">&#8803;</span>'
                        elseif lki >= 7 or wind <= 3.3 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cRed  .. ';">&#8803;</span>'
		            end                    
                else
                    if lki < 1 and wind < 1 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cDarkGray  .. ';">0</span>'
                        elseif lki <= 4 and wind > 2 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cGreen  .. ';">&#8803;</span>'
                        elseif pepsOverridemode == true and lki >= 5 and lki <= 7 and wind >= acceptableWindspeed then convertedFooterTable[i].alertColor = '<span style="color: ' .. cDarkYellow  .. ';">&#8803;</span>'
                        elseif lki >= 5 and lki <= 7 and wind > 2 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cOrange  .. ';">&#8803;</span>'
                        elseif lki >= 8 or wind <= 2 then convertedFooterTable[i].alertColor = '<span style="color: ' .. cRed  .. ';">&#8803;</span>'
		            end
	            end
	        end
        end

        local function updateforecastSetpointTable()
            local lki = _d.sa_forecastSetpointTable.LKI--sa_forecastSetpointTable
            local wind = _d.sa_forecastSetpointTable.windSpeed
            local setpointTime = getSetpointTimeAfterHours( _d.sa_forecastHours )
            dz.log( 'forecastSetpointTable check: LKI =  ' .. lki .. '; wind = ' .. wind .. '; setpoint = ' .. _d.sa_forecastHours .. '; setpointTime = ' .. setpointTime, dz.LOG_DEBUG )
            if _d.sa_useNewRIVMcriteria == true then
                if lki <= 3 and wind >= 3.4 then _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cGreen  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                    elseif pepsOverridemode == true and lki >= 4 and lki <= 6 and wind >= acceptableWindspeed then _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cDarkYellow  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                    elseif lki >= 4 and lki <= 6 and wind >= 3.4 then _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cOrange  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                    elseif lki >= 7 or wind <= 3.3 then  _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cRed  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                end
            else
                if lki <= 4 and wind > 2 then _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cGreen  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                    elseif pepsOverridemode == true and lki >= 5 and lki <= 7 and wind >= acceptableWindspeed then _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cDarkYellow  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                    elseif lki >= 5 and lki <=7 and wind > 2 then _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cOrange  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                    elseif lki >= 8 or wind <= 2 then  _d.sa_forecastSetpointTable.alertText = '<span style="color: ' .. cRed  .. ';">Verwachting voor ' .. setpointTime ..' uur: </span>'
                end
	        end
        end

		--====================
		-- Two functions to calculate averages of multiple rows to combine them:
		-- 1. The calculateAverage calculates the average of a given field over a specified range of rows,
		-- and convertTable uses this function to create a new table with the desired averages.
		-- 2. The convertTable function iterates over the original table in steps of numRows,
		-- calculating the average for each range of rows and storing the result in the new table.
		local function calculateAverage( table, startRow, stopRow, field )
			local sum = 0
			for i = startRow, stopRow do
				sum = sum + table[i][field]
			end
			return sum / (stopRow - startRow + 1)
		end

		--- Function to convert original table to new table with average per numRows.
		local function convertTable( origTable, numRecords, numRows )
			local newTable = {}
			local intLoopCounter = 0
			dz.log( 'In function we have to convert ' .. numRecords .. ' numRecords per ' .. numRows .. ' numRows.', dz.LOG_DEBUG )	
			
            dz.log( '==== Calculated averaged results ================================================', dz.LOG_DEBUG )
			for i = 1, numRecords, numRows do
			    intLoopCounter = intLoopCounter + 1
				local startRow = i
				local stopRow = math.min( i + numRows - 1, numRecords )
                local lki = calculateAverage( origTable, startRow, stopRow, "LKI" )
                local wind = calculateAverage( origTable, startRow, stopRow, "windSpeed" )
                dz.log( 'Combined record ' .. intLoopCounter .. ' for rows ' .. startRow .. '-' .. stopRow .. ' gives a calculated avglki = ' .. lki .. ' and avgwind = ' .. wind, dz.LOG_DEBUG )
                local newRecord = {
					    LKI = _u.round( lki, 0 ),
					    windSpeed = _u.round( wind, 1 ),
					    alertColor = ''
				        }
                table.insert( newTable, newRecord )
    		end
	        dz.log( 'After conversion and rounding, the newTable has ' .. #newTable .. ' rows.', dz.LOG_DEBUG )	
			return newTable
		end


        --===========================================================
		-- Now start to do something ============
        if clearHistoryLog == true then
            local saToday = dz.time.dateToDate(dz.time.rawDate,'yyyy-mm-dd', 'dd-mmm-yyyy', 0 )
            -- ONLY For testing set to today first to today
            --dz.data.saCleaningDate = dz.time.dateToDate(dz.time.rawDate,'yyyy-mm-dd', 'dd-mmm-yyyy', 0 )
            if dz.data.saCleaningDate == saToday then
                -- History not cleaned yet, so do it.
                dz.log( 'Do history cleaning for today!', dz.LOG_DEBUG )
		        -- Clear Lokale Stookwijzer log
                dz.openURL('http://' .. dz_ip .. ':' ..dz_port .. '/json.htm?type=command&param=clearlightlog&idx='.. sa_alert_idx )
                --Set the date for tomorrow.
                dz.data.saCleaningDate = dz.time.dateToDate(dz.time.rawDate,'yyyy-mm-dd', 'dd-mmm-yyyy', 3600 * 24 )
            end
        end
		
		-- Check the Wind ===========================================
		local windSpeedMs = dz.devices( meteowind_idx ).speedMs  -- Gets the current value needed for calculation
		local windDirection = dz.devices( meteowind_idx ).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 )
    	
		if windTextLong == true then
		    -- Cannot remember from where I got these naming definitions.
            if windSpeedMs >= 0 and windSpeedMs <= 0.2 then windText = '<br>Windstil (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596;0 bft &#8596; < 1 km/u'
                elseif windSpeedMs >= 0.3 and windSpeedMs <= 1.5 then windText = '<br>Zeer zwakke wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 1 bft&#8596; 1-5 km/u'
                elseif windSpeedMs >= 1.6 and windSpeedMs <= 3.3 then windText = '<br>Zwakke wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 2 bft &#8596; 6-11 km/u'
                elseif windSpeedMs >= 3.4 and windSpeedMs <= 5.4 then windText = '<br>Zeer matige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 3 bft &#8596; 12-19 km/u'
                elseif windSpeedMs >= 5.5 and windSpeedMs <= 7.9 then windText = '<br>Matige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 4 bft &#8596; 20-28 km/u'
                elseif windSpeedMs >= 8.0 and windSpeedMs <= 10.7 then windText = '<br>Vrij krachtige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 5 bft &#8596; 29-38 km/u'
                elseif windSpeedMs >= 10.8 and windSpeedMs <= 13.8 then windText = '<br>Krachtige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 6 bft &#8596; 39-49 km/u'
                elseif windSpeedMs >= 13.9 and windSpeedMs <= 17.1 then windText = '<br>Harde wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 7 bft &#8596; 50-61 km/u'
                elseif windSpeedMs >= 17.2 and windSpeedMs <= 20.7 then windText = '<br>Stormachtige wind (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 8 bft &#8596; 62-74 km/u'
                elseif windSpeedMs >= 20.8 and windSpeedMs <= 24.4 then windText = '<br>Storm (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 9 bft &#8596; 75-88 km/u'
                elseif windSpeedMs >= 24.5 and windSpeedMs <= 28.4 then windText = '<br>Zware storm (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 10 bft &#8596; 89-102 km/u'
                elseif windSpeedMs >= 28.5 and windSpeedMs <= 32.6 then windText = '<br>Zeer zware storm (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s &#8596; 11 bft &#8596; 103-117 km/u'
                elseif windSpeedMs > 32.6 then windText = '<br>Orkaan (' .. windDirection .. ') ' .. windSpeedMs .. ' m/s &#8596; 12 bft &#8596; >117 km/u'
            end
            --Add lineheight
            windText = '<span style="line-height:' .. lineHeight .. ';">' .. windText .. '</span>'
	    else
            if windSpeedMs >= 0 and windSpeedMs <= 0.2 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 0.3 and windSpeedMs <= 1.5 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 1.6 and windSpeedMs <= 3.3 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 3.4 and windSpeedMs <= 5.4 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 5.5 and windSpeedMs <= 7.9 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 8.0 and windSpeedMs <= 10.7 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 10.8 and windSpeedMs <= 13.8 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 13.9 and windSpeedMs <= 17.1 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 17.2 and windSpeedMs <= 20.7 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 20.8 and windSpeedMs <= 24.4 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 24.5 and windSpeedMs <= 28.4 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs >= 28.5 and windSpeedMs <= 32.6 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. 'm/s'
                elseif windSpeedMs > 32.6 then windText = ' - Wind: (' .. windDirection .. ') ' .. windSpeedMs .. ' m/s'
            end
        end

        --===================================
        -- Prepare the forecast prefix sign
		-- Set the sign for expected increase or decrease. arrowup = &#8593; arrowdown = &#8595; equal = &#8596;
        local lkiSign = '<strong><span style="color: ' .. cMagenta .. '">&#8596;</span></strong>'
		if _d.sa_avgForecastLKI > lkiValue then
		      lkiSign = '<strong><span style="color: ' .. cRed .. '">&#9650;</span></strong>'
		elseif _d.sa_avgForecastLKI < lkiValue then
		      lkiSign = '<strong><span style="color: ' .. cGreen .. '">&#9660;</span></strong>'
		end
        local windSign = '<strong><span style="color: ' .. cMagenta .. '">&#8596;</span></strong>'
		if _d.sa_avgForecastWindspeed > windSpeedMs then
		      windSign =  '<strong><span style="color: ' .. cGreen .. '">&#9650;</span></strong>'
		elseif  _d.sa_avgForecastWindspeed < windSpeedMs then
		      windSign = '<strong><span style="color: ' .. cRed .. '">&#9660;</span></strong>'
		end

        --===================================       
        -- Calculate and set the CURRENT alertText and code color.

        if _d.sa_forecastHours == 0 and _d.sa_avgForecastLKI == 0 then
	    		--Code Gray; No LKI data for current hour received.
	    		alertText = 'Niet beschikbaar!! Laatste LKI was '  .. lkiValue .. windText
	    		alertLevel = dz.ALERTLEVEL_GRAY
	    else
            if _d.sa_useNewRIVMcriteria == true then
                if lkiValue <= 3 and windSpeedMs >= 3.4 then
                    --Code = Yellow; You can light the fireplace, but take the neighbors into account. I set the Yellow to Green.
                    --LKI <= 4 and windsspeed > 2m/sec.
                    htmlAdviceColor = cGreen
                    htmlBackgroundColor = htmlLightBackgroundColor
                    alertText = '<span style="background-color: ' .. htmlBackgroundColor  .. '; color: ' ..  htmlAdviceColor .. ';">Geen stookalert.</span> LKI: '  .. lkiValue .. windText
                    alertLevel = dz.ALERTLEVEL_GREEN
                elseif pepsOverridemode == true and lkiValue >= 4 and lkiValue <= 6 and windSpeedMs >= acceptableWindspeed then
			        -- Added personal level Blauw-PLUS. LKI is higher, but there is wind.
		            htmlAdviceColor = cDarkYellow
		            htmlBackgroundColor = htmlDarkBackgroundColor
			        --Code = Blauw; You can light the fireplace, but take the neighbors into account.
			        --alertText = '<span style="background-color: ' ..  htmlAdviceColor .. '">Code geel-PLUS</span> LKI: '  .. lkiValue .. windText
			        alertText = '<span style="background-color: ' ..  htmlBackgroundColor .. '; color: ' ..  htmlAdviceColor .. ';">Lichte waarschuwing stookalert.</span> LKI: '  .. lkiValue .. windText
			        alertLevel = dz.ALERTLEVEL_GREEN                    
                elseif lkiValue >= 4 and lkiValue <= 6 and windSpeedMs >= 3.4 then
                    --Code Orange; It is better not to burn wood now.
                    --LKI >= 5 and <= 7 and windspeed > 2m/sec.
                    htmlAdviceColor = cOrange
                    htmlBackgroundColor = htmlLightBackgroundColor
                    alertText = '<span style="background-color: ' ..  htmlBackgroundColor .. '; color: ' ..  htmlAdviceColor .. ';">Waaarschuwing stookalert.</span> LKI: '  .. lkiValue .. windText
                    alertLevel = dz.ALERTLEVEL_ORANGE
                elseif lkiValue >= 7 or windSpeedMs <= 3.3 then
                    --Code Red; Do not burn wood.
                    --LKI >= 8 and/or the windspeed is low (<= 2m/sec).
                    htmlAdviceColor = cRed
                    htmlBackgroundColor = htmlLightBackgroundColor
                    alertText = '<span style="background-color: ' ..  htmlBackgroundColor .. '; color: ' .. htmlAdviceColor  .. ';">Stookalert!</span> LKI: '  .. lkiValue .. windText
                    alertLevel = dz.ALERTLEVEL_RED
                end
            else
                if lkiValue <= 4 and windSpeedMs > 2 then
                    --Code = Yellow; You can light the fireplace, but take the neighbors into account. I set the Yellow to Green.
                    --LKI <= 4 and windsspeed > 2m/sec.
                    htmlAdviceColor = cGreen
                    htmlBackgroundColor = htmlLightBackgroundColor
                    alertText = '<span style="background-color: ' .. htmlBackgroundColor  .. '; color: ' ..  htmlAdviceColor .. ';">Geen stookalert.</span> LKI: '  .. lkiValue .. windText
                    alertLevel = dz.ALERTLEVEL_GREEN
                elseif pepsOverridemode == true and lkiValue >= 5 and lkiValue <= 7 and windSpeedMs >= acceptableWindspeed then
			        -- Added personal level Blauw-PLUS. LKI is higher, but there is wind.
		            htmlAdviceColor = cDarkYellow
		            htmlBackgroundColor = htmlDarkBackgroundColor
			        --Code = Blauw; You can light the fireplace, but take the neighbors into account.
			        --alertText = '<span style="background-color: ' ..  htmlAdviceColor .. '">Code geel-PLUS</span> LKI: '  .. lkiValue .. windText
			        alertText = '<span style="background-color: ' ..  htmlBackgroundColor .. '; color: ' ..  htmlAdviceColor .. ';">Lichte waarschuwing stookalert.</span> LKI: '  .. lkiValue .. windText
			        alertLevel = dz.ALERTLEVEL_GREEN
                elseif lkiValue >= 5 and lkiValue <=7 and windSpeedMs > 2 then
                    --Code Orange; It is better not to burn wood now.
                    --LKI >= 5 and <= 7 and windspeed > 2m/sec.
                    htmlAdviceColor = cOrange
                    htmlBackgroundColor = htmlLightBackgroundColor
                    alertText = '<span style="background-color: ' ..  htmlBackgroundColor .. '; color: ' ..  htmlAdviceColor .. ';">Waaarschuwing stookalert.</span> LKI: '  .. lkiValue .. windText
                    alertLevel = dz.ALERTLEVEL_ORANGE
                elseif lkiValue >= 8 or windSpeedMs <= 2 then
                    --Code Red; Do not burn wood.
                    --LKI >= 8 and/or the windspeed is low (<= 2m/sec).
                    htmlAdviceColor = cRed
                    htmlBackgroundColor = htmlLightBackgroundColor
                    alertText = '<span style="background-color: ' ..  htmlBackgroundColor .. '; color: ' .. htmlAdviceColor  .. ';">Stookalert!</span> LKI: '  .. lkiValue .. windText
                    alertLevel = dz.ALERTLEVEL_RED
                end
            end
        end
    
        --===================================
        -- Set the forecast colors
        if _d.sa_useNewRIVMcriteria == true then
            if _d.sa_avgForecastLKI <= 3 and _d.sa_avgForecastWindspeed >= 3.4 then
                htmlForecastColor = cGreen
            elseif pepsOverridemode == true and _d.sa_avgForecastLKI >= 4 and _d.sa_avgForecastLKI <= 6 and _d.sa_avgForecastWindspeed >= acceptableWindspeed then
                htmlForecastColor = cDarkYellow
            elseif _d.sa_avgForecastLKI >= 4 and _d.sa_avgForecastLKI <= 6 and _d.sa_avgForecastWindspeed >= 3.4 then
                htmlForecastColor = cOrange
            elseif _d.sa_avgForecastLKI >= 7 or _d.sa_avgForecastWindspeed <= 3.3 then
                htmlForecastColor = cRed
            end
        else
            if _d.sa_avgForecastLKI <= 4 and _d.sa_avgForecastWindspeed > 2 then
                htmlForecastColor = cGreen
            elseif pepsOverridemode == true and _d.sa_avgForecastLKI >= 5 and _d.sa_avgForecastLKI <= 7 and _d.sa_avgForecastWindspeed >= acceptableWindspeed then
                htmlForecastColor = cDarkYellow
                htmlBackgroundColor = htmlDarkBackgroundColor
            elseif _d.sa_avgForecastLKI >= 5 and _d.sa_avgForecastLKI <= 7 and _d.sa_avgForecastWindspeed > 2 then
                htmlForecastColor = cOrange
            elseif _d.sa_avgForecastLKI >= 8 or _d.sa_avgForecastWindspeed <= 2 then
                htmlForecastColor = cRed
            end
        end

        --===================================
        if useForecastText == true then 
		-- Generate the new forecast.
            if _d.sa_forecastSetpointTable.LKI > 0 then    -- We have content, so use it.
                -- Use the values AT setpoint time.
                updateforecastSetpointTable()
                alertText = alertText .. '<br>' .. _d.sa_forecastSetpointTable.alertText .. 'LKI ' .. _d.sa_forecastSetpointTable.LKI  .. ' en wind ' .. _d.sa_forecastSetpointTable.windSpeed .. ' m/s'
            else
                -- Use the average UNTIL setpoint time.
	            alertText = alertText .. '<br><span style="line-height:' .. lineHeight .. '; background-color: ' ..  htmlBackgroundColor .. '; color: ' .. htmlForecastColor .. '">x&#772; verwachting</span> <span style="color: ' .. cBlue .. '">' .. _d.sa_forecastHours .. '</span> uur: LKI' .. lkiSign .. _d.sa_avgForecastLKI .. ' en wind' .. windSign .. _d.sa_avgForecastWindspeed .. ' m/s'
            end
        end

        --===================================        
        -- Generate Colorbar for the footer if wanted.
        -- Count How many fields for footer.
        numFooterRecords = getFooterFieldCount()
        dz.log( 'Found a total of ' .. #_d.sa_forecastTable .. ' records, wherein ' .. numFooterRecords .. ' records with inFooter = true.' , dz.LOG_DEBUG )
        
        -- For debug show original _d.sa_forecastTable.
        dz.log( '==== Original Table  ============================================================', dz.LOG_DEBUG )
        for i = 1, #_d.sa_forecastTable do
            dz.log( '_d.sa_forecastTable Record ' .. i .. ' lki = ' .. _d.sa_forecastTable[i].LKI .. '; Wind = ' .. _d.sa_forecastTable[i].windSpeed .. '; inFooter = ' .. tostring( _d.sa_forecastTable[i].inFooter ) .. '; Color = ' ..  _d.sa_forecastTable[i].alertColor , dz.LOG_DEBUG )
        end        
        
        if numFooterRecords > 0 then
            -- There are fields marked to display in the footer.
            local numCombineRows = 0
            if numFooterRecords <= 24 then
                numCombineRows = 1 -- value of fields per hour.
            elseif numFooterRecords >= 25 and numFooterRecords <= 48 then
                numCombineRows = 2 -- average of fields per 2 hour.
            elseif numFooterRecords >= 49 then
                numCombineRows = 3 -- average of fields per 3 hour.
            end
            
            -- Create a new table with wanted number of records.
            convertedFooterTable = convertTable( _d.sa_forecastTable, numFooterRecords, numCombineRows )
            
            -- Update records with Color            
            updateconvertedFooterTable()
            
            -- Start the footer
            local alertFooter = ''
            if numFooterRecords <= 24 then
                alertFooter = '<span style="font-size: 0.7rem;">' ..  numFooterRecords .. ' uur ' 
            else
                alertFooter = '<span style="font-size: 0.7rem;">' ..  numFooterRecords .. ' uur (x&#772; per ' .. numCombineRows .. ') '
            end

            dz.log( '==== New converted table with color added =======================================', dz.LOG_DEBUG )
            for i = 1, #convertedFooterTable do
                dz.log( 'convertedFooterTable Record ' .. i .. ' lki = ' .. convertedFooterTable[i].LKI .. '; Wind = ' .. convertedFooterTable[i].windSpeed .. '; Color = ' ..  convertedFooterTable[i].alertColor , dz.LOG_DEBUG )
                --alertFooter = alertFooter .. _d.sa_forecastTable[i].alertColor
                alertFooter = alertFooter .. convertedFooterTable[i].alertColor
            end
            -- Close the footer.
            alertFooter = alertFooter ..  '</span>'
            dz.log( 'Footer =  ' .. alertFooter  .. '.', dz.LOG_DEBUG )
            alertText = alertText .. '<br>' .. alertFooter
        end
        
        --===================================        
		-- Now finally update with new alertLevel and alertText we have found.
        dz.log( 'Update ' .. dz.devices( sa_alert_idx ).name  .. '.', dz.LOG_DEBUG )
		dz.devices( sa_alert_idx ).updateAlertSensor( alertLevel, alertText )
	end
}
-- That's All --------------------------------------------------

I hope I have not forgotten anything important. In that case, please let me know.
Dz on Ubuntu VM on DS718+ behind FRITZ!Box.
EvoHome; MELCloud; P1 meter; Z-Stick GEN5; Z-Wave-js-ui; Sonoff USB-Dongle Plus-E; Zigbee2Mqtt; MQTT; Greenwave powernodes 1+6; Fibaro switch, plugs, smoke; FRITZ!DECT 200. Scripts listed in profile interests.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest