Bonjour à tous,
Voici le compte-rendu de la mise en oeuvre de la régulation.
Les 3 premiers jours de fonctionnement sont très prometteurs quant au potentiel de régulation que permet cette solution.
Un GRAND merci à AllezLeLosc qui a développé cette solution.
1°) Le contexte
Maison de 180 m² avec
Sondes de température dans :
- Grand salon cathédrale (Sud - Bien isolé - Robinets ouverts aux max)
- Chambre parents de 15m² (Sud - Bien isolé - Robinet Thermostatique à 3)
- Chambre enfants 1 de 14m² (Nord - Garage en dessous sans isolation - Robinets ouverts aux max)
- Chambre enfant 2 de 12m² (Nord - Tres bien isolé - Robinet Thermostatique à 3)
2°) L'implémentation
- Hébergement : NAS Synology DS415+
- Sondes de température: 4 sondes connectées par module FIBARO System FGBS001
- Commande chaudière :Ethernet controller TCW112-CM avec le cable gris acheté connecté au contact sec
On voit le cable gris branché à la chaudière en bas à gauche de la photo.
Le switch 5 est mis à OFF afin de stopper la régulation par EcoRadioSystem. La régulation par rapport cyclique par le cable est alors prise en compte.
- Consolidation d'une température consolidée basée sur les 4 températures réelles via le script LUA suivant.
(Quand la T° d'une pièce passe en dessous de 18°C, on garde la T° conservée dans la variable Domoticz. Evite que lorsque l'on aère une pièce, la chaudière démarre à fond !)
Code : Tout sélectionner
--
-- Script : script_device_calcul_temp_consolidée.lua
--
-- Objectif : Calcul température consolidée basée sur les 4 sondes de température
--
-- Auteur : CBR
-- Date : 12/11/2016
-- Version : 1.1
---------------------------------------------------------------------------
commandArray = {}
if devicechanged['Temp. Ch Marie'] or devicechanged['Temp. Ch Léa'] or devicechanged['Temp. Ch Parents'] or devicechanged['Temp. Salon'] then
print "=========== Script Calcul Température Consolidée==========="
local temp_marie = tonumber(otherdevices_svalues['Temp. Ch Marie']) -- Temp relevée dans la chambre de Marie
local temp_lea = tonumber(otherdevices_svalues['Temp. Ch Léa']) -- Temp relevée dans la chambre de Léa
local temp_parents = tonumber(otherdevices_svalues['Temp. Ch Parents']) -- Temp relevée dans la chambre des Parents
local temp_salon = tonumber(otherdevices_svalues['Temp. Salon']) -- Temp relevée dans le Salon
local user_variable_name = "temp_marie" -- Nom de la variable
local temp_marie_previous = uservariables[user_variable_name]
local user_variable_name = "temp_lea" -- Nom de la variable
local temp_lea_previous = uservariables[user_variable_name]
local user_variable_name = "temp_parents" -- Nom de la variable
local temp_parents_previous = uservariables[user_variable_name]
local user_variable_name = "temp_salon" -- Nom de la variable
local temp_salon_previous = uservariables[user_variable_name]
if temp_marie < 18 then temp_marie = temp_marie_previous end -- Si température < 17 on garde la précédente mesure (ouverture fenetre)
if temp_lea < 18 then temp_lea = temp_lea_previous end
if temp_parents < 18 then temp_parents = temp_parents_previous end
if temp_salon < 18 then temp_salon = temp_salon_previous end
print ('Temp. Marie réelle = '..temp_marie)
print ('Temp. Léa réelle = '..temp_lea)
print ('Temp. Parents réelle = '..temp_parents)
print ('Temp. Salon réelle = '..temp_salon)
local temp_consolidee = ((temp_marie * 1) + (temp_lea * 0.5) + (temp_parents * 0.5) + (temp_salon * 0.75)) / 2.75
local idx = 80 -- Idx Temp. Consolidée
temp_consolidee = math.floor(temp_consolidee * 100 + 0.5) / 100 -- Arrondi au centième
commandArray['UpdateDevice'] = idx .. '|0|' .. tostring(temp_consolidee) -- On écrit la température calculée
commandArray['Variable:temp_marie'] = tostring(temp_marie) -- Sauvegarde pour prochain cycle et récupération dans _previous
commandArray['Variable:temp_lea'] = tostring(temp_lea)
commandArray['Variable:temp_parents'] = tostring(temp_parents)
commandArray['Variable:temp_salon'] = tostring(temp_salon)
print ('Temp. Marie prise en compte = '..temp_marie)
print ('Temp. Léa prise en compte = '..temp_lea)
print ('Temp. Parents prise en compte = '..temp_parents)
print ('Temp. Salon prise en compte = '..temp_salon)
print ('Temp. Consolidée = '..temp_consolidee)
print "=========== Fin Script Calcul Température Consolidée ==========="
end
return commandArray
3°) Le tableau de bord
SwitchRegul : Arrêt / Marche régulation
BoilerFault : Arrêt de la régulation en cas de défaut chaudière : En cours d'implémentation via entrée module TCW112 (Fera l'objet d'un post)
RegulFault : Indique un passage dans la section Exception du script Python. (Notamment quand accès HTTP au module TCW112 est KO)
4°) Le fichier de paramétrage actuel (NewPID.cfg)
Code : Tout sélectionner
[ParamGlobal]
Trace=1
Debug=2
Periode=30
ScsAdress=34
[ParamPID1]
Kp=10
Ki=0.25
Kd=10
Bm=0.1
LimitBas=24
ErrMaxTempAmb=1
SetPointStepTempDepart=64
[ParamPID2]
K1=1
K2=0
K3=0
Bm=1
ErrMaxTempDepart=10
RapCyc0=50
RapCycMax=100
[ParamBoost]
Delay=300
DelayTimeBoostDemand=120
BoostDeltaTemp=10
SlowDownDelay=60
5°) Le script Python de régulation
Basé sur la dernière version d'AllezLeLosc, j'ai effectué les évolutions suivantes :
- Mise en place d'un mode Trace qui alimente le fichier TRACE.CSV afin de permettre l'analyse du comportement de la régulation dans EXCEL
- Mise en place d'un mode Debug avec plusieurs niveaux 1:Infos / 2: Détails / 3: Détails + I/O
- Intégration du code d'I/O domoticz dans le script (En lieu et place des appels au module DomoJson) - Pas de différence sur le fond
- Mise en oeuvre d'une fonction Boiler afin de faciliter l'adaptation au module de commande de la chaudière.
- Ajouts de nombreux commentaires permettant de comprendre la cinématique de la régulation.
Code : Tout sélectionner
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#***************************************************************
# Régulation Chaudière Frisquet
#***************************************************************
__namescript__='Regulateur-Prod-V2r4.py'
__author__= 'SSO & CBR'
__version__= '2.4'
__status__= 'Exploitation'
__created__='13/02/16'
__modified__='03/11/16'
#***************************************************************
# Repertoire par défaut
#***************************************************************
workpath = '/volume1/APPSTORE/domoticz/var/scripts/python'
#workpath = '/home/administrator/Regulation'
#***************************************************************
# Imports Module Python
#***************************************************************
#import pdb #Activation du mode Debug Python
#import sys
#import json
#import domoticz
#import time
import re
import ConfigParser
import requests
from requests.auth import HTTPBasicAuth
import time
import DbError
from datetime import datetime
import csv
#***************************************************************
# Log trace mesures
#***************************************************************
def Trace(Phase,BoostMode,BoostDemand,SetPointTempAmb,TempAmb,ErrNowTempAmb,ErrPreviousTempAmb,Derivee1,Integral1,TempDepart,SetPointTempDepart,ErrPrevious0TempDepart,ErrPreviousTempDepart,Periode,RapCyc):
if trace != 0:
now = time.localtime(time.time())
fname = workpath + '/trace.csv'
file = open(fname, "ab")
writer = csv.writer(file)
writer.writerow( (time.strftime("%d/%m/%y %H:%M:%S", now),Phase,BoostMode,BoostDemand,SetPointTempAmb,TempAmb,ErrNowTempAmb,ErrPreviousTempAmb,Derivee1,Integral1,TempDepart,SetPointTempDepart,ErrPrevious0TempDepart,ErrPreviousTempDepart,Periode,RapCyc) )
file.close()
return(0)
#***************************************************************
# Renvoie le nombre de secondes d'une date "16/08/2013 09:51:43"
#***************************************************************
def Secondes(s):
#s = "2013-08-16 09:51:43"
d = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
r = time.mktime(d.timetuple())
return (r)
#*********************************************
# Extrait valeur numérique
#*********************************************
def Valeur(chaine):
pat = "[^A-Za-z%]+" # expression régulière à utilise
prog = re.compile(pat) # création de l'objet d'analyse
result = prog.search(chaine) # extraction de la chaîne à l'aide de l'expression régulière
return (result.group())
#*********************************************
# Ecriture Device Domoticz
#*********************************************
def WriteDevice(domoticz_idx,param,val):
write_url_text1='/json.htm?type=command¶m='
write_url_text2='&idx='
write_url_text3='&'
requete='http://'+domoticz_ip+':'+domoticz_port+write_url_text1+param+write_url_text2+str(domoticz_idx)+write_url_text3+val
if debug > 2: DbError.Write("Write Device requete",requete)
r=requests.get(requete,auth=HTTPBasicAuth(user,password))
if r.status_code != 200:
DbError.Write(__namescript__,"-1-Erreur Lecture API Domoticz")
return("KO")
else:
return("OK")
#*********************************************
# Lecture Device Domoticz
#*********************************************
def ReadDevice(domoticz_idx,param):
read_url_text1='/json.htm?type=devices&rid='
requete='http://'+domoticz_ip+':'+domoticz_port+read_url_text1+str(domoticz_idx)
if debug > 2: DbError.Write("Read Device requete",requete)
r=requests.get(requete,auth=HTTPBasicAuth(user,password))
domoticzjson=r.json()
for i, v in enumerate(domoticzjson["result"]):
val= domoticzjson["result"][i][param]
if r.status_code != 200:
DbError.Write(__namescript__,"-2-Erreur Lecture API Domoticz")
return("KO")
else:
return(val)
#*********************************************
# Ecriture Device Pilotage chaudière
#*********************************************
# Cette section peut etre remplacée pour piloter un device quelconque.
def WriteBoiler(val):
requete='http://192.168.0.251/status.xml?r1='+str(val)
if debug > 2: DbError.Write("Write Boiler requete",requete)
r=requests.get(requete,auth=HTTPBasicAuth(user,password))
if debug > 2: DbError.Write("Write Boiler return",r)
if r.status_code != 200:
DbError.Write(__namescript__,"-1-Erreur Ecriture carte TCW112")
return("KO")
else:
return("OK")
#*********************************************
#***************************************************************
# Paramètres
#***************************************************************
#--------------Généraux-------------------
domoticz_ip='192.168.0.252'
domoticz_port='8084'
user='admin'
password='Omc2jay6;'
StandBy=False
CfgPID=ConfigParser.ConfigParser()
CfgPID.read(workpath + '/NewPid.cfg')
trace=CfgPID.getint('ParamGlobal','Trace')
debug=CfgPID.getint('ParamGlobal','Debug')
Periode=CfgPID.getint('ParamGlobal','Periode')
#----------------------------------
#--------------Idx-------------------
IdxSwitchRegul='78' # Regulation On/Off
IdxBoilerFault='79' # Chaudière en sécurité
IdxSetPointTempAmb='105' # Consigne température ambiante
IdxTempAmb='80' # Mesure température ambiante
IdxRapCyclique='81' # Rapport Cyclique commande
IdxCommande='110' # Switch commande chaudière
IdxTempDepart='100' # Mesure température départ chaudière
IdxSetPointTempDepart='82' # Consigne température départ chaudière
IdxRegulFault='113' # Erreur Regulation
#----------------------------------
#------Paramètres PID1--------------
ErrPreviousTempAmb=0.0
T0=Secondes(ReadDevice(IdxTempAmb,'LastUpdate'))
Integral1=0.0
Derivee1=0.0
SetPointTempDepart=0.0
StepTempDepart=False
ReleaseSetPointTempDepart=False
#----------------------------------
#------Paramètres PID2--------------
ErrPreviousTempDepart=0.0
ErrPrevious0TempDepart=0.0
RapCyc=0.0
#----------------------------------
#------Paramètres Relance----------
BoostMode=True # Au 1er cycle du script, on est par défaut en "boost mode"
BoostDemand=False
ExitFromBm=False
#----------------------------------
TempDepart=0
Status=Trace("Phase","BoostMode","BoostDemand","SetPointTempAmb","TempAmb","ErrNowTempAmb","ErrPreviousTempAmb","Derivee1","Integral1","TempDepart","SetPointTempDepart","ErrPrevious0TempDepart","ErrPreviousTempDepart","Periode","RapCyc")
while True:
try:
if debug > 0: DbError.Write("==================================================================","=")
SwitchRegul=ReadDevice(IdxSwitchRegul,"Data")
BoilerFault=ReadDevice(IdxBoilerFault,"Data")
if SwitchRegul=='On' and BoilerFault=='Off': # Si arrêt demandé ou en sécurité, on passe en section ARRET COMPLET
StandBy=False
CfgPID.read(workpath + '/NewPid.cfg')
#------Paramètres Trace debug --------------
trace=CfgPID.getint('ParamGlobal','Trace')
debug=CfgPID.getint('ParamGlobal','Debug')
#------Paramètres PID1--------------
Kp=CfgPID.getfloat('ParamPID1','Kp') # 10
Ki=CfgPID.getfloat('ParamPID1','Ki') # 0,25
Kd=CfgPID.getfloat('ParamPID1','Kd') # 10
Bm1=CfgPID.getfloat('ParamPID1','Bm') # 0,1°C
ErrMaxTempAmb=CfgPID.getfloat('ParamPID1','ErrMaxTempAmb') # 1°C
LimitBasTempDepart=CfgPID.getfloat('ParamPID1','LimitBas') # 24°C
SetPointStepTempDepart=CfgPID.getfloat('ParamPID1','SetPointStepTempDepart') # 64°C
#----------------------------------
#------Paramètres PID2--------------
K1=CfgPID.getfloat('ParamPID2','K1') # 1
K2=CfgPID.getfloat('ParamPID2','K2') # 0
K3=CfgPID.getfloat('ParamPID2','K3') # 0
Bm2=CfgPID.getfloat('ParamPID2','Bm') # 1
ErrMaxTempDepart=CfgPID.getfloat('ParamPID2','ErrMaxTempDepart') # 10
RapCyc0=CfgPID.getfloat('ParamPID2','RapCyc0') # 50
RapCycMax=CfgPID.getfloat('ParamPID2','RapCycMax') # 100
#----------------------------------
#------Paramètres Relance----------
BoostDelay=CfgPID.getfloat('ParamBoost','Delay') # 300s, Durée de la phase de boost, tant que delta t° non atteint
DelayTimeBoostDemand=CfgPID.getfloat('ParamBoost','DelayTimeBoostDemand') # 120s
BoostDeltaTemp=CfgPID.getfloat('ParamBoost','BoostDeltaTemp') # 10°C, delta t° définissant la sortie de la phase de boost
SlowDownDelay=CfgPID.getfloat('ParamBoost','SlowDownDelay') # 60s, délai fin de de boucle boost
#----------------------------------
SetPointTempAmb=float(Valeur(ReadDevice(IdxSetPointTempAmb,"Data")))
TempAmb=float(Valeur(ReadDevice(IdxTempAmb,"Data")))
ErrNowTempAmb=SetPointTempAmb-TempAmb
if BoostMode: # Au 1er cycle du script, on est par défaut en "boost mode"
if debug > 0: DbError.Write("BoostMode",BoostMode)
if ErrNowTempAmb>Bm1: # Si Boost Mode et Si l'écart de température entre T° Consigne et T° Ambiante > 0,1°C, on lance la phase de boost
if debug > 1: DbError.Write("BoostMode",'ErrNowTempAmb>Bm1')
TempDepart=float(Valeur(ReadDevice(IdxTempDepart,"Data")))
StartTempDepart=TempDepart
StopTempDepart=StartTempDepart+BoostDeltaTemp # quand temp stop atteinte, on sort du "boost mode" **1
StartTimeBoostMode=time.time()
RapCyc=100.0
Status=WriteDevice(IdxRapCyclique,"udevice","svalue=" + str(RapCyc)) # on demande 100% de puissance à la chaudière
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz du rapport cyclique")
Status=WriteBoiler(1)
if Status!='OK':
DbError.Write(__namescript__,Status)
TempDepart=float(Valeur(ReadDevice(IdxTempDepart,"Data")))
while TempDepart<StopTempDepart and (time.time()-StartTimeBoostMode)<BoostDelay: # **1 ou délai "boost mode" dépassé
if debug > 1: DbError.Write("BoostMode","Boucle montée en température durant " + str(BoostDelay) + " s tant que T° départ " + str(TempDepart) + "°C n'atteint pas " + str(StopTempDepart) + "°C")
time.sleep(Periode)
TempDepart=float(Valeur(ReadDevice(IdxTempDepart,"Data")))
RapCyc=0.0
Status=WriteDevice(IdxRapCyclique,"udevice","svalue=" + str(RapCyc)) # on stoppe la commande chaudière
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz du rapport cyclique")
Status=WriteBoiler(0)
if Status!='OK':
DbError.Write(__namescript__,Status)
SetPointInitTempDepart=float(Valeur(ReadDevice(IdxTempDepart,"Data"))) # la temp de départ devient en sortie de boost la temp de référence de départ PID
SetPointTempDepart=SetPointInitTempDepart
if debug > 1: DbError.Write("BoostMode","Latence " + str(SlowDownDelay) + " s")
time.sleep(SlowDownDelay) # On attend afin de laisser place à l'inertie de la chaudière afin qu'elle redescende en puissance. (Commande n'implique pas une réaction immédiate)
BoostMode=False
Integral1=0.0
Status=Trace("ErrNowTempAmb>Bm1",BoostMode,BoostDemand,SetPointTempAmb,TempAmb,ErrNowTempAmb,ErrPreviousTempAmb,Derivee1,Integral1,TempDepart,SetPointTempDepart,ErrPrevious0TempDepart,ErrPreviousTempDepart,Periode,RapCyc)
else: # Si Boost Mode et Si l'écart de température entre T° Consigne et T° Ambiante =< 0,1°C, on met la chaudière à l'arrêt
if debug > 1: DbError.Write("BoostMode",'ErrNowTempAmb<Bm1')
Status=WriteDevice(IdxSetPointTempDepart,"udevice","svalue=0.0")
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz de la consigne de température de départ")
Status=WriteDevice(IdxRapCyclique,"udevice","svalue=0")
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz du rapport cyclique")
Status=Trace("ErrNowTempAmb<Bm1",BoostMode,BoostDemand,SetPointTempAmb,TempAmb,ErrNowTempAmb,ErrPreviousTempAmb,Derivee1,Integral1,TempDepart,SetPointTempDepart,ErrPrevious0TempDepart,ErrPreviousTempDepart,Periode,RapCyc)
time.sleep(Periode) #CBR rajout afin d'éviter une boucle sans délai
else: # Si hors "Boost Mode"
#----------------PID 1-------------------------
if ErrNowTempAmb>ErrMaxTempAmb: # (1-0) Si écart entre T° Consigne et T° Ambiante > 1°C, on force le "Boost Mode" pour le prochain cycle et on passe au PID 2 en gardant la meme consigne (==> 2)
BoostMode=True # (1-1)
if debug > 1: DbError.Write("PID",'Forcage boost mode PID 1 - Ecart > ' + str(ErrMaxTempAmb))
elif abs(ErrNowTempAmb)>Bm1: # (1-2) Sinon, si l'écart est supérieur à +/- 0,1°C, on calcule le PID
if debug > 1: DbError.Write("PID",'Ecart > +/- 0,1°C, calcul PID')
if ErrNowTempAmb!=ErrPreviousTempAmb: # Si l'écart actuel est différent de l'écart précédent, on calcule la dérivée.
T=Secondes(ReadDevice(IdxTempAmb,'LastUpdate'))
try:
Derivee1=(ErrNowTempAmb-ErrPreviousTempAmb)/(T-T0)
except ZeroDivisionError: # Si T = T0, la dérivée = 0
Derivee1=0.0
ErrPreviousTempAmb=ErrNowTempAmb
T0=T
if ExitFromBm: # (3) On sort de la Bande Morte, dans ce cas on soustrait le facteur "P" afin qu'il ne soit pas pris en compte dans le calcul et on annule le facteur "I", on ne garde que la Dérivée
if debug > 1: DbError.Write("PID",'ExitFromBm')
SetPointInitTempDepart=SetPointTempDepart-Kp*ErrNowTempAmb
Integral1=0.0
ExitFromBm=False # Au prochain cycle, on calculera le PID complet
SetPointTempDepart=SetPointInitTempDepart+Kp*ErrNowTempAmb+Ki*(Integral1+ErrNowTempAmb)+Kd*Derivee1 # On calcul le PID 1 afin de définir la consigne T° Chaudière
if debug > 1: DbError.Write("PID",'SetPointTempDepart calculé : ' + str(SetPointTempDepart))
if SetPointTempDepart<LimitBasTempDepart: # (4-0) - Si Consigne T° Chaudière inférieure à la limite basse
if ErrNowTempAmb>Bm1: # Et si écart entre T° Consigne et T° Ambiante > 1°C, on fixe la consigne T° Chaudière à la limite basse
SetPointTempDepart=LimitBasTempDepart
else: # Sinon on fixe la consigne T° Chaudière à 0°C
SetPointTempDepart=0.0
if not BoostDemand: # Et on demande le boost mode durant le DelayTimeBoostDemand sur les cycles suivants, pourquoi ???
if debug > 1: DbError.Write("PID",'Boost Demand ! ')
BoostDemand=True
StartTimeBoostDemand=time.time()
else:
if (time.time()-StartTimeBoostDemand)>DelayTimeBoostDemand:
BoostMode=True
if debug > 1: DbError.Write("PID",'Forcage boost mode PID 1 - ???')
BoostDemand=False
elif SetPointTempDepart>=SetPointStepTempDepart: # (4-1) - Si Consigne T° Chaudière supérieure à la limite haute
if not StepTempDepart: # Initialisation séquence de 3600 secondes, soit une heure
StepTempDepart=True
StartTimeStepTempDepart=time.time()
else:
if (time.time()-StartTimeStepTempDepart)>3600 and not ReleaseSetPointTempDepart: # Tant que l'on boucle depuis moins d'une heure, ...
ReleaseSetPointTempDepart=True
if not ReleaseSetpointTempDepart: # ... On fixe la Consigne T° Chaudière à la limite haute
SetPointTempDepart=SetPointStepTempDepart
else: # Sinon, au dela du délai d'une heure, on calcule l'intégrale par la somme des écarts
Integral1=Integral1+ErrNowTempAmb
else: # (4-2) - Si Consigne T° Chaudière entre les limite basses et hautes, on calcule l'intégrale par la somme des écarts
Integral1=Integral1+ErrNowTempAmb
StepTempDepart=False # On réinitialise
ReleaseSetPointTempDepart=False # On réinitialise
else: # (1-3) Sinon, si l'écart est inférieur à +/- 0,1°C, on applique uniquement le "D" du PID pour stabiliser
ExitFromBm=True # On positionne cette variable pour qu'au prochain calcul PID les facteurs "P" et "I" ne soient pas appliqués (==> 3)
if debug > 1: DbError.Write("PID",'Ecart =< +/- 0,1°C, calcul P uniquement')
if ErrNowTempAmb!=ErrPreviousTempAmb:
T=Secondes(ReadDevice(IdxTempAmb,'LastUpdate'))
try:
Derivee1=(ErrNowTempAmb-ErrPreviousTempAmb)/(T-T0)
except ZeroDivisionError:
Derivee1=0.0
ErrPreviousTempAmb=ErrNowTempAmb
T0=T
SetPointTempDepart=SetPointTempDepart+Kd*Derivee1
# (2)
Status=WriteDevice(IdxSetPointTempDepart,"udevice","svalue=" + str(round(SetPointTempDepart,1)))
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz de la consigne de température de départ")
#----------------------------------------------
#----------------PID 2-------------------------
TempDepart=float(Valeur(ReadDevice(IdxTempDepart,'Data')))
ErrNowTempDepart=SetPointTempDepart-TempDepart
if ErrNowTempDepart>ErrMaxTempDepart: # Si écart entre T° Consigne chaudière et T° départ chaudière > 10°C ...
BoostMode=True # ..., on passe en "boost mode" à la prochaine boucle
if debug > 1: DbError.Write("PID",'Forcage boost mode PID 2 - Ecart > ' + str(ErrMaxTempDepart))
else: # Si écart entre T° Consigne chaudière et T° départ chaudière < 10°C
if SetPointTempDepart==0.0: # Si consigne T° Chaudière à 0, on demande l'arret de la chauffe
RapCyc=0.0
else:
if abs(ErrNowTempDepart)>Bm2: # Si écart entre T° Consigne chaudière et T° départ chaudière supérieur à +/- 1°C
if ErrNowTempDepart>0: # Si écart positif, on majore le rapport cyclique par rapport au 50% proportionnellement à l'écart (K1) + au delta de l'écart T-1 (K2) + aux deltas des écarts T-2 (K3)
RapCyc=RapCyc0+K1*(ErrNowTempDepart-Bm2)+K2*(ErrNowTempDepart-ErrPreviousTempDepart)+K3*(ErrNowTempDepart+ErrPrevious0TempDepart-2*ErrPreviousTempDepart)
else: # Si écart négatif, on minore le rapport cyclique par rapport au 50% proportionnellement à l'écart (K1) + au delta de l'écart T-1 (K2) + aux deltas des écarts T-2 (K3)
RapCyc=RapCyc0+K1*(ErrNowTempDepart+Bm2)+K2*(ErrNowTempDepart-ErrPreviousTempDepart)+K3*(ErrNowTempDepart+ErrPrevious0TempDepart-2*ErrPreviousTempDepart)
#Si en asservissement on veut accélérer alors on interdit de freiner, si on va trop vite et inversement.
if RapCyc<RapCyc0 and ErrNowTempAmb>Bm1: # Si RapCyc < 50% et écart entre T° Consigne et T° ambiante > 0,1°C...
RapCyc=RapCyc0 # ..., alors on fixe RapCyc à 50%, autrement dit, on garde la meme température départ chaudière
elif RapCyc>RapCyc0 and ErrNowTempAmb<-Bm1: # Si RapCyc > 50% et écart entre T° Consigne et T° ambiante < -0,1°C...
RapCyc=RapCyc0 # ..., alors on fixe RapCyc à 50%, autrement dit, on garde la meme température départ chaudière
else: # Si écart entre T° Consigne chaudière et T° départ chaudière inférieur à +/- 1°C
RapCyc=RapCyc0 # ..., alors on fixe RapCyc à 50%, autrement dit, on garde la meme température départ chaudière
if RapCyc>RapCycMax: # Si max atteint,on bride au max
RapCyc=RapCycMax
elif RapCyc<0.0: # Si 0 atteint,on bride à 0
RapCyc=0.0
#----------------------------------------------
#------------Debug & Trace PID---------------
if debug > 0: DbError.Write("---------------------------------------------------------","-")
if debug > 1: DbError.Write("PID1 : BoostDemand",BoostDemand)
if debug > 1: DbError.Write("PID1 : SetPointTempAmb",SetPointTempAmb)
if debug > 0: DbError.Write("PID1 : TempAmb",TempAmb)
if debug > 0: DbError.Write("PID1 : ErrNowTempAmb",ErrNowTempAmb)
if debug > 1: DbError.Write("PID1 : ErrPreviousTempAmb",ErrPreviousTempAmb)
if debug > 1: DbError.Write("PID1 : Derivee1",Derivee1)
if debug > 1: DbError.Write("PID1 : Integral1",Integral1)
if debug > 0: DbError.Write("PID2 : TempDepart",TempDepart)
if debug > 0: DbError.Write("PID2 : SetPointTempDepart",SetPointTempDepart)
if debug > 0: DbError.Write("PID2 : ErrNowTempDepart",ErrNowTempDepart)
if debug > 1: DbError.Write("PID2 : ErrPrevious0TempDepart",ErrPrevious0TempDepart)
if debug > 1: DbError.Write("PID2 : ErrPreviousTempDepart",ErrPreviousTempDepart)
if debug > 1: DbError.Write("PID2 : RapCyc",RapCyc)
if debug > 0: DbError.Write("----","---------------------------------")
Status=Trace("PID",BoostMode,BoostDemand,SetPointTempAmb,TempAmb,ErrNowTempAmb,ErrPreviousTempAmb,Derivee1,Integral1,TempDepart,SetPointTempDepart,ErrPrevious0TempDepart,ErrPreviousTempDepart,Periode,RapCyc)
#------------ Sauvegarde pour cycle suivant ---------------
ErrPrevious0TempDepart=ErrPreviousTempDepart # On sauvegarde l'écart T-2
ErrPreviousTempDepart=ErrNowTempDepart # On sauvegarde l'écart T-1
#------------Pilotage chaudière---------------
Status=WriteDevice(IdxRapCyclique,"udevice","svalue=" + str(RapCyc))
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz du rapport cyclique")
TOn=RapCyc*Periode/100 # On calcule la durée ON proportionnellement au rapport cyclique
TOff=Periode-TOn # On calcule la durée OFF afin que le TOn + TOff soit égal à la période
if TOn!=0.0:
Status=WriteBoiler(1)
if Status!='OK':
DbError.Write(__namescript__,Status)
else:
time.sleep(TOn) # On laisse à On durant la durée TOn
if TOff!=0.0:
Status=WriteBoiler(0)
if Status!='OK':
DbError.Write(__namescript__,Status)
else:
time.sleep(TOff) # On laisse à On durant la durée TOff
#----------------------------------------------
else: # Si arrêt demandé ou en sécurité, on passe en section ARRET COMPLET
#-------- Mise à zéro de la commande------------
if debug > 0: DbError.Write("STOP - SwitchRegul",SwitchRegul)
if debug > 0: DbError.Write("STOP - BoilerFault",BoilerFault)
Status=WriteDevice(IdxRapCyclique,"udevice","svalue=0")
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz du rapport cyclique")
#----------------------------------------------
if not StandBy: # On exécute cette section si passage de ON à OFF de la régulation
#-------- Mise au repos du relais--------------
Status=WriteBoiler(0)
if Status!='OK':
DbError.Write(__namescript__,Status)
#----------------------------------------------
#------Paramètres PID1--------------
ErrPreviousTempAmb=0.0
T0=Secondes(ReadDevice(IdxTempAmb,'LastUpdate'))
Integral1=0.0
Derivee1=0.0
SetPointTempDepart=0.0
#----------------------------------
#------Paramètres PID2--------------
ErrPreviousTempDepart=0.0
ErrPrevious0TempDepart=0.0
#----------------------------------
#------Paramètres Relance----------
BoostMode=True
#----------------------------------
StandBy=True
time.sleep(Periode) # Temporisation en mode ARRET
# Fin de la boucle WHILE
except Exception, e: # Exception, on ne doit jamais passer par là.
try:
DbError.Write("************** EXCEPTION **************","************** EXCEPTION **************")
DbError.Write("EXCEPTION : ",e)
Status=WriteDevice(IdxRegulFault,"switchlight","switchcmd=On")
if Status!='OK':
DbError.Write(__namescript__,"Erreur d'écriture dans Domoticz de l'erreur de régulation")
except Exception, e:
pass
time.sleep(Periode)
6°) Le script regulation.sh qui lance la régulation au démarrage du NAS. (Dans /usr/local/etc/rc.d avec un chmod 755)
Code : Tout sélectionner
#!/bin/sh
cd /volume1/APPSTORE/domoticz/var/scripts/python
./Regulateur-Prod-V2r4.py >>Regulateur-Prod-V2r4.txt 2>&1 </dev/null &
Le fichier Regulateur-Prod-V2r4.txt constitue le LOG que le mode Debug alimente.
7°) 1er bilan
Asservissement global de la température consolidée à la consigne de 20°c.
(Le PIC est lié au fait que ma fille a mis un chauffage d'appoint car elle avait froid à son bureau)
Asservissement de la température de départ chaudière à la consigne calculée par l’algorithme PID.
(On voit que l'asservissement a passé à 0°C la consigne départ chaudière en réponse au fort écart positif de T° ambiante suite au PIC)
Les PICs vert sont liés au chauffage de l'Eau Sanitaire (Ballon de la chaudière) Douches de ces dames
On voit ci-après que la régulation sur la base de la Température Consolidée ne permet pas d'avoir une température correctement régulée partout, notamment dans la chambre de Marie. Je dois travailler le calcul de la température consolidée afin d'améliorer l'équilibre global et aussi régler les têtes thermostatiques des chambres de Parents & Léa, si tant est que cela ait un impact sur la T° de ces 2 pièces.
8°) Gestion T°C Jour / Nuit (Non activée dans le bilan ci-dessus)
Via scheduler du NAS:
9°) A venir
- Optimisation du calcul de la T° Consolidée
- Mise en place de l'acquisition du défaut chaudière via le module TCW112 (Si AllezLeLosc a une idée de comment, je suis preneur)
Tof