[PROJET] Gestion de piscine

Forum dédié aux questions concernant la configuration de votre serveur sous Linux
stephdes
Messages : 285
Inscription : 06 oct. 2016, 20:50

Re: [PROJET] Gestion de piscine

Message par stephdes »

Bonsoir à tous,

bon j'en profite pour faire un update de ma bataille de sondes... Et apporter, peut-être, une réponse à neosqualls.

En fait le Wemos intègre déjà un pont diviseur sur l'ADC : la tension est abaissée de 3,3 à 1V. Le fait de rajouter une résistance en série dans le pont diviseur permettrait d'abaisser la tension max de 5 à 3,3v. Regarde ici : https://www.letscontrolit.com/wiki/inde ... ge_divider
Suis pas expert mais dans mon cas, ça fonctionne. Ta résistance est dans le bon sens ?
Concernant ton capteur, tu aurais mieux fait de prendre un 0 - 72,5 : certes un peu plus cher mais plus précis pour notre utilisation :
https://fr.aliexpress.com/item/Hot-Sale ... 76061.html

J'en reviens à ma bataille de sonde... N'arrivant pas à obtenir de résultats fiables, j'ai donc :

- Suivi le câblage d'Aleph0 SAUF que je n'ai pas de résistance ni de condensateurs : est-ce un problème ?
- Racheté une 2° carte phidget 1130 pour avoir les 2 mêmes carte.
- je me suis fabriqué une chambre d'analyse pour une poignées d'euros
- Racheté une sonde ORP car je pensais que la mienne était HS.
- Vérifié, revérifié les formules dans Esp_Easy rien n'y fait...

Le pH me donne un écart de 0,2 par rapport à la mesure donc c'est plutôt pas mal. En revanche l'ORP me donne -230mV... Avec la formule 1000*(2.5-%value%/5333)/1.037 :cry:

Je n'arrive pas à voir où ça foire... Des idées ?
MiniPC W10 + RFXCom 433e + RFLINK
Elec : Téléinfo
Alexa + Alhau
MQTT...
(merci easydomoticz !)
aleph0
Messages : 328
Inscription : 27 avr. 2016, 08:06
Localisation : Hérault (34)

Re: [PROJET] Gestion de piscine

Message par aleph0 »

Salut !

Les résistances sont des pull-up sur le reset et le Rx, et les condensateurs sont là pour stabiliser l'alimentation car de mon côté la wemos reboote toute les 2 minutes si je ne les met pas. Si tu as un fonctionnement stable de la wemos sans eux, tu peux t'en passer.

La formule d'étalonnage que je donne sur mon screenshot d'esp-easy n'est valable que dans 1 cas : l'ADS1115 (donc 16bits de précision), gain au 2/3 et carte phidget. Si on change un des 3, il faut changer la formule. Tant qu'on passe en revue les vérification simples, il y a un switch sur la phidget pour la passer en mode pH ou en mode ORP : Est-tu OK sur ces points ?
neosqualls
Messages : 132
Inscription : 29 janv. 2017, 13:15
Localisation : Gironde

Re: [PROJET] Gestion de piscine

Message par neosqualls »

Bonsoir Stephdes,

Merci pour les précisions, j'utilise un ADS1115 et un level shifter pour mon capteur de pression c'est pourquoi j'essayais de décortiquer ton montage à base de wemos, j'ai fini par obtenir une pression stable. Il faudrait que je trouve les raccords en 8/13 pour mettre au même endroit mon mano et mon capteur. Mon installation à bien avancé et j'ai hâte de tester la pompe PH mais encore faudrait il qu'elle me soit livrée !!!!

Voici une petite photo de mon boitier, j'ai utilisé des jack pour raccorder les sondes et capteurs, la pompe sera probablement dans un petit boitier séparé mais controlé par le relais lui même commandé par le raspberry.

Voici une petite photo, ça commence à faire usine à gaz ! Si j'arrive à obtenir un comportement stable de la pompe j'investirai dans une sonde.


Image
stephdes
Messages : 285
Inscription : 06 oct. 2016, 20:50

Re: [PROJET] Gestion de piscine

Message par stephdes »

Salut !

Suite de mes aventures... Ce matin j'ai fait quelques tests supplémentaires :

- Switch de la carte : bien sur ORP
- Matériel utilisé : Wemos D1 mini + ADS1115 (https://fr.aliexpress.com/item/16-Bit-I ... .0.XIr2OH) + level shifter : https://fr.aliexpress.com/item/5pcs-lot ... 0.0.dJmbsE
- Version de Esp Easy : v2.0.0-dev13
- Réglage du gain : 2/3

Alors, j'ai fait quelques mesures en supprimant la formule :

- Solution 635mV : 9815
- Solution 460mV : 10815
- Eau de la chambre d'analyse (mise dans un verre) : 13211
- Valeur de la carte sans sonde connectée : A vide : 12502
- Valeur de la sonde piscine en marche : 14513

Je ne sais plus quoi penser...


Image
Image
MiniPC W10 + RFXCom 433e + RFLINK
Elec : Téléinfo
Alexa + Alhau
MQTT...
(merci easydomoticz !)
aleph0
Messages : 328
Inscription : 27 avr. 2016, 08:06
Localisation : Hérault (34)

Re: [PROJET] Gestion de piscine

Message par aleph0 »

Ben ça me paraît pas si mal tout ça moi !

Avec la formule, rawvalue=9815 donne 636 mV, et 10815 donne 455mV, c'est très proche de tes solutions de calibration ! Donc pour moi, ta mesure d'ORP fonctionne correctement. Si on part de cette hypothèse, ton eau de piscine serait à 22mV, c'est à dire qu'elle ne serait plus chlorée du tout. En cette saison, il n'y a rien de surprenant.

Il faut maintenant vérifier comment évolue la mesure piscine en marche : Sort ta sonde de la piscine, met la dans un verre contenant la solutions de calibration à 635mV, bouche le trou comme tu peux, met la piscine en route et observe l'influence sur la mesure. Si tu as un linux dans les parages, configure la wemos pour qu'elle envoi ses logs dans le syslog du linux, comme ça tu peux récupérer les mesures en temps réel puis les tracer avec excel, ça aide à comprendre les phénomènes.

Chez moi, la mesure d'ORP est assez variable filtration en marche. Pour avoir une valeur exploitable, je prends une moyenne sur 15min (30 valeurs, j'échantillonne 2x/min). J'ai aussi noté un phénomène similaire, où la mesure d'ORP baisse d'~60mV quand je démarre la filtration. J'ai mis ça sur le compte d'une variation du +5V lorsque le relai de pilotage de la pompe est enclenchée, mais sans vraiment chercher à vérifier l’hypothèse. Pour ça il faudrait que je mette la pompe en marche sans utiliser le relai, en la branchant sur une prise indépendante. Maintenant que j'y pense, les condensateurs ont peut-être une influence bénéfique sur le phénomène :idea: !

Les derniers essais à faire pour comprendre comment tout ça réagit et d'observer l'évolution de l'ORP, sonde dans un verre, lorsque on rajoute qqes goutes d'eau de javel dans l'eau (de l'extrait, à 9,6% sinon c'est pas réaliste). Il faut faire le test avec de l'eau claire puis le refaire avec de l'eau de la piscine. Normalement, l'eau de la piscine contient du stabilisant, ce qui va ralentir la montée en ORP lorsqu'on ajoute de la javel : les premières gouttes vont faire monter l'ORP jusqu'à un pallier, ensuite les gouttes suivantes sont "absorbées" par le stabilisant et l'ORP reste ~stable. Lorsque le stabilisant est saturé en chlore, l'ORP re-augmente à nouveau
aleph0
Messages : 328
Inscription : 27 avr. 2016, 08:06
Localisation : Hérault (34)

Re: [PROJET] Gestion de piscine

Message par aleph0 »

Pour éclaircir tous les phénomènes parasites dont on doit s'affranchir, voici le script que j'utilise pour la gestion. Il est assez commenté, j'espère qu'il est compréhensible sinon... demandez !!

Script LUA de type "All"

Code : Tout sélectionner

