[LUA] Gestion piscine

Vous avez créé un script LUA dont vous êtes fier, un .sh génial, un programme Python hors du commun, un Tuto, c'est ici que vous pouvez les partager.
mickaelr30
Messages : 210
Inscription : 29 août 2015, 15:57

[LUA] Gestion piscine

Message par mickaelr30 »

bonjour,
les beaux jours arrivants, il va falloir remettre en route la régulation de la piscine.
du coup j'ai repris le scripts d'aleph0 viewtopic.php?f=9&t=3795 et je l'ai modifié car j'utilise les sorties de la raspberry qui est installée dans le local de la piscine équipée d'une carte 8 relais.
voici le scripts bibliothèque :

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 ConsoDev(switch,ConsoMarche,ConsoVeille)
    -- Retourne la consommation d'un appareil commandé
    -- selon qu'il est en marche ou en veille
    if otherdevices[switch] == 'On' then
        return ConsoMarche
    else
        return ConsoVeille
    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 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
et le scripts 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
    - 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 du débit bas de la pompe de filtration (pas utilisé dans mon cas)
    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...)

4. Gestion de la filtration
4.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 (pas utilisé dans mon cas)
--]]

-- Paramètres communs
local dbPath   = "/home/pi/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_HC         = "Heures creuses EDF"           -- Indicateur d'heure creuses EdF

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_ORPt           = "Tension sonde Redox"     -- Valeur de la tension de la sonde Redox
local dev_ORPi            = "ORP"                    		 -- ORP instantanné
local dev_ORPm         = "Redox moyen"           	 -- redox moyen sur 5 min
local Cal_QCl              = 0.186                  	         -- 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"
local var_ORPvetal       = "Valeur étalon Redox"	 -- Valeur étalon de l'échantillon Redox 
local var_ORPvlue	       = "Valeur lue Redox"	 -- Valeur de la tension de l'échantillon 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_pHt        	= "Tension sonde pH"          -- Valeur de la tension de la sonde pH
local dev_pHi       	        = "pH"                        -- pH instantanné
local dev_pHm              = "pH moyen"                  -- pH moyen sur 5 min
local Cal_QAc               = 0.186                       -- 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"
local var_pHetal1         = "Valeur étalon 1 pH"         -- Valeur étalon de l'échantillon n°1
local var_pHetal2        = "Valeur étalon 2 pH"         -- Valeur étalon de l'échantillon n°2
local var_pHvetal1       = "Valeur lue étalon 1 pH"     -- Valeur de la tension de l'échantillon n°1
local var_pHvetal2      = "Valeur lue étalon 2 pH"     -- Valeur de la tension de l'échantillon n°2

local dev_PpeDos	    ="Relais marche" --1=pompe dosage en marche
local dev_ClAc	    ="Relais chlore"   --0=pompe chlore ; 1=pompe acide
    
-- Réglages
local cst_PpeFl_Qn=14          -- Débit nominal de la pompe de filtration en m3/h
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_Pisc_V=55          -- volume du bassin, en m3
local cst_PpeFl_W =750    -- Puissance électrique de la pompe piscine
local cst_Adj     =0.25  -- Coefficient à ajuster en + ou en - pour filtrer plus ou moins longtemps




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

dofile('/home/pi/domoticz/scripts/lua/bibliotheque.lua')

-- Functions specific to this script 
function RaspInj(Ppe,LocalDev,Pause,Duree)
-- Demande à la raspberry d'injecter du chlore pour une Duree après un délai de Pause
-- 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

    print ("Temps de pause avant démarrage : "..tostring(Pause).."sec")
    
    if Ppe=="Cl" then
        print ("Durée d'injection de Chlore : "..tostring(Duree).."sec")
        UpdateSw(LocalDev,'On AFTER '..tostring(Pause))
    elseif Ppe=="Ac" then
        print ("Durée d'injection d'Acide : "..tostring(Duree).."sec")
        UpdateSw(LocalDev,'On AFTER '..tostring(Pause))
    else
        print("Erreur RaspInj : Pompe inconnue. Doit être Cl ou Ac")
    end
    -- La pompe est normalement arrêtée par la raspberry et son statut mis à jour
    -- On double ici la commande d'arrêt au cas ou
    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é 
