[LUA] Script chauffage Hysteresis et PID, multi radiateurs et thermomètres
Publié : 26 déc. 2016, 22:45
Hello,
Nouveau sur ce forum et débutant en domotique, j'ai le plaisir de partager le script que j'utilise pour chauffer mon chalet.
Il est situé à 1000m d'altitude, et équipé outre d'un poêle et d'un plancher chauffant (à la difficulté à chauffer et à l'inertie folles),
d'une paire de radiateurs électriques à fil pilote que j'ai équipés de relais DIO pour mode Hors-Gel ou Comfort.
Ce script sert à commander ces radiateurs, qui constituent les principales sources de chaleur.
Je remercie grandement les membres de ce forum pour leurs idées, leur code, leur aide aux membres du forum,
et en particulier Vil1driver, Guitoo et Jackyhi, dont je me suis largement inspiré du travail.
J'ai voulu un script assez complet, et voici les fonctionnalités implémentées :
- Multi- radiateurs ;
- Multi-thermomètres, avec détection de défaillance et fallback ;
- Multi-algorithmes, avec Hysteresis ou PID ;
- Comptage de la puissance instantanée et consommée ;
- Mode Présence ;
- Mono-zone. Plusieurs zones demandent plusieurs instances du script.
Le script demande de renseigner quelques paramètres simples dont les noms des composants.
Les composants nécessaires à l'utilisation du script sont :
- Un thermostat Confort
- Un thermostat Eco
- Un selecteur de mode de controle (Off / Hysteresis / PID )
- Un selecteur de mode de fonctionnement (Off / Eco / Confort / Forcé )
- Un switch de présence
- Un compteur d'energie
Les noms de ces devices sont à renseigner en paramètres au début du script.
En ce qui concerne les variables utilisateurs nécessaires, il faut en créer 4, et elles sont nommées :
- fISumLast, c'est un Float qu'on initialise à 0. C'est la valeur de l'intégrale du PID;
- fTempLastInput, c'est un Float qu'on initialise à 0. C'est la dernière température relevée pour le calcul du PID;
- fSetpointLast, c'est un Float qu'on initialise à 0. C'est la dernière consigne. Permet de vérifier si la consigne
a changée entre deux executions du script.
- iResetDeviceMeterTime, c'est un Integer qu'on initialise à 0. La variable est utile pour la mise à jour du compteur
électrique virtuel en mode PID.
Les thermomètres sont à déclarer dans un tableau, qui renseigne leur nom et le delta constaté par rapport au thermomètre de référence.
Si le thermomètre principal ne répond plus pendant plus de 10 minutes, le premier des suivants est utilisé et son delta est appliqué
pour constituer une "température virtuelle" dans la zone à chauffer. Un email d'alerte est envoyé. Si aucun thermomètre n'a répondu
durant les dernières 10 minutes, le script s'arrête sans action.
Les radiateurs sont à déclarer dans un tableau, qui renseigne, outre le nom attribué au périphérique dans Domoticz,
sa puissance en Watts (utilisé pour calcul de conso) et ses commandes pour l'allumer et l'éteindre.
Pour la partie PID, on doit régler les 3 fameux paramètres P, I et D, et là ça se complique un peu, mais rien de bien méchant.
Je me réfère aux travaux décrits sur les sites brettbeauregard.com [1], rhaaa.fr [2] et controlguru.com [3], qui sont passionants et très bien faits.
J'en remercie ici vivement leurs auteurs.
Notre controle de chauffage est ce qui s'appelle, en science de l'automatique, un régulateur PID.
Wikipedia : "Action Proportionnelle - Intégrale - Dérivée, [...] est un des outils les plus utilisés pour l'asservissement d'un système."
Et en effet, nous asservissons les radiateurs à la température de la pièce. L'équation de calcul du PID va nous donner une
valeur qui va déterminer la puissance instantanée du chauffage à produire pour arriver à la consigne. Le script va
appliquer cette valeur de puissance à nos radiateurs (en les allumant X minutes toutes les Y minutes, car ils fonctionnent seulement
en mode On/Off).
Pour calculer cette valeur, il nous faut déterminer une fois pour toutes les 3 coefficients Kp, Ki, et Kd, inhérents à
notre système de chauffage, notre habitation, ses pertes et son inertie.
Il y plusieurs étapes pour calculer ces valeurs, et je pense que cela vaut le coup de le faire ! L'efficience de notre
chauffage, le confort et les économies d'énergie sont à la clé ! Je vous conseille de mettre tout ca dans une feuille de calcul,
afin de pouvoir faire varier les paramètres facilement.
Il y a 8 valeurs à calculer :
1- Kp, le gain du process. Quel écart de température j'aurais si je change la commande de ma source de chaleur de tant ?
Kp = ΔPV / ΔCO avec PV (Process Variable) la température de la pièce et CO (Controller Output), le pourcentage de puissance
des radiateurs (le réglage).
Pour obtenir cette valeur, j'ai fait tourner le script avec des valeurs basiques, j'ai attendu d'avoir un bon plat
dans la courbe de température et j'ai regardé dans le log à combien de % le chauffage fonctionnait.
En augmentant la consigne et en stabilisant la température, même observation. On obtient un écart de température et
un écart de puissance, on a Kp (exprimé en °C/%)
2- Tp, la constante de temps. Décrit la vitesse de variation du chauffage.
Il a été determiné que cette valeur est atteinte quand la réaction (température de la pièce) attend 63% de la consigne.
Je lis sur un graphe que si je passe de ma consigne de 20°C à 21°C, la pièce atteint 21°C en une heure.
Les 63% d'une heure correspondent à 37.8 minutes, soit 2268 secondes.
3- θp, le temps mort, qui est le temps passé entre l'action et la réaction.
Je l'ai determiné en mesurant en combien de temps mon thermomètre se mettait à monter lorsque je passait
d'une température de 20 à 22.
4- Tc, le temps de réponse. C'est la vitesse à laquelle le système de chauffage va réagir. Si il est rapide, on
peut s'attendre à un dépassement de la consigne. Si il est lent, on n'aura pas cette tendance. Une valeur moyenne
doit être bonne pour un chauffage.
On choisit son paramètre Tc en se plaçant dans un des trois cas :
4-1- Réglage Aggressif : Tc est la valeur la plus grande entre 0.1 x Tp et 0.8 x θp
4-2- Réglage Intermédiaire : Tc est la valeur la plus grande entre 1 x Tp et 8 x θp
4-3- Réglage Conservateur : Tc est la valeur la plus grande entre 10 x Tp et 80 x θp
5- Kc, le gain du controleur. Ce sera notre P. Il donne la pente principale à prendre pour rattraper la consigne.
Kc = P = (1/Kp)*(Tp/(θp+Tc))
6- I, le coefficient d'intégration. Il pondère ce que le système a accumulé en dépassement de consigne.
I = Kc/Ti
7- Td, temps de dérivation, permet de calculer notre dernier paramètre. Il donne du poids à l'action dérivée.
Td = (Tp x θp)/(2Tp+θp)
8- D, le coefficient de dérivation. Il améliore la stabilité du système, notamment en amortissant les oscillations.
D = Kc * Td
Comme il est difficile d'obtenir des valeurs exactes, il peut être intéressant de jouer un peu dans les plages
de valeurs que l'on mesure (température, temps et réglage de la puissance de chauffe) pour s'approcher au mieux
d'un triplet de valeurs Kp, Ki, Kd qui donne une courbe la plus plate possible.
Le blog Coding Laboratory a une très belle interface pour se rendre compte de l'importance de chacun des paramètres P, I et D :
http://codinglab.blogspot.ch/2016/04/on ... ainer.html
Pour ma part, avec un réglage légerement aggressif afin d'avoir une montée rapide en température, j'obtient ce type de graphe de température, par exemple pour une montée à 15 degrés :
Limitation : le script ne supporte que les thermomètres qui mesurent aussi l'humidité.
Je suis curieux d'avoir des retours sur l'utilisation éventuelle de ce script, en particulier en terme de
confort de chauffage et d'économie d'energie. Merci d'avance.
Greg
[1] http://brettbeauregard.com/blog/category/pid/
[2] http://www.rhaaa.fr/regulation-pid-comment-la-regler-12
[3] http://controlguru.com
Nouveau sur ce forum et débutant en domotique, j'ai le plaisir de partager le script que j'utilise pour chauffer mon chalet.
Il est situé à 1000m d'altitude, et équipé outre d'un poêle et d'un plancher chauffant (à la difficulté à chauffer et à l'inertie folles),
d'une paire de radiateurs électriques à fil pilote que j'ai équipés de relais DIO pour mode Hors-Gel ou Comfort.
Ce script sert à commander ces radiateurs, qui constituent les principales sources de chaleur.
Je remercie grandement les membres de ce forum pour leurs idées, leur code, leur aide aux membres du forum,
et en particulier Vil1driver, Guitoo et Jackyhi, dont je me suis largement inspiré du travail.
J'ai voulu un script assez complet, et voici les fonctionnalités implémentées :
- Multi- radiateurs ;
- Multi-thermomètres, avec détection de défaillance et fallback ;
- Multi-algorithmes, avec Hysteresis ou PID ;
- Comptage de la puissance instantanée et consommée ;
- Mode Présence ;
- Mono-zone. Plusieurs zones demandent plusieurs instances du script.
Le script demande de renseigner quelques paramètres simples dont les noms des composants.
Les composants nécessaires à l'utilisation du script sont :
- Un thermostat Confort
- Un thermostat Eco
- Un selecteur de mode de controle (Off / Hysteresis / PID )
- Un selecteur de mode de fonctionnement (Off / Eco / Confort / Forcé )
- Un switch de présence
- Un compteur d'energie
Les noms de ces devices sont à renseigner en paramètres au début du script.
En ce qui concerne les variables utilisateurs nécessaires, il faut en créer 4, et elles sont nommées :
- fISumLast, c'est un Float qu'on initialise à 0. C'est la valeur de l'intégrale du PID;
- fTempLastInput, c'est un Float qu'on initialise à 0. C'est la dernière température relevée pour le calcul du PID;
- fSetpointLast, c'est un Float qu'on initialise à 0. C'est la dernière consigne. Permet de vérifier si la consigne
a changée entre deux executions du script.
- iResetDeviceMeterTime, c'est un Integer qu'on initialise à 0. La variable est utile pour la mise à jour du compteur
électrique virtuel en mode PID.
Les thermomètres sont à déclarer dans un tableau, qui renseigne leur nom et le delta constaté par rapport au thermomètre de référence.
Si le thermomètre principal ne répond plus pendant plus de 10 minutes, le premier des suivants est utilisé et son delta est appliqué
pour constituer une "température virtuelle" dans la zone à chauffer. Un email d'alerte est envoyé. Si aucun thermomètre n'a répondu
durant les dernières 10 minutes, le script s'arrête sans action.
Les radiateurs sont à déclarer dans un tableau, qui renseigne, outre le nom attribué au périphérique dans Domoticz,
sa puissance en Watts (utilisé pour calcul de conso) et ses commandes pour l'allumer et l'éteindre.
Pour la partie PID, on doit régler les 3 fameux paramètres P, I et D, et là ça se complique un peu, mais rien de bien méchant.
Je me réfère aux travaux décrits sur les sites brettbeauregard.com [1], rhaaa.fr [2] et controlguru.com [3], qui sont passionants et très bien faits.
J'en remercie ici vivement leurs auteurs.
Notre controle de chauffage est ce qui s'appelle, en science de l'automatique, un régulateur PID.
Wikipedia : "Action Proportionnelle - Intégrale - Dérivée, [...] est un des outils les plus utilisés pour l'asservissement d'un système."
Et en effet, nous asservissons les radiateurs à la température de la pièce. L'équation de calcul du PID va nous donner une
valeur qui va déterminer la puissance instantanée du chauffage à produire pour arriver à la consigne. Le script va
appliquer cette valeur de puissance à nos radiateurs (en les allumant X minutes toutes les Y minutes, car ils fonctionnent seulement
en mode On/Off).
Pour calculer cette valeur, il nous faut déterminer une fois pour toutes les 3 coefficients Kp, Ki, et Kd, inhérents à
notre système de chauffage, notre habitation, ses pertes et son inertie.
Il y plusieurs étapes pour calculer ces valeurs, et je pense que cela vaut le coup de le faire ! L'efficience de notre
chauffage, le confort et les économies d'énergie sont à la clé ! Je vous conseille de mettre tout ca dans une feuille de calcul,
afin de pouvoir faire varier les paramètres facilement.
Il y a 8 valeurs à calculer :
1- Kp, le gain du process. Quel écart de température j'aurais si je change la commande de ma source de chaleur de tant ?
Kp = ΔPV / ΔCO avec PV (Process Variable) la température de la pièce et CO (Controller Output), le pourcentage de puissance
des radiateurs (le réglage).
Pour obtenir cette valeur, j'ai fait tourner le script avec des valeurs basiques, j'ai attendu d'avoir un bon plat
dans la courbe de température et j'ai regardé dans le log à combien de % le chauffage fonctionnait.
En augmentant la consigne et en stabilisant la température, même observation. On obtient un écart de température et
un écart de puissance, on a Kp (exprimé en °C/%)
2- Tp, la constante de temps. Décrit la vitesse de variation du chauffage.
Il a été determiné que cette valeur est atteinte quand la réaction (température de la pièce) attend 63% de la consigne.
Je lis sur un graphe que si je passe de ma consigne de 20°C à 21°C, la pièce atteint 21°C en une heure.
Les 63% d'une heure correspondent à 37.8 minutes, soit 2268 secondes.
3- θp, le temps mort, qui est le temps passé entre l'action et la réaction.
Je l'ai determiné en mesurant en combien de temps mon thermomètre se mettait à monter lorsque je passait
d'une température de 20 à 22.
4- Tc, le temps de réponse. C'est la vitesse à laquelle le système de chauffage va réagir. Si il est rapide, on
peut s'attendre à un dépassement de la consigne. Si il est lent, on n'aura pas cette tendance. Une valeur moyenne
doit être bonne pour un chauffage.
On choisit son paramètre Tc en se plaçant dans un des trois cas :
4-1- Réglage Aggressif : Tc est la valeur la plus grande entre 0.1 x Tp et 0.8 x θp
4-2- Réglage Intermédiaire : Tc est la valeur la plus grande entre 1 x Tp et 8 x θp
4-3- Réglage Conservateur : Tc est la valeur la plus grande entre 10 x Tp et 80 x θp
5- Kc, le gain du controleur. Ce sera notre P. Il donne la pente principale à prendre pour rattraper la consigne.
Kc = P = (1/Kp)*(Tp/(θp+Tc))
6- I, le coefficient d'intégration. Il pondère ce que le système a accumulé en dépassement de consigne.
I = Kc/Ti
7- Td, temps de dérivation, permet de calculer notre dernier paramètre. Il donne du poids à l'action dérivée.
Td = (Tp x θp)/(2Tp+θp)
8- D, le coefficient de dérivation. Il améliore la stabilité du système, notamment en amortissant les oscillations.
D = Kc * Td
Comme il est difficile d'obtenir des valeurs exactes, il peut être intéressant de jouer un peu dans les plages
de valeurs que l'on mesure (température, temps et réglage de la puissance de chauffe) pour s'approcher au mieux
d'un triplet de valeurs Kp, Ki, Kd qui donne une courbe la plus plate possible.
Le blog Coding Laboratory a une très belle interface pour se rendre compte de l'importance de chacun des paramètres P, I et D :
http://codinglab.blogspot.ch/2016/04/on ... ainer.html
Pour ma part, avec un réglage légerement aggressif afin d'avoir une montée rapide en température, j'obtient ce type de graphe de température, par exemple pour une montée à 15 degrés :
Limitation : le script ne supporte que les thermomètres qui mesurent aussi l'humidité.
Je suis curieux d'avoir des retours sur l'utilisation éventuelle de ce script, en particulier en terme de
confort de chauffage et d'économie d'energie. Merci d'avance.
Greg
[1] http://brettbeauregard.com/blog/category/pid/
[2] http://www.rhaaa.fr/regulation-pid-comment-la-regler-12
[3] http://controlguru.com
Code : Tout sélectionner
--[[
Type : Time
Name : script_time_heating.lua
Description : Controls heating in a room with hysterisis or PID method
Usage : Place in scripts/lua directory of Domoticz instance
This script provides heating control in a room, providing different modes (Comfort, Eco), according
to presence in the room and to a couple of setpoints. The heating control is proposed through
two differents algorithms (Hysterisis or PID). The script supports multiple temperature control
devices used in fallback, and multiple heating devices used simultaneously.
A power consumption computation is made, based on the declared power consumption of each heating device.
Required Domoticz configuration :
- A Thermostat to set the comfort temperature, created with Dummy hardware and a Thermostat device
Variable : sSetPointConfortDeviceName
- A Thermostat to set the eco temperature, created with Dummy hardware and a Thermostat device
Variable : sSetPointEcoDeviceName
- A Mode switch to set and program with timers the heating scenarii, created with Dummy hardware and a Selector Switch device
Variable : sHeatingModeDeviceName
- A Control Mode switch to switch between the Hysterisis and PID ways of control.
Variables : sHeatingControlModeDeviceName
sHeatingControlModePIDName, sHeatingControlModeHysteresisName are used for the name given to either mode.
- A Presence detection switch. The switch would be controlled either by hand or by some external scripts/commands.
Variable : sPresenceDeviceName
- An Electricity consumption meter, created with an Instant+Counter meter
Variable : sPowerMeterDeviceName
- Heaters
One or more heaters have to be declared in an array, together with their power consumption and commands.
Variable : aHeatingDeviceNames
- Temperature sensors
They are declared in a array, with fall back mode implemented. The script is written for sensors that gather both temperature and humidity.
Variable : aTempSensorDeviceNames
- User variables
- fISumLast, a float value initialized to 0, that's the PID current integral value ;
- fTempLastInput, a float value initialized to 0, used for the PID derivative computation ;
- fSetpointLast, a float value initialized to 0, used to check a setpoint change ;
- iResetDeviceMeterTime, an integer value initialized to 0, used to record the power meter reset time.
The PID parameters are to be determined for your own installation. Three parameters need to be calculated,
according to the following formulas :
Kp = ΔPV / ΔCO -- On steady temperature states, ratio of temperature difference for command difference
Tp -- Time needed to reach 63% of the setpoint
θp -- Time between action on the heating and measurable reaction of the temperature sensor
Tc -- Response time, highest value among 0.1Tp or 0.8θp (agressive), 1Tp or 8θp (middle), 10Tp or 80θp (conservative)
Kc = P = (1/Kp)*(Tp/(θp+Tc)) -- Our fPIDKp
I = Kc/Ti -- Our fPIDKi
Td = (Tp x θp)/(2Tp+θp) -- Needed to compute the last constant
D = Kc * Td -- Our fPIDKd
This script distributed under the GPL license. Please see https://www.gnu.org/licenses/gpl.html
--]]
-- Zone name
local sZoneName = 'Living room'
-- Setpoint
local sSetPointConfortDeviceName = 'Thermostat Confort' -- Thermostat for comfort Setpoint
local sSetPointEcoDeviceName = 'Thermostat Eco' -- Thermostat for eco Setpoint
-- Power Meter
local sPowerMeterDeviceName = 'Consommation Chauffage' -- Power meter based on configured heating devices consumption
-- Modes
-- These modes belong to the heating mode selector switch
local sHeatingModeDeviceName = 'Mode Chauffage'
local sHeatingModeComfortName = 'Confort'
local sHeatingModeEcoName = 'Eco'
local sHeatingModeForceOnName = 'Force'
local sHeatingModeFrostFreeName = 'Off'
-- Heating control mode (PID or Hysteresis)
local sHeatingControlModeDeviceName = 'Mode Controle Chauffage'
local sHeatingControlModePIDName = 'PID' -- Selection switch name for PID
local sHeatingControlModeHysteresisName = 'Hysteresis' -- Selection switch name for hysterisis mode
local fHysteresis = 0.4 -- Hysterisis value
-- Sensors
-- Will be choosen in fallback. Second parameter is a delta to the first temp sensor
local aTempSensorDevices = {
{ 'Living-room 1',0 } ,
{ 'Chambre Enfant',1.2 } -- The room is usually ~1.2 deg hotter than the living room
}
local sPresenceDeviceName = 'Presence' -- Presence device switch. Heating goes in Eco mode when Presence is Off.
-- Heaters
-- Domoticz Device Name, power in Watts, On command, Off command.
local aHeatingDevices = {
{ 'Hors-Gel Radiateur Salon Est', 1500 , 'Off AFTER 3', 'On' } ,
{ 'Hors-Gel Radiateur Salon Sud', 1500 , 'Off AFTER 3', 'On' }
}
-- PID - Proportional, Integral, Derivative - Parameters
local iHeatingCycleDuration = 10 -- Duration of a heating cycle in minutes, to be adapted to the heating devices
local fPIDKp = 113 -- Change this !
local fPIDKi = 0.05 -- Change this !
local fPIDKd = 30045 -- Change this !
-- Alerts
local sEmailContact = 'name@domain.tld'
-- Debug
local bDebug = true
---------------------------------------------- End of configuration section ----------------------------------------------
-- Variables
local fCurrentTemp = 1000
-- Statics variables
-- Heating commands, constants not to be changed
local sHeatingOnCommand = 'ON'
local sHeatingOnForCommand = 'ONFOR'
local sHeatingOffCommand = 'OFF'
-- Functions
function prDebug(sString)
if(bDebug) then
print ( sString )
end
end
function addCommand( sDevice, sCommand )
commandArray[#commandArray+1] = { [sDevice] = sCommand }
end
function timeDifference(sDeviceName)
sDeviceTimeChanged = otherdevices_lastupdate[sDeviceName]
sYear = string.sub(sDeviceTimeChanged, 1, 4)
sMonth = string.sub(sDeviceTimeChanged, 6, 7)
sDay = string.sub(sDeviceTimeChanged, 9, 10)
sHour = string.sub(sDeviceTimeChanged, 12, 13)
sMinutes = string.sub(sDeviceTimeChanged, 15, 16)
sSeconds = string.sub(sDeviceTimeChanged, 18, 19)
t1 = os.time()
t2 = os.time{year=sYear, month=sMonth, day=sDay, hour=sHour, min=sMinutes, sec=sSeconds}
return os.difftime (t1, t2)
end
function round(fNumber, iPrecision)
local iMult = 10^(iPrecision or 0)
return math.floor(fNumber * iMult + 0.5) / iMult
end
function commandHeating(sCommand,iTime)
local fIntervalEnergy = 0
local fInstantPower = 0
-- Send command to heaters, calculate power and energy
for iHeatingDeviceIndex = 1, #aHeatingDevices do
local sActiveHeatingDevice = aHeatingDevices[iHeatingDeviceIndex]
fInstantPower = fInstantPower + sActiveHeatingDevice[2]
if( sCommand == sHeatingOnForCommand ) then
fIntervalEnergy = fIntervalEnergy + (tonumber(fInstantPower) / 60 ) * iHeatingCycleDuration
else
fIntervalEnergy = fIntervalEnergy + tonumber(fInstantPower) / 60
end
if( sCommand == sHeatingOnCommand ) then
prDebug('Heating device : - '..sActiveHeatingDevice[1]..' - , power : ' .. sActiveHeatingDevice[2]..' Watts gets command : '..sActiveHeatingDevice[3])
addCommand(sActiveHeatingDevice[1],sActiveHeatingDevice[3])
end
if( sCommand == sHeatingOnForCommand ) then
prDebug('Heating device : - '..sActiveHeatingDevice[1]..' - , power : ' .. sActiveHeatingDevice[2]..' Watts gets command : '..sActiveHeatingDevice[3]..' FOR '..tostring(iTime))
addCommand(sActiveHeatingDevice[1],sActiveHeatingDevice[3] .. ' FOR ' .. tostring(iTime)) -- Can be missed :-( --> zwave!
end
if( sCommand == sHeatingOffCommand ) then
prDebug('Heating device : - '..sActiveHeatingDevice[1]..' - , power : ' .. sActiveHeatingDevice[2]..' Watts gets command : '..sActiveHeatingDevice[4])
addCommand(sActiveHeatingDevice[1],sActiveHeatingDevice[4])
end
end
-- Energy accounting
-- Get current power-meter values
sCurrentPower,sCurrentTotalEnergy = string.match(otherdevices_svalues[sPowerMeterDeviceName], "(%d+%.*%d*);(%d+%.*%d*)" )
local fCurrentTotalEnergy = tonumber(sCurrentTotalEnergy)
if(sCommand == sHeatingOffCommand ) then -- No instant power consumption
addCommand('UpdateDevice', iPowerMeterIdx .. '|0|0;' .. sCurrentTotalEnergy)
return
end
fUpdatedEnergy = fCurrentTotalEnergy + fIntervalEnergy
if(sCommand == sHeatingOnForCommand ) then -- System heating iTimes minutes
addCommand('UpdateDevice', iPowerMeterIdx .. '|0|' .. fInstantPower .. ';' .. fUpdatedEnergy)
addCommand('Variable:iResetDeviceMeterTime',tostring(iTime))
end
if(sCommand == sHeatingOnCommand ) then -- System in On/Off mode, heating happends until a stop command
-- Total instant power update
addCommand('UpdateDevice', iPowerMeterIdx .. '|0|' .. fInstantPower .. ';' .. fUpdatedEnergy)
end
end
-- Init
commandArray = {}
iPowerMeterIdx = otherdevices_idx[sPowerMeterDeviceName]
-- Get time
local sCurrentTime = string.sub(os.date("%X"), 1, 5)
local sCurrentMinute = os.date('%M')
-- Check temperature sensors, choose the first one with response time < 10 min
-- set setpoint delta
for iTempSensorIndex = 1, #aTempSensorDevices do
prDebug('Temp Sensor Device : '..aTempSensorDevices[iTempSensorIndex][1]..' - Delta to reference : ' .. aTempSensorDevices[iTempSensorIndex][2] )
sTempSensorDeviceName = aTempSensorDevices[iTempSensorIndex][1]
sTempSensorDeltaToReference = aTempSensorDevices[iTempSensorIndex][2]
-- Last seen ?
local sTempSensorLastSeen = string.sub(os.date("!%X", timeDifference(sTempSensorDeviceName)), 1, 5)
-- if (sTempSensorLastSeen >= '00:10' and sTempSensorLastSeen < '00:12') then
if (sTempSensorLastSeen >= '00:10') then
prDebug('Temp sensor missing')
addCommand('SendEmail',sZoneName..' Heating Alert#Temperature sensor '..sTempSensorDeviceName..' has not updated for more than 10m.#'..sEmailContact)
else -- Sensor has been seen, we use it
sCurrentTemp,sCurrentHumidity = string.match(otherdevices_svalues[sTempSensorDeviceName],"(%d+%.*%d*);(%d+%.*%d*)")
fCurrentTemp = tonumber(sCurrentTemp) + tonumber(sTempSensorDeltaToReference)
prDebug ('Current measured temperature and delta applied : '..fCurrentTemp)
break
end
end
if ( fCurrentTemp == 1000) then return commandArray end -- No temp sensor, end of execution
-- Check for energy accounting timer to reset the meter
local iResetMeterDeviceTime = tonumber(uservariables['iResetDeviceMeterTime'])
if ( iResetMeterDeviceTime > 0 ) then -- There is an ongoing power consumption
local iNewResetDeviceMeterTime = iResetMeterDeviceTime - 1
if( iNewResetDeviceMeterTime == 0) then
-- reset energy counters on time
sCurrentPower,sCurrentTotalEnergy = string.match(otherdevices_svalues[sPowerMeterDeviceName], "(%d+%.*%d*);(%d+%.*%d*)" )
addCommand('UpdateDevice', iPowerMeterIdx .. '|0|0;' .. sCurrentTotalEnergy)
end
addCommand('Variable:iResetDeviceMeterTime',tostring(iNewResetDeviceMeterTime))
end
-- Get current heating mode
sHeatingMode = otherdevices[sHeatingModeDeviceName]
-- Heating in Eco or Comfort, needs a calculation
if ( sHeatingMode == sHeatingModeComfortName
or
sHeatingMode == sHeatingModeEcoName
)
then
-- Inits
bPresence = false
fSetPointTemp = 7
-- Check for presence
sPresenceDeviceStatus = otherdevices[sPresenceDeviceName]
if ( sPresenceDeviceStatus == 'On' ) then
prDebug('Presence is on')
bPresence = true
else
prDebug('Presence is off')
bPresence = false
end
-- Comfort mode, set heating setpoint to thermostat value, only applies if Presence is true.
if( (sHeatingMode == sHeatingModeComfortName) and bPresence ) then
fSetPointTemp = tonumber(otherdevices_svalues[sSetPointConfortDeviceName])
prDebug ('Heating in *Comfort mode*, Setpoint : '..fSetPointTemp..' at '..sCurrentTime)
end
-- Eco mode, setpoint adjusted. Use this setpoint if no Presence.
if( sHeatingMode == sHeatingModeEcoName or bPresence == false) then
fSetPointTemp = tonumber(otherdevices_svalues[sSetPointEcoDeviceName])
prDebug ('Heating in *Eco mode*, Setpoint : '..fSetPointTemp..' at '..sCurrentTime)
end
-- Control mode
local sControlMode = otherdevices[sHeatingControlModeDeviceName]
prDebug('Heating Control mode : ' .. sControlMode)
if( sControlMode == sHeatingControlModeHysteresisName ) then -- Control mode set to hysteresis
-- Decision to heat or not
-- Is it cold ?
if(fCurrentTemp <= (fSetPointTemp - fHysteresis) ) then
prDebug ('Heating needed with delta : ' .. fCurrentTemp - fSetPointTemp)
-- startHeating(aHeatingDeviceNames) --GH get rid of variable
commandHeating(sHeatingOnCommand)
end
-- Is it warm ?
if(fCurrentTemp >= (fSetPointTemp + fHysteresis) ) then
prDebug ('Stopping heating needed with delta : ' .. fCurrentTemp - fSetPointTemp)
-- stoptHeating(aHeatingDeviceNames)
commandHeating(sHeatingOffCommand)
end
end
if( sControlMode == sHeatingControlModePIDName ) then -- Control mode set to PID
-- We control the execution frequency
if( sCurrentMinute % iHeatingCycleDuration == 0) then -- We run the cycle
-- Set PID coefficients according to sample time
local fPIDTKi = fPIDKi * iHeatingCycleDuration * 60
local fPIDTKd = fPIDKd / (iHeatingCycleDuration * 60)
-- Current temperature error
fTempError = fSetPointTemp - fCurrentTemp
-- Compute new Integral
-- If Setpoint changed, reset the integral action
if( fSetPointTemp ~= tonumber(uservariables['fSetpointLast']) ) then
fISum=0
else
-- Get the last Integral
fISum = uservariables['fISumLast']
end
fISum = fISum + (fPIDTKi * fTempError)
-- if(fISum > 255) then fISum = 255 end
if(fISum > 255) then fISum = 255 end -- Integral limitation
if(fISum < 0) then fISum = 0 end
-- Derivative computation
local fDeltaInput = fCurrentTemp - uservariables['fTempLastInput'] -- to number
-- Sum up the PID output
local fPIDOutput = fPIDKp * fTempError + fISum - fPIDTKd * fDeltaInput
prDebug('PID Output : fPIDKp * fTempError = ' ..fPIDKp..' * '..fTempError..' = '..fPIDKp*fTempError..', fISum + '..fPIDTKi * fTempError..' = '..fISum..', -fPIDTKd * fDeltaInput = '..fPIDTKd..' * '..fDeltaInput..' = -' .. fPIDTKd*fDeltaInput..', Output : '..fPIDOutput)
if(fPIDOutput > 255) then fPIDOutput = 255 end
if(fPIDOutput < 0) then fPIDOutput = 0 end
-- Update user variables for next cycle
addCommand('Variable:fTempLastInput',tostring(fCurrentTemp))
addCommand('Variable:fISumLast',tostring(fISum))
addCommand('Variable:fSetpointLast',tostring(fSetPointTemp))
-- Heating cycle duration calculation
if( fPIDOutput > 0 ) then -- heat
fCycleDuration = round(fPIDOutput * iHeatingCycleDuration / 255,0)
if( fCycleDuration == 0 ) then fCycleDuration = 1 end -- Heating is needed, let's go for at least 1 minute
prDebug('Going to heat for : ' ..fCycleDuration..' minutes during the next '..round(iHeatingCycleDuration*60)..' seconds ('..iHeatingCycleDuration..' minutes) period.' )
commandHeating(sHeatingOnForCommand,fCycleDuration)
else -- Output null or negative, stop the heating
prDebug('No heating needed')
commandHeating(sHeatingOffCommand)
end
end -- Time period
end -- PID mode
end
-- Heating forced on
if ( sHeatingMode == sHeatingModeForceOnName )
then
prDebug('Heaters forced On')
commandHeating(sHeatingOnCommand)
end
-- Heating forced off
if ( sHeatingMode == sHeatingModeFrostFreeName )
then
prDebug('Heaters forced Off - Anti-frost mode')
commandHeating(sHeatingOffCommand)
end
return commandArray