--[[ Script de gestion de la piscine
Fonctions :

1. Gestion de la chloration
1.1 En mode Off
    On ne fait rien ; si la pompe chlore était en marche au moment du basculement, on l'arrête
    
1.2 En mode manuel
    Sur appuie du bouton "injection chlore", on actionne la pompe à chlore
    pour une durée correspondant au dosage demandé par la consigne manuelle
    Si la filtration est arrếtée, on la redémarre le temps de l'injection et du mélange

1.2 En mode automatique : 
    La chloration automatique est demandée par le script de filtration. Il 
    s'assure donc lui-même que le filtration est démarrée depuis suffisament
    longtemps pour que les mesures de pH et de redox soient significative de
    l'eau de la piscine et non pas de l'eau des tuyaux ou du filtre

    Dans le cas où l'utilisateur demande une chloration par le bouton
    alors qu'on est filtration arrếtée et en mode auto... on utilise un temps
    de pré-filtration plus long pour avoir le temps d'avoir des mesures pas trop fausses

1.3 Suivi du chlore injecté et de la quantité restante dans le bidon
    On incrémente le compteur de chlore injecté 
    et on décrémente le device de chlore restant pour pouvoir avertir quand il est 
    presque vide et ne pas essayer d'injecter plus de chlore qu'il n'en reste dans 
    le bidon

2. Filtrage des mesures reçues par les capteurs pH et ORP
    Les mesures sont envoyées toutes les 30s par la wemos.
    - On prends d'abord une moyenne sur 10 mesures (5min) pour filtrer les trop 
    grosses fluctuations
    - On ne prends en comptes les mesures que quand la filtration marche, et 
    5 minutes après le début de celle-ci pour ne pas tenir compte des mesures
    sur l'eau des tuyaux et du filtre
    
3. Gestion des reboot de la wemos
    Si la wemos reboot pendant la filtration ou la chloration, il faut pouvoir
    lui indiquer de reprendre le travail là où elle l'avait laissé :
    - On renvoi l'état de la pompe de filtration
    - On recalcule combien de chlore a été injecté et si il en reste à injecter
    et on renvoi des longpulses correspondant
    
4. Gestion du débit bas de la pompe de filtration
    Si le débit de la pompe de filtration est plus bas que la normale, il y a un problème.
    On définit 2 seuils :
    - L'un pour alerter que les filtres ou pré-filtres sont encrassés et qu'il est temps 
    de les nettoyer
    - L'autre pour arrêter la pompe si le débit est très faible (désamorçage, 
    pompe coincée, etc...)

5. Gestion de la filtration
5.1 Calculer la durée et la période optimale de filtration
5.2 Démarrer / arrêter la filtration
     Si température eau > 12°C, selon planning calculé au dessus
     Sinon, marche si température air <1°C, arrêt si >2°C

6. Estime le bilan thermique de la piscine en fonction des apports solaire et des
   dépertitions par convection
--]]

-- Paramètres communs
local dbPath   = "/var/domoticz/domoticz.db"
local dev_TAir = "Température Extérieure" -- Température extérieure
local dev_TEau = "Température piscine"    -- Température eau piscine
local dev_Status = "Status Piscine"       -- Capteur texte de log des évènements

-- Paramètres pour fonction 1. Filtration
local var_PPStart = "PpePiscStart"            -- Variable contenant l'heure de départ de la filtration
local var_PPDuree = "PpePiscDuree"            -- Variable contenant la durée de la filtration
local idx_DebitFl = 208                       -- Index du débimètre de filtration, utilisé pour déduire la durée de filtration effective
local dev_DureeFl = "Temps de filtration"     -- Capteur pour logger la durée de filtration calculée
local dev_PpeFl   = "Filtration piscine"      -- Interrupteur de commande de la pompe
local dev_ModeFl  = "Mode filtration piscine" -- Selecteur du mode de la pompe (Manu/Auto HC/AutoHP)
local dev_PpeFl_Q = "Débit filtration piscine"
local dev_cptFl   = "Consommation Piscine"    -- Compteur de consommation électrique piscine

local dev_FiltT      ="Filtration temporisée" --Bouton de demande de la filtration temporisée
local dev_DureeFlManu="Durée de filtration manu (min)" -- Durée voulue

-- Paramètre pour chloration automatique
local dev_Melange    = 'Mélange en cours'        -- Dummy switch pour indiquer au script filtration de ne pas arrếter la pompe

local dev_ModeCl     = "Mode chloration piscine" -- Sélecteur de mode de Chloration
local dev_InjCl      = "Injection Chlore"        -- Bouton de demande d'injection de chlore
local dev_PpeCl      = "Pompe Chlore"            -- Commande de la pompe à Chlore
local dev_CptCl      = "Consommation Chlore"     -- Totaliseur de chlore injecté
local dev_FreeCl     = "Chlore libre"            -- Estimateur de Chlore libre
local dev_SPCl       = "Dose de Chlore (ml)"     -- Consigne pour injection manuelle
local dev_SPORP      = "Consigne redox (mV)"     -- Consigne pour injection automatique
local dev_SPBidonCl  = "Capacité bidon chlore (ml)" -- Contenance du nouveau bidon de chlore
local dev_QBidonCl   = "Chlore restant"          -- Quantité de chlore restante dans le bidon
local dev_ChtBidonCl = "Changement bidon chlore" -- Bouton de changement du bidon de chlore
local dev_ORPi       = "ORP"                     -- ORP instantanné
local dev_ORPm       = "Redox piscine"           -- redox moyen sur 5 min
local Cal_QCl        = 1.5                       -- débit pompe doseuse en ml/s
local var_PpeClOld   = "Etat pompe chlore"
local var_PpeClTime  = "Heure Cht état pompe chlore"
local var_PpeClTodo  = "Reste a faire pompe chlore"
local var_LastORP    = "Last_ORP"
local var_IntORP     = "Integrale ORP"
local var_KpRedox    = "Kp redox"

-- Paramètres pour régulation pH
local dev_ModeAc     = "Mode régulation pH"        -- Sélecteur de mode de régulation pH
local dev_InjAc      = 'Injection Acide'           -- Bouton de demande d'injection d'acide
local dev_PpeAc      = 'Pompe Acide'               -- Commande de la pompe d'Acide
local dev_CptAc      = 'Consommation Acide'        -- Totaliseur de chlore injecté

local dev_SPAc       = "Dose Acide (ml)"           -- Consigne pour injection manuelle
local dev_SPpH       = "Consigne pH"               -- Consigne pour injection automatique
local dev_SPBidonAc  = "Capacité bidon acide (ml)" -- Contenance du nouveau bidon de acide
local dev_QBidonAc   = "Acide restant"             -- Quantité d'acide restante dans le bidon
local dev_ChtBidonAc = "Changement bidon acide"    -- Bouton de changement du bidon de acide
local dev_pHi        = "pH"                        -- pH instantanné
local dev_pHm        = "pH piscine"                -- pH moyen sur 5 min
local Cal_QAc        = 1.145                       -- débit pompe doseuse en ml/s
local var_PpeAcOld   = "Etat pompe acide"
local var_PpeAcTime  = "Heure Cht état pompe acide"
local var_PpeAcTodo  = "Reste a faire pompe acide"
local var_KpAcide    = "Kp acide"

-- Réglages
local cst_DebitFlNom=100      -- Débit nominal de la pompe de filtration en l/min
local FiltrPreInjection=60    -- durée en secondes de démarrage de la filtration avant l'injection Chlore
local FiltrPostInjection=1500 -- durée en secondes du mélange nécessaire après une injection chlore
local seuil_PpeFl_Q_LL=20     -- seuil d'arrêt de la pompe sur débit bas en l/min desactivé en attendant de trouver qqch de fiable
local seuil_PpeFl_Q_L =80     -- seuil d'alerte sur débit bas pompe en l/min

local cst_pctCl=9.6           -- % de chlore dans la javel
local cst_volPisc=24          -- volume du bassin, en m3
local cst_PpeFl_W =312    -- Puissance électrique de la pompe piscine

--Paramètres wemos
local dev_reboot="Wemos Pisc Boot"
local addr_Wemos="192.168.1.249"        -- Adresse IP de la Wemos

-- -------------------------------
-- Bibliothèque de fonctions
-- -------------------------------
local debug=1

dofile('/opt/domoticz/scripts/lua/bibliotheque.lua')

