Malheureusement la station la plus proche pour moi ne remonte ses données que très rarement sur le réseau aqicn.org, il n'est pas rare de constater l'absence de mise à jour depuis plusieurs mois.
Du coup c'est une station à plus de 200 km de mon domicile qui est sélectionnée, c'est moins pertinent.
J'ai conservé l'esprit du programme de Papoo pour utiliser les données issues de ma région.
Il n'existe pas d'API, le programme est donc basé sur le mapping d'une page html du site http://www.atmo-hdf.fr/.
L'indice de pollution est déterminé toutes les 15 mn selon la norme française Atmo qui va de 1 - Très bon à 10 - Très mauvais.
Si la station renvoie une mesure qui date de plus de 3 heures, elle n'est pas mise à jour dans Domoticz, ce qui se traduira plus tard par un bandeau rouge sur l'indicateur correspondant, témoin de cette ancienneté.
J'ai gardé le principe d'alerte qu'avait mis au point Papoo, tout en évitant les répétitions ; l'alerte n'est envoyée que s'il y a un changement d'indice de pollution.
Pour personnaliser le script, il faut remplacer <station> par le code d'une une station parmi les suivantes :
1134 Saint-Amand-les-Eaux
1079 Noeux-les-Mines
1114 Wattignies
1115 Lens Varsovie
1137 Boulogne-sur-Mer
1138 Lille Leeds
1539 Amiens 14 Juillet.
1541 Saint-Quentin Victor Hugo
1139 Isbergues
1142 Saint-Pol-sur-Mer
1523 Arrest
1534 Beauvais Aéroport
1527 Creil
1509 Nogent sur Oise
1530 Rieux
1526 Saint-Quentin Stade
1529 Roye
1519 Salouël
1125 Saint-Laurent-Blangy
1110 Béthune Stade
1049 Outreau
1047 Sangatte
1120 Calais Berthelot
1126 Calais Parmentier
1030 Malo-les -Bains
1038 Mardyck
1118 Gravelines PC/Drire
1127 Cappelle-la-Grande
1129 Grande-Synthe
1003 Douai Theuriet
1093 Harnes
1007 Maubeuge
1065 Lille Fives
1073 Roubaix Serres
1059 Marcq-en-Baroeul
1066 Halluin
1132 Campagne-lès-Boulonnais
1133 Cartignies
1051 Saint-Omer
1001 Valenciennes Acacias
1006 Valenciennes Wallon
1011 Denain
1535 Amiens Saint-Pierre
1542 Beauvais Dr. Lamotte
Il faut aussi créer la variable Atmo_indice avec pour valeur initiale 0.
Elle servira à mémoriser l'indice Atmo pour ne pas renvoyer d'alerte si le niveau de change pas.
Comme d'habitude, les id des devices créés dans Domoticz pour les mesures seront indiqués dans le script.
Il faut aussi renseigner le chemin vers votre cURL.
J'ai noté 2 évolutions dans ma todo list :
- Permettre de switcher l'indice Atmo en indice CiteAir Européen ; ça permet de se comparer à d'autres villes en Europe.
- Ajouter la faculté d'utiliser plusieurs stations pour mesurer l'ensemble des polluants ; toutes les stations ne les mesurent pas systématiquement.
Le script :
Code : Tout sélectionner
#!/usr/bin/lua
--[[
Script récoltant les données des polluants mesurés dans une ville des Hauts de France
Auteur : Chrominator (quelques morceaux de code viennent du script_time_qualite_air.lua et ont pour auteur papoo)
cf https://easydomoticz.com/forum/viewtopic.php?f=17&t=4044&hilit=script_time_qualite_air.lua
Version 1.0
Changes :
2017-09-09 : Initial version
2017-09-27 : Warn only in case of index change + detail of pollutant indexes sent in notification
2017-10-27 : Exit if no pollutant is available from the station
2019-04-04 : stations document moved from http to https - changed.
2020-01-03 : Particles pollutant names changed - no parenthesis anymore since 2019-12-12.
TODO : Convert in European Citeair indexes on demand
Ability to use several stations in order to get all pollutants.
Principe :
Récupération de la page affichant les mesures des Hauts de France http://www.atmo-hdf.fr/acceder-aux-donnees/mesures-des-stations.html
La page est téléchargée, elle contient toutes les mesures de toutes les stations
puis focus sur la station ciblée parmi les suivantes :
1134 Saint-Amand-les-Eaux
1079 Noeux-les-Mines
1114 Wattignies
1115 Lens Varsovie
1137 Boulogne-sur-Mer
1138 Lille Leeds
1539 Amiens 14 Juillet.
1541 Saint-Quentin Victor Hugo
1139 Isbergues
1142 Saint-Pol-sur-Mer
1523 Arrest
1534 Beauvais Aéroport
1527 Creil
1509 Nogent sur Oise
1530 Rieux
1526 Saint-Quentin Stade
1529 Roye
1519 Salouël
1125 Saint-Laurent-Blangy
1110 Béthune Stade
1049 Outreau
1047 Sangatte
1120 Calais Berthelot
1126 Calais Parmentier
1030 Malo-les -Bains
1038 Mardyck
1118 Gravelines PC/Drire
1127 Cappelle-la-Grande
1129 Grande-Synthe
1003 Douai Theuriet
1093 Harnes
1007 Maubeuge
1065 Lille Fives
1073 Roubaix Serres
1059 Marcq-en-Baroeul
1066 Halluin
1132 Campagne-lès-Boulonnais
1133 Cartignies
1051 Saint-Omer
1001 Valenciennes Acacias
1006 Valenciennes Wallon
1011 Denain
1535 Amiens Saint-Pierre
1542 Beauvais Dr. Lamotte
Chaque polluant est comparé aux seuils définissant l'indice de pollution.
1 = Très bon 10 = Très mauvais
L'indice le plus haut parmi SO² NO² O³ et PM10 procure l'indice de qualité de l'air.
]]---------------------------------------------------------------------------------------------------
--variables à modifier----------------
-- Renseigner le chemin vers votre cURL
-- OSx : à définir
if package.config:sub(1,1) == '\\' then curl = "D:/Tools/curl-7.55.1-win64-mingw/bin/curl.exe" -- Windows
else curl = "/usr/bin/curl" -- Linux
end
-- n° de la station qui mesure la qualité de l'air
-- (Voir valeur dans les commentaires ci-dessus)
local station = <station> -- Le code de votre station sans les < >
local NO2_indice = 0
local O3_indice = 0
local PM10_indice = 0
local PM25_indice = 0
local SO2_indice = 0
local poldom = "SO2"
local laps = 3 -- Nombre d'heures au terme desquelles on ne met plus à jour domoticz (la mesure est trop ancienne).
local dz_air_quality = nil -- renseigner l'id du device Qualité de l'air associé si souhaité, sinon nil (custom, nom de l'axe : Atmo)
local dz_poll_dominant = nil -- renseigner l'id du device polluant dominant associé si souhaité, sinon nil (type text)
local dz_indice_alert = nil -- renseigner l'id du device alert pollution associé si souhaité, sinon nil (dummy - alert)
local dz_so2 = nil -- renseigner l'id du device dioxyde de soufre associé si souhaité, sinon nil (custom, nom de l'axe : µg/m³)
local dz_no2 = nil -- renseigner l'id du device dioxyde d'azote associé si souhaité, sinon nil (custom, nom de l'axe : µg/m³)
local dz_no = nil -- renseigner l'id du device monoxyde d'azote associé si souhaité, sinon nil (custom, nom de l'axe : µg/m³)
local dz_o3 = nil -- renseigner l'id du device Ozone associé si souhaité, sinon nil (custom, nom de l'axe : µg/m³)
local dz_pm10 = nil -- renseigner l'id du device taux de particules associé si souhaité, sinon nil (custom, nom de l'axe : µg/m³)
local dz_pm25 = nil -- renseigner l'id du device taux de particules associé si souhaité, sinon nil (custom, nom de l'axe : µg/m³)
local dz_city_name = nil -- renseigner l'idx du device city_name associé si souhaité, sinon nil (type text)
local send_notification = 5 -- 0: aucune notification, 1: toutes, 4: Bon, 5: Moyen, 7: Médiocre, 9: Mauvais, 10: Très mauvais
DEBUG = false
function voir_les_logs (s) -- nécessite la variable DEBUG
if DEBUG then
if s ~= nil then
print ("<font color='#f3031d'>".. s .."</font>")
else
print ("<font color='#f3031d'>aucune valeur affichable</font>")
end
end
end
-- Decode les paramètres de la station ou de la mesure
function DecodeParm(chaine, parm)
-- Renvoie la valeur du parametre
i, j = string.find(chaine, '"'..parm..'"')
k, l = string.find(chaine, ':"', j)
if k == nil then return nil end
m, n = string.find(chaine, '","', j)
if m == nil then
m, n = string.find(chaine, '"}', j)
end
return string.sub(chaine, l+1, m-1)
end
-- Recherche la mesure du polluant et renvoie les valeurs mesurées
function DecodePol(chaine, pol)
cherche='"NCON":"'..pol..'",'
-- Renvoie les informations du polluant
i, j = string.find(chaine, cherche)
k, l = string.find(chaine, '},', j)
poluant = string.sub(chaine, j, k)
-- voir_les_logs(poluant)
return DecodeParm(poluant, "J_DATE"), DecodeParm(poluant, "UNITE"), DecodeParm(poluant, "value")
end
-- Transforme les caractères unicodes en caractères lisibles.
function Unicode(chaine)
chaine = string.gsub(chaine, "\\u00e0", "à")
chaine = string.gsub(chaine, "\\u00e2", "â")
chaine = string.gsub(chaine, "\\u00e4", "ä")
chaine = string.gsub(chaine, "\\u00e7", "ç")
chaine = string.gsub(chaine, "\\u00e8", "è")
chaine = string.gsub(chaine, "\\u00e9", "é")
chaine = string.gsub(chaine, "\\u00ea", "ê")
chaine = string.gsub(chaine, "\\u00eb", "ë")
chaine = string.gsub(chaine, "\\u00ee", "î")
chaine = string.gsub(chaine, "\\u00ef", "ï")
chaine = string.gsub(chaine, "\\u00f4", "ô")
chaine = string.gsub(chaine, "\\u00f6", "ö")
chaine = string.gsub(chaine, "\\u00f9", "ù")
chaine = string.gsub(chaine, "\\u00fb", "û")
chaine = string.gsub(chaine, "\\u00fc", "ü")
return chaine
end
-- Fonction déterminant l'indice du polluant
function AttribueIndice(pol, idx)
for i=10, 1, -1 do
if tonumber(pol) <= idx[i] then
indice = i
end
end
return indice
end
-- Vérifie que la mesure n'est pas trop ancienne, pour décider de mettre à jour Domoticz ou pas.
function CheckLapse(DATE, outdated)
p="(%d+)-(%d+)-(%d+) (%d+):(%d+)"
year,month,day,hour,min=DATE:match(p)
horodatage = os.time({year=year,month=month,day=day,hour=hour,min=min})
voir_les_logs(os.date("%c", horodatage))
timelapse = os.time()-horodatage
voir_les_logs("Dernière mesure il y a "..tostring(timelapse).." secondes ("..os.date("%H", timelapse-3600).." heures "..os.date("%M", timelapse-3600).." minutes).")
if timelapse > 3600*outdated then return false
else return true
end
end
commandArray = {}
time = os.date("*t")
if time.min %15 == 0 then -- tous les 1/4 d'heure
--[[
Seuils de l’indice français Atmo
Valeurs obtenues sur http://www.atmo-hdf.fr/tout-savoir-sur-l-air/indice-de-la-qualite-de-l-air.html
--]]
-- Dioxyde de soufre (SO2)
SO2_idx = {}
SO2_idx[1]=39
SO2_idx[2]=79
SO2_idx[3]=119
SO2_idx[4]=159
SO2_idx[5]=199
SO2_idx[6]=249
SO2_idx[7]=299
SO2_idx[8]=399
SO2_idx[9]=499
SO2_idx[10]=9000
-- Poussières en suspension (PM10)
PM10_idx = {}
PM10_idx[1]=6
PM10_idx[2]=13
PM10_idx[3]=20
PM10_idx[4]=27
PM10_idx[5]=24
PM10_idx[6]=41
PM10_idx[7]=49
PM10_idx[8]=64
PM10_idx[9]=79
PM10_idx[10]=9000
-- Dioxyde d'azote (NO2)
NO2_idx = {}
NO2_idx[1]=29
NO2_idx[2]=54
NO2_idx[3]=84
NO2_idx[4]=109
NO2_idx[5]=134
NO2_idx[6]=164
NO2_idx[7]=199
NO2_idx[8]=274
NO2_idx[9]=399
NO2_idx[10]=9000
-- Ozone (0³)
O3_idx = {}
O3_idx[1]=24
O3_idx[2]=54
O3_idx[3]=79
O3_idx[4]=104
O3_idx[5]=129
O3_idx[6]=149
O3_idx[7]=179
O3_idx[8]=209
O3_idx[9]=239
O3_idx[10]=9000
-- Indice Atmo
Atmo = {}
Atmo[0]='0 - N/D'
Atmo[1]='1 - Très bon'
Atmo[2]='2 - Très bon'
Atmo[3]='3 - Bon'
Atmo[4]='4 - Bon'
Atmo[5]='5 - Moyen'
Atmo[6]='6 - Médiocre'
Atmo[7]='7 - Médiocre'
Atmo[8]='8 - Mauvais'
Atmo[9]='9 - Mauvais'
Atmo[10]='10 - Très Mauvais'
local atmo=assert(io.popen(curl..' "https://www.atmo-hdf.fr/acceder-aux-donnees/mesures-des-stations.html"'))
local InfoAtmo = atmo:read('*all')
atmo:close()
i, j = string.find(InfoAtmo, '"NSIT":"'..station..'"')
-- Si la station a bien été trouvée on continue, sinon fin de traitement
--
if i ~= 0 then
k, l = string.find(InfoAtmo, ',{', j)
site=string.sub(InfoAtmo, i, k-1)
-- Recherche des mesures de la station en question et réduction à ce périmètre.
i, j = string.find(site, '"MESURE"')
k, l = string.find(site, '}}')
mesure = string.sub(site, i, k)
LibelleSite = Unicode(DecodeParm(site, 'ISIT'))
Longi = DecodeParm(site, 'LONGI')
Lati = DecodeParm(site, 'LATI')
-- Récupère la liste des poluants mesurés
-- Toutes les stations ne mesurent pas tous les poluants
nopol = DecodeParm(site, "NOPOL")
if nopol == nil then voir_les_logs("Aucun poluant n'est mesuré sur cette station - Fin du programme")
else
voir_les_logs("Station : "..LibelleSite)
voir_les_logs("Longitude : "..Longi)
voir_les_logs("Latitude : "..Lati)
-- voir_les_logs(mesure)
if dz_city_name ~= nil then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_city_name ..'|0|'.. LibelleSite} end
-- Dioxyde de soufre, SO2
if string.find(nopol, "01") ~= nil then
SO2J_DATE, SO2unite, SO2 = DecodePol(mesure, "Dioxyde de soufre")
voir_les_logs("Dernière mesure : "..SO2J_DATE)
voir_les_logs("Unité "..SO2unite)
voir_les_logs("Mesure : "..SO2)
if dz_so2 ~= nil and CheckLapse(SO2J_DATE, laps) then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_so2 ..'|0|'.. SO2} end
SO2_indice = AttribueIndice(SO2, SO2_idx)
voir_les_logs("indice SO2 :"..SO2_indice)
end
-- Dioxyde d'azote NO2
if string.find(nopol, "03") ~= nil then
NO2J_DATE, NO2unite, NO2 = DecodePol(mesure, "Dioxyde d'azote")
voir_les_logs("Dernière mesure : "..NO2J_DATE)
voir_les_logs("Unité "..NO2unite)
voir_les_logs("Mesure : "..NO2)
if dz_no2 ~= nil and CheckLapse(NO2J_DATE, laps) then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_no2 ..'|0|'.. NO2} end
NO2_indice = AttribueIndice(NO2, NO2_idx)
voir_les_logs("indice NO2="..NO2_indice)
if SO2_indice < NO2_indice then poldom = "NO2" end
end
-- Monoxyde d'azote NO
if string.find(nopol, "02") ~= nil then
-- voir_les_logs(mesure)
NOJ_DATE, NOunite, NO = DecodePol(mesure, "Monoxyde d'azote")
voir_les_logs("Dernière mesure : "..NOJ_DATE)
voir_les_logs("Unité "..NOunite)
voir_les_logs("Mesure : "..NO)
if dz_no ~= nil and CheckLapse(NOJ_DATE, laps) then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_no ..'|0|'.. NO} end
end
-- Ozone O3
if string.find(nopol, "08") ~= nil then
O3J_DATE, O3unite, O3 = DecodePol(mesure, "Ozone")
voir_les_logs("Dernière mesure : "..O3J_DATE)
voir_les_logs("Unité "..O3unite)
voir_les_logs("Mesure : "..O3)
if dz_o3 ~= nil and CheckLapse(O3J_DATE, laps) then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_o3 ..'|0|'.. O3} end
O3_indice = AttribueIndice(O3, O3_idx)
voir_les_logs("indice O³="..O3_indice)
if math.max(SO2_indice, NO2_indice) < O3_indice then poldom = "O³" end
end
-- Particules (PM10)
if string.find(nopol, "24") ~= nil then
PM10J_DATE, PM10unite, PM10 = DecodePol(mesure, "Particules PM10")
voir_les_logs("Dernière mesure : "..PM10J_DATE)
voir_les_logs("Unité "..PM10unite)
voir_les_logs("Mesure : "..PM10)
if dz_pm10 ~= nil and CheckLapse(PM10J_DATE, laps) then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_pm10 ..'|0|'.. PM10} end
PM10_indice = AttribueIndice(PM10, PM10_idx)
voir_les_logs("indice PM10="..PM10_indice)
if math.max(SO2_indice, NO2_indice, O3_indice) < PM10_indice then poldom = "PM10" end
end
-- Particules (PM2.5)
if string.find(nopol, "39") ~= nil then
PM25J_DATE, PM25unite, PM25 = DecodePol(mesure, "Particules PM2.5")
voir_les_logs("Dernière mesure : "..PM25J_DATE)
voir_les_logs("Unité "..PM25unite)
voir_les_logs("Mesure : "..PM25)
if dz_pm25 ~= nil and CheckLapse(PM25J_DATE, laps) then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_pm25 ..'|0|'.. PM25} end
end
-- L'indice de qualité de l'air prends la valeur de l'indice le plus défavorable.
local air_quality = math.max(NO2_indice, O3_indice, PM10_indice, SO2_indice)
voir_les_logs("Qualité Air = "..air_quality)
voir_les_logs("Poluant dominant : "..poldom)
if dz_air_quality ~= nil then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_air_quality ..'|0|'.. air_quality} end
if dz_poll_dominant ~= nil then commandArray[#commandArray+1] = {['UpdateDevice'] = dz_poll_dominant ..'|0|'.. poldom} end
if uservariables['Atmo_indice'] ~= tostring(air_quality) then
commandArray['Variable:Atmo_indice'] = tostring(air_quality)
if dz_indice_alert ~= nil then
if tonumber(air_quality) <= 2 then -- niveau 1 ou 2
commandArray[#commandArray+1] = {['UpdateDevice'] = dz_indice_alert..'|1|Pas de Pollution'}
if send_notification > 0 and send_notification < 2 then
commandArray[#commandArray+1] = {['SendNotification'] = 'Alerte Pollution#Pas de Pollution!<br><br>Indice Atmo : '..Atmo[air_quality]..'<br>Poluant dominant : '..poldom..'<br><br>Indice NO2 : '..Atmo[NO2_indice]..'<br>Indice o3 : '..Atmo[O3_indice]..'<br>Indice SO2 : '..Atmo[SO2_indice]..'<br>Indice PM10 : '..Atmo[PM10_indice]..'#0'}
end
voir_les_logs("--- --- --- Pas de pollution --- --- ---",DEBUG)
elseif tonumber(air_quality) <= 4 then -- niveau 3 ou 4
commandArray[#commandArray+1] = {['UpdateDevice'] = dz_indice_alert..'|2|Pollution Faible'}
if send_notification > 0 and send_notification < 3 then
commandArray[#commandArray+1] = {['SendNotification'] = 'Alerte Pollution#Pollution Faible!<br><br>Indice Atmo : '..Atmo[air_quality]..'<br>Poluant dominant : '..poldom..'<br><br>Indice NO2 : '..Atmo[NO2_indice]..'<br>Indice o3 : '..Atmo[O3_indice]..'<br>Indice SO2 : '..Atmo[SO2_indice]..'<br>Indice PM10 : '..Atmo[PM10_indice]..'#0'}
end
voir_les_logs("--- --- --- Pollution Faible --- --- ---",DEBUG)
elseif tonumber(air_quality) <= 5 then -- niveau 5
commandArray[#commandArray+1] = {['UpdateDevice'] = dz_indice_alert..'|3|Pollution Moyenne'}
if send_notification > 0 and send_notification < 4 then
commandArray[#commandArray+1] = {['SendNotification'] = 'Alerte Pollution#Pollution Moyenne!<br><br>Indice Atmo : '..Atmo[air_quality]..'<br>Poluant dominant : '..poldom..'<br><br>Indice NO2 : '..Atmo[NO2_indice]..'<br>Indice o3 : '..Atmo[O3_indice]..'<br>Indice SO2 : '..Atmo[SO2_indice]..'<br>Indice PM10 : '..Atmo[PM10_indice]..'#0'}
end
voir_les_logs("--- --- --- Pollution Moyenne --- --- ---",DEBUG)
elseif tonumber(air_quality) <= 7 then -- niveau 6 ou 7
commandArray[#commandArray+1] = {['UpdateDevice'] = dz_indice_alert..'|3|Pollution Forte'}
if send_notification > 0 and send_notification < 5 then
commandArray[#commandArray+1] = {['SendNotification'] = 'Alerte Pollution#Pollution Forte!<br><br>Indice Atmo : '..Atmo[air_quality]..'<br>Poluant dominant : '..poldom..'<br><br>Indice NO2 : '..Atmo[NO2_indice]..'<br>Indice o3 : '..Atmo[O3_indice]..'<br>Indice SO2 : '..Atmo[SO2_indice]..'<br>Indice PM10 : '..Atmo[PM10_indice]..'#0'}
end
voir_les_logs("--- --- --- Pollution Forte --- --- ---",DEBUG)
elseif tonumber(air_quality) <= 9 then -- niveau 8 ou 9
commandArray[#commandArray+1] = {['UpdateDevice'] = dz_indice_alert..'|3|Pollution très Forte'}
if send_notification > 0 and send_notification < 6 then
commandArray[#commandArray+1] = {['SendNotification'] = 'Alerte Pollution#Pollution très Forte!<br><br>Indice Atmo : '..Atmo[air_quality]..'<br>Poluant dominant : '..poldom..'<br><br>Indice NO2 : '..Atmo[NO2_indice]..'<br>Indice o3 : '..Atmo[O3_indice]..'<br>Indice SO2 : '..Atmo[SO2_indice]..'<br>Indice PM10 : '..Atmo[PM10_indice]..'#0'}
end
voir_les_logs("--- --- --- Pollution très Forte --- --- ---",DEBUG)
elseif tonumber(air_quality) > 9 then -- niveau 10
commandArray[#commandArray+1] = {['UpdateDevice'] = dz_indice_alert..'|4|Pollution extrèmement forte'}
if send_notification > 0 and send_notification < 7 then
commandArray[#commandArray+1] = {['SendNotification'] = 'Alerte Pollution#Pollution extrèmement forte!<br><br>Indice Atmo : '..Atmo[air_quality]..'<br>Poluant dominant : '..poldom..'<br><br>Indice NO2 : '..Atmo[NO2_indice]..'<br>Indice o3 : '..Atmo[O3_indice]..'<br>Indice SO2 : '..Atmo[SO2_indice]..'<br>Indice PM10 : '..Atmo[PM10_indice]..'#0'}
end
voir_les_logs("--- --- --- Pollution extrèmement forte --- --- ---",DEBUG)
else
voir_les_logs("niveau non defini")
end
end
end
end
end
end