Aller au contenu

Recommended Posts

HEATING MANAGER

v. 2.0 - 16/11/2017

 

Heating Manager est un programme permettant d'utiliser le Home Center 2 pour gérer avec une grande souplesse le chauffage d'une habitation :

 

  • régulation sur un mode Proportionnel avec prise en compte des déperditions (Ubat) ou sur le principe de l'hysteresis
  • utilisation possible des panneaux de chauffage natifs, de modules virtuels ou de variables globales pour définir la température de consigne
  • prise en compte de l'ouverture / fermeture des ouvrants via périphériques Z-Wave, modules virtuels ou variables globales
  • prise en compte de l'absence via périphériques Z-Wave, modules virtuels ou variables globales
  • possibilité de forcer un mode absence ou présence depuis vos modules virtuels ou variables globales dédiées
  • paramétrage évolutif en fonction de vos conditions personnalisées...

 

Ces développements doivent beaucoup aux échanges stimulants intervenus sur ce fil de discussion, qui m'ont amené à aller bien plus loin que je ne l'aurai imaginé au départ. Que tous soient remerciés ;).

 

_ _ _

 

PRE-REQUIS

 

Ce programme a été conçu et testé sur le Home Center 2 tournant avec le firmware v. 4.140.

 

  • La commande des radiateurs requiert des modules acceptant les instructions 'turnOn' et 'turnOff', ou des modules virtuels avec un bouton pour mettre en route le chauffage et un pour l'arrêter si la commande du radiateur n'est pas faite par l'intermédiaire d'un module Z-Wave inclus dans le réseau de la HC2.
  • La température actuelle peut être récupérée depuis un module de type 'com.fibaro.temperatureSensor' ou depuis un module virtuel pour les sondes non Z-Wave. Il en va de même pour la température extérieure qui est utilisée dans le mode Proportionnel + Ubat, avec également la possibilité d'utiliser la température fournie par l'un des plugins météo (qui est l'option retenue par défaut).
  • La détection de l'absence implique de disposer de détecteurs de mouvements, étant présupposé pour les périphériques Z-Wave que 0 = plus de mouvement et 1 = mouvement détecté. Il est également possible d'utiliser les informations en provenance d'un périphérique non Z-Wave enregistrées dans un module virtuel ou une variable globale.
  • La détection de l'ouverture / fermeture des portes et fenêtres implique la présence de détecteurs d'ouverture, étant présupposé pour les modules Z-Wave que 0 = fermé et 1 = ouvert. Il est également possible d'utiliser les informations en provenance d'un périphérique non Z-Wave enregistrées dans un module virtuel ou une variable globale.

 

_ _ _

 

INSTALLATION

 

1. DEFINITION DU PLANNING DE CHAUFFE

 

La première étape est de définir un planning de chauffe dans lequel Heating Manager ira chercher la température de consigne à atteindre.

Ce planning de chauffe peut être mis en place en utilisant les panneaux de chauffage fournis nativement par le Home Center 2. Attention cependant, un bug a été constaté, évoqué dans ce fil, entraînant l'absence de prise en compte du changement de température de consigne après minuit.

Il peut également être mis en place en utilisant n'importe quel module virtuel ou bien celui fourni avec Heating Manager   >>>>   Heating_Provider.vfib

 

5a0cc15f8568d_HeatingProvider.png.9c287f91f7cae736f411593de0a2139d.png

 

Pour utiliser ce module virtuel, il faut adapter la configuration donnée en exemple à votre cas en modifiant, et en ajoutant le cas échéant, les étiquettes, chaque étiquette correspondant à une zone de chauffage et devant être nommée avec le préfixe lbl.

La définition du planning de chauffe pour chacun de ces zones intervient dans le main loop du module virtuel, dans la partie Configuration :

HeatingSetpoint:add(zone, day, time, temperature)
  + zone correspond au nom d''un étiquette du module virtuel, sans le préfixe ''lbl''
  + day est soit l''un des jours de la semaine (en anglais), soit "weekday" (du lundi au vendredi), soit "weekend", soit "week"
  + time est l''heure à laquelle la température de consigne devra être appliquée "hh:mm"
  + temperature est la température de consigne devant être appliquée à compter de l''horaire défini juste avant

Il est important que la construction du planning de chauffe soit faite de manière chronologique, le module virtuel ne faisant pas de tri chronologique, ce qui peut entraîner un comportement s'écartant de celui attendu.

 

2. INSTALLATION DU GESTIONNAIRE DE CONSIGNE   >>>>   Heating_Panel.vfib

 

5a0cc4bd7eec0_HeatingPanel.png.fb010e8bf5b92f31576f0087b9257498.png

 

Heating Panel est le module virtuel qui s'occupe de récupérer la température de consigne applicable là où vous l'avez défini, et de lui appliquer les éventuelles modifications résultant de l'ouverture d'une porte / fenêtre, de l'absence temporaire ou déclarée, etc., afin que la scène qui sera installée plus tard puisse venir la chercher pour accomplir sa tâche.

 

Le module virtuel doit avoir autant d'étiquettes que de pièces à chauffer, l'ID de chaque étiquette devant impérativement être sous la forme "lbl" + le nom de la pièce concernée débarrassé de tous espaces, accents et caractères spéciaux. Ainsi, pour la pièce "Séjour Principal #1", l'ID de l'étiquette doit être "lblSejourPrincipal1". En effet, pour faciliter la configuration du programme, celui-ci détermine automatiquement l'étiquette à utiliser en récupérant le nom de la pièce à laquelle chaque radiateur est assigné.

 

La configuration est effectué par l'intermédiaire de différentes fonctions qu'il vous faut renseigner dans la partie Configuration du main loop :

 

    HeatingPanel:addZone(label, panel)
      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + panel      = ID du panneau de chauffage associé à la pièce
                     ou {ID d''un module virtuel, ID de l''étiquette contenant la température de consigne}
                     ou Nom d''une variable globale contenant la température de consigne

La fonction HeatingPanel:addZone permet d'ajouter les pièces à chauffer. Il faut garder à l'esprit que cette fonction, ainsi que les suivantes, est exécutée à chaque boucle du main loop, soit toutes les trois secondes, ce qui signifie qu'elle n'a pas nécessairement à être statique et que vous pouvez faire précéder son exécution par une ou plusieurs boucles conditionnelles pour déterminer la source de température de consigne à utiliser.

 

    HeatingPanel:addAutoAbsence(label, id, [extension])

      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + id         = ID du détecteur de mouvement
                     ou {ID d''un module virtuel, nom de l''étiquette, valeur si absence}
                     ou {nom d''une variable globale, valeur si absence}
      + extension  = nombre de minutes depuis que a propriété value du détecteur est passée à absence de mouvement avant de mettre à jour la consigne
    __________________________________________________________________________________________

    HeatingPanel:setAutoAbsence(label, [reduction], [duration], [limit], [cumulative])

      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + reduction  = baisse de température à appliquer en cas d''absence
      + duration   = étalement dans le temps de la baisse de température, en minutes
      + limit      = température minimale à respecter
      + cumulative = si true, tous les détecteurs doivent être en position absence de mouvement ; si false, un seul déclenche la baisse de température

La fonction HeatingPanel:addAutoAbsence permet d'ajouter à une pièce définie avec la fonction HeatingPanel:addZone la gestion des absences et à paramétrer éventuellement un délai entre le moment où plus aucun mouvement n'est détecté et le moment où la baisse de température est appliquée. Plusieurs détecteurs de mouvement peuvent être rattachés à la même zone, il suffit d'exécuter plusieurs fois la fonction avec le même paramètre label.

la fonction HeatingPanel:setAutoAbsence permet pour sa part part de paramétrer la gestion des absences au niveau de la pièce.

 

    HeatingPanel:addOpening(label, id, [extension])

      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + id         = ID du détecteur d''ouverture
                     ou {ID d''un module virtuel, nom de l''étiquette, valeur si ouvert}
                     ou {nom d''une variable globale, valeur si ouvert}
      + extension  = nombre de minutes depuis que l''ouverture est constatée avant de mettre à jour la consigne
    __________________________________________________________________________________________

    HeatingPanel:setOpening([cumulative])

      + cumulative = si true, tous les détecteurs doivent être en position ouverture ; si false, un seul déclenche la baisse de température

La fonction HeatingPanel:addOpening permet d'ajouter à une zone définie avec la fonction HeatingPanel:addZone la gestion des ouvrants et à paramétrer éventuellement un délai entre le moment où le détecteur passe en position ouverture et le moment où la baisse de température est appliquée. Plusieurs détecteurs d'ouverture peuvent être rattachés à la même zone, il suffit là encore d'exécuter plusieurs fois la fonction.

La fonction HeatingPanel:setOpening permet pour sa part part de définir le seul paramètre disponible à l'échelle de la pièce.

 

    HeatingManager:addDeclaredAbsence(label, id)

      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + id         = {ID d''un module virtuel, nom de l''étiquette, valeur si absence}
                     or {nom d''une variable globale, valeur si absence}
    ____________________________________________________________________________________________

    HeatingManager:setDeclaredAbsence(label, [cumulative], [setpoint])

      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + cumulative = si true, tous les valeurs doivent correspondre à une absence; si false, une seule est suffisante
      + setpoint   = température de consigne à appliquer en cas d''absence déclarée

Les fonctions HeatingManager:addDeclaredAbsence et HeatingManager:setDeclaredAbsence permettent de paramétrer une pièce pour pouvoir changer la température de consigne en cas d'absence déclarée dans un autre module virtuel ou dans une variable globale. La première fonction peut être répétée pour pouvoir utiliser plusieurs modules virtuels / variables globales, tandis que la seconde permet de définir les paramètres généraux de l'absence déclarée à l'échelle de la pièce.

 

    HeatingManager:addDeclaredPresence(label, id)

      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + id         = {ID d''un module virtuel, nom de l''étiquette, valeur si présence}
                     or {nom d''une variable globale, valeur si présence}
    ____________________________________________________________________________________________

    HeatingManager:setDeclaredPresence(label, [cumulative], [setpoint])

      + label      = ID de l''étiquette sans le préfixe 'lbl' : "SejourPrincipal1"
      + cumulative = si true, tous les valeurs doivent correspondre à une présence; si false, une seule est suffisante
      + setpoint   = température de consigne à appliquer en cas de présence déclarée

Les fonctions HeatingManager:addDeclaredPresence et HeatingManager:setDeclaredPresence permettent de paramétrer une pièce pour pouvoir changer la température de consigne en cas de présence déclarée dans un autre module virtuel ou dans une variable globale. La première fonction peut être répétée pour pouvoir utiliser plusieurs modules virtuels / variables globales, tandis que la seconde permet de définir les paramètres généraux de la présence déclarée à l'échelle de la pièce.

 

Dans toutes ces fonctions, les paramètres mentionnés entre [ ] sont optionnels. S'ils ne sont pas renseignés par vos soins, ce sont les paramètres par défaut qui sont utilisés et qui peuvent naturellement être modifiés :

--Paramètres par défaut pour la gestion des ouvrants
HeatingManager.opening_extension      = 0        -- temps en minutes entre le constat d''ouverture et la coupure du chauffage
HeatingManager.opening_cumulative     = false    -- si true, tous les détecteurs doivent être sur ouverture ; si false, il en suffit d''un seul

--Paramètres par défaut pour la gestion automatique de l''absence
HeatingManager.aAbsence_extension     = 25       -- temps en minutes entre le constat d''absence et la modification de la consigne
HeatingManager.aAbsence_reduction     = 1        -- diminution de température à appliquer
HeatingManager.aAbsence_duration      = 60       -- étalement dans le temps en minutes de cette diminution
HeatingManager.aAbsence_stepdrop      = 0.5      -- paliers de baisse de température
HeatingManager.aAbsence_limit         = 18       -- température minimale à respecter
HeatingManager.aAbsence_cumulative    = true     -- si true, tous les détecteurs doivent être sur absence ; si false, il en suffit d''un seul

--Paramètres par défaut pour les absences déclarées
HeatingManager.dAbsence_cumulative    = true     -- si true, toutes les valeurs doivent être sur absence ; si false, il en suffit d''une seule
HeatingManager.dAbsence_setpoint      = 8        -- température de consigne en cas d''absence déclarée

--Paramètre par défaut pour les présences déclarées
HeatingManager.dPresence_cumulative   = true     -- si true, toutes les valeurs doivent être sur présence ; si false, il en suffit d''une seule
HeatingManager.dPresence_setpoint     = 22       -- température de consigne en cas de présence déclarée

Enregistrez les modifications. Le module virtuel devrait normalement commencer à se mettre à jour et afficher pour chaque pièce la température de consigne définie. Si vous ouvrez une fenêtre ou une porte asservissant le chauffage d'une pièce, vous devez voir la température de consigne passer à 0 °C dans les trois secondes, délai d'exécution de la boucle principale. Même chose pour la détection de l'absence, vous devriez voir la température de consigne commencer à baisser conformément à ce que vous avez paramétré.

 

Pour les curieux, le code du main loop du module virtuel ci-dessous :

 

Révélation

--[[
    Heating Manager v. 2.0 (2017) by OJC

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses></http:>.
--]]


----------------------------------------------------------------------------------------------
--                                      LIBRARY                                             --
----------------------------------------------------------------------------------------------

if (not lib) then
  lib = {}
  function lib:iif(a,b,c) if (a) then return b else return c end end
  function lib:round(a,b) return math.floor(a * (10^(b or 0)) + 0.5) / (10^(b or 0)) end
end


----------------------------------------------------------------------------------------------
--                                HEATING MANAGER FUNCTIONS                                 --
----------------------------------------------------------------------------------------------

local collection, rooms = {}, {}

if (not HeatingManager) then
  
  HeatingManager = {}
  
  function HeatingManager:addAutoAbsence(label, ID, extension)
    if (type(collection[label].aAbsence) ~= "table") then collection[label].aAbsence = {} end
    table.insert(collection[label].aAbsence, {ID = ID, extension = (extension or self.autoAbsence_extension)})
  end
  
  function HeatingManager:addOpening(label, ID, extension)
    if (type(collection[label].opening) ~= "table") then collection[label].opening = {} end
    table.insert(collection[label].opening, {ID = ID, extension = (extension or self.opening_extension)})
  end
  
  function HeatingManager:addDeclaredAbsence(label, ID)
    if type(collection[label].dAbsence ~= "table") then collection[label].dAbsence = {} end
    table.insert(collection[label].dAbsence, {ID = ID})
  end
  
  function HeatingManager:addDeclaredPresence(label, ID)
    if type(collection[label].dPresence ~= "table") then collection[label].dPresence = {} end
    table.insert(collection[label].dPresence, {ID = ID})
  end
  
  function HeatingManager:addZone(label, panel)
    table.insert(rooms, label)
    collection[label] = {label = label, panel = panel}
  end

  function HeatingManager:getLinkedID(d)
    local HMID = json.decode(fibaro:getGlobalValue("HeatingManagerIDs"))
    if (HMID[string.lower(d)] == nil) then
      lib:log("[FATAL ERROR] " .. d .. " was not found !", 4)
      fibaro:abort()
    end
    return HMID[string.lower(d)]
  end

  function HeatingManager:getSetPoint(source)
    if (type(tonumber(source)) == "number") then
      local panel = api.get("/panels/heating?id=" .. source)
      return panel.properties.currentTemperature
    elseif (type(source) == "string") then
      local v = fibaro:getGlobalValue(source)
      return string.match(v, "[0-9]+[,.][0-9]+") or string.match(v, "[0-9]+")
    elseif (type(source) == "table") then
      local v = fibaro:getValue(source[1], "ui." .. source[2] .. ".value")
      return string.match(v, "[0-9]+[,.][0-9]+") or string.match(v, "[0-9]+")
    end
  end

  function HeatingManager:setAutoAbsence(label, reduction, duration, limit, cumulative)
    collection[label].aAbsence.reduction   = reduction   or self.aAbsence_reduction
    collection[label].aAbsence.duration    = duration    or self.aAbsence_duration
    collection[label].aAbsence.limit       = limit       or self.aAbsence_limit
    collection[label].aAbsence.cumulative  = cumulative  or self.aAbsence_cumulative
  end

  function HeatingManager:setDeclaredAbsence(label, cumulative, setpoint)
    collection[label].dAbsence.cumulative  = cumulative  or self.dAbsence_cumulative
    collection[label].dAbsence.setpoint    = setpoint    or self.dAbsence_setpoint
  end
  
  function HeatingManager:setDeclaredPresence(label, cumulative, setpoint)
    collection[label].dPresence.cumulative = cumulative  or self.dPresence_cumulative
    collection[label].dPresence.setpoint   = setpoint    or self.dPresence_setpoint
  end
  
  function HeatingManager:setIdsVariable()
    if (fibaro:getGlobalValue("HeatingManagerIDs") == nil) then api.post("/globalVariables", {name="HeatingManagerIDs", isEnum=0}) end
    local HMID = json.decode(fibaro:getGlobalValue("HeatingManagerIDs"))
    if (tostring(HMID) == "" or tostring(HMID) == "null") then HMID = {manager = 0, panel = 0, controller = 0} end
    if (HMID.panel ~= fibaro:getSelfId()) then
      HMID.panel = fibaro:getSelfID()
      fibaro:setGlobal("HeatingManagerIDs", json.encode(HMID))
    end
  end  

  function HeatingManager:setGDataVariable()
    if not gData then
      gData = {aAbsence = {}}
      for i, _ in ipairs(rooms) do
        gData.aAbsence[i] = {applied = 0, elapsed = os.time()}
      end
    end
  end


----------------------------------------------------------------------------------------------
--                                   MAIN FUNCTION                                          --
----------------------------------------------------------------------------------------------

  function HeatingManager:run()

    for i, label in ipairs(rooms) do

      local item = collection[label]
      local setPoint = self:getSetPoint(item.panel)
      local newTemp = setPoint
      local isAbsent, isOpen, isPresent = false, false, false

  --DECLARED ABSENCE MANAGEMENT---------------------------------------------------------------
  
      if (item.dAbsence ~= nil) then
        local value
        for _, sensor in ipairs(item.dAbsence) do
          if (type(tonumber(sensor.ID[1])) == "number") then
            value = fibaro:getValue(sensor.ID[1], "ui." .. sensor.ID[2] .. ".value")
            isAbsent = (tostring(value) == tostring(sensor.ID[3]))
          elseif (type(sensor.ID[1]) == "string") then
            value = fibaro:getGlobalValue(sensor.ID[1])
            isAbsent = (tostring(value) == tostring(sensor.ID[2]))
          end
          if (isAbsent) and (not (item.dAbsence.cumulative or self.dAsbence_cumulative)) then break end
        end
        if (isAbsent) then
          newTemp = item.dAbsence.setpoint or self.dAbsence_setpoint
        end
      end
  
  --DECLARED PRESENCE MANAGEMENT------------------------------------------------------------
      if not (isAbsent) then
        if (item.dPresence ~= nil) then
          local value
          for _, sensor in ipairs(item.dPresence) do
            if (type(tonumber(sensor.ID[1])) == "number") then
              value = fibaro:getValue(sensor.ID[1], "ui." .. sensor.ID[2] .. ".value")
              isPresent = (tostring(value) == tostring(sensor.ID[3]))
            elseif (type(sensor.ID[1]) == "string") then
              value = fibaro:getGlobalValue(sensor.ID[1])
              isPresent = (tostring(value) == tostring(sensor.ID[2]))
            end
            if (isPresent) and (not (item.dPresence.cumulative or self.dPresence_cumulative)) then break end
          end
          if (isPresent) then
            newTemp = item.dPresence.setpoint or self.dPresence_setpoint
          end
        end
      end
      
  --AUTOMATIC ABSENCE MANAGEMENT--------------------------------------------------------------
      if not (isAbsent) and not (isPresent) then
        if (item.aAbsence ~= nil) then
          local value, modificationTime
          for _, sensor in ipairs(item.aAbsence) do
            if (type(sensor.ID) == "table") then
              if (type(tonumber(sensor.ID[1]))) == "number" then
                value, modificationTime = fibaro:get(sensor.ID[1], "ui." .. sensor.ID[2] .. ".value")
                isAbsent = (tostring(value) == tostring(sensor.ID[3]))
              elseif (type(sensor.ID[1]) == "string") then
                value, modificationTime = fibaro:getGlobal(sensor.ID[1])
                isAbsent = (tostring(value) == tostring(sensor.ID[2]))
              end
            elseif (type(tonumber(sensor.ID)) == "number") then
              value, modificationTime = fibaro:get(sensor.ID, "value")
              isAbsent = (tonumber(value) == 0)
            end
            isAbsent = (isAbsent) and ((os.time() - modificationTime) >= ((sensor.extension or self.autoAbsence_extension) * 60))
            if (isAbsent) and (not (item.aAbsence.cumulative or self.autoAbsence_cumulative)) then break end
          end
          if (isAbsent) then
            local duration  = item.aAbsence.duration or self.autoAbsence_duration
            local reduction = item.aAbsence.reduction or self.autoAbsence_reduction
            local interval  = lib:round((duration * 60) / ((reduction - self.aAbsence_setdrop)/self.aAbsence_setdrop))
            if (gData.aAbsence[i].applied < reduction) then
              if ((os.time() - gData.aAbsence[i].elapsed) >= interval) then
                gData.aAbsence[i].applied = gData.aAbsence[i].applied + self.aAbsence_setdrop
                gData.aAbsence[i].elapsed = os.time()
              end
            end
            newTemp = lib:iif((setPoint - gData.aAbsence[i].applied) < (item.aAbsence.limit or self.autoAbsence_limit), (item.aAbsence.limit or self.autoAbsence_limit), setPoint - gData.aAbsence[i].applied)
          else
            gData.aAbsence[i].applied = 0
            gData.aAbsence[i].elapsed = os.time()
          end
        end
      end

  --OPENING MANAGEMENT------------------------------------------------------------------------
      if (item.opening ~= nil) then
        local value, modificationTime
        for _, sensor in ipairs(item.opening) do
          if (type(sensor.ID) == "table") then
            if (type(tonumber(sensor.ID[1]))) == "number" then
              value, modificationTime = fibaro:get(sensor.ID[1], "ui." .. sensor.ID[2] .. ".value")
              isOpen = (tostring(value) == tostring(sensor.ID[3]))
            elseif (type(sensor.ID[1]) == "string") then
              value, modificationTime = fibaro:getGlobal(sensor.ID[1])
              isOpen = (tostring(value) == tostring(sensor.ID[2]))
            end
          elseif (type(tonumber(sensor.ID)) == "number") then
            value, modificationTime = fibaro:get(sensor.ID, "value")
            isOpen = (tonumber(value) == 1)
          end
          isOpen = (isOpen) and ((os.time() - modificationTime) >= ((sensor.extension or self.opening_extension) * 60))
          if (isOpen) and (not (item.opening.cumulative or self.opening_cumulative)) then break end
        end
        if (isOpen) then
          newTemp = 0
        end
      end
  
  --UPDATING VIRTUAL DEVICE-------------------------------------------------------------------
      if (newTemp ~= tonumber(string.match(fibaro:getValue(fibaro:getSelfId(), "ui.lbl".. item.label ..".value"), "[0-9.]+"))) then
        fibaro:call(fibaro:getSelfId(),"setProperty","ui.lbl" .. item.label .. ".value", newTemp .. " °C")
      end

    end
  end

end

----------------------------------------------------------------------------------------------
--                                     CONFIGURATION                                        --
----------------------------------------------------------------------------------------------

--Defaults parameters for opening management
HeatingManager.opening_extension      = 0        -- minutes since door/window sensor value = 0 before updating setpoint
HeatingManager.opening_cumulative     = false    -- if true, all sensors value must be 0; if false, only one is enough

--Defaults parameters for automatic absence management
HeatingManager.aAbsence_extension     = 25       -- minutes since motion sensor value = 0 before updating setpoint
HeatingManager.aAbsence_reduction     = 1        -- objective of setpoint drop
HeatingManager.aAbsence_duration      = 60       -- temporal smoothing in minutes of setpoint drop
HeatingManager.aAbsence_stepdrop      = 0.5      -- step of setpoint drop for absence management
HeatingManager.aAbsence_limit         = 18       -- minimum setpoint
HeatingManager.aAbsence_cumulative    = true     -- if true, all sensors value must be 0; if false, only one is enough

--Defaults parameters for declared absence
HeatingManager.dAbsence_cumulative    = true     -- if true, all sensors value must be 0; if false, only one is enough
HeatingManager.dAbsence_setpoint      = 8        -- setpoint in case of declared absence

--Defaults parameters for declared presence
HeatingManager.dPresence_cumulative   = true     -- if true, all sensors value must be 0; if false, only one is enough
HeatingManager.dPresence_setpoint     = 22       -- setpoint in case of declared presence
  

--[[
    HeatingManager:addZone(label, panel)

      + label      = label ID without prefix 'lbl'
      + panel      = heating panel ID
                     or {ID of virtual device, ID of setpoint label}
                     or global variable name
    ____________________________________________________________________________________________

    HeatingManager:addAutoAbsence(label, id, [extension])

      + label      = label ID without prefix 'lbl'
      + id         = ID of motion sensor 
                     or {ID of virtual device, name of label, value if absence}
                     or {name of global variable, value if absence}
      + extension  = minutes since motion sensor value = 0 before updating setpoint
    ____________________________________________________________________________________________

    HeatingManager:setAutoAbsence(label, [reduction], [duration], [limit], [cumulative])

      + label      = label ID without prefix 'lbl'
      + reduction  = objective of setpoint drop
      + duration   = temporal smoothing in minutes of setpoint drop
      + limit      = minimum setpoint
      + cumulative = if true, all sensors value must be 0; if false, only one is enough
    ____________________________________________________________________________________________

    HeatingManager:addOpening(label, id, [extension])

      + label      = label ID without prefix 'lbl'
      + id         = ID of opening sensor 
                     or {ID of virtual device, name of label, value if absence}
                     or {name of global variable, value if absence}
      + extension  = minutes since door/window sensor value = 0 before updating setpoint
    ____________________________________________________________________________________________

    HeatingManager:setOpening([cumulative])

      + cumulative = if true, all sensors value must be 0; if false, only one is enough
    ____________________________________________________________________________________________

    HeatingManager:addDeclaredAbsence(label, id)

      + label      = label ID without prefix 'lbl'
      + id         = {ID of virtual device, name of label, value if absence}
                     or {name of global variable, value if absence}
    ____________________________________________________________________________________________

    HeatingManager:setDeclaredAbsence(label, [cumulative], [setpoint])

      + label      = label ID without prefix 'lbl'
      + cumulative = if true, all sensors value must be 0; if false, only one is enough
      + setpoint   = setpoint in case of declared absence
    ____________________________________________________________________________________________

    HeatingManager:addDeclaredPresence(label, id)

      + label      = label ID without prefix 'lbl'
      + id         = {ID of virtual device, name of label, value if presence}
                     or {name of global variable, value if presence}
    ____________________________________________________________________________________________

    HeatingManager:setDeclaredPresence(label, [cumulative], [setpoint])

      + label      = label ID without prefix 'lbl'
      + cumulative = if true, all sensors value must be 0; if false, only one is enough
      + setpoint   = setpoint in case of declared presence

--]]

if (not jT) then jT = json.decode(fibaro:getGlobalValue("HomeTable")) end --Delete this line if global variable HomeTable is not used

if (not Zone) then
  Zone = {}
  Zone.Jour = "lblZoneJour"
  Zone.Nuit = "lblZoneNuit"
  Zone.Bain = "lblZoneBain"
end

--Salle de Bains
HeatingManager:addZone("SalledeBains", lib:iif(fibaro:getValue(jT.SalledeBains.Porte, "value") == "0", {jT.VD.HeatingProvider, Zone.Bain}, {jT.VD.HeatingProvider, Zone.Jour}))
HeatingManager:addAutoAbsence("SalledeBains", jT.SalledeBains.Mouvement)

--Salon TV
HeatingManager:addZone("SalonTV", lib:iif(fibaro:getGlobalValue("SalonTV_mode"), {jT.VD.HeatingProvider, Zone.Jour}, {jT.VD.HeatingProvider, Zone.Nuit}))
HeatingManager:addAutoAbsence("SalonTV", jT.SalonTV.Mouvement)

--Séjour
HeatingManager:addZone("Sejour", {jT.VD.HeatingProvider, Zone.Jour})
HeatingManager:addAutoAbsence("Sejour", jT.Sejour.MouvementSalon)
HeatingManager:addAutoAbsence("Sejour", jT.Sejour.MouvementRepas)
HeatingManager:addOpening("Sejour", jT.Sejour.BaieD, 3)

--Chambre
HeatingManager:addZone("Chambre", {jT.VD.HeatingProvider, Zone.Nuit})
HeatingManager:addAutoAbsence("Chambre", jT.Chambre.Mouvement)
HeatingManager:addOpening("Chambre", jT.Dressing.Fenetre)

--Chambre Droite
HeatingManager:addZone("ChambreDroite", {jT.VD.HeatingProvider, Zone.Nuit})
HeatingManager:addAutoAbsence("ChambreDroite", jT.ChambreDroite.Mouvement)
HeatingManager:addOpening("ChambreDroite", jT.ChambreDroite.Velux)

--Chambre Gauche
HeatingManager:addZone("ChambreGauche", {jT.VD.HeatingProvider, Zone.Nuit})
HeatingManager:addAutoAbsence("ChambreGauche", jT.ChambreGauche.Mouvement)
HeatingManager:addOpening("ChambreGauche", jT.ChambreGauche.Velux)

--Cuisine
HeatingManager:addZone("Cuisine", {jT.VD.HeatingProvider, Zone.Jour})
HeatingManager:addAutoAbsence("Cuisine", jT.Cuisine.Mouvement)


----------------------------------------------------------------------------------------------
--                                         LOOP                                             --
----------------------------------------------------------------------------------------------

HeatingManager:setGDataVariable()
HeatingManager:setIdsVariable()
HeatingManager:run()

 

 

3. INSTALLATION DU CONTROLEUR

 

Il faut maintenant installer le module virtuel permettant de sélectionner et configurer le mode de régulation du chauffage   >>>>  Heating_Controller.vfib

 

5a0ccf616050f_HeatingController.thumb.png.055caf270d8de13cbe60f2c183c395e3.png

 

Mode permet de sélectionner le mode de régulation du chauffage : Proportionnel + Ubat ou Hysteresis. Le premier des deux est repris du mode de régulation utilisé nativement par la centrale domotique eedomus, et fait l'objet d'une présentation ICI.

 

En mode Proportionnel + Ubat, les paramètre disponibles sont :

  • Le coefficient proportionnel kP utilisé par défaut, qui sert également de point de départ à la détermination automatique du coefficient le plus pertinent pour chaque pièce
  • Le mode d'apprentissage, qui laisse le programme modifier le coefficient proportionnel pour l'adapter au mieux à chaque pièce
  • Le coefficient de déperditions thermiques kT utilisé par défaut
  • La durée du cycle de chauffage
  • La durée minimale d'une période de chauffe

 

En mode Hysteresis, les paramètres disponibles sont :

  • L'hysteresis proprement dite
  • Le cycle suivant lequel la température courant va être comparée à la température de consigne

 

C'est également là que vous pouvez choisir d'afficher plus ou moins d'informations dans la fenêtre de debug de la scène. Le bouton Apply Change relance la scène avec les paramètres modifiés.

L'étiquette Automatic kP est utilisée par la scène pour enregistrer les données lorsque le mode d'apprentissage est activé, ma confiance dans les variables globales n'étant pas suffisante pour envisager d'y stocker ce type de données destinées à avoir un caractère de permanence. Le bouton Reset Learning force la scène à recommencer l'apprentissage des coefficients proportionnels.

 

4. CREATION DE LA SCENE

 

Il faut maintenant créer une nouvelle scène en mode Lua et y coller le code   >>>>   Heating Manager v. 2.0.lua ou ci-dessous :

 

Révélation

--[[
%% autostart
--]]

--[[
    Heating Manager v. 2.0 (2017) by OJC

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
--]]

--[[
    Versions Notes

    2.0   Add Hysteresis Mode
          Add Controller virtual device
          Improve opening and absence management
          Code optimized for execution in single scene instance
    1.3   Add the possibility of using virtual device as heater controller
          Add the possibilité of using virtual device or global variable as setpoint provider
    1.2   New regulation code, inspired by eedomus box
          Add the possibility of using virtual temperature sondes
          Configuration simplification for both Manager and Panel
    1.1   Add motion sensor management
          Improve door/window sensor management
          Code optimization
    1.0   Initial release
--]]


----------------------------------------------------------------------------------------------
--                                      LIBRARY                                             --
----------------------------------------------------------------------------------------------

local lib = {}
function lib:arrows(a) local b = string.gsub(a, "► ", "") b = string.gsub(b, " ◄", "") return b end
function lib:ddigit(a) if (a < 10) then return "0"..a else return a end end
function lib:iif(a,b,c) if (a) then return b else return c end end
function lib:log(a,b) print('<span style="color:' .. ({"YellowGreen", "SkyBlue", "NavajoWhite", "Tomato"})[b or 1] .. ';">' .. a .. '</span>') end
function lib:round(a,b) return math.floor(a * (10^(b or 0)) + 0.5) / (10^(b or 0)) end
function lib:temp2num(a) return string.match(a, "[0-9]+[,.][0-9]+") or string.match(a, "[0-9]+") end
function lib:trim(a) local a = a for i, j in ipairs({"Ç","ç","[-èéêë']+","[-ÈÉÊË']+","[-àáâãäå']+","[-@ÀÁÂÃÄÅ']+","[-ìíîï']+","[-ÌÍÎÏ']+","[-ðòóôõö']+","[-ÒÓÔÕÖ']+","[-ùúûü']+","[-ÙÚÛÜ']+","[-ýÿ']+","Ý","%W"}) do a = string.gsub(a, j, ({"C","c","e","E","a","A","i","I","o","O","u","U","y","Y",""})[i]) end return a end


----------------------------------------------------------------------------------------------
--                              VARIABLES AND FUNCTIONS                                     --
----------------------------------------------------------------------------------------------

local HeatingManager, collection, outdoorSonde = {}, {}, nil
local modePT, hysteresis, default_kP, auto_kP, default_kT, cycle, minCycle, logInfo

local params_kP = {}
params_kP.step = 0.1
params_kP.average = 10
params_kP.start = false
params_kP.learning = {}

function HeatingManager:addHeater(idHeater, idSonde, localP, localT)
  local heater, on, off
  if type(idHeater) == "table" then
    heater, on, off = idHeater[1], idHeater[2], idHeater[3]
  else heater, on, off = idHeater, 0, 0 end
  self:getCoefficients()
  if (params_kP.start) then params_kP.kP[tostring(heater)] = {default_kP, 0, 0} end
  table.insert(collection, {
      heater = heater,
      name = fibaro:getName(heater),
      onOff = lib:iif(on ~= 0, {on, off}, 0),
      sonde = idSonde or self:getTemperatureSonde(fibaro:getRoomID(heater)),
      kP = localP or lib:iif(auto_kP, params_kP.kP[tostring(heater)][1], default_kP),
      kT = localT or default_kT,
      panel = "lbl" .. lib:trim(fibaro:getRoomNameByDeviceID(heater)),
      room = fibaro:getRoomNameByDeviceID(heater),
      stopTime = 0,
      lastSetpoint = 0})
  if (params_kP.start) then fibaro:call(self:getLinkedID("Controller"),"setProperty","ui.lblkP.value", json.encode(params_kP.kP)) end
end

function HeatingManager:getCoefficients()
  params_kP.kP = json.decode(fibaro:getValue(self:getLinkedID("Controller"), "ui.lblkP.value"))
  if (params_kP.kP == nil) then
    params_kP.start = true
    params_kP.kP = {}
  end
end

function HeatingManager:getCommand(item)
  local current, setpoint, outdoor, P, hC = 0, 0, 0, 0, 0
  current, setpoint, outdoor = self:getTemperatures(item)  
  if (current == nil) then
    lib:log("[ERROR] " .. item.room .. " have no Temperature Sonde !", 4)
    return 0, 0
  end
  if (logInfo) then lib:log("[INFO] Current = " .. current .. " °C - Setpoint = " .. setpoint .." °C - Outdoor = " .. outdoor .. " °C") end
  P = tonumber((math.min((item.kP * (setpoint - current)) + (item.kT * (setpoint - outdoor)), 100)) / 100)
  hC = math.max(math.min(minCycle, cycle), P * cycle) * 60
  item.lastSetpoint = setpoint
  return P, hC
end

function HeatingManager:getLinkedID(d)
  local HMID = json.decode(fibaro:getGlobalValue("HeatingManagerIDs"))
  if (HMID[string.lower(d)] == nil) then
    lib:log("[FATAL ERROR] " .. d .. " was not found !", 4)
    fibaro:abort()
  end
  return HMID[string.lower(d)]
end

function HeatingManager:getReadableTime(t)
  local d = ""
  local h = math.floor(t/3600)
  local m = math.floor(t/60 - (h * 60))
  local s = math.floor(t - h * 3600 - m * 60)
  if (h > 0) then d = d .. lib:ddigit(h) .. " h " end
  if (m > 0) then d = d .. lib:iif(h ~= 0, lib:ddigit(m), m) .. " m " end
  if (s > 0) then d = d .. lib:iif(m ~= 0, lib:ddigit(s), s) .. " s" end
  return d
end

function HeatingManager:getTemperatures(item)
  local c, s, o = 0, 0, 0
  if (type(item.sonde) == "number") then
    c = fibaro:getValue(item.sonde, "value")
  elseif (type(item.sonde) == "table") then
    c = lib:temp2num(fibaro:getValue(item.sonde[1], "ui." .. item.sonde[2] .. ".value"))    
  end
  s = lib:temp2num(fibaro:getValue(self:getLinkedID("Panel"), "ui." .. item.panel .. ".value"))
  if (outdoorSonde == nil) then
    o = api.get("/weather").Temperature
  elseif (type(outdoorSonde) == "number") then
    o = fibaro:getValue(outdoorSonde, "value")
  elseif (type(outdoorSonde) == "table") then
    o = lib:temp2num(fibaro:getValue(outdoorSonde[1], "ui." .. outdoorSonde[2] .. ".value"))
  end
  return c, s, o or s
end

function HeatingManager:getTemperatureSonde(ID)
  local r = api.get("/rooms?id=" .. ID)
  return r.defaultSensors.temperature
end

function HeatingManager:setCoefficients(item)
  local current, setpoint, heater = 0, 0, tostring(item.heater)
  current, setpoint = self:getTemperatures(item)
  if (setpoint - current == 0) then return nil end
  if (logInfo) then lib:log("[INFO] Current T° is " .. current .. " °C and Setpoint is " .. setpoint .." °C") end
  if (tonumber(params_kP.kP[heater][3]) == tonumber(params_kP.average)) then
    local average = lib:round(params_kP.kP[heater][2] / params_kP.kP[heater][3], 1)
    if (math.abs(average) > 0.1 and math.abs(average) < 1) then params_kP.kP[heater][1] = item.kP - (params_kP.step * (average/math.abs(average))) end
    params_kP.kP[heater][2] = 0
    params_kP.kP[heater][3] = 0
    if (logInfo) then lib:log("[INFO] Average error is " .. tostring(average) .. " °C, so kP is " .. lib:iif(tonumber(average) >  0, "decrease", "increase") .. " from " .. item.kP .. " to " .. params_kP.kP[heater][1]) end
    item.kP = params_kP.kP[heater][1]
  else
    params_kP.kP[heater][2] = params_kP.kP[heater][2] + (current - setpoint)
    params_kP.kP[heater][3] = params_kP.kP[heater][3] + 1
  end
  fibaro:call(self:getLinkedID("Controller"),"setProperty","ui.lblkP.value", json.encode(params_kP.kP))
  params_kP.learning[heater] = false
end

function HeatingManager:setIdsVariable()
  if (fibaro:getGlobalValue("HeatingPanelID") ~= nil) then api.delete("/globalVariables/HeatingPanelID") end
  if (fibaro:getGlobalValue("HeatingManagerGV") ~= nil) then api.delete("/globalVariables/HeatingManagerGV") end
  if (fibaro:getGlobalValue("HeatingManagerIDs") == nil) then api.post("/globalVariables", {name="HeatingManagerIDs", isEnum=0}) end
  local HMID = json.decode(fibaro:getGlobalValue("HeatingManagerIDs"))
  if (tostring(HMID) == "" or tostring(HMID) == "null") then HMID = {manager = 0, panel = 0, controller = 0} end
  if (HMID.manager ~= __fibaroSceneId) then
    HMID.manager = __fibaroSceneId
    fibaro:setGlobal("HeatingManagerIDs", json.encode(HMID))
  end
end

function HeatingManager:setOutdoorSonde(idSonde)
  outdoorSonde = idSonde
end

function HeatingManager:startCycle(item, P, hC, isOverride)
  if (auto_kP and params_kP.learning[tostring(item.heater)]) then self:setCoefficients(item) end
  item.stopTime = os.time() + hC
  if (P > 0) then
    self:startHeater(item, hC)
    if (P < 1) then 
      params_kP.learning[tostring(item.heater)] = not (isOverride)
      setTimeout(function() if (os.time() >= item.stopTime) then self:stopHeater(item) end end, hC * 1000)
    end
  else
    if (logInfo) then lib:log("[INFO] Heating is not required in ".. item.room .. "( " .. item.name .. ")") end
    self:stopHeater(item)
  end
end

function HeatingManager:startHeater(item, hC)
  if (hC ~= nil) then hC = " for " .. self:getReadableTime(hC) else hC = "" end
  lib:log("[ACTION] Start heating in " .. item.room .. " (" .. item.name .. ")" .. hC, 2)
  if type(item.onOff) == "number" then
    if fibaro:getValue(item.heater, "value") == "0" then fibaro:call(item.heater, "turnOn") end
  else
    fibaro:call(item.heater, "pressButton", item.onOff[1])
  end
end

function HeatingManager:stopHeater(item)
  if type(item.onOff) == "number" then
    if fibaro:getValue(item.heater, "value") ~= "0" then
      lib:log("[ACTION] Stop heating in " .. item.room .. " (" .. item.name .. ")", 2)
      fibaro:call(item.heater, "turnOff") 
    end
  else
    lib:log("[ACTION] Stop heating in " .. item.room .. " (" .. item.name .. ")", 2)
    fibaro:call(item.heater, "pressButton", item.onOff[2])
  end
  item.stopTime = 0
end


----------------------------------------------------------------------------------------------
--                                    MAIN FUNCTIONS                                        --
----------------------------------------------------------------------------------------------

function HeatingManager:StartHeatingManager()
  local P, hC = 0, 0
  for _, item in ipairs(collection) do
    if (logInfo) then lib:log("[INFO] Checking " .. item.room .. "( " .. item.name .. ")...") end
    P, hC = self:getCommand(item)
    self:startCycle(item, P, hC)
  end
  setTimeout(function() self:StartHeatingManager() end, cycle * 60 * 1000)
end

function HeatingManager:StartHysteresisManager()
  for _, item in ipairs(collection) do
    if (logInfo) then lib:log("[INFO] Checking " .. item.room .. "( " .. item.name .. ")...") end
    local current, setpoint = self:getTemperatures(item)
    if (logInfo) then lib:log("[INFO] Current = " .. current .. " °C - Setpoint = " .. setpoint .." °C") end
    if (setpoint - current >= tonumber(hysteresis)) then
      self:startHeater(item)
    else
      self:stopHeater(item)
    end
  end
  setTimeout(function() self:StartHysteresisManager() end, cycle * 1000)
end

function HeatingManager:StartOverrideManager()
  local curSetpoint, P, hC = 0, 0, 0
  for _, item in ipairs(collection) do
    curSetpoint = lib:temp2num(fibaro:getValue(self:getLinkedID("Panel"), "ui." .. item.panel .. ".value"))
    if curSetpoint ~= item.lastSetpoint then
      lib:log("[INFO] Setpoint has changed for " .. item.room)
      P, hC = self:getCommand(item)
      self:startCycle(item, P, hC, true)
    end
  end
  setTimeout(function() self:StartOverrideManager() end, 1000)
end


----------------------------------------------------------------------------------------------
--                                AUTOMATIC CONFIGURATION                                   --
----------------------------------------------------------------------------------------------

HeatingManager:setIdsVariable()

modePT     =  lib:iif(lib:arrows(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblMode.value")) ~= "Hysteresis", true, false)
hysteresis = tonumber(lib:arrows(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblHysteresis.value")))
default_kP = tonumber(lib:arrows(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblDefaultkP.value")))
auto_kP    =  lib:iif(lib:arrows(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblLearningMode.value")) == "YES", true, false)
default_kT = tonumber(lib:arrows(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblDefaultkT.value")))
cycle      = tonumber(lib:arrows(string.gsub(string.gsub(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblCycle.value"), " sec", ""), " min", "")))
minCycle   = tonumber(lib:arrows(string.gsub(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblMinCycle.value"), " min", "")))
logInfo    =  lib:iif(lib:arrows(fibaro:getValue(HeatingManager:getLinkedID("Controller"), "ui.lblLogInfo.value")) == "YES", true, false)


----------------------------------------------------------------------------------------------
--                                 MANUAL CONFIGURATION                                     --
----------------------------------------------------------------------------------------------

--[[
    HeatingManager:addHeater(idHeater, [idSonde], [localP], [localT])

      + idHeater = ID of heater control device or {ID of heater virtuel device, order number of the ON button, order number of the OFF button}
      + idSonde  = ID of temperature sonde or {ID of temperature virtual device, ID of temperature label}
                   Optional : if not set, room temperature sonde is used
      + localP   = Optional : if not set, automatic mode is used or, if disabled, default_kP
      + localT   = Optional : if not set, default_kT is used

    HeatingManager:setOutdoorSonde(idSonde)
    
      + idSonde = ID of outdoor temperature sonde or {ID of virtual outdoor temperature sonde, ID of outdoor temperature label}
                  Optional : If not set, outdoor temperature provided by weather plugin is used
--]]


local jT = json.decode(fibaro:getGlobalValue("HomeTable")) --Delete this line if global variable HomeTable is not used

HeatingManager:addHeater(jT.SalonTV.Radiateur)
HeatingManager:addHeater(jT.Sejour.RadiateurSalon)
HeatingManager:addHeater(jT.Sejour.RadiateurRepas, jT.Sejour.TemperatureRepas)
HeatingManager:addHeater(jT.Cuisine.Radiateur)
HeatingManager:addHeater(jT.SalledeBains.Radiateur)
HeatingManager:addHeater(jT.Chambre.Radiateur)
HeatingManager:addHeater(jT.ChambreDroite.Radiateur)
HeatingManager:addHeater(jT.ChambreGauche.Radiateur)

--HeatingManager:setOutdoorSonde()


----------------------------------------------------------------------------------------------
--                                       BOOT CODE                                          --
----------------------------------------------------------------------------------------------

if (fibaro:countScenes() > 1) then
  lib:log("[FATAL ERROR] Already running ! Please kill previous instance first.", 4)
  fibaro:abort()
end

lib:log("Heating Manager v. 2.0 (2017) by OJC", 3)

if (modePT) then
  lib:log("[MODE] Proportional + Ubat Mode selected", 2)
  HeatingManager:StartHeatingManager()
  HeatingManager:StartOverrideManager()
else
  lib:log("[MODE] Hysteresis Mode selected", 2)
  HeatingManager:StartHysteresisManager()
end

 

 

La configuration des dispositifs de chauffage se fait avec la fonction HeatingManager:addHeater :

    HeatingManager:addHeater(idHeater, [idSonde], [localP], [localT])

      + idHeater = ID du module permettant de commander le radiateur
                   ou {ID du module virtual, n° du bouton ON, n° du bouton OFF}
      + idSonde  = ID de la sonde de température à laquelle est asservie le radiateur
                   ou {ID du module virtuel, ID de l''étiquette contenant la température}
                   [Paramètre optionnel à défaut duquel c''est la sonde de température principale de la pièce qui est utilisée]
      + localP   = Paramètre optionnel à défaut duquel la valeur définie par défaut ou automatiquement selon le cas est utilisée
      + localT   = Paramètre optionnel à défaut duquel la valeur définie par défaut est utilisée

 

Si vous disposez d'une sonde de température extérieure, elle peut être déclarée en utilisant la fonction HeatingManager:setOutdoorSonde(ID Device or {ID VirtualDevice, ID Label}), donc soit un module de type 'com.fibaro.temperatureSensor', soit un module virtuel.

 

Enregistrez la scène qui devrait démarrer automatiquement puisqu'elle est en %% autostart.

 

L'installation est terminée. :D

 

_ _ _

 

ICONES

 

5a00ee187e067_SetpointProviderforHeatingManager.png.811f86b7d309ea30aec8d34d17e1e1bf.png59fcfe5ec1ecc_PanelforHeatingManager.png.7865cb73774b1eb200ebff4a72deeae0.png106193-3d-glossy-orange-orb-icon-signs-nuclear1.png.bc84a63b440da9c1eafa5f2c4ee31748.png59fcfe5e6db74_HeatingManager.png.cda96eb4b25e388bf8dfee352bb06c8c.png

+ Provider + Panel + Controller + Manager +      

 

 

 

 

 

 

Modifié par OJC
Publication de la v. 2.0

Partager ce message


Lien à poster
Partager sur d’autres sites

Bonsoir,

 

On ne t'arrete plus ;-)

Bravo, sympa, gros boulot ;-)

Partager ce message


Lien à poster
Partager sur d’autres sites

@pepite  Merci ! Faut bien que je refasse mon installation... :)

 

Je suis en train d'implémenter la gestion des absences via le détecteur de mouvement...

Partager ce message


Lien à poster
Partager sur d’autres sites

Voilà la version 1.1.0, avec la gestion des absences et un code revu de fond en comble :).

 

Maintenant, j'attaque la gestion du PID proprement dit !!

Partager ce message


Lien à poster
Partager sur d’autres sites

Encore une excellente initiative ... Bravo.

 

Juste encore une remarque ... j'insiste ... sur la manière d'aller récupérer le VD.

 

Ta méthode getDeviceIDbyName parcours tous les devices afin de trouver le bon alors qu'il te suffirait de faire ainsi :

 

function getDeviceIDbyName(deviceName)
  local device = api.get("/devices?name="..deviceName.."&enabled=true&visible=true")
  if (device) then 
	  return device.id 
  end
  log("Critical Error : Virtual Device Dawn & Dusk Panel not found !","Tomato",true)
  fibaro:abort()
end

C'est juste un peu plus performant.

 

Sinon, il existe : fibaro:getRoomNameByDeviceID(id_du_device)

 

D'ailleurs ... ne serait-ce pas un copier/coller restant "Critical Error : Virtual Device Dawn & Dusk Panel not found !" :2:

 

Je me permet ces petits commentaires car vu le niveau de ton code, manque pas grand chose pour qu'il soit parfait :60:

Partager ce message


Lien à poster
Partager sur d’autres sites

Lol, oublie mon message, je l'ai posté pile poil avant ta révision de code.

Partager ce message


Lien à poster
Partager sur d’autres sites

Eh ben, belle optimisation ;-)

 

J'ai pas importé le VD, mais est-il possible de passer le panneau de chauffage en mode manuel ou en mode vacances et inversement depuis le VD ? ou

 

Simple suggestion, enfin question

 

Et si la sonde de temperature est une sonde hors HC2 ? ;-)

Modifié par pepite

Partager ce message


Lien à poster
Partager sur d’autres sites

Merci :)

 

Je n'ai pas prévu de pouvoir passer le panneau de chauffage en mode manuel ou vacances depuis le Module Virtuel puisqu'on peut le faire facilement depuis l'app Android ou iOS. Même si ce ne doit pas être bien compliqué à rajouter, je n'en vois pas trop l'intérêt, du coup...

 

En l'état, le programme ne fonctionne qu'avec une sonde de température ZWave présente comme module dans la HC2. Mais ce n'est pas très compliqué non plus de rajouter une option pour qu'il aille la chercher ailleurs que dans le value d'un module (ou qu'il le comprenne tout seul en fonction du paramétrage de la sonde), à condition cependant qu'elle soit enregistrée quelque part dans la HC2... Parce que préconfigurer le programme en fonction de toutes les API des box/sondes les plus courantes... Outre le fait que je n'en ai pas l'utilité, ça va vite devenir indigeste !

Le plus simple serait d'envisager deux cas :

> La température est stockée dans un Module Virtuel, donc il suffit de connaître l'ID du module et celui de l'étiquette pour avoir une sonde 'virtuelle'

> La température est stockée dans une Variable Globale, il suffit de connaître le nom (voire plus si les températures sont stockées sous forme de table 'jsonée'

Comment font ceux qui utilisent des sondes non ZWave ? Plutôt Module Virtuel ou plutôt Variable Globale ?

Modifié par OJC

Partager ce message


Lien à poster
Partager sur d’autres sites

Pour ce qui est du passage en mode vacances ou manuel, l'intérêt est de pouvoir à partir d'une autre scène ouautre VD qui gere ton absence de pouvoir aller cliquer sur le bouton automatiquement en fonction des conditions ;-)

 

Exemple : avec GEA, je pars en vacances ou en WE pendant la periode hivernale, en fonction des conditions, j'appuie sur le bouton vacances, lorsque je reviens, j'appuie sur le bouton ON ;-)....

J'ai des invités..je passe en manuel ;-)

 

Pour les sondes exterieures, je dirais :

 - VD + VG avec affchage des valeurs dans les etiquettes

 - VD + affichage des valeurs directement dans les étiquettes, les étiquettes ayant la même étendue que les Variables globales en moins risqué ;-)

Partager ce message


Lien à poster
Partager sur d’autres sites

Sur le premier point, j'ai prévu de faire le pendant de la gestion d'absence en permettant d'overrider la température de consigne du panneau de chauffage en cas de présence dans la pièce, soit détectée par un détecteur de mouvement, soit affirmée depuis un autre Module Virtuel via une Variable Globale (parce que Madame a froid quand elle reste sans bouger à regarder un film :mellow:). Je peux du coup faire en sorte qu'une absence prolongée soit notifiée via la même Variable Globale pour passer les panneaux de chauffage en mode Vacances. Mais je n'ai pas très envie d'un Module Virtuel faisant doublon avec le natif Fibaro, surtout avec les limites en terme d'IHM.

Et puis GEA permet de modifier une Variable Globale sur conditions :P

 

Sur le deuxième point, je mets dans le cahier des charges la possibilité de spécifier soit l'ID d'un module, soit une table avec l'ID du Module Virtuel et l'ID de l'étiquette, ex. {12, "lblSejour"}, la fonction type() permettant de faire le distingo et de gérer la suite ;)

Modifié par OJC

Partager ce message


Lien à poster
Partager sur d’autres sites

Très bon e idée ;-) le WAF toujours le WAF on en est tous là ;-)

Je ne pensais pas à un doublon, par exemple avec GEA, scène unique qui gère beaucoup beaucoup de choses ;-)

 

Oui type tu peux décider ;-)

Partager ce message


Lien à poster
Partager sur d’autres sites

Pour la régulation, je crois que je vais pomper l'algorithme de chauffage de la box eedomus, qui m'a l'air plutôt bien pensé en plus d'être d'une simplicité absolue...

 

Y aurait-il des retours d'expérience sur l'efficacité de cet algorithme ?

Partager ce message


Lien à poster
Partager sur d’autres sites

ce sujet m'intéresse énormément étant donnée que j'ai un plancher chauffant électrique avec plusieurs zones, actuellement j'utilise des sonde nethamo+IPX800 qui commande les fil pilote (ON-OFF) j'ai conservé les ancien thermostat qui me servent uniquement de commande et j'ai repris un VD qui fait thermostat mais uniquement ON-OFF

Partager ce message


Lien à poster
Partager sur d’autres sites

Hum, mais cela me plait beaucoup :)

On peut avoir un visuel du VD du coup ?

Partager ce message


Lien à poster
Partager sur d’autres sites

@Nico Je mettrais une image à la prochaine mise à jour. Tu verras toutefois que très basique, juste des étiquettes avec le nom de la pièce d'un côté et la température de consigne de l'autre...

Partager ce message


Lien à poster
Partager sur d’autres sites

Mais je croyais qu'on pouvait changer la consigne directement depuis le VD ?

Partager ce message


Lien à poster
Partager sur d’autres sites

Non, pour modifier la consigne, il faut aller dans le panneau de chauffage ou avec l'application Android / iPhone dans Température/Zones/Manuel. Je n'ai pas fait un doublon avec ce qui existe déjà nativement.

 

Ce que fait le Module Virtuel, c'est de modifier automatiquement la consigne en cas d'ouverture/fermeture d'un ouvrant et en cas d'absence, en fonction du paramétrage.

Partager ce message


Lien à poster
Partager sur d’autres sites

Et voici la dernière mouture, avec au menu un algorithme pour la régulation du chauffage qui tient bien la route et qui est celui proposé nativement par la centrale domotique eedomus.

J'ai également revu la manière de configurer le module virtuel et la scène, je commençais à me noyer dans ma variable de type table !!

Et puis les icônes :)

Partager ce message


Lien à poster
Partager sur d’autres sites

bonjour OJC,

lorsque je fais un début sur le module virtuel j'ai cette erreur:[ERROR] 08:57:05: line 209: bad argument #1 to 'decode' (string expected, got nil)

alors qu'il n'a rien

Partager ce message


Lien à poster
Partager sur d’autres sites

pour la commande du radiateur j'utilise un module virtuel avec 2 bouton ON-OFF, est ce possible d'utiliser ta scène ou quelle motif faire?.

Sino bravo pour le travail:60:

Partager ce message


Lien à poster
Partager sur d’autres sites

@iman Il faut supprimer la ligne jT = json.decode(fibaro:getGlobalValue("HomeTable")) située ~ ligne 208, qui est liée à la configuration utilisée en exemple, dans laquelle les ID de tous les modules sont stockés dans une variable globale pour faciliter leur utilisation dans les scripts.

 

Pour la commande de ton radiateur, voir post suivant.

Modifié par OJC

Partager ce message


Lien à poster
Partager sur d’autres sites

Tiens, j'ai modifié le code (pas testé de manière approfondie puisque je ne fonctionne pas comme ça pour commander mon chauffage) : Heating Manager v. 1.2.1b.lua

 

Tu fais comme ça : HeatingManager:addHeater({ID de ton module virtuel, n° du bouton ON, n° du bouton OFF}, idSonde, localP, localT)

 

Dis moi si ça marche.

Partager ce message


Lien à poster
Partager sur d’autres sites

Merci pour le retour, je test cela en fin de journée 

Partager ce message


Lien à poster
Partager sur d’autres sites

Doit ton créer une scène pour chaque chauffage ou il suffit d’itentifier tous les id modules virtuel et tous id des sondes?

Partager ce message


Lien à poster
Partager sur d’autres sites

Footer title

This content can be configured within your theme settings in your ACP. You can add any HTML including images, paragraphs and lists.

Footer title

This is an example of a list.

Footer title

This content can be configured within your theme settings in your ACP. You can add any HTML including images, paragraphs and lists.

Footer title

This content can be configured within your theme settings in your ACP. You can add any HTML including images, paragraphs and lists.

×
/* Navigation */ function ipsfocusNavigation() { var navwidth = 0; var morewidth = $('.ipsNavBar_primary .focus-nav-more').outerWidth(true); $('.ipsNavBar_primary > ul > li:not(.focus-nav-more)').each(function() { navwidth += $(this).outerWidth( true ) + 2; }); var availablespace = $('.ipsNavBar_primary').outerWidth(true) - morewidth; if (availablespace > 0 && navwidth > availablespace) { var lastItem = $('.ipsNavBar_primary > ul > li:not(.focus-nav-more)').last(); lastItem.attr('data-width', lastItem.outerWidth(true)); lastItem.prependTo($('.ipsNavBar_primary .focus-nav-more > ul')); ipsfocusNavigation(); } else { var firstMoreElement = $('.ipsNavBar_primary li.focus-nav-more li').first(); if (navwidth + firstMoreElement.data('width') < availablespace) { firstMoreElement.insertBefore($('.ipsNavBar_primary .focus-nav-more')); } } if ($('.focus-nav-more li').length > 0) { $('.focus-nav-more').removeClass('focus-nav-hidden'); } else { $('.focus-nav-more').addClass('focus-nav-hidden'); } } $(window).on('load',function(){ $(".ipsNavBar_primary").removeClass("focus-nav-loading"); ipsfocusNavigation(); }); $(window).on('resize',function(){ ipsfocusNavigation(); }); // Make hover navigation work with touch devices // http://osvaldas.info/drop-down-navigation-responsive-and-touch-friendly ;(function(e,t,n,r){e.fn.doubleTapToGo=function(r){if(!("ontouchstart"in t)&&!navigator.msMaxTouchPoints&&!navigator.userAgent.toLowerCase().match(/windows phone os 7/i))return false;this.each(function(){var t=false;e(this).on("click",function(n){var r=e(this);if(r[0]!=t[0]){n.preventDefault();t=r}});e(n).on("click touchstart MSPointerDown",function(n){var r=true,i=e(n.target).parents();for(var s=0;s ul > li:has(ul)').doubleTapToGo(); var browserResponsiveWidth = 980; var defaultBrowserWidth = $(window).width(); var headerHeight = $("#header").height(); var headerWrap = $(".headerWrap"); var headerBackgrounds = $(".headerBackgrounds"); var headerBlur = $(".headerBlur"); var blurEnd = 110; var headerEffects = function(){ var amountScrolled = $(window).scrollTop(); // Make navigation fixed if( amountScrolled >= headerHeight ){ headerWrap.addClass("fixedBlur"); } else { headerWrap.removeClass("fixedBlur"); } // Blur header if( (amountScrolled <= blurEnd) ){ headerWrap.removeClass("blurred"); } else { headerWrap.addClass("blurred"); } // Parallax effect var translateHeader = amountScrolled / 2; if( amountScrolled <= headerHeight ){ headerBackgrounds.css( "margin-top", translateHeader + "px" ); } else { headerBackgrounds.css( "margin-top", (headerHeight / 2) + "px" ); } } if( $('body').hasClass('wDesktop') ){ $(window).scroll(function(){ headerEffects(); }); }; });