-- Functions specific to this script 
function WemosInj(IP,Ppe,LocalDev,Pause,Duree)
-- Demande à la wemos d'injecter du chlore pour une Duree après un délai de Pause
-- IP = IP de la wemos
-- Ppe = "Cl" ou "Ac"
-- LocalDev= Bouton local de commande de la pompe
-- Pause = Durée de la pause avant l'injection
-- Duree = Durée de l'injection

    cmd = "http://"..IP.."/control?cmd=event,SetPause="..tostring(Pause)
    if debug==1 then print(cmd) end
    os.execute('curl "'..cmd..'"')
    
    if Ppe=="Cl" then
        cmd = "http://"..IP.."/control?cmd=event,InjCl="..tostring(Duree)
        if debug==1 then print(cmd) end
        os.execute('curl "'..cmd..'"')
    elseif Ppe=="Ac" then
        cmd = "http://"..IP.."/control?cmd=event,InjAc="..tostring(Duree)
        if debug==1 then print(cmd) end
        os.execute('curl "'..cmd..'"')
    else
        print("Erreur WemosInj : Pompe inconnue. Doit être Cl ou Ac")
    end
    -- La pompe est normalement arrêtée par la wemos et son statut mis à jour par la wemos
    -- On double ici la commande d'arrêt au cas où le wifi soit coupé au moment de l'arrêt
    UpdateSw(LocalDev,'Off AFTER '..tostring(Pause+Duree))
end

function ChgBidon(dev_ChtBidon,dev_QBidon,dev_SPBidon)
--[[Gestion du changement de bidon
dev_ChtBidon = swicth indiquant un changement de bidon
dev_QBidon   = Device stockant la quantité restante dans le bidon
dev_SPBidon  = Setpoint contenant la nouvelle quantité dans le bidon
--]]
    if devicechanged[dev_ChtBidon] and otherdevices[dev_ChtBidon]=="On" then
        UpdateSw(dev_ChtBidon,"Off")
        UpdateDev(dev_QBidon,0,otherdevices_svalues[dev_SPBidon])
    end
end

function MaJInjection(dev_Ppe,dev_Cpt,dev_QBidon,var_PpeOld,var_PpeTime,var_PpeTodo,Cal_Q,contenu,wrn_QBidon)
--[[ Calcul de la quantité injecté en tenant compte des reboot wemos
- Les commandes de pompes sont passées par des longpulses ou par le bouton
- Le statut du bouton est mis à jour par la Wemos
- En cas de passage de off à on, on stocke l'heure dans une variable (nb de sec depuis minuit)
- En cas de passage de on à off, on calcule la durée de marche et on met à jour les compteurs avec ça

dev_Ppe = Switch de commande de la pompe d'injection
dev_Cpt = Compteur de suivi de la quantité injectée
dev_QBidon=Device de quantité restante

var_PpeOld = État précédent de la pompe d'injection
var_PpeTime= Heure d'enclenchement de la pompe d'injection
var_PpeTodo= Durée d'injection restant à faire

cal_Q = calibration de la pompe d'injection

contenu = texte spécifiant le contenu du bidon
--]]

    local val_PpeOld, val_PpeTime, Q, val_QBidon, Duree_effective

    if devicechanged[dev_Ppe] then
        val_PpeOld=uservariables[var_PpeOld]
        if otherdevices[dev_Ppe]=="On" and val_PpeOld=="Off" then
            -- Passage de Off à On
            print("Démarrage "..dev_Ppe)
            UpdateVar(var_PpeOld,"On")

            time = os.date("*t")
            UpdateVar(var_PpeTime,3600*time.hour+60*time.min+time.sec)
        end
        if otherdevices[dev_Ppe]=="Off" and val_PpeOld=="On" then
            -- Passage de On à Off
            print("Arrêt "..dev_Ppe)
            UpdateVar(var_PpeOld,"Off")
            
            time = os.date("*t")
            
            val_PpeTime=uservariables[var_PpeTime]
            Duree_effective=3600*time.hour+60*time.min+time.sec-val_PpeTime
            Q=round(Duree_effective*Cal_Q,0)
            
            print("Quantitée réellement injectée :"..tostring(Q).." ml")
            
            -- Mise à jour du compteur de consommation de produit
            UpdateCpt(dev_Cpt,Q)
            
            -- Mise à jour de la quantité restante de chlore
            val_QBidon=otherdevices_svalues[dev_QBidon]-Q
            UpdateDev(dev_QBidon,0,val_QBidon)

            --Vérification quantité restante dans le bidon
            if val_QBidon<=wrn_QBidon then
                Notification('Il reste seulement '..val_QBidon..' ml de '..contenu..' : Remplir le bidon rapidement')
            end
            
            --Mise à jour de la variable "reste à faire"
            UpdateVar(var_PpeTodo,math.max(0,uservariables[var_PpeTodo]-Duree_effective))
        end
    end
end

