[Tuto] Fil pilote et régulation PID
Publié : 24 janv. 2016, 08:55
Certains d'entre vous m'ont demandé les scripts qui permettent à Domoticz de commander un radiateur électrique avec régulation PID.
Il ne s'agit ni plus ni moins que de l'adaptation à mon besoin de l'excellent travail de Guitoo.
Bien entendu je ne pourrais pas être tenu pour responsable de l'usage que vous ferez de ce qui est décrit ici.
Pré-requis :
Disposer d'un radiateur électrique avec fil pilote tel que décrit dans l'article de l'Atelier du Geek.
C'est un Interrupteur télécommandé qui gère le fil pilote.
Un radiateur sans fil pilote pourrait également être utilisé mais dans ce cas un interrupteur suffisamment puissant devra gérer l'allumage et l'extinction du radiateur.
La logique ON et OFF des scripts devra aussi être inversée (fil pilote alimenté en 220V = pas de chauffage).
Une sonde de température dans la pièce où se situe le radiateur électrique (pour respecter la consigne).
Principe :
Mon chauffage fonctionne grâce à une chaudière à gaz.
Cette chaudière est régulée grâce à un thermostat connecté QIVIVO.
C'est dans cet espace que sont définis les différents programmes (températures et horaires).

Il y a une très grande pièce dans laquelle le radiateur lié à la chaudière n'est pas vraiment suffisant pour le volume à chauffer.
Plutôt que d'ajouter un radiateur et ses tuyaux dans cette pièce, j'ai opté pour un radiateur électrique de 1500W avec fil pilote.
Les scripts suivants servent à commander le fil pilote pour chauffer la pièce en fonction de la consigne QIVIVO du moment.
A noter le démarrage de ce radiateur électrique a lieu à 3 conditions :
, inutile de chauffer lorsque ça se produit...
Le premier est un script shell lancé par la crontab de root sur mon raspberry pi toutes les 30 mn.
Il va chercher la consigne du moment sur Internet dans mon espace QIVIVO, je ne veux pas solliciter ce serveur trop fréquemment inutilement.
S'agissant d'un radiateur d'appoint, la prise en compte d'un changement de consigne avec un retard de 30 mn est acceptable.
Si l'interrupteur d'arrêt général passe sur ON, alors ce script sera aussi lancé toutes les 5 mn par le script qui exécute la régulation PID.
Il doit être exécuté rapidement sans quoi Domoticz envoie un warning selon lequel l'exécution a duré plus de 10s.
Ca n'évite pas le timeout à tous les coups, mais dans mon cas ça ne mange pas de pain (le proxy sert à d'autres choses).
Ce n'est pas un pré-requis, et le script peut être modifié pour sortir vers Internet directement (supprimer tout ce qui fait référence au proxy).
Ce script met à jour un thermostat virtuel (consigne) et une sonde de température virtuelle (température mesurée par le thermostat QIVIVO).
La consigne est également mémorisée dans un fichier pour intercepter le changement de consigne dans un autre script (passage d'une consigne de nuit à une autre consigne).
Le second script est celui qui gère la régulation PID elle-même, c'est celui de Guitoo avec quelques changements notamment pour prendre en compte la consigne QIVIVO stockée sur disque et effacer les écarts mémorisés jusqu'alors (qui deviennent non pertinents).
Le dernier script est celui qui agit sur le fil pilote.
Il est issu de celui de Guitoo, avec quelques modifications.
Attention à la logique inverse due au fil pilote ;
Ces scripts fonctionnent selon ce principe depuis Septembre 2015 avec comme résultat une température agréable dans la fameuse pièce difficile à chauffer.
La liaison avec la consigne QIVIVO date de fin Octobre 2015, auparavant la consigne était définie par un Setpoint virtuel de Domoticz.
Dans ma configuration, un planning sur l'interrupteur général active le chauffage d'appoint en fin d'après midi seulement.
Il ne s'agit ni plus ni moins que de l'adaptation à mon besoin de l'excellent travail de Guitoo.
Bien entendu je ne pourrais pas être tenu pour responsable de l'usage que vous ferez de ce qui est décrit ici.
Pré-requis :
Disposer d'un radiateur électrique avec fil pilote tel que décrit dans l'article de l'Atelier du Geek.
C'est un Interrupteur télécommandé qui gère le fil pilote.
Un radiateur sans fil pilote pourrait également être utilisé mais dans ce cas un interrupteur suffisamment puissant devra gérer l'allumage et l'extinction du radiateur.
La logique ON et OFF des scripts devra aussi être inversée (fil pilote alimenté en 220V = pas de chauffage).
Une sonde de température dans la pièce où se situe le radiateur électrique (pour respecter la consigne).
Principe :
Mon chauffage fonctionne grâce à une chaudière à gaz.
Cette chaudière est régulée grâce à un thermostat connecté QIVIVO.
C'est dans cet espace que sont définis les différents programmes (températures et horaires).

Il y a une très grande pièce dans laquelle le radiateur lié à la chaudière n'est pas vraiment suffisant pour le volume à chauffer.
Plutôt que d'ajouter un radiateur et ses tuyaux dans cette pièce, j'ai opté pour un radiateur électrique de 1500W avec fil pilote.
Les scripts suivants servent à commander le fil pilote pour chauffer la pièce en fonction de la consigne QIVIVO du moment.
A noter le démarrage de ce radiateur électrique a lieu à 3 conditions :
- Bouton d'arrêt général dans Domoticz sur ON (Interrupteur virtuel)
- Porte d'entrée close
- Porte menant au s/sol close
Le premier est un script shell lancé par la crontab de root sur mon raspberry pi toutes les 30 mn.
Il va chercher la consigne du moment sur Internet dans mon espace QIVIVO, je ne veux pas solliciter ce serveur trop fréquemment inutilement.
S'agissant d'un radiateur d'appoint, la prise en compte d'un changement de consigne avec un retard de 30 mn est acceptable.
Si l'interrupteur d'arrêt général passe sur ON, alors ce script sera aussi lancé toutes les 5 mn par le script qui exécute la régulation PID.
Il doit être exécuté rapidement sans quoi Domoticz envoie un warning selon lequel l'exécution a duré plus de 10s.
C'est pour cela que l'accès à Internet passe par un proxy-cache.2016-01-23 17:31:03.647 Error: EventSystem: Warning!, lua script /home/pi/domoticz/scripts/lua/script_device_thermostatv3.lua has been running for more than 10 seconds
Ca n'évite pas le timeout à tous les coups, mais dans mon cas ça ne mange pas de pain (le proxy sert à d'autres choses).
Ce n'est pas un pré-requis, et le script peut être modifié pour sortir vers Internet directement (supprimer tout ce qui fait référence au proxy).
Ce script met à jour un thermostat virtuel (consigne) et une sonde de température virtuelle (température mesurée par le thermostat QIVIVO).
La consigne est également mémorisée dans un fichier pour intercepter le changement de consigne dans un autre script (passage d'une consigne de nuit à une autre consigne).
Code : Tout sélectionner
#!/bin/bash
#################################################################################################
#
# Script recherchant la température de consigne du thermostat Qivivo
# Auteur : Chrominator
# Version 1.4
# Changes :
# 2015-10-24 : Initial version
# 2015-12-31 : Récupération de la température intérieure mesurée
# 2016-01-22 : Mise à la consigne chaudière de la tête thermostatique du radiateur dans le couloir
# 2016-01-24 : Test avant de mettre à jour si QIVIVO est en cours de maintenance
# (Pas de température en retour)
#
# Principe :
# Connexion à la page "Votre espace" http://www.qivivo.com/login
# puis http://www.qivivo.com/login-check qui renvoie vers la page des réglages du thermostat
# Ensuite mise à jour des températures de consigne et mesurées dans Domoticz
#
#
#################################################################################################
username="pseudonyme de connexion QIVIVO"
password="mot de passe QIVIVO"
useragent="Mozilla/5.0 (Windows NT 10.0; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0"
domoticz_url="http://127.0.0.1:8080"
#
# Utilisation du proxy pour éviter les problèmes de lenteur d'exécution
#
http_proxy="ip du proxy:port"
proxy_user="domoticz"
proxy_passwd="mot de passe"
#
# Index des dispositifs de Domoticz
#
# Setpoint virtuel
TempIdx="129"
# Sonde virtuelle
CorridorIdx="141"
# Nom du fichier qui mémorise la consigne de température
filetcons="/var/tmp/knowtcons.dat"
#Initialisation du répertoire sur disque qui va stocker le cookie
set http_proxy=$http_proxy
if [ ! -d "/var/tmp/surf" ]; then mkdir /var/tmp/surf
fi
if test -f /var/tmp/surf/cookies.txt
then
rm /var/tmp/surf/cookies.txt
fi
# Suppression éventuelle du fichier d'une précédente exécution.
if [ -f "/var/tmp/surf/.qivivo" ]; then rm /var/tmp/surf/.qivivo
fi
#
# On se connecte à la page d'accueil
wget --post-data "_username=$username&_password=$password" --no-check-certificate --keep-session-cookies --save-cookies=/var/tmp/surf/cookies.txt --load-cookies=/var/tmp/surf/cookies.txt -U "$useragent" --referer="http://www.qivivo.com/login" --delete-after --quiet --proxy-user="$proxy_user" --proxy-password="$proxy_passwd" http://www.qivivo.com/login >/dev/null
#
# On va chercher la page qui contient la consigne
wget --post-data "_username=$username&_password=$password" --no-check-certificate --keep-session-cookies --load-cookies=/var/tmp/surf/cookies.txt -U "$useragent" --referer="http://www.qivivo.com/login" --output-document="/var/tmp/surf/.qivivo" --quiet --proxy-user="$proxy_user" --proxy-password="$proxy_passwd" http://www.qivivo.com/login_check > /dev/null
#
# On cherche le litéral knowtcons dans le html reçu :
# <span class="knowtcons" style="display:none">20.0</span>
# On va compter les caractères précédents ce qu'on veut récupérer, pas rusé et ça marchera tant que la page ne sera pas modifiée.
#
temp=`cat /var/tmp/surf/.qivivo |grep 'knowtcons'`
if [ $? == 0 ]; then
knowtcons=`expr substr "$temp" 50 4`
echo "La température de consigne est de $knowtcons°C"
echo $knowtcons > $filetcons
#
# On cherche maintenant la température à l'intérieur du type :
# <span class="tin_color">20</span>.<span class="tin_dixieme">5°
#
temp=`cat /var/tmp/surf/.qivivo |grep 'tin_color'`
temp_inside=`expr substr "$temp" 37 2`"."`expr substr "$temp" 73 1`
curl "$domoticz_url/json.htm?type=command¶m=udevice&idx=$TempIdx&nvalue=0&svalue=$temp_inside"
# Mise à jour de la consigne du radiateur
curl "$domoticz_url/json.htm?type=command¶m=udevice&idx=$CorridorIdx&nvalue=0&svalue=$knowtcons"
echo "La température intérieure est de $temp_inside°C"
else
echo "Erreur lors de la recherche de la température"
fi
Code : Tout sélectionner
-- script_device_thermostatv3.lua ------------------------------------
----------------------------------------------------------------------
-- G PETREMENT 2015 --------------------------------------------------
-- This script is a heating regulation use to maintain temperatur ----
-- at a specific value. ----------------------------------------------
----------------------------------------------------------------------
--------------------------------
--- Variables utilisateur ------
--------------------------------
local VarConsigne = "Consigne QIVIVO" --Température de consigne
local IDConsigne = '20' -- IDX de l'afficheur de consigne
local sonde = 'Thermometre Salon' --Nom de la sonde de température
local InterOnOff = 'Chauffage On/Off' --Interrupteur général de mise en route du chauffage
--local IDerreur = '41' --IDX de l'afficheur d'erreur
--local IDSomerreur = '42' --IDX de l'afficheur Somme d'erreur
--local IDDeltaErreur = '43' --IDX de l'afficheur Différence d'erreur
local IDCmdChauff = '48' --IDX de l'afficheur Proportionnel Commande Chauffage
local domoticz_url = '127.0.0.1:8080' --Adresse IP:port de Domoticz
local VarKp = "Kp" --Variable Domoticz : Gain proportinnel
local VarKi = "Ki" --Variable Domoticz : Gain intégral
local VarKd = "Kd" --Variable Domoticz : Gain dérivé
local VarTempErr = 'TemperatureErreur' --Variable domoticz pour l'erreur de temperature
local VarSomErr = 'SommeErreur' --Variable domoticz pour la somme des erreurs
local VarDeltaErr = 'DeltaErreur' --Variable domoticz pour la différences des erreurs
local VarCmdChauff = 'CmdChauff' --Variable Domoticz de commande du chauffage : permet de passer la commande à script_time_chauff.lua
local VarCmdImax = 'CmdImax' --Variable domoticz qui défini le gain intégral maximum
local fichier = '/var/tmp/dom_tmperr.txt' --Chemin d'acces du fichier sur un RAMdisque pour réduire l'usure de la carte SD
local fcons = '/var/tmp/knowtcons.dat' -- Chemin vers la nouvelle consigne
local nbr_stk = 22 --Quantité de valeurs utilisées pour le calcul de la dérivée de l'erreur (ne pas mettre 0!!)
--------------------------------------------------------------
-- Fonction pour arrondir un nombre à la décimale souhaitée --
--------------------------------------------------------------
math.round = function(number, precision)
precision = precision or 0
local decimal = string.find(tostring(number), ".", nil, true);
if ( decimal ) then
local power = 10 ^ precision;
if ( number >= 0 ) then
number = math.floor(number * power + 0.5) / power;
else
number = math.ceil(number * power - 0.5) / power;
end
-- convert number to string for formatting
number = tostring(number);
-- set cutoff
local cutoff = number:sub(decimal + 1 + precision);
-- delete everything after the cutoff
number = number:gsub(cutoff, "");
else
-- number is an integer
if ( precision > 0 ) then
number = tostring(number);
number = number .. ".";
for i = 1,precision
do
number = number .. "0";
end
end
end
return number;
end
---------------------------------------------------------
--- Fonction pour calculer la commande de chauffage ---
---------------------------------------------------------
Chauff = function(Kp,Err,Ki,SomErr, Kd, DifErr)
local cmd = Kp*Err + Ki*SomErr + Kd*DifErr
print ('cmd =('..Kp..'*'..Err..')+('..Ki..'*'..SomErr..')+('..Kd..'*'..DifErr..')')
-- La commande de chauffage est un nombre entier compris entre 0 et 100
if cmd < 0 then
cmd = 0
elseif cmd > 100 then
cmd = 100
else
cmd = math.round(cmd, 0)
end
return cmd
end
-----------------------------
---- Programme principal ----
-----------------------------
commandArray = {}
--La sonde Oregon 'Salon' émet toutes les 40 secondes. Ce sera approximativement la fréquence d'exécution de ce script.
if (devicechanged[sonde]) then
-- Initialisation des variables
local t = {} --Table buffer avec le fichier texte
local SomErreur = tonumber(uservariables[VarSomErr]) --Somme des erreurs
local Kp = tonumber(uservariables[VarKp])
local Ki = tonumber(uservariables[VarKi])
local Kd = tonumber(uservariables[VarKd])
local CmdImax = tonumber(uservariables[VarCmdImax]) --Limitation de l'action intégrale (debug)
local consigne = otherdevices_svalues[VarConsigne] --Température de consigne existante
-- Lecture de l'ancienne consigne
local c = io.input(fcons)
local consigne_nouvelle = c:read()
c:close()
if (consigne_nouvelle ~= nil ) then
consigne_nouvelle = math.round(tonumber(consigne_nouvelle), 2)
print('Ancienne consigne='..consigne, 'Nouvelle consigne='..consigne_nouvelle)
if (consigne ~= consigne_nouvelle) then
print('Reinitialisation de la somme et suppression du fichier')
commandArray['Variable:'..VarSomErr]='0'
os.remove(fichier) -- Modif du 03/11/2015 pour éviter les valeurs énormes de Kd
consigne = consigne_nouvelle
end
-- Update de la nouvelle consigne
local url = domoticz_url..'/json.htm?type=command¶m=udevice&idx='..IDConsigne..'&nvalue=0&svalue='..consigne_nouvelle
commandArray[1]={['OpenURL']=url }
end
-- Recuperation des minutes
time=os.time()
minutes=tonumber(os.date('%M',time))
hours=tonumber(os.date('%H',time))
freq=tonumber(string.sub(os.date('%M',time), 2))
-- Execution de cette partie du script toutes les 5 minutes si le chauffage est en fonction
-- On limite de cette manière les accès inutiles au www Qivivo
local OnOff = otherdevices[InterOnOff]
if( ((freq==5) or (freq==0)) and OnOff == 'On' ) then
print('-- Récupération de la consigne QIVIVO à '..hours..'h'..minutes)
os.execute('/home/pi/domoticz/scripts/get_tcons.sh')
end
---------------------------------
--- Calcul du proportionnel ---
---------------------------------
--Temperature relevée dans le salon
local temperature = devicechanged[sonde..'_Temperature']
temperature = tonumber(math.round(temperature,2))
-- Update de la sonde d'erreur
local TmpErreur = consigne - temperature
-- local url = domoticz_url..'/json.htm?type=command¶m=udevice&idx='..IDerreur..'&nvalue=0&svalue='..TmpErreur
-- commandArray[1]={['OpenURL']=url }
------------------------------
--- Calcul de la dérivée ---
------------------------------
--Creation si le ficher n'existe pas
local f = io.open(fichier, "a+")
f:close()
--Lecture du fichier et remplissage de la table t
f = io.open(fichier, "r")
for line in f:lines() do
if line ~= nil then
table.insert(t,line)
end
end
f:close()
-- Ajout de la nouvelle ligne a la table
table.insert(t,tostring(os.time()..";"..math.round(TmpErreur,2)))
--Suppression de l'élement le plus vieu
while #t > nbr_stk do
table.remove(t,1)
end
-- Eriture dans fichier'
f = io.open(fichier,"w")
for key,value in pairs(t) do
f:write(value.."\n")
end
f:close()
-- Calcul Difference entre deux erreurs à 15min d'interval + calibration
local MoyErr1 = 0
local MoyErr2 = 0
for i=1,#t/2 do
MoyErr1 = MoyErr1 + tostring(string.sub(t[i],string.find(t[i], ";")+1))
MoyErr2 = MoyErr2 + tostring(string.sub(t[#t+1-i],string.find(t[#t+1-i], ";")+1))
end
--Le delta erreur est calculé sur la moyenne de 22 erreurs ce qui permet de 'lisser' la variation
local DeltaErreur = tonumber(math.round(((MoyErr2 / (#t/2)) - (MoyErr1 / (#t/2)))*8 ,2)) --Delta en d°/heure
print('--DeltaErreur = ('..MoyErr2..'/'..(#t/2)..') - ('..MoyErr1..'/'..(#t/2)..') * 8= '..DeltaErreur)
-- Update de la sonde DeltaErreur
-- url = domoticz_url..'/json.htm?type=command¶m=udevice&idx='..IDDeltaErreur..'&nvalue=0&svalue='..DeltaErreur
-- commandArray[2]={['OpenURL']=url }
-------------------------------
--- Calcul de l'intégrale ---
-------------------------------
-- Calcul de la somme des erreurs + Update de la sonde Somme d'erreur
-- Si la l'erreur est supérieure à +-1° on ne somme pas pour limiter l'impact des changements de consigne
if ((TmpErreur > -1) and (TmpErreur < 1) and (Ki ~= 0)) then
SomErreur = tonumber(math.round(TmpErreur + SomErreur,2))
if (SomErreur > (CmdImax/Ki)) then
SomErreur = CmdImax/Ki
elseif (SomErreur < 0) then
SomErreur = 0
end
end
-- url = domoticz_url..'/json.htm?type=command¶m=udevice&idx='..IDSomerreur..'&nvalue=0&svalue='..SomErreur
-- commandArray[3]={['OpenURL']=url }
------------------------------------------
--- Calcul de la commande de chauffage ---
------------------------------------------
print('Consigne = '..consigne..' Temperature = '..temperature)
local CmdChauff = Chauff(Kp, TmpErreur, Ki, SomErreur, Kd, DeltaErreur)
-- Update de la mesure Commande
url = domoticz_url..'/json.htm?type=command¶m=udevice&idx='..IDCmdChauff..'&nvalue=0&svalue='..CmdChauff
commandArray[4]={['OpenURL']=url }
print('--Commande de chauffage : '..CmdChauff..' / Kp ='..tostring(Kp*TmpErreur)..' / Ki ='..tostring(Ki*SomErreur)..' / Kd ='..tostring(Kd*DeltaErreur))
--Passage des variables à domoticz
commandArray['Variable:'..VarTempErr]= tostring(TmpErreur)
commandArray['Variable:'..VarSomErr]= tostring(SomErreur)
commandArray['Variable:'..VarDeltaErr]= tostring(DeltaErreur)
commandArray['Variable:'..VarCmdChauff]= tostring(CmdChauff)
end
return commandArray
Il est issu de celui de Guitoo, avec quelques modifications.
Attention à la logique inverse due au fil pilote ;
- Fil pilote alimenté en 220V = Arrêt du radiateur - pas de chauffe
- Fil pilote non alimenté = Démarrage du radiateur - chauffe
Code : Tout sélectionner
-- script_time_cmdchauffv2.lua ---------------------------------------
-- G PETREMENT -------------------------------------------------------
----------------------------------------------------------------------
-- Modif Chrominator pour adaptation fil pilote radiateur électrique
----------------------------------------------------------------------
-- Permet de commander relais de pilotage de chaudiere à gaz ---------
-- Le cycle fait 900s / 15min ---------------------------------------
-- La commande est un nombre de 0 à 100 ------------------------------
-- Le cycle minimum est de 5min -
----------------------------------------------------------------------
local InterOnOff = 'Chauffage On/Off' --Interrupteur général de mise en route du chauffage
local VarCmdChauff = 'CmdChauff' --Variable de commande de chauffage définie par script_device_thermostat
local CycleMini = 300 --Durée de chauffe minimum en secondes (fonction de la chaudière)
-- local InterRelais = 'Piface RelayChaud' --Sortie vers relais chaudière
local InterRelais = 'Radiateur Salon' --Sortie vers fil pilote radiateur
local domoticz_url = '127.0.0.1:8080' --Adresse IP:port de Domoticz
local entree = 'Porte Entrée' -- Nom de la porte qui stoppera le chauffage en cas d'ouverture
local ssol = 'Porte s/sol' -- Nom de la porte qui stoppera le chauffage en cas d'ouverture
-- Fonction retournant la différence de temps en secondes
function timedifference(s)
year = string.sub(s, 1, 4)
month = string.sub(s, 6, 7)
day = string.sub(s, 9, 10)
hour = string.sub(s, 12, 13)
minutes = string.sub(s, 15, 16)
seconds = string.sub(s, 18, 19)
t1 = os.time()
t2 = os.time{year=year, month=month, day=day, hour=hour, min=minutes, sec=seconds}
difference = os.difftime (t1, t2)
return difference
end
commandArray = {}
-- Recuperation des variables
local CmdChauff = uservariables[VarCmdChauff]
local OnOff = otherdevices[InterOnOff]
local pentree = otherdevices[entree]
local pssol = otherdevices[ssol]
-- Recuperation des minutes
time=os.time()
minutes=tonumber(os.date('%M',time))
hours=tonumber(os.date('%H',time))
-- Execution de cette partie du script toutes les 15 minutes
-- Contacte la chaudière et modifie la variable domoticz Cycle
if( (minutes==15) or (minutes==0) or (minutes==30) or (minutes==45) ) then
print('-- Lancement du script à '..hours..'h'..minutes)
print('-- OnOff ='..OnOff..' / CmdChauff ='..CmdChauff)
print('-- Porte Entree = '..pentree..' / Porte s/sol ='..pssol)
if (OnOff == 'On' and pentree == 'Off' and pssol == 'Off') then --Si l'inter général est ON et les portes fermées
local Cycle = CmdChauff * 9 --Calcul du temps de cycle en seconde
if Cycle < CycleMini then --Si le cycle est trop court on coupe
commandArray[InterRelais]='On'
print('Cycle trop court :'..Cycle..'s >> Pilote On = Radiateur Off')
elseif Cycle > 870 then --Si le cycle est trop long (> 14min30) on allume sans coupure
commandArray[InterRelais]='Off'
print('Cycle long :'..Cycle..'s >> Pilote Off = Radiateur On')
else --Enfin on lance pour X seconde
print('Cycle lancé pour : '..Cycle..' secondes')
commandArray[1]={[InterRelais]='Off'}
commandArray[2]={[InterRelais]='Off AFTER '..tostring(Cycle)}
end
else
commandArray[InterRelais]='On'
print('Radiateur Off')
end
end
return commandArray
La liaison avec la consigne QIVIVO date de fin Octobre 2015, auparavant la consigne était définie par un Setpoint virtuel de Domoticz.
Dans ma configuration, un planning sur l'interrupteur général active le chauffage d'appoint en fin d'après midi seulement.