- Les commandes de pompes sont passées par des longpulses ou par le bouton
- Le statut du bouton est mis à jour par la raspberry
- 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 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, MidiSol, 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(0,cst_Adj*cst_Pisc_V/cst_PpeFl_Qn*((val_TEau))-10),0) -- Résultat en minutes 
    --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")
    print("Début de filtration : "..HDebFilt)
    
    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("Température de la piscine : "..otherdevices_svalues[dev_TEau].." °C")
        --val_TEau = tonumber(string.match(otherdevices_svalues[dev_TEau],"(%d+.%d*);.*"))
        --val_TEau = tonumber(string.match(otherdevices_svalues[dev_TEau],"(.-);.*"))
    val_TEau=tonumber(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")
               
                    UpdateSw(dev_PpeFl,'On')
                    UpdateDev(dev_Status,0,"Démarrage par timer")
                
            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")
               
                    --[[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")
                             
            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 gst_HorsGel 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")
             
                    UpdateDev(dev_Status,0,"Démarrage par température")
                    UpdateSw(dev_PpeFl,'On')
               
            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")
               
                    UpdateDev(dev_Status,0,"Arrêt par température")
                    UpdateSw(dev_PpeFl,'Off')
              
            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],"(.-);.*"))
    val_TEau=tonumber(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()
    --[[
    ******************************************************************
       Gestion du débit bas de la pompe de filtration
    ******************************************************************
    Le débit est remonté 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
    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 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()
    --[[****************************************************************************
       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_pHt]) then --moyenne glissante sur 15min (30 valeurs)
            UpdateDev(dev_pHm,0,(29*tonumber(otherdevices_svalues[dev_pHm])+otherdevices_svalues[dev_pHi])/30)
        end
        if (devicechanged[dev_ORPt]) 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_P=PID_Kp*100*(Cl_voulu - Cl_actuel)/cst_pctCl*cst_Pisc_V
        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")
        print ("Chlore actuel : "..tostring(Cl_actuel))
        print ("Chlore voulu : "..tostring(Cl_voulu))
       
    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
        RaspInj(Pr,dev_Ppe,FiltrPreInjection,Ppe_Duree)

        --Positionnement de la variable "reste à faire pompe" 
        --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()
  --[[Gère les pompes peristaltiques
    Elles ne doivent pas être commandées directement, 
    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="Relais marche" --1=pompe dosage en marche
	-- local dev_ClAc="Relais chlore"   --0=pompe chlore ; 1=pompe acide
    
    -- Action (uniquement pour debug et/ou amorçage des pompes)
  
    
    --Mise à jour des switches sans actions
    if devicechanged[dev_PpeAc]=="On" then
        UpdateSw(dev_PpeDos,"On")
		UpdateSw(dev_ClAc,"On")
        
    elseif devicechanged[dev_PpeCl]=="On" then
		UpdateSw(dev_PpeDos,"On")
		UpdateSw(dev_ClAc,"Off")
		
    elseif devicechanged[dev_PpeAc]=="Off" or devicechanged[dev_PpeCl]=="Off" then
        UpdateSw(dev_PpeDos,"Off")
        UpdateSw(dev_ClAc,"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)
    --OneWeekAgo=os.date("%Y-%m-%d",os.time()-1*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

function CalcORP()
--[[fonction pour le calcul de la valeur de l'ORP en tenant compte de la valeur étalon]]--
 local val_VORP

val_ORPt = tonumber(otherdevices_svalues[dev_ORPt])
val_ORPvetal = uservariables[var_ORPvetal]
val_ORPvlue = uservariables[var_ORPvlue]

	if devicechanged[dev_ORPt] then
    	val_VORP = round(((val_ORPt*val_ORPvetal)/val_ORPvlue),0)

	    if val_VORP >2000 then
	   val_VORP = 2000
	    print ("Erreur de mesure sur la sonde de redox")
	    elseif val_VORP <-2000 then
    	   val_VORP = -2000
	    print ("erreur de mesure sur la sonde de redox")
    end
    
UpdateDev(dev_ORPi,0,val_VORP)	

	end
end

function CalcpH()
--[[fonction pour le calcul de la valeur du pH en fonction de 2 valeurs pour l'étalonnage de la sonde pH]]--
local val_VpH

val_pHt = tonumber(otherdevices_svalues[dev_pHt])
val_pHetal1 = uservariables[var_pHetal1]
val_pHetal2 = uservariables[var_pHetal2]
val_pHvetal1 = uservariables[var_pHvetal1]
val_pHvetal2 = uservariables[var_pHvetal2]

    if devicechanged[dev_pHt] then
        val_VpH = round(val_pHetal1+(val_pHetal2-val_pHetal1)*((val_pHt-val_pHvetal1)/(val_pHvetal2-val_pHvetal1)),1)
    
        if val_VpH >14 then
            val_VpH=14
        print ("Valeur erronnée du pH")
        elseif val_VpH<0 then
            val_VpH=0
        print ("Valeur erronnée du pH ")
        end
    
UpdateDev(dev_pHi,0,val_VpH)

    end
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 
    --if (((time.min-1) % 5)== 0) then
        CalcFiltration()
        UpdateVar(var_KpRedox,round(CalcAvgKp(dev_ORPm,dev_CptCl),0))
        UpdateVar(var_KpAcide,round(CalcAvgKp(dev_pHm, dev_CptAc),0))
    end

    -- MàJ du calcul de la pompe de filtration
    if (((time.min-2) % 15)== 0) then ChkFiltration() end
    
    -- Mise à jour du compteur de consommation de la piscine toutes les 5min
if (((time.min-2) % 5)== 0) then 
    UpdateP1(dev_cptFl,dev_HC,ConsoDev(dev_PpeFl,cst_PpeFl_W,0),0,5)
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')

    -- 2. 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

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

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

    -- 6. 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é")
            QAc=0
        end

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

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

    -- 9. Calcul de la quantité de Chlore injecté 
    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)

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

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

    -- 12. Calcul de l'ORP en fonction des étalons
    CalcORP()
    
    -- 13. Calcul du pH en fonction des étalons
    CalcpH()
    
    --14. Vérification du débit de la pompe
    --ChkFlow()

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

return commandArray
voici les variables, j'ai ajouté également 2 variables pour faire un étalonnage de la sonde de chlore et 4 pour faire l'étalonnage de la sonde de pH
Image

les interrupteurs :
Image

les mesures :
Image

pour le branchement des 2 pompes j'ai gardé le meme principe
par contre il faudra que je fasse quelques modif car avec imperihome les thermostat ne vont que jusqu'à 100 et de 0.5 à 0.5 donc surement faire un calcul pour faire du x10 ou /10 pour ce ce soit pratique avec imperihome
Dernière modification par mickaelr30 le 15 mai 2019, 14:21, modifié 3 fois.
Domoticz + Imperihome + Mydomoathome
1-wire : 12 DS18b20 + 3 DS2413
Z-Wave : 8 FGR221 + 3 FGS222 + 3 FGSD002
téléinfo : 2 modules ( consommation + production)
Mysensors+2 éclairages ws2812 et WLED
2 caméra IP +1 raspberry piscine + 1 nuc domoticz
fab0030
Messages : 102
Inscription : 24 nov. 2016, 17:04

Re: nouvelle Gestion piscine

Message par fab0030 »

Je ne dirais qu'un mot : bravo :D ;)
jnoel68
Messages : 17
Inscription : 26 avr. 2016, 13:29

Re: nouvelle Gestion piscine

Message par jnoel68 »

Un grand bravo Mickaël, je vais essayer de m'inspirer de ce que tu as fait pour l'adapter chez moi.

Quelques questions cependant :
- peux-tu détailler les sondes utilisées et branchements que tu as fait sur ton raspberry pour tout ton système ?
- pour ma part, j'ai mon domoticz à la maison, le local piscine est à 20 mètres sans cablage Ethernet, mais on y chope le Wifi, surtout avec les relais Wifi que j'y met l'été, je ne me suis pas encore lancé dans un système domoticz avec double raspberry, je pense que ce doit être possible maitre/esclave ... procèdes-tu comme cela ou bien tu as ton domoticz "piscine" dédié ?
- pas de chlore chez moi, mais un système d’électrolyse au sel, est-ce qu'on peut adapter le fonctionnement à ce système ?

Merci
mickaelr30
Messages : 210
Inscription : 29 août 2015, 15:57

Re: nouvelle Gestion piscine

Message par mickaelr30 »

bonjour Jnoel68,
pour les sondes de température, j'utilise des sondes 1-wire étanche que j'ai mis dans des colliers de prise en charge et des presse étoupe d'électricien.
pour la sonde de pH et de chlore, j'utilise un arduino uno branché en USB sur le raspberry de la piscine qui communique en mysensors pour récupérer les valeurs, une carte d'extension est installée sur l'arduino (sans faire de pub j'ai celle-ci :https://piscine.swimo.fr/fr/accessoires ... lemon-p-28, les mesures sont assez stables). les 2 sondes sont mises dans des filtres à eau qui font chambre de tranquillisation.
j'ai un domoticz pour la maison et un pour la piscine qui communiquent ensemble (maitre/esclave), et je peux tous modifier également via impérihome.
les deux raspberry sont connectés en RJ45 mais ils peuvent aussi fonctionner en wifi
pour l’électrolyseur, je dirai que oui c'est faisable, on doit pouvoir remplacer la pompe de chlore.
je suis en train de me demander même s'il n'y aurait pas possibilité de faire un électrolyseur fait maison :P
Domoticz + Imperihome + Mydomoathome
1-wire : 12 DS18b20 + 3 DS2413
Z-Wave : 8 FGR221 + 3 FGS222 + 3 FGSD002
téléinfo : 2 modules ( consommation + production)
Mysensors+2 éclairages ws2812 et WLED
2 caméra IP +1 raspberry piscine + 1 nuc domoticz
aleph0
Messages : 328
Inscription : 27 avr. 2016, 08:06
Localisation : Hérault (34)

Re: nouvelle Gestion piscine

Message par aleph0 »

@mickaelr30

Salut ! C'est chouette ce que tu as fait de mon script :) il est pas facile à prendre en main, ça fait plaisir de voir les gens se l'approprier pour l'adapter à leur besoin :)

@jnoel68 : Pour les sondes de températures, j'utilisais un thermomètre flotant en 433MHz, mais il a eu une faible durée de vie (1,5 an). Du coup j'ai décidé de m'en passer et maintenant j'estime la température de l'eau avec un calcul basé sur le soleil et la météo. Il marche pas mal, il est à +/-1° de la réalité sur plusieurs mois... Si ça intéresse des gens, je pourrais le décrire sur le forum !
jnoel68
Messages : 17
Inscription : 26 avr. 2016, 13:29

Re: nouvelle Gestion piscine

Message par jnoel68 »

aleph0 a écrit : 16 mai 2019, 11:31 @jnoel68 : Pour les sondes de températures, j'utilisais un thermomètre flotant en 433MHz, mais il a eu une faible durée de vie (1,5 an). Du coup j'ai décidé de m'en passer et maintenant j'estime la température de l'eau avec un calcul basé sur le soleil et la météo. Il marche pas mal, il est à +/-1° de la réalité sur plusieurs mois... Si ça intéresse des gens, je pourrais le décrire sur le forum !
@aleph, merci pour le feedback

J'avais aussi lu que les thermomètres flottants avaient une durée de vie limitée ... pour ceux qui marchaient parce que pour certains ce n'était pas le cas ... et j'ai un volet roulant sur la piscine, donc pas trop compatible, sauf à le mettre dans le skimmer

Ensuite, je vois bien que l'autre solution c'est les sondes via 1-wire, mais là aussi je lis bcp de problème. Entre les capteurs pas forcément tous fiables à la base, celles qu'il faut ajuster avec la bonne résistance pour que ce soit assez précis ... et le côté technique (que ce soit electro, soudure ou autre), j'ai l'impression que c'est pas aussi simple que tout le monde veut bien le dire .. bref je me sens pas trop de me lancer la dedans

Sinon, sans vouloir faire de pub, j'ai vu ce site http://electrolyseur.fr/ qui a l'air d'être fait par un particulier plein d'imagination ... si quelqu'un à des retours ou déjà essayé les produits qu'il propose je suis preneur

Preneur aussi d'un tuto ou explication de ton script pour le calcul de température basé sur le soleil et la météo, je vois pas trop comment ça peut être précis, donc j'aimerais bien comprendre et au besoin l'adapter

Merci
mickaelr30
Messages : 210
Inscription : 29 août 2015, 15:57

Re: nouvelle Gestion piscine

Message par mickaelr30 »

aleph0,
j'y ai passé par mal d'heure et quelques nuits blanches pour arrivée à comprendre ton script :lol:
maintenant ça va pas mal !!!
Domoticz + Imperihome + Mydomoathome
1-wire : 12 DS18b20 + 3 DS2413
Z-Wave : 8 FGR221 + 3 FGS222 + 3 FGSD002
téléinfo : 2 modules ( consommation + production)
Mysensors+2 éclairages ws2812 et WLED
2 caméra IP +1 raspberry piscine + 1 nuc domoticz
jnoel68
Messages : 17
Inscription : 26 avr. 2016, 13:29

Re: nouvelle Gestion piscine

Message par jnoel68 »

J'ai passé pas mal de temps pour décortiquer le script, vraiment pas mal du tout, un grand bravo !
Ce que je ne comprends bien, c'est comment tu envoies vraiment les ordres aux pompes (j'ai bien vu les appels aux fonctions DoInjection, RaspInj, ManagePumps et ChkFiltration). Tout se fait uniquement grâce aux UpdateSw 'On AFTER' et 'Off AFTER', mais après comment cela communique aux pompes doseuses et à la pompe de filtration (juste un switch On/Off certainement pour celle là ?)
Pour les capteurs, j'ai vu ta ref à un arduino (que je connais de nom, mais je n'ai jamais essayé d'en faire quoi que ce soit) et à une carte d'extension installée sur celui-ci. Si je veux simplifier, est-il possible de tout brancher sur le 1-wire du raspberry pour m'éviter ce genre de composant supplémentaire ?
Pour les pompes doseuses, quel modèles as-tu pris, et comment cela s'interface avec le système (via l'arduino ou de simples switch On/Off également) ?

Bref, encore pas mal de questions avant que je ne m'y mette ...
D'avance merci
mickaelr30
Messages : 210
Inscription : 29 août 2015, 15:57

Re: nouvelle Gestion piscine

Message par mickaelr30 »

bonjour,
Alors pour l'activation des sorties pour commander les pompes, cela se passe sur cette partie de programme :

Code : Tout sélectionner

 if devicechanged[dev_PpeAc]=="On" then
        UpdateSw(dev_PpeDos,"On")
		UpdateSw(dev_ClAc,"On")
        
    elseif devicechanged[dev_PpeCl]=="On" then
		UpdateSw(dev_PpeDos,"On")
		UpdateSw(dev_ClAc,"Off")
		
    elseif devicechanged[dev_PpeAc]=="Off" or devicechanged[dev_PpeCl]=="Off" then
        UpdateSw(dev_PpeDos,"Off")
        UpdateSw(dev_ClAc,"Off")
    end
Si tu veux tout faire fonctionner sans arduino, il faudrait que tu te reportes sur le poste original viewtopic.php?f=9&t=3795
Après il doit exister d'autres systèmes mais il va falloir développer d'autres trucs
Pour mes pompes, se sont des pompes péristaltiques de récupération. Il faut surtout utiliser les bons tuyaux car le Ph- bouffe tout.
Domoticz + Imperihome + Mydomoathome
1-wire : 12 DS18b20 + 3 DS2413
Z-Wave : 8 FGR221 + 3 FGS222 + 3 FGSD002
téléinfo : 2 modules ( consommation + production)
Mysensors+2 éclairages ws2812 et WLED
2 caméra IP +1 raspberry piscine + 1 nuc domoticz
n1co44
Messages : 1
Inscription : 11 déc. 2019, 12:44

Re: nouvelle Gestion piscine

Message par n1co44 »

Bonjour,

Pouvez vous indiquer le code que vous avez injecter dans l'arduino, pour pouvoir envoyé les valeurs de ph et orp sur le raspberry connecté en usb?

Merci d'avance.
Répondre