Alors alors,
Voici l'algo de Logread en python dispo ici
https://github.com/999LV/SmartVirtualThermostat:
Code : Tout sélectionner
"""
<plugin key="SVT" name="Smart Virtual Thermostat" author="logread" version="0.3.8" wikilink="https://www.domoticz.com/wiki/Plugins/Smart_Virtual_Thermostat.html" externallink="https://github.com/999LV/SmartVirtualThermostat.git">
<params>
<param field="Address" label="Domoticz IP Address" width="200px" required="true" default="127.0.0.1"/>
<param field="Port" label="Port" width="40px" required="true" default="8080"/>
<param field="Username" label="Username" width="200px" required="false" default=""/>
<param field="Password" label="Password" width="200px" required="false" default=""/>
<param field="Mode1" label="Inside Temperature Sensors (csv list of idx)" width="100px" required="true" default="0"/>
<param field="Mode2" label="Outside Temperature Sensors (csv list of idx)" width="100px" required="false" default=""/>
<param field="Mode3" label="Heating Switches (csv list of idx)" width="100px" required="true" default="0"/>
<param field="Mode5" label="Calculation cycle, Minimum Heating time per cycle, Pause On delay, Pause Off delay, Forced mode duration (all in minutes)" width="200px" required="true" default="30,0,2,1,60"/>
<param field="Mode6" label="Logging Level" width="75px">
<options>
<option label="Debug" value="Debug"/>
<option label="Verbose" value="Verbose"/>
<option label="Normal" value="Normal" default="true" />
</options>
</param>
</params>
</plugin>
"""
import Domoticz
import json
import urllib.parse as parse
import urllib.request as request
from datetime import datetime, timedelta
import time
import base64
class deviceparam:
def __init__(self, unit, nvalue, svalue):
self.unit = unit
self.nvalue = nvalue
self.svalue = svalue
class BasePlugin:
def __init__(self):
self.debug = False
self.calculate_period = 30 # Time in minutes between two calculations (cycle)
self.minheatpower = 0 # if heating is needed, minimum heat power (in % of calculation period)
self.deltamax = 0.2 # allowed temp excess over setpoint temperature
self.pauseondelay = 2 # time between pause sensor actuation and actual pause
self.pauseoffdelay = 1 # time between end of pause sensor actuation and end of actual pause
self.forcedduration = 60 # time in minutes for the forced mode
self.InTempSensors = []
self.OutTempSensors = []
self.Heaters = []
self.InternalsDefaults = {
'ConstC': 60, # inside heating coeff, depends on room size & power of your heater (60 by default)
'ConstT': 1, # external heating coeff,depends on the insulation relative to the outside (1 by default)
'nbCC': 0, # number of learnings for ConstC
'nbCT': 0, # number of learnings for ConstT
'LastPwr': 0, # % power from last calculation
'LastInT': 0, # inside temperature at last calculation
'LastOutT': 0, # outside temperature at last calculation
'LastSetPoint': 20, # setpoint at time of last calculation
'ALStatus': 0} # AutoLearning status (0 = uninitialized, 1 = initialized, 2 = disabled)
self.Internals = self.InternalsDefaults.copy()
self.heat = False
self.pause = False
self.pauserequested = False
self.pauserequestchangedtime = datetime.now()
self.forced = False
self.intemp = 20.0
self.outtemp = 20.0
self.setpoint = 20.0
self.endheat = datetime.now()
self.nextcalc = self.endheat
self.lastcalc = self.endheat
self.nextupdate = self.endheat
self.nexttemps = self.endheat
self.learn = True
return
def onStart(self):
if Parameters["Mode6"] == 'Debug':
self.debug = True
Domoticz.Debugging(1)
DumpConfigToLog()
else:
Domoticz.Debugging(0)
# create the child devices if these do not exist yet
devicecreated = []
if 1 not in Devices:
Options = {"LevelActions": "||",
"LevelNames": "Off|Auto|Forced",
"LevelOffHidden": "false",
"SelectorStyle": "0"}
Domoticz.Device(Name="Thermostat Control", Unit=1, TypeName="Selector Switch", Switchtype=18, Image=15,
Options=Options, Used=1).Create()
#Devices[1].Update(nValue=0, sValue="0") # default is Off state
devicecreated.append(deviceparam(1, 0, "0")) # default is Off state
if 2 not in Devices:
Options = {"LevelActions": "||",
"LevelNames": "Off|Normal|Economy",
"LevelOffHidden": "true",
"SelectorStyle": "0"}
Domoticz.Device(Name="Thermostat Mode", Unit=2, TypeName="Selector Switch", Switchtype=18, Image=15,
Options=Options, Used=1).Create()
devicecreated.append(deviceparam(2, 0, "10")) # default is normal mode
if 3 not in Devices:
Domoticz.Device(Name="Thermostat Pause", Unit=3, TypeName="Switch", Image=9, Used=1).Create()
devicecreated.append(deviceparam(3, 0, "")) # default is Off
if 4 not in Devices:
Domoticz.Device(Name="Setpoint Normal", Unit=4, Type=242, Subtype=1, Used=1).Create()
devicecreated.append(deviceparam(4, 0, "20")) # default is 20 degrees
if 5 not in Devices:
Domoticz.Device(Name="Setpoint Economy", Unit=5, Type=242, Subtype=1, Used=1).Create()
devicecreated.append(deviceparam(5 ,0, "20")) # default is 20 degrees
if 6 not in Devices:
Domoticz.Device(Name="Thermostat temp", Unit=6, TypeName="Temperature").Create()
devicecreated.append(deviceparam(6, 0, "20")) # default is 20 degrees
# if any device has been created in onStart(), now is time to update its defaults
for device in devicecreated:
Devices[device.unit].Update(nValue=device.nvalue, sValue=device.svalue)
# build lists of sensors and switches
self.InTempSensors = parseCSV(Parameters["Mode1"])
Domoticz.Debug("Inside Temperature sensors = {}".format(self.InTempSensors))
self.OutTempSensors = parseCSV(Parameters["Mode2"])
Domoticz.Debug("Outside Temperature sensors = {}".format(self.OutTempSensors))
self.Heaters = parseCSV(Parameters["Mode3"])
Domoticz.Debug("Heaters = {}".format(self.Heaters))
# splits additional parameters
params = parseCSV(Parameters["Mode5"])
if len(params) == 5:
self.calculate_period = params[0]
if self.calculate_period < 5:
Domoticz.Error("Invalid calculation period parameter. Using minimum of 5 minutes !")
self.calculate_period = 5
self.minheatpower = params[1]
if self.minheatpower > 100:
Domoticz.Error("Invalid minimum heating parameter. Using maximum of 100% !")
self.minheatpower = 100
self.pauseondelay = params[2]
self.pauseoffdelay = params[3]
self.forcedduration = params[4]
if self.forcedduration < 30:
Domoticz.Error("Invalid forced mode duration parameter. Using minimum of 30 minutes !")
self.calculate_period = 30
else:
Domoticz.Error("Error reading Mode5 parameters")
# loads persistent variables from dedicated user variable
# note: to reset the thermostat to default values (i.e. ignore all past learning),
# just delete the relevant "<plugin name>-InternalVariables" user variable Domoticz GUI and restart plugin
self.getUserVar()
# if mode = off then make sure actual heating is off just in case if was manually set to on
if Devices[1].sValue == "0":
self.switchHeat(False)
def onStop(self):
Domoticz.Debugging(0)
def onCommand(self, Unit, Command, Level, Hue):
Domoticz.Debug("onCommand called for Unit {}: Command '{}', Level: {}".format(Unit, Command, Level))
if Unit == 3:
self.pauserequestchangedtime = datetime.now()
svalue = ""
if str(Command) == "On":
nvalue = 1
self.pauserequested = True
else:
nvalue = 0
self.pauserequested = False
else:
if Level > 0:
nvalue = 1
else:
nvalue = 0
svalue = str(Level)
Devices[Unit].Update(nValue=nvalue, sValue=svalue)
if Unit in (1, 2, 4, 5): # force recalculation if control or mode or a setpoint changed
self.nextcalc = datetime.now()
self.learn = False
self.onHeartbeat()
def onHeartbeat(self):
now = datetime.now()
if Devices[1].sValue == "0": # Thermostat is off
if self.forced or self.heat: # thermostat setting was just changed so we kill the heating
self.forced = False
self.endheat = now
Domoticz.Debug("Switching heat Off !")
self.switchHeat(False)
if self.nexttemps <= now:
# call the Domoticz json API for a temperature devices update, to get the lastest temps...
self.readTemps()
elif Devices[1].sValue == "20": # Thermostat is in forced mode
if self.forced:
if self.endheat <= now:
self.forced = False
self.endheat = now
Domoticz.Debug("Forced mode Off !")
Devices[1].Update(nValue=1, sValue="10") # set thermostat to normal mode
self.switchHeat(False)
else:
self.forced = True
self.endheat = now + timedelta(minutes=self.forcedduration)
Domoticz.Debug("Forced mode On !")
self.switchHeat(True)
if self.nexttemps <= now:
# call the Domoticz json API for a temperature devices update, to get the lastest temps...
self.readTemps()
else: # Thermostat is in mode auto
if self.forced: # thermostat setting was just changed from "forced" so we kill the forced mode
self.forced = False
self.endheat = now
self.nextcalc = now # this will force a recalculation on next heartbeat
Domoticz.Debug("Forced mode Off !")
self.switchHeat(False)
elif (self.endheat <= now or self.pause) and self.heat: # heat cycle is over
self.endheat = now
self.heat = False
if self.Internals['LastPwr'] < 100:
self.switchHeat(False)
# if power was 100(i.e. a full cycle), then we let the next calculation (at next heartbeat) decide
# to switch off in order to avoid potentially damaging quick off/on cycles to the heater(s)
elif self.pause and not self.pauserequested: # we are in pause and the pause switch is now off
if self.pauserequestchangedtime + timedelta(minutes=self.pauseoffdelay) <= now:
Domoticz.Debug("Pause is now Off")
self.pause = False
elif not self.pause and self.pauserequested: # we are not in pause and the pause switch is now on
if self.pauserequestchangedtime + timedelta(minutes=self.pauseondelay) <= now:
Domoticz.Debug("Pause is now On")
self.pause = True
self.switchHeat(False)
elif self.pause and self.nexttemps <= now: # added to update thermostat temp even in pause mode
# call the Domoticz json API for a temperature devices update, to get the lastest temps...
self.readTemps()
elif (self.nextcalc <= now) and not self.pause: # we start a new calculation
self.nextcalc = now + timedelta(minutes=self.calculate_period)
Domoticz.Debug("Next calculation time will be : " + str(self.nextcalc))
# make current setpoint used in calculation reflect the select mode (10= normal, 20 = economy)
if Devices[2].sValue == "10":
self.setpoint = float(Devices[4].sValue)
else:
self.setpoint = float(Devices[5].sValue)
# call the Domoticz json API for a temperature devices update, to get the lastest temps...
if self.readTemps():
# do the thermostat work
self.AutoMode()
else:
# make sure we switch off heating if there was an error with reading the temp
self.switchHeat(False)
# check if need to refresh setpoints so that they do not turn red in GUI
if self.nextupdate <= now:
self.nextupdate = now + timedelta(minutes=int(Settings["SensorTimeout"]))
Devices[4].Update(nValue=0, sValue=Devices[4].sValue)
Devices[5].Update(nValue=0, sValue=Devices[5].sValue)
def AutoMode(self):
if self.intemp > self.setpoint + self.deltamax:
Domoticz.Debug("Temperature exceeds setpoint: no heating")
power = 0
else:
if self.learn:
self.AutoCallib()
else:
self.learn = True
if self.outtemp is None:
power = round((self.setpoint - self.intemp) * self.Internals["ConstC"], 1)
else:
power = round((self.setpoint - self.intemp) * self.Internals["ConstC"] +
(self.setpoint - self.outtemp) * self.Internals["ConstT"], 1)
if power < 0:
power = 0 # lower limit
elif power > 100:
power = 100 # upper limit
if power <= self.minheatpower:
power = self.minheatpower # minimum heating per cycle in % of cycle time
heatduration = round(power * self.calculate_period / 100)
WriteLog("Calculation: Power = {} -> heat duration = {} minutes".format(power, heatduration), "Verbose")
if power == 0:
self.switchHeat(False)
Domoticz.Debug("No heating required !")
else:
self.endheat = datetime.now() + timedelta(minutes=heatduration)
Domoticz.Debug("End Heat time = " + str(self.endheat))
self.switchHeat(True)
if self.Internals["ALStatus"] < 2:
self.Internals['LastPwr'] = power
self.Internals['LastInT'] = self.intemp
self.Internals['LastOutT'] = self.outtemp
self.Internals['LastSetPoint'] = self.setpoint
self.Internals['ALStatus'] = 1
self.saveUserVar() # update user variables with latest learning
self.lastcalc = datetime.now()
def AutoCallib(self):
now = datetime.now()
if self.Internals['ALStatus'] != 1: # not initalized... do nothing
Domoticz.Debug("Fist pass at AutoCallib... no callibration")
pass
elif self.Internals['LastPwr'] == 0: # heater was off last time, do nothing
Domoticz.Debug("Last power was zero... no callibration")
pass
elif self.Internals['LastPwr'] == 100 and self.intemp < self.Internals['LastSetPoint']:
# heater was on max but setpoint was not reached... no learning
Domoticz.Debug("Last power was 100% but setpoint not reached... no callibration")
pass
elif self.intemp > self.Internals['LastInT'] and self.Internals['LastSetPoint'] > self.Internals['LastInT']:
# learning ConstC
ConstC = (self.Internals['ConstC'] * ((self.Internals['LastSetPoint'] - self.Internals['LastInT']) /
(self.intemp - self.Internals['LastInT']) *
(timedelta.total_seconds(now - self.lastcalc) /
(self.calculate_period * 60))))
WriteLog("New calc for ConstC = {}".format(ConstC), "Verbose")
self.Internals['ConstC'] = round((self.Internals['ConstC'] * self.Internals['nbCC'] + ConstC) /
(self.Internals['nbCC'] + 1), 1)
self.Internals['nbCC'] = min(self.Internals['nbCC'] + 1, 50)
WriteLog("ConstC updated to {}".format(self.Internals['ConstC']), "Verbose")
elif self.outtemp is not None and self.Internals['LastSetPoint'] > self.Internals['LastOutT']:
# learning ConstT
ConstT = (self.Internals['ConstT'] + ((self.Internals['LastSetPoint'] - self.intemp) /
(self.Internals['LastSetPoint'] - self.Internals['LastOutT']) *
self.Internals['ConstC'] *
(timedelta.total_seconds(now - self.lastcalc) /
(self.calculate_period * 60))))
WriteLog("New calc for ConstT = {}".format(ConstT), "Verbose")
self.Internals['ConstT'] = round((self.Internals['ConstT'] * self.Internals['nbCT'] + ConstT) /
(self.Internals['nbCT'] + 1), 1)
self.Internals['nbCT'] = min(self.Internals['nbCT'] + 1, 50)
WriteLog("ConstT updated to {}".format(self.Internals['ConstT']), "Verbose")
def switchHeat(self, switch):
if switch: # heating on
self.heat = True
Domoticz.Debug("Heating On")
# switch on heater(s)
for heater in self.Heaters:
DomoticzAPI("type=command¶m=switchlight&idx={}&switchcmd=On".format(heater))
Domoticz.Debug("End Heat time = " + str(self.endheat))
else:
self.heat = False
Domoticz.Debug("Heating Off")
# switch off heater(s)
for heater in self.Heaters:
DomoticzAPI("type=command¶m=switchlight&idx={}&switchcmd=Off".format(heater))
def readTemps(self):
# set update flag for next temp update (used only when in off, forced mode or pause is active)
self.nexttemps = datetime.now() + timedelta(minutes=self.calculate_period)
# fetch all the devices from the API and scan for sensors
noerror = True
listintemps = []
listouttemps = []
devicesAPI = DomoticzAPI("type=devices&filter=temp&used=true&order=Name")
if devicesAPI:
for device in devicesAPI["result"]: # parse the devices for temperature sensors
idx = int(device["idx"])
if idx in self.InTempSensors:
if "Temp" in device:
Domoticz.Debug("device: {}-{} = {}".format(device["idx"], device["Name"], device["Temp"]))
# check temp sensor is not timed out
if not SensorTimedOut(device["LastUpdate"]):
listintemps.append(device["Temp"])
else:
Domoticz.Error("skipping timed out temperature sensor {}".format(device["Name"]))
else:
Domoticz.Error("device: {}-{} is not a Temperature sensor".format(device["idx"], device["Name"]))
elif idx in self.OutTempSensors:
if "Temp" in device:
Domoticz.Debug("device: {}-{} = {}".format(device["idx"], device["Name"], device["Temp"]))
# check temp sensor is not timed out
if not SensorTimedOut(device["LastUpdate"]):
listouttemps.append(device["Temp"])
else:
Domoticz.Error("skipping timed out temperature sensor {}".format(device["Name"]))
else:
Domoticz.Error("device: {}-{} is not a Temperature sensor".format(device["idx"], device["Name"]))
# calculate the average inside temperature
nbtemps = len(listintemps)
if nbtemps > 0:
self.intemp = round(sum(listintemps) / nbtemps, 1)
Devices[6].Update(nValue=0,
sValue=str(self.intemp)) # update the dummy device showing the current thermostat temp
else:
Domoticz.Error("No Inside Temperature found... Switching Thermostat Off")
Devices[1].Update(nValue=0, sValue="0") # switch off the thermostat
noerror = False
# calculate the average outside temperature
nbtemps = len(listouttemps)
if nbtemps > 0:
self.outtemp = round(sum(listouttemps) / nbtemps, 1)
else:
Domoticz.Debug("No Outside Temperature found...")
self.outtemp = None
WriteLog("Inside Temperature = {}".format(self.intemp), "Verbose")
WriteLog("Outside Temperature = {}".format(self.outtemp), "Verbose")
return noerror
def getUserVar(self):
variables = DomoticzAPI("type=command¶m=getuservariables")
if variables:
# there is a valid response from the API but we do not know if our variable exists yet
novar = True
varname = Parameters["Name"] + "-InternalVariables"
valuestring = ""
if "result" in variables:
for variable in variables["result"]:
if variable["Name"] == varname:
valuestring = variable["Value"]
novar = False
break
if novar:
# create user variable since it does not exist
WriteLog("User Variable {} does not exist. Creation requested".format(varname), "Verbose")
DomoticzAPI("type=command¶m=saveuservariable&vname={}&vtype=2&vvalue={}".format(
varname, str(self.InternalsDefaults)))
self.Internals = self.InternalsDefaults.copy() # we re-initialize the internal variables
else:
try:
self.Internals.update(eval(valuestring))
except:
self.Internals = self.InternalsDefaults.copy()
return
else:
Domoticz.Error("Cannot read the uservariable holding the persistent variables")
self.Internals = self.InternalsDefaults.copy()
def saveUserVar(self):
varname = Parameters["Name"] + "-InternalVariables"
DomoticzAPI("type=command¶m=updateuservariable&vname={}&vtype=2&vvalue={}".format(
varname, str(self.Internals)))
global _plugin
_plugin = BasePlugin()
def onStart():
global _plugin
_plugin.onStart()
def onStop():
global _plugin
_plugin.onStop()
def onCommand(Unit, Command, Level, Hue):
global _plugin
_plugin.onCommand(Unit, Command, Level, Hue)
def onHeartbeat():
global _plugin
_plugin.onHeartbeat()
# Plugin utility functions ---------------------------------------------------
def SensorTimedOut(datestring):
def LastUpdate(datestring):
dateformat = "%Y-%m-%d %H:%M:%S"
# the below try/except is meant to address an intermittent python bug in some embedded systems
try:
result = datetime.strptime(datestring, dateformat)
except TypeError:
result = datetime(*(time.strptime(datestring, dateformat)[0:6]))
return result
return LastUpdate(datestring) + timedelta(minutes=int(Settings["SensorTimeout"])) < datetime.now()
def parseCSV(strCSV):
listvals = []
for value in strCSV.split(","):
try:
val = int(value)
except:
pass
else:
listvals.append(val)
return listvals
def WriteLog(message, level="Normal"):
if Parameters["Mode6"] == "Verbose" or Parameters["Mode6"] == "Debug":
Domoticz.Log(message)
elif level == "Normal":
Domoticz.Log(message)
def DomoticzAPI(APICall):
resultJson = None
url = "http://{}:{}/json.htm?{}".format(Parameters["Address"], Parameters["Port"], parse.quote(APICall, safe="&="))
try:
req = request.Request(url)
if Parameters["Username"] != "":
WriteLog("Add authentification for user {}".format(Parameters["Username"]), "Debug")
credentials = ('%s:%s' % (Parameters["Username"], Parameters["Password"]))
encoded_credentials = base64.b64encode(credentials.encode('ascii'))
req.add_header('Authorization', 'Basic %s' % encoded_credentials.decode("ascii"))
response = request.urlopen(req)
if response.status == 200:
resultJson = json.loads(response.read().decode('utf-8'))
if resultJson["status"] != "OK":
Domoticz.Error("Domoticz API returned an error: status = {}".format(resultJson["status"]))
resultJson = None
else:
Domoticz.Error("Domoticz API: http error = {}".format(response.status))
except:
Domoticz.Error("Error calling '{}'".format(url))
return resultJson
# Generic helper functions
def DumpConfigToLog():
for x in Parameters:
if Parameters[x] != "":
Domoticz.Debug("'" + x + "':'" + str(Parameters[x]) + "'")
Domoticz.Debug("Device count: " + str(len(Devices)))
for x in Devices:
Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x]))
Domoticz.Debug("Device ID: '" + str(Devices[x].ID) + "'")
Domoticz.Debug("Device Name: '" + Devices[x].Name + "'")
Domoticz.Debug("Device nValue: " + str(Devices[x].nValue))
Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'")
Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
return
© 2018 GitHub, Inc.
Terms
Privacy
Security
Status
Help
Contact GitHub
API
Training
Shop
Blog
About
Press h to open a hovercard with more details.
Voici l'analyse que j'en fait: (je m'excuse d'avance pour les approximations qui j’espère ne seront pas trop hasardeuses dans mon pseudo-code)
Il s'avère que c'est un "double" algo P (et non PID) qui ajuste le coefficient Kp (appelé ici ConstP) automatiquement et qui en rajoute un, disons Ke (appellé ici ConstT) par rapport à l'écart (consigne - temperature extérieure).
Le calcul du temps de chauffe se fait ainsi:
Code : Tout sélectionner
FONCTION AutoMode()
//un peu d'hysterisis:
SI (temperature dépasse la consigne d'un seuil prédéfini)
tempsChauffe = 0
SINON //calcul du temps de chauffe
SI (quelque chose a changé (consigne, mode, ...))
on ne fait pas d'autocalibrage ce coup ci (repris au prochain calcul)
FINSI //pas clair pour moi car depend du parametre Unit dans la fonction onCommand et je ne comprends pas bien à quoi cela correspond...
SI (pas de temperature extérieure disponible) //mode P simple
power = ConstC * err /100
SINON
power = [ ConstC * err + ConstT * (temperatureConsigne - temperatureExterieure) ] /100
FINSI
//filtrage des valeurs trop petites ou trop grandes
power = constrain(power, min, 100%) //min peut etre 0 ou plus si on ne veut pas éteindre completement un plancher chauffant par ex.
tempsChauffe = power * PeriodeDeCalcul
FINSI
return tempsChauffe
FINAutoMode
//calibrage auto: on part de ConstC = 60 et ConstT = 1
// ConstC va baisser petit à petit et ConstT augmenter
FONCTION autoCalib()
SI (si on a chauffé et qu'on a dépassé la température de consigne) //affinage de ConstC
aux = ConstC * (temperature qu'il fallait gagner / temperature gagnée) * %deChauffe
//mise à jour de ConstC en faisant la moyenne des nbCC anciennes valeurs et de cette nouvelle (limité, en gros, aux 50 derniers apprentissages puisque nbCC <= 50)
ConstC = (ConstC * nbCC + aux)/(nbCC+1)
SINON SI (on a chauffé sans atteindre la consigne et consigne > temperature exterieure) //affinage de ConstT
aux = ConstT * [1 + (temperature qu'il fallait gagner) / (tempConsigne - temperatureExterieure) * %deChauffe ]
//mise à jour de ConstT en faisant la moyenne des nbCT anciennes valeurs et de cette nouvelle (limité, en gros, aux 50 derniers apprentissages puisque nbCT <= 50)
ConstT = (ConstT * nbCT + aux)/(nbCT+1)
FinSINON
FINautoCalib
Au final, avant d'essayer de calculer finenenent Tp, Tc, θp comme Greg pour obtenir des valeurs théoriques de Kc, Ki et Kd, je pense garder l'algo PID de Vil1driver, en l'initialisant avec:
Ki = Kd = 0
Kp et Ke en autocalibrage avec le principe ci-dessus pendant 2 à 3 jours (respectivement ConstP et ConstT)
puis ajuster Ki et Kd manuellement (tout en laissant l'autocalibrage ?)
@vil1driver: merci, j'ai lu aussi entre temps le principe de la courbe d'eau (
http://blog.elyotherm.fr/2013/08/reglag ... auffe.html) et ce sera certainement une inspiration pour anticiper le chauffage (avec le calcule de Ke ?). Bref, beaucoup de tests en perspective !
Sachant qu'au final, la seule solution théoriquement "propre" serait de déterminer une fois pour toute Kp, Tc, θp et utiliser les équations différentielles pour modéliser la pièce et anticiper la température future en fonction de la température extérieure et de la consigne qu'on envisage d'appliquer (du prédictif quoi).
[edit]
bon, je bosse sur une implémentation de tout ça mais je rame, je rame... Enfin, je devrais dire "j'apprends" ! J'ai forcément dû raconter des bêtises parmi ce qui est écrit au dessus...
Je ferai un retour quand j'aurai obtenu quelque chose de satisfaisant.