function RenvoiMarche(Ppe,dev_Ppe,Ppe_Restant)
    if Ppe_Restant>5 then
        -- On est bien dans le cas où la wemos à rebooté pendant l'injection Chlore, on renvoi un LongPulse
        -- du temps restant. Note : On a perdu 6s, soit 4ml dans le processus, temps nécessaire à la wemos
        -- pour informer de son reboot :(
        print("Redémarrage "..dev_Ppe.." pour "..Ppe_Restant.."s")
        WemosInj(addr_Wemos,Ppe,dev_Ppe,60,Ppe_Restant)
    else
        print("Pas d'injection "..Ppe.." en cours : Pas besoin de redémarrer la pompe "..Ppe)
    end
end

function ChkQBidon(Q,dev_QBidon,contenu)
    -- Vérifie que la quantité Q se trouve bien dans le bidon dev_QBidon
    -- et renvoi le min de Q ou de la quantité restante
    local val_QBidon
    local min_QBidon=300 -- on ne pompe pas en dessous de 300ml dans le bidon (desamorçage)
    
    val_QBidon=tonumber(otherdevices_svalues[dev_QBidon])
    if Q > val_QBidon-min_QBidon then
        print("Erreur : On demande a injecter "..Q.."ml de "..contenu.." alors qu'il reste seulement "..val_QBidon.."ml dans le bidon" )
        print("Quantité à injecter réduite en conséquence")
        
        Notification('Bidon de '..contenu..' vide : Le remplir immédiatement')
    end
    return math.min(Q,val_QBidon-min_QBidon)
end

function CalcFreeCl(pH,ORP)
    -- Calcule le chlore libre en fonction du pH et du redox de l'eau
    pH=tonumber(pH)
    ORP=tonumber(ORP)
    return round(10^((ORP-715-50*(7-pH))/(300+50*(7-pH))),2)
end

function CalcFiltration()
    --[[ Calcule 
    - la durée optimale de filtration en fonction de la Température
    de l'eau, du débit de la pompe et du volume du bassin
    - L'heure de début de la filtration selon qu'on veut filtrer en heure creuses ou pleines

    Utilise les variables globales suivantes
    dev_Teau : device du thermomètre d'eau
    dev_Mode : le sélecteur de mode de filtration
    cst_Qpompe : débit nominal de la pompe en m3/h
    cst_Vbassin : Volume du bassin en m3
    var_PPStart : Nom de la variable qui va stocker l'heure de départ de la filtration
    var_PPDuree : Nom de la vatiable qui va stocker la durée de filtration
    --]]
    
    local val_TEau, DebutFiltration, dureeFiltration, HDebFilt, HFinFiltr
    
    print("Script Piscine : Calcul de la filtration")
    val_TEau=os.capture('sqlite3 '..dbPath..' "select temp_avg from temperature_calendar where devicerowid='..otherdevices_idx[dev_TEau]..' order by date desc limit 1"')
    val_TEau=round(val_TEau,1)
    print("Température moyenne hier : "..val_TEau.." °C")

    dureeFiltration=round(60*math.max(10/60,0.65*(val_TEau-10)),0) -- Résultat en minutes ; durée min=10min
    print("Durée de filtration : "..dureeFiltration.." min")

    if otherdevices[dev_ModeFl] == "Auto HC" then
        -- Mode Auto Heures Creuses
        -- On filtre jusqu'à 7h00 si possible (=420 min), et on déborde si nécessaire
        DebutFiltration=math.max(10,420-dureeFiltration)
    else
        -- Mode Auto Heures Pleines ou Manu
        --[[ On filtre en journée, centré sur le midi solaire
        MidiSol=round((timeofday['SunriseInMinutes']+timeofday['SunsetInMinutes'])/2,0) -- en minutes
        DebutFiltration=round(MidiSol-dureeFiltration/2,0) -- en minutes
        --]]
        
        -- On termine au coucher du soleil pour avoir le temps de remonter le taux de chlore
        -- après les grosses chaleurs
        DebutFiltration=timeofday['SunsetInMinutes']-dureeFiltration
    end
    HDebFilt=os.date("%H:%M", DebutFiltration*60-3600)
    HFinFiltr=os.date("%H:%M", (DebutFiltration+dureeFiltration)*60-3600)
    print("Filtration de "..HDebFilt.." à "..HFinFiltr.." soit "..dureeFiltration.." min")

    UpdateVar(var_PPStart,HDebFilt)
    UpdateVar(var_PPDuree,dureeFiltration)
    UpdateDev(dev_Status,0,"------ Nouvelle journée ------")
    UpdateDev(dev_Status,0,"Filtration de "..HDebFilt.." à "..HFinFiltr)
end

function ChkFiltration()
    --[[Gestion de la filtration (Mode de la filtration Manu / Auto HC / Auto HP)

    Start / Stop de la pompe
    Demande de chloration automatique
    Demande d'acidification automatique
    --]]

    local Min_last_injection=23*60+30 -- Heure maxi de la denière injection en minute
    local Min_last_stop=23*60+45      -- Heure maxi pour arrêter la filtration en minute
    
    print("Script Piscine : Gestion de la filtration")

    PpeDuree=tonumber(uservariables[var_PPDuree])
    val_ModeFl=otherdevices[dev_ModeFl]
    val_PpeFl=otherdevices[dev_PpeFl]
    val_FlagCl=otherdevices[dev_Melange]    
    if (val_ModeFl ~= "Manu") then
        print(otherdevices_svalues[dev_TEau])
        --val_TEau = tonumber(string.match(otherdevices_svalues[dev_TEau],"(%d+.%d*);.*"))
        val_TEau = tonumber(string.match(otherdevices_svalues[dev_TEau],"(.-);.*"))

        if val_TEau >= 12 or time.wday==1 then
            -- La témpérature d'eau est >=12°C : on utilise la temporisation
            -- le dimanche, on by-pass la température. couplée à une durée de filtration mini non nulle, ça
            -- permet de forcer le démarrage de la pompe 1x/semaine en hiver pour éviter les grippages
            MinDuJour=60*time.hour+time.min

            PpeStart=tonumber(string.sub(uservariables[var_PPStart],1,2))*60+tonumber(string.sub(uservariables[var_PPStart],4,5))

            -- Calcul de la durée de fonctionnement de la pompe aujourd'hui
            -- Basé sur le débit de la pompe
            today = os.date("%Y-%m-%d")
            --PpeFait=os.capture('sqlite3 '..dbPath..' "select sum(percentage) from percentage where devicerowid='..idx_DebitFl..' and Date(date)=\''..today..'\'"')*5/cst_DebitFlNom
            PpeFait=os.capture('sqlite3 '..dbPath..' "select sum(value3) from multimeter where devicerowid='..otherdevices_idx[dev_cptFl]..' and Date(date)=\''..today..'\'"')*5/cst_PpeFl_W
            print("Déjà effectué : "..PpeFait.." minutes sur "..PpeDuree)
            
            if (MinDuJour >= PpeStart and PpeFait < PpeDuree and val_PpeFl == "Off") then
                print("Filtration piscine : Démarrage par timer")
                if IsAlive(addr_Wemos) then
                    UpdateSw(dev_PpeFl,'On')
                    UpdateDev(dev_Status,0,"Démarrage par timer")
                else
                    UpdateDev(dev_Status,0,"Wemos injoignable pour démarrage pompe :(")
                end
            elseif val_PpeFl == "On" and ((val_FlagCl == "Off" and PpeFait >= PpeDuree) or MinDuJour>=Min_last_stop) then
                -- Hint : On n'arrête pas la pompe si une chloration est en cours
                print("Filtration piscine : Arrêt par timer")
                if IsAlive(addr_Wemos) then
                    --[[Après l'arrêt de la pompe, la chute de pression permet à un peu d'acide et de chlore de rejoindre
                    la canalisation principale et risque d'endomager les sondes (elasticité des tuyaux d'injection). C'est 
                    pour cette raison qu'on rallume la filtration 1 minutes après l'arrêt pour évacuer vers la piscine
                    ce petit surplus de produit
                    ]]--
    
                    UpdateSw(dev_PpeFl,'Off')
                    UpdateSw(dev_PpeFl,'On AFTER 2 MINUTE')
                    UpdateSw(dev_PpeFl,'Off AFTER 3 MINUTES')
                    UpdateDev(dev_Status,0,"Arrêt par timer")
                else
                    UpdateDev(dev_Status,0,"Wemos injoignable pour arrêt pompe :(")
                end                
            end
            
            --[[ Gestion de l'acidification automatique :
            Si on est en acidification automatique, une seule acidification à 30min après la filtration
            !! Prendre garde que les tests pour acide et chlore soient mutuellement exclusif
            de manière à ne pas positionner les 2 switches en même temps
            --]]
            if otherdevices[dev_ModeAc]== "Auto" and val_PpeFl=="On" and val_FlagCl=="Off"
            and PpeFait >= 25 and PpeFait <=PpeDuree/2  then
                print("Demande d'acidification automatique pendant filtration en automatique")
                UpdateSw(dev_InjAc,'On')
                DdeInjAc=true
            end            
            --[[ Gestion de la chloration automatique :
            Si on est en chloration automatique, et que la filtration a marché 1h
            et qu'il reste encore 1/2h de filtration avant la fin
            --]]
            if otherdevices[dev_ModeCl]== "Auto" and val_PpeFl=="On" and val_FlagCl=="Off" and DdeInjAc~=true
            and PpeFait > PpeDuree/2 and (PpeDuree-PpeFait) >30 and MinDuJour<=Min_last_injection then
                print("Demande de chloration automatique pendant filtration en automatique")
                UpdateSw(dev_InjCl,'On')
            end
        elseif val_TEau<12 then
            --[[ La température de l'eau est <12°C : on utilise la température 
            Démarrage si température d'air <= 1
            Arrêt si température d'air >= 1.5 et température eau >= 0.5
            --]]
            val_Tair = tonumber(otherdevices_svalues[dev_TAir])

            if (val_Tair <= 1) and val_PpeFl == "Off" then
                -- Filtration On
                print("Filtration piscine : Démarrage par température")
                if IsAlive(addr_Wemos) then
                    UpdateDev(dev_Status,0,"Démarrage par température")
                    UpdateSw(dev_PpeFl,'On')
                else
                    UpdateDev(dev_Status,0,"Wemos injoignable démarrage pompe par température")
                    Notification("Wemos injoignable pour mode hors gel !!!")
                end
            elseif val_Tair >= 1.5 and val_TEau>=0.5 and val_PpeFl == "On" and val_FlagCl == "Off" then
                -- Filtration Off
                print("Filtration piscine : Arrêt par température")
                if IsAlive(addr_Wemos) then
                    UpdateDev(dev_Status,0,"Arrêt par température")
                    UpdateSw(dev_PpeFl,'Off')
                else
                    UpdateDev(dev_Status,0,"Wemos injoignable pour arrêt par température")
                end
            end
        end -- filtration timer/temperature 
    else
        print("Mode manu, pas de changement")

        if otherdevices[dev_ModeCl]== "Auto" and val_FlagCl=="Off" and val_PpeFl=="On" then
            print("Demande de chloration automatique pendant filtration en manuel")
            UpdateSw(dev_InjCl,'On')
        end
        
    end -- end mode auto/manu

    -- Mise à jour du capteur loggant la durée de filtration
    UpdateDev(dev_DureeFl,0,round(PpeDuree/60,2))
end

function BilanThermique()
    -- Calcule le bilan thermique de la piscine
    local dev_BThPisc = "Bilan Th piscine" -- Virtual custom sensor pour bilan puissance piscine
    local dev_sunAlt  = "Sun Altitude"
    local var_ASP     = "ApportSolairePiscine" -- variable contenant l'incidence solaires, en W/m², estimés par le script script_time_SolarSensor.lua
    local dev_TEauEst = "Température piscine estimée"
    local Sbache = 20 -- m²
    local Sparoi = 20 -- m²
    local h = 10.5    -- W/m²K, coefficient de convection
    local Ksol = 1.2  -- W/m²K, conductance thermique du sol
    local Tsol = 15   -- °C, température du sol sous la piscine, à 10cm de profondeur
    local eps = 0.95  -- emissivité de la bache
    local eff = 0.8   -- %, proportion de la bache touchant l'eau directement

    local val_TEau, val_Tair, val_sunAlt, val_ASP, Deperditions,NewTEau
    
    local TEau_estimee=0
    local lcldbg=0
    
    -- val_TEau = tonumber(string.match(otherdevices_svalues[dev_TEau],"(%d+.%d*);.*"))
    val_TEau = tonumber(string.match(otherdevices_svalues[dev_TEau],"(.-);.*"))

    if val_TEau >= -10 and val_TEau <= 45 then
        -- On ne calcule le bilan thermique que si la température d'eau est réaliste...
        -- Car on reçoit parfois des 74.9°C :(
        -- Il manque les pertes par évaporation et par rayonnement en hivers...
        
        val_Tair = otherdevices_svalues[dev_TAir]
        val_sunAlt=otherdevices[dev_sunAlt]
        
        val_ASP = uservariables[var_ASP]*eps*eff*Sbache*math.sin(math.rad(val_sunAlt))
        Deperditions = round((val_TEau-val_Tair)*((Sbache+Sparoi)*h)+(val_TEau-Tsol)*Sbache*Ksol,0)        
        UpdateDev(dev_BThPisc,0,val_ASP-Deperditions)
        
        --Workaround pour le thermomètre de piscine cassé -> calcul d'une estimation sur la base du bilan thermique
        val_TEauEst = tonumber(string.match(otherdevices_svalues[dev_TEauEst],"(.-);.*"))
        -- val_TEauEst=val_TEau
        NewTEau=round(val_TEauEst+(val_ASP-Deperditions)/4128/24/1000*5*60,3)
        if lcldbg==1 then
            print("TEau "..val_TEauEst)
            print("TAir "..val_Tair)
            print("ASP "..val_ASP)
            print("Perte "..Deperditions)
            print("Nouvelle température d'eau "..NewTEau)
        end

        UpdateDev(dev_TEauEst,0,tostring(NewTEau)..";13;0")
                    
        if TEau_estimee==1 then
            -- NewTEau=18.8
            UpdateDev(dev_TEau,0,tostring(NewTEau)..";13;0")
        end
        
    else
        print("Température d'eau incorrecte ; bilan thermique non calculé")
    end
end

function ChkFlow()
    --[[
    ******************************************************************
    4. Gestion du débit bas de la pompe de filtration
    ******************************************************************
    Le débit est remonté par la wemos toutes les minutes. La première minute est 
    donc potentiellement à 0 et la deuxième pas forcément exacte
    On ne prends donc une action d'à partir de la deuxième minute de filtration
    ou 2 minutes après un reboot wemos pour laisser le temps au débit d'être 
    mesuré correctement
    --]]

    if debug==2 then
        print("État filtration : "..otherdevices[dev_PpeFl])
        print("Lastupdate : "..lastupdate(dev_PpeFl).." s")
        print("Débit filtration : "..otherdevices[dev_PpeFl_Q].." l/min")
    end

    if devicechanged[dev_PpeFl_Q] and otherdevices[dev_PpeFl] == "On" 
    and lastupdate(dev_PpeFl)>120 and lastupdate(dev_reboot)>120 then
        val_PpeQ=tonumber(otherdevices[dev_PpeFl_Q])
        if val_PpeQ<=seuil_PpeFl_Q_LL then
            Notification("Débit pompe piscine trop bas ("..val_PpeQ.." l/min)")
            -- print("débit pompe parti en couille : "..val_PpeQ.." l/min")
            
            -- On passe la filtration en manu et on arrête la pompe (pour éviter que la filtration auto ne la redémarre !
            -- Penser à interdire la chloration dans ce cas là : Hint : ne chlorer que si le débit est suffisant
  
            -- UpdateSw(dev_ModeFl,'Set Level 10')
            UpdateSw(dev_PpeCl,'Off')
            UpdateSw(dev_PpeFl,'Off AFTER 5')
        elseif val_PpeQ<=seuil_PpeFl_Q_L then
            time = os.date("*t")
        
            -- On n'alerte pour ça que le samedi à 14h car l'action n'est pas urgente
            -- Todo : Encore faut-il qu'on pompe à cette heure là, mais c'est probable
            if time.wday==7 and time.hour==14 and time.min==00 then
                Notification('Débit pompe piscine bas : Filtres encrassés')
            end
        end
    end
end

function CalcAvg()
    --[[****************************************************************************
    2. Calcul du pH et ORP moyen sur une minute pour la régulation
    ********************************************************************************
    Uniquement quand la filtration fonctionne depuis plus de 5min
    et que l'injection est arrêtée car elle perturbe la mesure
    --]]

    if otherdevices[dev_PpeFl]=="On" and lastupdate(dev_PpeFl)>=300 
    and otherdevices[dev_PpeCl]=="Off" and otherdevices[dev_PpeAc]=="Off" then
        if (devicechanged[dev_pHi]) then --moyenne glissante sur 15min (30 valeurs)
            UpdateDev(dev_pHm,0,(29*tonumber(otherdevices[dev_pHm])+otherdevices[dev_pHi])/30)
        end
        if (devicechanged[dev_ORPi]) then
            -- Calcul de la moyenne glissante sur 15 min (30 valeurs) pour affichage et action P de la régulation
            val_ORPi=otherdevices[dev_ORPi]
            val_ORPm=(29*tonumber(otherdevices[dev_ORPm])+tonumber(val_ORPi))/30
            UpdateDev(dev_ORPm,0,val_ORPm)
            
            -- Calcul de la moyenne glissante sur 60min (120 valeurs) pour action D de la régulation ORP
            UpdateVar(var_LastORP,(119*uservariables[var_LastORP]+val_ORPi)/120)
            
            --[[ Calcul de l'intégrale ORPm-SPORP pour l'action I de la régulation ORP
            val_SPORP=otherdevices_svalues[dev_SPORP]
            UpdateVar(var_IntORP,uservariables[var_IntORP]+(val_SPORP-val_ORPm)/30) --]]
        end
    end
end

function CalcClAuto()
    -- La chloration auto est uniquement déclenchée pendant la filtration.
    
    local PID_Kp=9.7               -- ml de Chlore par mV de redox voulu...
    local PID_Ki=0                -- Paramètre integrale
    local PID_Kd=10               -- Paramètre dérivée
    local PID_dB=0                -- bande morte : 10mV

    val_SPORP=otherdevices_svalues[dev_SPORP] -- lecture de la consigne redox
    val_ORP=otherdevices[dev_ORPm]            -- lecture de la valeur redox actuelle (moyenne 5min)
    val_LastORP=uservariables[var_LastORP]    -- lecture de la valeur redox précédente (il y a 30min)
    val_pH=tonumber(otherdevices[dev_pHm])

    if math.abs(val_SPORP-val_ORP)>PID_dB and (val_LastORP-val_ORP) >= -2 and val_pH<7.5 then
        val_IntORP=uservariables[var_IntORP]      -- lecture de l'erreur intégrale
        
        --Calcul de la quantité de chlore à injecter
        
        Cl_actuel=CalcFreeCl(val_pH,val_ORP)
        Cl_voulu=CalcFreeCl(val_pH,val_SPORP)

        QCl_P=PID_Kp*100*(Cl_voulu - Cl_actuel)/cst_pctCl*cst_volPisc
        QCl_I=PID_Ki*val_IntORP
        QCl_D=PID_Kd*(val_LastORP-val_ORP)
        
        QCl=round(math.max(0,QCl_P+QCl_I+QCl_D),0)
    else
        if math.abs(val_SPORP-val_ORP)<=PID_dB then 
            print("ORP dans la bande morte : aucune action à prendre")
        end
        if (val_LastORP-val_ORP) <= -1 then
            print("ORP croissante : aucune action à prendre")
        end
        if val_pH>=7.5 then
            print("pH >=7.5 : Régulation automatique non pertinente")
        end

        QCl=0
    end

    if debug==1 then
        print("Consigne..............: "..tostring(val_SPORP).." mV")
        print("Mesure actuelle.......: "..tostring(round(val_ORP,0)).." mV")
        print("Mesure il y a 60 min..: "..tostring(round(val_LastORP,0)).." mV")
        print("Action proportionnelle: "..tostring(round(QCl_P or 0,0)).." ml")
        print("Action intégrale......: "..tostring(round(QCl_I or 0,0)).." ml")
        print("Action dérivée........: "..tostring(round(QCl_D or 0,0)).." ml")
        print("Action totale.........: "..tostring(QCl).." ml")
    end

    return QCl
end

function CalcAcAuto()
    local val_SPpH, val_pH, QAc
    local PID_Kp=1500 -- 150 ml pour 0.1 de pH
    
    val_SPpH=otherdevices_svalues[dev_SPpH]
    val_pH=tonumber(otherdevices[dev_pHm])
    
    QAc_P=PID_Kp*(val_pH-val_SPpH)
    
    QAc=round(math.max(0,QAc_P),0)
    
    if debug==1 then
        print("Consigne..............: "..tostring(val_SPpH))
        print("Mesure actuelle.......: "..tostring(round(val_pH,2)))
        print("Action proportionnelle: "..tostring(round(QAc_P or 0,0)).." ml")
        print("Action totale.........: "..tostring(QAc).." ml")
    end
    
    return QAc
end

function DoInjection(Q,Cal_Q,dev_Ppe,produit,var_PpeTodo)
    --[[ gère l'injection d'une quantité Q de produit avec la pompe dev_Ppe et le coefficient
    de calibration Cal_Q et met à jour la variable var_PpeTodo
    
    Variable globale : 
    dev_PpeFl, la pompe de filtration
    dev_Melange, le flag de mélange en cours
    FiltrPreInjection
    FiltrPostInjection
    --]]
    local FiltrationSup,Ppe_Duree,Pr
    
    if Q >= 5 then
        -- On ne prends une action que si la consigne d'injection suffisante
        -- Side effect : Régler la consigne à 0 permet de désactiver l'injection facilement
        
        print("Script Injection "..produit.." piscine : Injection de "..Q.."ml de "..produit)
        
        if otherdevices[dev_PpeFl]=='Off' then
            -- On demande d'injecter du chlore alors que la filtration est arrếtée
            -- Il faut donc :
            --    * démarrer la filtration un peu avant la chloration (~60s)
            --    * Injecter le Chlore (Q/Cal_Q secondes)
            --    * Maintenir la filtration ~30min après l'injection pour répartir le chlore dans tout le bassin
            
            FiltrationSup=Q+FiltrPreInjection+FiltrPostInjection
            print("La filtration est arrêtée, on la redémarre pour une durée de "..tostring(round(FiltrationSup/60,0)).." minutes")
            
            UpdateSw(dev_PpeFl,'On FOR '..tostring(round(FiltrationSup/60,0)))
            UpdateDev(dev_Status,0,"Démarrage pour injection pendant "..tostring(round(FiltrationSup/60,0)).." minutes")
        else
            FiltrPreInjection=0
            FiltrationSup=Q+FiltrPostInjection 
        end
        -- Mise en marche de la pompe doseuse pour une durée correspondante à la quantité de Chlore à injecter
        Ppe_Duree=round(Q/Cal_Q,0)
        
        if produit=="chlore" then Pr="Cl" else Pr="Ac" end
        WemosInj(addr_Wemos,Pr,dev_Ppe,FiltrPreInjection,Ppe_Duree)

        --Positionnement de la variable "reste à faire pompe" pour savoir d'où repartir en cas de reboot wemos 
        --pendant l'injection
        UpdateVar(var_PpeTodo,Ppe_Duree)

        -- Mise à jour du switch "Mélange en cours" pour éviter que le script
        -- de filtration n'arrête la Pompe pendant la chloration ou le temps de mélange
        -- ou ne redemande une nouvelle chloration pendant le temps de mélange
        UpdateSw(dev_Melange,'On FOR '..tostring(round(FiltrationSup/60,0)))
        
        UpdateDev(dev_Status,0,"Injection de "..Q.."ml de "..produit)
    else
        print("Script Injection "..produit.." piscine: La consigne ("..Q.." ml) est trop faible : Pas d'injection")
    end
end

function ManagePumps(IP)
    --[[Gère les pompes peristaltiques
    Elles ne doivent pas être commandées directement, mais par le biais des rules
    InjCl, InjCl en production
    PpeCl_On, PpeAc_On et Ppe_Off en tests
    Qui gèrent la commutation Cl/Ac et la mise en marche des pompes
    ]]--
    local dev_PpeDos="$Pompe dosage" --1=pompe dosage en marche
    local dev_ClAc="$Chlore-Acide"   --0=pompe chlore ; 1=pompe acide
    
    -- Action (uniquement pour debug et/ou amorçage des pompes)
    if false then
        if devicechanged[dev_PpeCl]=="On" and otherdevices[dev_PpeAc]=="Off" then
            cmd = "http://"..IP.."/control?cmd=event,PpeCl_On"
            if debug==1 then print(cmd) end
            os.execute('curl "'..cmd..'"')
        elseif devicechanged[dev_PpeAc]=="On" and otherdevices[dev_PpeCl]=="Off" then
            cmd = "http://"..IP.."/control?cmd=event,PpeAc_On"
            if debug==1 then print(cmd) end
            os.execute('curl "'..cmd..'"')        
        elseif devicechanged[dev_PpeCl]=="Off" or devicechanged[dev_PpeAc]=="Off" then
            cmd = "http://"..IP.."/control?cmd=event,Ppe_Off"
            if debug==1 then print(cmd) end
            os.execute('curl "'..cmd..'"')        
        end
    end
    
    --Mise à jour des switches sans actions
    if devicechanged[dev_PpeDos]=="On" then
        if otherdevices[dev_ClAc]=="Off" then
            UpdateSw(dev_PpeCl,"On")
        else
            UpdateSw(dev_PpeAc,"On")
        end
    elseif devicechanged[dev_PpeDos]=="Off" then
        if otherdevices[dev_PpeCl]=="On" then    
            UpdateSw(dev_PpeCl,"Off")
        else
            UpdateSw(dev_PpeAc,"Off")
        end
    end
end

function CalcAvgKp(mesure,conso)
--[[fonction pour recalculer le Kp moyen sur une semaine pour le pH et l'ORP
mesure : device contenant la mesure
conso : device counter contenant la quantité injectée]]--
    local OneWeekAgo,Kp,sql
    local dbg=0
    
    OneWeekAgo=os.date("%Y-%m-%d",os.time()-7*24*3600)
    
    sql=      "select avg(value/(Percentage_Max-Percentage_Min))"
    sql=sql.." from percentage_calendar,meter_calendar"
    sql=sql.." where percentage_calendar.devicerowid="..otherdevices_idx[mesure]
    sql=sql.." and meter_calendar.devicerowid="..otherdevices_idx[conso]
    sql=sql.." and meter_calendar.date=percentage_calendar.date"
    sql=sql.." and meter_calendar.date>='"..OneWeekAgo.."'"
    
    if dbg==1 then
        print("CalcAvgKp")
        print(OneWeekAgo)
        print(sql)
    end    
    
    Kp=os.capture('sqlite3 '..dbPath..' "'..sql..'"')
    print("Kp moyen hebdo pour "..mesure.." : "..tostring(Kp))

    return Kp
end
-- -------------------------------
-- Fin bibliothèque de fonctions
-- -------------------------------

commandArray = {}

if devicechanged == nil then -- partie "Time"
    time = os.date("*t")
   
    --Calcul de la durée optimale de filtration et de l'heure de démarrage
    if time.hour==0 and time.min ==1 then 
        CalcFiltration()
        UpdateVar(var_KpRedox,round(CalcAvgKp(dev_ORPm,dev_CptCl),0))
        UpdateVar(var_KpAcide,round(CalcAvgKp(dev_pHm, dev_CptAc),0))
    end

    -- Marche/Arrêt de la pompe de filtration
    if (((time.min-2) % 15)== 0) then ChkFiltration() end

    --[[ Mise à jour des capteurs et consignes toutes les heures
    simple recopie de la valeur pour qu'ils ne passent pas en rouge... --]]
    if time.min==5 then
        RefreshDev(dev_CptCl)
        RefreshDev(dev_QBidonCl)
        RefreshDev(dev_CptAc)
        RefreshDev(dev_QBidonAc)
        RefreshDev(dev_FreeCl)
        RefreshDev(dev_pHm)
        RefreshDev(dev_ORPm)
        
        RefreshSP(dev_SPBidonCl)
        RefreshSP(dev_SPORP)
        RefreshSP(dev_SPCl)
        RefreshSP(dev_SPBidonAc)
        RefreshSP(dev_SPpH)
        RefreshSP(dev_SPAc)
        RefreshSP(dev_DureeFlManu)
    end

    --[[ Bilan thermique mis à jour toutes les 5 minutes décalée d'1 minute pour
    être sûr que les variables calculées par le script Solar sont bien à jour--]]
    if  (((time.min - 1)% 5)==0) then BilanThermique() end

    --[[ Calcul du chlore libre, seulement pendant la filtration
    Toutes les minutes et 5 minutes après le démarrage de la pompe de filtraton--]]
    if otherdevices[dev_PpeFl]=="On" and lastupdate(dev_PpeFl)>=300 then
        UpdateDev(dev_FreeCl,0,CalcFreeCl(otherdevices[dev_pHm],otherdevices[dev_ORPm]))
    end
     -- Fin partie "Time"
else -- partie "Device"
    -- 0. Gestion de la filtration temporisée
    if devicechanged[dev_FiltT]=="On" and otherdevices[dev_PpeFl]=="Off" and otherdevices[dev_ModeFl]=="Manu" then
        dureeFiltration=tostring(round(60*tonumber(otherdevices[dev_DureeFlManu])))
        print("Piscine : "..dureeFiltration.." s")
        UpdateSw(dev_PpeFl,"On FOR "..dureeFiltration.." SECONDS")
    end
    
    --    1. Gestion de la chloration
    if devicechanged[dev_InjCl]=="On" and otherdevices[dev_Melange] == "Off" then
        UpdateSw(dev_InjCl,'Off')

        -- Détermination de la dose à injecter
        if otherdevices[dev_ModeCl]== "Manu" then 
            print("Chloration demandée en mode manu")
            QCl=round(otherdevices_svalues[dev_SPCl],0) -- Quantité de Chlore, en ml
        elseif otherdevices[dev_ModeCl]== "Auto" then 
            QCl=CalcClAuto()
        else
            print("Chloration demandée en mode Off : Ignoré")
            QCl=0
        end

        -- Vérification qu'on a bien la quantité demandée dans le bidon
        QCl=ChkQBidon(QCl,dev_QBidonCl,"chlore")

        -- Injection du chlore et asservissement à la filtration
        DoInjection(QCl,Cal_QCl,dev_PpeCl,"chlore",var_PpeClTodo)
    end
    
    -- Gestion de l'injection d'acide
    if devicechanged[dev_InjAc]=="On" and otherdevices[dev_Melange] == "Off" then
        UpdateSw(dev_InjAc,'Off')

        -- Détermination de la dose à injecter
        if otherdevices[dev_ModeAc]== "Manu" then 
            print("Acidification demandée en mode manu")
            QAc=round(otherdevices_svalues[dev_SPAc],0) -- Quantité de Chlore, en ml
        elseif otherdevices[dev_ModeAc]== "Auto" then 
            QAc=CalcAcAuto()
        else
            print("Acidification demandée en mode Off : Ignoré")
            QCl=0
        end

        -- Vérification qu'on a bien la quantité demandée dans le bidon
        QAc=ChkQBidon(QAc,dev_QBidonAc,"acide")

        -- Injection du chlore et asservissement à la filtration
        DoInjection(QAc,Cal_QAc,dev_PpeAc,"acide",var_PpeAcTodo)
    end

    --1.3 Calcul de la quantité de Chlore injecté en tenant compte des reboot wemos
    MaJInjection(dev_PpeCl,dev_CptCl,dev_QBidonCl,var_PpeClOld,var_PpeClTime,var_PpeClTodo,Cal_QCl,"Chlore",1500)
    MaJInjection(dev_PpeAc,dev_CptAc,dev_QBidonAc,var_PpeAcOld,var_PpeAcTime,var_PpeAcTodo,Cal_QAc,"Acide",500)

    -- 1.4 Gestion du changement de bidon de chlore ou acide
    ChgBidon(dev_ChtBidonCl,dev_QBidonCl,dev_SPBidonCl)
    ChgBidon(dev_ChtBidonAc,dev_QBidonAc,dev_SPBidonAc)

    -- 2. Calcul du pH et ORP moyen
    CalcAvg()

    --[[3. Gestion du reboot wemos
    5s après l'information de reboot pour laisser le temps à la wemos d'initialiser tous ses plugins :
    - On repositionne l'état de la filtration après 20s 
    - On renvoi une demande à la wemos de la durée restante, différée de 60s (Rule InjCl)--]]
    if (devicechanged[dev_reboot]) and otherdevices[dev_reboot]=="Off" then
        print("Reboot wemos piscine :(")
        print("Renvoi de l'état de la pompe de filtration dans 20s")
        UpdateSw(dev_PpeFl,"On AFTER 20")
        
        --Les pompes acide et chlore ne marchent jamais en même temps. Donc au moment du Reboot
        -- au maximum l'une d'entre elle était en marche -> on peut renvoyer les 2 sans se soucier
        -- de savoir laquelle
        RenvoiMarche("Cl",dev_PpeCl,uservariables[var_PpeClTodo])
        RenvoiMarche("Ac",dev_PpeAc,uservariables[var_PpeAcTodo])
    end

    --Vérification du débit de la pompe
    --ChkFlow()

    --Gestion des switches de pompes peristaltiques
    ManagePumps(addr_Wemos)
end --Fin partie Device

return commandArray
bibliotheque.lua

Code : Tout sélectionner

--Bibliothèque de fonctions lua pour scripts domoticz 

function round(num, dec)
    if num == 0 then
        return 0
    else
        local mult = 10^(dec or 0)
        return math.floor(num * mult + 0.5) / mult
    end
end

function lastupdate(obj,isvar)
    -- retourne le nombre de secondes écoulées depuis la dernière mise à jour d'un capteur ou variable
    -- isvar : true si obj est une variable ; false ou optionel sinon
    isvar=isvar or false
    if isvar then
        s=uservariables_lastupdate[obj]
    else
        s=otherdevices_lastupdate[obj]
    end

    s_year = string.sub(s, 1, 4)
    s_month = string.sub(s, 6, 7)
    s_day = string.sub(s, 9, 10)
    s_hour = string.sub(s, 12, 13)
    s_minutes = string.sub(s, 15, 16)
    s_seconds = string.sub(s, 18, 19)

    t2 = os.time{year=s_year, month=s_month, day=s_day, hour=s_hour, min=s_minutes, sec=s_seconds}
    return os.difftime (os.time(), t2)
end

function Notification(texte)
    -- Envoi "texte" en notification
    print(texte)
    commandArray[#commandArray+1] = {['SendNotification']="Domoticz#"..texte..'#0'}
end

function RefreshDev(device)
    -- Recopie la valeur actuelle dans un capteur pour qu'il ne passe pas en rouge
    commandArray[#commandArray+1] = {['UpdateDevice'] = otherdevices_idx[device]..'|0|'..tostring(otherdevices_svalues[device])}
end

function RefreshSP(device)
    -- Recopie la valeur actuelle dans un setpoint pour qu'il ne passe pas en rouge
    commandArray[#commandArray+1] = {['SetSetPoint:'..otherdevices_idx[device]] = tostring(otherdevices_svalues[device])}
end

function UpdateSw(device,commande)
    --Update un switch Domoticz
    commandArray[#commandArray+1] = {[device] = commande}
end

function UpdateVar(variable,valeur)
    --Update une variable Domoticz
    commandArray[#commandArray+1] = {['Variable:'..variable] = tostring(valeur)}
end

function UpdateDev(device,nvalue,svalues)
    --Met à jour un device numérique Domoticz
    commandArray[#commandArray+1] = {['UpdateDevice'] = otherdevices_idx[device]..'|'..tostring(nvalue)..'|'..tostring(svalues)}
end

function UpdateCpt(device,incr)
    -- Met à jour un counter
    -- device : nom du compteur
    -- incr   : Quantité à ajouter au compteur
    
    local Cpt
    
    Cpt = otherdevices_svalues[device]
    Cpt=Cpt or 0 -- Utile pour les compteurs nouvellement crées
    
    commandArray[#commandArray+1] = {['UpdateDevice'] = tostring(otherdevices_idx[device]).."|0|"..tostring(Cpt+incr)}
end

function UpdateP1(device,HC,conso,prod,freq)
    -- Met à jour le P1 smart counter
    -- device : nom du compteur
    -- HC     : "On" si heures creuses, "Off" si heures pleines
    -- conso  : consommation instantannée
    -- prod   : production instantannée
    -- freq   : frequence d'exécution du script (en minutes) 
    
    local u1, u2, p1, p2
    local dbg=0
    
    u1, u2, p1, p2 = string.match(otherdevices_svalues[device],"(.-);(.-);(.-);(.-);.*")
    
    -- Utile pour les compteurs nouvellement crées
    u1=u1 or 0
    u2=u2 or 0
    p1=p1 or 0
    p2=p2 or 0

    if dbg == 1 then
        print("Mise à jour de "..tostring(device))
        print("u1 "..tostring(u1))
        print("u2 "..tostring(u2))
        print("p1 "..tostring(p1))
        print("p2 "..tostring(p2))
        print("conso "..tostring(conso))
        print("prod "..tostring(prod))
    end

    if otherdevices[HC] == 'Off' then
        -- svalue=USAGE1;USAGE2;RETURN1;RETURN2;CONS;PROD
        commandArray[#commandArray+1] = {['UpdateDevice'] = otherdevices_idx[device]..'|0|'..tostring(u1+conso*freq/60)..';'..tostring(u2)..';'..tostring(p1+prod*freq/60)..';'..tostring(p2)..';'..tostring(conso)..';'..tostring(prod)}
    else
        commandArray[#commandArray+1] = {['UpdateDevice'] = otherdevices_idx[device]..'|0|'..tostring(u1)..';'..tostring(u2+conso*freq/60)..';'..tostring(p1)..';'..tostring(p2+prod*freq/60)..';'..tostring(conso)..';'..tostring(prod)}
    end
end

function os.capture(cmd, raw)
    local f = assert(io.popen(cmd, 'r'))
    local s = assert(f:read('*a'))
    f:close()
    if raw then return s end
    s = string.gsub(s, '^%s+', '')
    s = string.gsub(s, '%s+$', '')
    s = string.gsub(s, '[\n\r]+', ' ')
    return s
end

function IsAlive(IP)
    if os.capture("ping -qnc 1 -w1 "..IP.." >/dev/null && echo On || echo Off")=="On" then
	return true
    else
        return false
    end
end

function WemosCmd(IP,Cmd,Dbg)
    commande = "http://"..IP.."/control?cmd="..Cmd
    if dbg==1 then print(commande) end
    os.execute('curl "'..commande..'" &')
end

function UpdateRmt(credentials,ipp,local_dev,remote_idx)
    --[[mise à jour du même capteur sur un domoticz distant
    credentials : login:pw sur le domoticz distant
    ipp : ip:port du domoticz distant
    local_dev : nom du capteur local à envoyer
    remote_idx : index du capteur distant à mettre à jour
    ]]--
    
    cmd = "http://"..credentials.."@"..ipp.."/json.htm?type=command&param=udevice&idx="..tostring(remote_idx).."&nvalue=0&svalue="..otherdevices_svalues[local_dev]
    --print(cmd)
    os.execute('curl "'..cmd..'" &')
end
stephdes
Messages : 285
Inscription : 06 oct. 2016, 20:50

Re: [PROJET] Gestion de piscine

Message par stephdes »

Bonsoir !

En fait j'ai une piscine au sel avec électrolyseur. Ce qui est étrange c'est que l'année dernière j'arrivais plutôt bien à lire le Redox... C'est pour cela que j'ai acheté une sonde toute neuve mais rien n'y fait.

Du coup ce matin j'ai fait une mesure avec le réactif : chlore inférieur à 0,2 PPM. Je laisse donc la piscine bâchée et je lance la filtration en auto avec production de chlore. Ce qui normalement va booster le chlore de la piscine.

Ce soir je reviens et je check le chlore avec le réactif : 1,5 à 2 PPM. Côté sonde, c'est illisible !

C'est quand même étrange car je n'ai jamais eu de variation de lecture de Redox l'année dernière et là, voici ce que ça donne. Il doit y avoir un problème quelque part mais où... Déjà ce qui me gêne c'est la différence entre le résultat de l'eau de la chambre d'analyse dans un verre et de la même eau dans la chambre...

Vraiment bizarre ce truc... Serait-ce dû à l'électrolyseur... Faudrait que je le coupe complètement pour voir car la sonde est plus proche du système que l'année dernière mais bon je trouve ça louche quand même

Image
MiniPC W10 + RFXCom 433e + RFLINK
Elec : Téléinfo
Alexa + Alhau
MQTT...
(merci easydomoticz !)
aleph0
Messages : 328
Inscription : 27 avr. 2016, 08:06
Localisation : Hérault (34)

Re: [PROJET] Gestion de piscine

Message par aleph0 »

attends, la différence que tu donnes c'est entre l'eau de la piscine dans un verre et l'eau dans le chambre d'analyse alors que tu n'as pas lancé la filtration ou l'électrolyseur ? L'année dernière tu lisais correctement et tu n'as rajouté qu'une carte phidget pour le pH depuis, c'est bien ça ?

Tu es sûr de tes alimentations ? puissance ? "propreté" du +5V ?
stephdes
Messages : 285
Inscription : 06 oct. 2016, 20:50

Re: [PROJET] Gestion de piscine

Message par stephdes »

Exact : quand je vide l'eau contenue dans ma chambre d'analyse dans un verre et que je demonte la sonde pour mesurer dans le verre j'ai déjà un écart de mesure...

Après essai, l'electrolyseur n'y est pour rien. Chose étrange quand je débranche le ground du phidget ORP la mesure du pH passe à 1,5 au lieu de 7,5... Faudrait que je débranche le ground du phidget pH pour voir l'effet sur l'ORP

Mon alim c'est une Aukey 4 ports 5v 2A chacune. Un seul port alimente la wemos, les 2 phidgets et un shield relay. Le 3,3 est donné par la wemos

En fait j'ai fait beaucoup de changements sur l'installation et à vrai dire je ne sais plus où creuser pour mon problème.



Faudrait déjà que je remette tout à plat sur une plaque de montage.
MiniPC W10 + RFXCom 433e + RFLINK
Elec : Téléinfo
Alexa + Alhau
MQTT...
(merci easydomoticz !)
nickous
Messages : 30
Inscription : 24 juil. 2015, 16:31

Re: [PROJET] Gestion de piscine

Message par nickous »

aleph0 a écrit :Pour éclaircir tous les phénomènes parasites dont on doit s'affranchir, voici le script que j'utilise pour la gestion. Il est assez commenté, j'espère qu'il est compréhensible sinon... demandez !!
Alleluia Aleph0
tu m'as donné le coup de grâce...
...pour me lancer enfin et initier la construction de mon système de supervision. :idea:
J'avais approvisionné tout le matériel mais, pas très doué en script Domoticz, je ne savais pas comment démarrer!
Alors avec tes infos sur le fofo anglais + celles-ci + -achèvement final- les scripts lua super bien commentés, je me sens d'attaque.

J'ai quand même quelques questions:
  • Dans la fonction CalcFiltration(), il n'y a pas de paramètre <Volume du bassin>, le coef "0.65" est sans doute à ajuster selon sa configuration

    Code : Tout sélectionner

     dureeFiltration=round(60*math.max(10/60,0.65*(val_TEau-10)),0)
  • Où se calcule la consommation de la pompe de filtration?
    je pense que le device <dev_cptFl> doit être mis à jour par la fonction <UpdateP1>
    mais quand et comment est lancée cette fonction? Un script time lua?
  • Dans le cas du mode HC, tu ne te bases pas sur les heures creuses EDF?
    J'ai pas compris le coup du 07h00
  • Calibration des sondes
    En fait j'ai pas du tout compris comment tu fais pour les étalonner :?
    Comment déterminer et appliquer les coef de correction? Cà se passe sur le Wemos?
    Sur d'autre postes j'ai noté la méthode : détermination point bas, point haut puis calcul par régression lineaire? Qu'en penses-tu?
  • Les rules sur le Wemos
    je sèche un peu pour appliquer les règles d'injection Chlore et Acide
  • Les Variables et Device Domoticz
    Pour me conforter dans mon paramétrage, y-aurait-il moyen d'avoir quelques copies d'écran supplémentaires des variables et device déclarées dans Domoticz (comment çà j'abuse... :roll: :oops:)
Pour le matériel de dosage, je suis parti sur une pompe SEKO en 230V. J'espère éviter les soucis de tuyauterie/acide et surtout les problèmes de masse qui semble souvent revenir. A ce propos, la masse du poolterre, c'est une masse indépendante du circuit électrique ou c'est la même? J'ai lu les 2 avis...?!

A part la calibration que je n'ai rien compris (mon côté apprenti), je remercie et félicite encore Aleph0 pour son partage, la clarté de ses explications et aussi sa disponibilité.
Merci
Répondre