[Script] Régulation PID d'un chauffage par pompes à chaleur

Discussion et échanges de scripts pour la box eedomus

[Script] Régulation PID d'un chauffage par pompes à chaleur

Messagepar Herbert » 16 Août 2018 09:28

Régulateur de type PID. Les instructions sont en tête du script.

Code : Tout sélectionner
<?
/*****************************************************************************************************************
Un chauffage par climatiseurs réversibles air/air est habituellement contrôlé par télécommandes manuelles infrarouges (IR), et régulé par les thermostats internes à chaque unité intérieure (split). Mais ces thermostats mesurent la température à proximité immédiate des splits et non pas l'ambiance des pièces. L’objectif du script est donc d’optimiser le réglage de la température d'ambiance pour améliorer le confort et limiter la consommation électrique.

Pour ce faire, j'utilise les dispositifs suivants : télécommandes z-wave REMOTEC ZXT-120 (alimentées sur secteur), et capteurs de température à 433 Mhz (qui jouent le rôle de thermostats d'ambiance) de marque OREGON (sur piles) - 1 dans chaque pièce et au minimum 1 à l'extérieur.
On peut optionnellement mesurer la puissance instantanée (watts) du chauffage pour détecter d'éventuelles mises en dégivrage (j'utilise un compteur ampèremétrique AEON LABS DSB09).

La régulation compare en permanence (lorsque le chauffage est en route) la température mesurée avec une consigne de référence (périphérique de type "état" - listes de valeurs), et ajuste en conséquence à la hausse ou à la baisse la consigne interne du split pour stabiliser la température de la pièce autour de la valeur désirée. L’algorithme est de type PID, soit une somme de trois facteurs
(Proportionnel + Intégrale + Dérivée).
La consigne de référence peut être réglée (automatiquement et/ou manuellement) à son minimum en cas d’absence de la maison, et à une autre valeur (en fonction de la saison) en cas de présence.

Le démarrage du chauffage peut aussi être automatisé (par règles eedomus) si les conditions sont réunies (par exemple si nous sommes présents et si les températures des capteurs sont inférieures aux consignes). De même pour l'arrêt.
Le script est lancé une première fois au démarrage avec le paramètre &start=1, et ensuite à intervalles constants (par exemple 1 minute) avec le paramètre &start=0.
Et bien sûr les télécommandes IR manuelles restent utilisables lorsque la régulation automatique est inactive.

Le PID est réinitialisé automatiquement sur mise en dégivrage des pompes à chaleur (requiert un compeur spécifique) ou sur changement de consigne.
Au démarrage, la consigne est réduite de 1°C sur température extérieure négative,

Cet exemple est valable pour un seul split, mais on peut facilement en rajouter d'autres.

IMPORTANT : les paramètres du programme (kp, ki, kd) doivent être ajustés aux caractéristiques des pièces. Une phase de mise au point (qui peut être assez longue) est donc nécessaire avant utilisation opérationnelle ; pour info, j'utilise moi-même les valeurs suivantes avec un échantillonnage de 1 minute : [&kp=70.0&ki=120.0&aw=0.20&kd=0.09&lag=0.75&tol=0.3&pseuil=0.06&tseuil=0.016 ]

Références (non exhaustives) :
https://fr.wikipedia.org/wiki/R%C3%A9gulateur_PID
https://fr.wikipedia.org/wiki/M%C3%A9thode_de_Ziegler-Nichols
http://www.mstarlabs.com/apeng/techniques/pidsoftw.html
********************************************************************************************************************/

function sdk_val($idper) {
//Lecture Valeur périphérique
  $per = getValue($idper) ;
  $id_val = $per["value"] ;
  return $id_val ;
}

function sdk_pid($seterr,$lag,$int,$delT,$Tx,$Ty) {
/****** Réponse Proportionnelle ********/
  $pidout1 = $seterr ;
/****** Réponse Intégrale ********/
  if($Tx != 0.0) {
     $int1 = $int * $delT ;
     $int2 = $int1 / $Tx ;
   } else {
     $int2 = 0.0 ; // Pour la mise au point des paramètres*/
   }
  $pidout2 = $pidout1 + $int2 ;
/****** Réponse Dérivée ********/
  $der1 = $lag * $Ty ; // Single pole filter
  $der2 = $der1 / $delT ;
 
  $pidout3 = $pidout2 + $der2 ;
  return $pidout3 ;
}

function sdk_prop($temp,$cons,$seuil) {
/****** Réponse Proportionnelle ********/
  if($temp <= $cons + $seuil) {
     $seterror = $temp - $cons ;
  } else {
     $seterror = $seuil ; // On limite l'erreur si la pièce chauffe toute seule par les vitrages
  } 
  return $seterror ;
}

function sdk_spf($temp,$deriv,$lagcut,$lagstate) {
/****** Réponse dérivée : Single pole filter ****************/
  $chg = $temp - $deriv ; // Si la consigne change rapidement
  $x_lagcut = 1.0 - $lagcut ;
  $lag = $x_lagcut * $lagstate ;
  $cut = $lagcut * $chg ;
  $state = $lag + $cut ; // $lagstate
  return $state ;
}

function sdk_int($out,$max,$min,$integral,$seterr,$anti) {
//Soft Integrator Anti-Windup
  if($out >= $max or $out <= $min) {
     $seterr_2 = $anti * $seterr ;
     $int = $integral + $seterr_2 ;
  } else {
     $int = $integral + $seterr ;
  }
  return $int ;
}

function sdk_out($out,$max,$min) {
//Ajustements aux limites
  if($out >= $max) {
     $out = $max ;
  } else if ($out <= $min) {
     $out = $min ;
  } else {
     $out = $out ;
  }
  return $out ;
}

/*****************************************************************************************************************
 Ce script doit être lancé à intervalles réguliers. Les paramètres ki et kd sont réglés pour un échantillonnage
de 1 minute, mais $Ti et $Td changent si <> 1 minute. Tous les paramètres (sauf $init) sont de type "Float".
*****************************************************************************************************************/
$init = getArg('start') ; // Indicateur d'initialisation de l'intégration
$Kgain = getArg('kp') ; // Coefficient de proportionnalité de l'erreur
$Ti = getArg('ki') ; // Coefficient de proportionnalité de la somme des erreurs (échantillonnage = 1 minute)
$Td = getArg('kd') ; // Coefficient de proportionnalité de la variation de l'erreur (échantillonnage = 1 minute)

// Soft Integrator Anti-Windup : As it cures the windup problem, the integral clamping strategy sometimes delays desirable
// integrator action. The soft anti-windup strategy reduces integrator changes rather than completely eliminating them. Select a
// reduction factor in the range 0.05 to 0.25.
// A reduction factor of 0 is the same as the clamping anti-windup strategy. A reduction factor of 1 is the same as no anti-windup correction.
$anti_w = getArg('aw') ; // Anti-windup

// Improving Derivative Response : Single pole filter. This requires an additional lag variable for filter state,
// plus an additional parameter between 0.0 and 1.0 to configure the cutoff frequency.
// A lag parameter value of 0.15 to 0.35 usually works well. The lower this cutoff level, the better the high frequency
// noise rejection but the more likely that effectiveness of the derivative term is reduced.
$lagcut = getArg('lag') ; // Single pole filter (ex: 0.75)

/*********** Périphériques  *********************************************/
$idcons1 = xxxxxx ; // A remplacer par le code API de la consigne Pilotage Room1 (Float)
$idsetpt1 = xxxxxx  ; // A remplacer par le code API de la consigne REMOTEC ZXT-120
$idtemp1 = xxxxxx ; // A remplacer par le code API de la température OREGON Room1
$idtemp_ext = xxxxxx ; // A remplacer par le code API du capteur OREGON (Température Extérieure)
$idpow = xxxxx ; // Puissance mesurée en Watts (Float) - OPTIONNEL
$iddegivr = xxxxxx ; // Indicateur de dégivrage (périphérique "état" / listes de valeurs) - OPTIONNEL
$idreset = xxxxxx ; // Indicateur de reset (périphérique "état" / listes de valeurs)

switch ($init) {
    case 1 :
      /*********** Initialisation des variables  *********/
      saveVariable('intr1', 0.0) ; // Room1
      saveVariable('derr1', 0.0) ; // Room1
      saveVariable('previous', time()) ;
      saveVariable('r1state',0.0) ; // $lagstate pour Single pole filter Room1
      saveVariable('power', 1.0) ; // Puissance (Watts) - évite division par zéro
      saveVariable('tmpr1', 1.0) ; // Température du Room1 - évite division par zéro
      saveVariable('defrost', 0) ; // Indicateur de dégivrage

      /******** Température extérieure (calculer la moyenne si plusieurs capteurs) **********
      Si température extérieure < 0, réajuster toutes les consignes pilotages -1°C (attention consigne mini = 16) */
      $temp_ext = sdk_val($idtemp_ext) ; // OREGON Température Extérieure)
      $cons1 = sdk_val($idcons1) * 1.0 ; // Consigne Pilotage Room1 (Float)
      if ($temp_ext < 0.0) {
          if ($cons1 >= 17.0) {
              $cons1 = $cons1 - 1.0 ;
              setValue($idcons1, round($cons1)) ; // Consigne Pilotage Room1 (Integer)       
          } else {
              $cons1 = 16.0 ; // Minimum
              setValue($idcons1, round($cons1)) ; // Consigne Pilotage Room1 (Integer)       
          }               
      }
      saveVariable('r1pil',$cons1) ; // Sauvegarde valeur pour détecter les changements
      break ;
   
    case 0 :
      $IR1 = sdk_val($idsetpt1) ; // Consigne IR Room1         
      $cons1 = sdk_val($idcons1) * 1.0 ; // Consigne Pilotage Room1 (Float)
      $temp1 = sdk_val($idtemp1) ; // Température Room1

      /*********************** TESTS Dégivrage (OPTIONNEL) ********************************/
      $pow_prev = loadVariable('power') ; // Précédente Puissance
      $pow = sdk_val($idpow) ; // Puissance en Watts (Float)
      saveVariable('power', $pow) ; // Stockage valeur actuelle Puissance (ne change que toutes les 6 minutes)         
      $pow_var = ($pow - $pow_prev) / $pow_prev ; // Variation relative de la puissance entre 2 lectures (6 minutes)
      $pow_tol = getArg('pseuil') ; // Seuil de variation de puissance (0.06)

      $tmp_prev = loadVariable('tmpr1') ; // Précédente température Room1
      saveVariable('tmpr1', $temp1) ; // Stockage valeur actuelle température Room1         
      $tmp_var = ($temp1 - $tmp_prev) / $tmp_prev ; // Variation relative de la température Room1
//      $tmp_tol = 0.005 ; // Seuil de détection d'un possible dégivrage (0,005 trop faible)
      $tmp_tol = getArg('tseuil') ; // Seuil de variation de température

      /************* On teste la séquence des variations relatives de puissance et de température (Room1) *********************/
      /************* On positionne un indicateur : 0 (pas de dégivrage, 1 (dégivrage POSSIBLE), 2 (dégivrage CONFIRME) ********/
      /******************************************* 3 (dégivrage TERMINE) ******************************************************/     
      $degivr = loadVariable('defrost') ; // Précédent indicateur de dégivrage
      if ($degivr == 0 or $degivr == 1) { // On part d'une situation SANS dégivrage ou avec dégivrage POSSIBLE
         if ($pow_var <= -$pow_tol) { // DIMINUTION de puissance >= seuil (négatif)
              if ($tmp_var <= -$tmp_tol and $tmp_prev <= $cons1 ) {
                 $degivr = 2 ; // Diminution simultanée de puissance ET de température, Dégivrage CONFIRME
              } else {
                 $degivr = 1 ; // Diminution de puissance SANS diminution de température
              }
          } else if ($pow_var <= 0) { /// DIMINUTION de puissance <= zéro         
              if ($degivr == 1 and $tmp_var <= -$tmp_tol and $tmp_prev <= $cons1) {
                  $degivr = 2 ; // Diminution de température >= seuil (négatif), Dégivrage CONFIRME
              } else if ($degivr == 0) {
                  $degivr = 0 ; // Diminution de température SANS diminution de puissance significative
              } else {
                  $degivr = 1 ; // Variation de température < seuil (négatif) 
              }
          } else { // AUGMENTATION de puissance
              if ($degivr == 1 and $tmp_var <= -$tmp_tol and $tmp_prev <= $cons1) { // Diminution de température >= seuil (négatif)
                  $degivr = 3 ; // DEGIVRAGE CONFIRME, le chauffage a redémarré
              } else {
                  $degivr = 0 ; // Pas de dégivrage (ou Reset)
              }
          }
      } else if ($degivr == 2) { // On part d'une situation avec dégivrage CERTAIN (et température < Consigne)
         if ($pow_var > 0) { // AUGMENTATION de puissance (ATTENTION, périodicité = 6 minutes)
             $degivr = 3 ; // DEGIVRAGE TERMINE, le chauffage a redémarré 
         }
      }   
      saveVariable('defrost', $degivr) ; // Valeur actuelle de l'indicateur de dégivrage
      setValue($iddegivr, $degivr) ; // Indicateur Dégivrage     
     
      /*********** Réinitialisation des variables en cas de dégivrage (ou changement de consigne pilotage) *********/ 
      $prev_cons1 = loadVariable('r1pil') ;
      saveVariable('r1pil',$cons1) ; // Sauvegarde valeur pour détecter les changements
     
      if ($cons1 == $prev_cons1) {
          $chg_pil = 0 ; // Pas de changement de consigne pilotage
      } else {
          $chg_pil = 1 ; // Changement de consigne pilotage     
      } 

      if ($degivr == 3 or $chg_pil == 1) {

          if ($chg_pil == 1) {
              setValue($idreset, 1) ; // Reset sur changement de consigne
          } else {
              setValue($idreset, 2) ; // Reset sur dégivrage           
              saveVariable('defrost', 0) ; // Indicateur de dégivrage
          }

          saveVariable('intr1', 0.0) ; // Room1
          saveVariable('derr1', 0.0) ; // Room1
          saveVariable('previous', time()) ; // Heure du reset     
          saveVariable('r1state',0.0) ; // $lagstate pour Single pole filter Room1

      } else {
          setValue($idreset, 0) ; // Pas de reset
      }

      /************** TRAITEMENT **********************************************/
      $prev = loadVariable('previous') ; // Heure du précédent calcul
      $sec = time() ; // Heure courante, mesurée en secondes depuis le début de l'époque UNIX
      $dur_sec = $sec - $prev ; // Durée en secondes depuis le précédent calcul
      $dur = $dur_sec / 60.0 ; // Durée en minutes (float)

      /******  Chauffage Room1 ******/   
      $integral1 = loadVariable('intr1') ; // Précédente intégration Room1
      $deriv1 = loadVariable('derr1') ; // Erreur Précédente Room1

//      setValue(xxxxxx, $deriv1) ; // Test PID - A RETESTER APRES VERIFS RESET **********************

      $lagstate1 = loadVariable('r1state') ; // Single pole filter Room1

      $maxi1 = 31 ; // Consigne maximum Room1
      $mini1 = 16 ; // Consigne minimum Room1
     
      /****** Calculs PID *****/
      // u  =  -Kp * ( err + integral(err)/Ti + deriv(err)*Td )
      /* When approximating the integral using rectangular rule integration, the integral adjustment at each time step becomes the integrand (setpoint error) times the update time interval. When approximating the derivative using a first difference, the derivative estimate is the difference in successive setpoint error values divided by the update time interval.*/

      /****** Réponse Proportionnelle ********/
      $thresh   = getArg('tol') ; // Seuil de filtrage de l'erreur positive
      $seterr1 = sdk_prop($temp1,$cons1,$thresh) ; // Réponse proportionnelle Room1

      /****** Réponse dérivée : Single pole filter ****************/
      $state1 = sdk_spf($temp1,$deriv1,$lagcut,$lagstate1) ; // Réponse dérivée Room1
      saveVariable('r1state', $state1) ;

      /****** Résultats ********/
      $pidout_F = -$Kgain * sdk_pid($seterr1,$state1,$integral1,$dur,$Ti,$Td) ;
      $pidout1 = round($pidout_F) ;
      //saveVariable('derr1', $seterr1) ;
      saveVariable('derr1', $temp1) ; // to avoid the setpoint level spikes

      /****** Soft Integrator Anti-Windup (if outside limits) ******/
      $new_int1 = sdk_int($pidout1,$maxi1,$mini1,$integral1,$seterr1,$anti_w) ;
      saveVariable('intr1', $new_int1) ;

      /******  Ajustements aux limites ******/
      $pidout1 = sdk_out($pidout1,$maxi1,$mini1) ;
     
      /******  Réglages périphériques  ******/
      $i = 1 ;
      $repeat = 3 ; // On répète l'opération au cas où le split ne l'ait pas enregistrée
      while ($i <= $repeat):
        if($pidout1 != $IR1) {
           setValue($idsetpt1, $pidout1) ; // Consigne IR Room1
        }

        if ($i < $repeat) usleep(5000000) ; // Attend 5 secondes
        $IR1 = sdk_val($idsetpt1) ;
        $i++ ;
      endwhile ;

      saveVariable('previous', $sec) ; // Heure du calcul
      break;
}

?>
Pièces jointes
pid.jpg
Exemple de régulation à 19°C
pid.jpg (105.64 Kio) Consulté 13093 fois
Dernière édition par Herbert le 22 Déc 2018 08:57, édité 2 fois.
Herbert
 
Messages : 31
Inscription : 16 Août 2018

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar stef31 » 16 Août 2018 09:53

C'est une régulation de chauffage via Hysteresis.
Les régulations de chauffage via PID ne donnent pas ce type de courbe.
EEDOMUS V2 - RFX avec RTS
En attente de l'EEDOMUS V4
stef31
 
Messages : 2947
Inscription : 20 Août 2013
Localisation : 31

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar Herbert » 16 Août 2018 11:19

... Voir le code SVP (et le lien ci-après) ...
https://fr.wikipedia.org/wiki/R%C3%A9gulateur_PID
Herbert
 
Messages : 31
Inscription : 16 Août 2018

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar stef31 » 16 Août 2018 13:19

Le script est de qualité mais tu sous entends dans ton premier post qu'il fait une régulation PID d'une PAC.
Or d'après ce que j'ai vu de ton script, tu réajustes les consignes en fonction d'une sonde de température déportée pour être au plus proche de la consigne de température que tu le souhaites.
Je n'ai pas regardé plus en détail ton script mais il faudrait que tu expliques un peu plus le but de ce script.
Pour ma part, j'adopte une correction des consignes de ma PAC en fonction d'une sonde OREGON aussi mais sans passer par quelque chose d'aussi complexe.
Il me semble que la fonction PID reste gérée par la PAC, ce qui serait logique car ce type d'équipement n'apprécie pas les arrêts/marches trop fréquents.
EEDOMUS V2 - RFX avec RTS
En attente de l'EEDOMUS V4
stef31
 
Messages : 2947
Inscription : 20 Août 2013
Localisation : 31

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar Herbert » 16 Août 2018 14:06

... Or d'après ce que j'ai vu de ton script, tu réajustes les consignes en fonction d'une sonde de température déportée pour être au plus proche de la consigne de température que tu le souhaites.
Je n'ai pas regardé plus en détail ton script mais il faudrait que tu expliques un peu plus le but de ce script ...
... Il me semble que la fonction PID reste gérée par la PAC, ce qui serait logique car ce type d'équipement n'apprécie pas les arrêts/marches trop fréquents.


J'ai essayé d'expliquer tout ça en résumé dans les commentaires en tête de script, et j'ai aussi indiqué 3 références parmi les articles qui m'ont inspiré; comme je baigne dedans, peut-être ne suis-je pas assez explicite ?
Mon modèle de PAC (Mitsubishi) est assez ancien et je ne sais pas ce que des modèles plus récents peuvent offrir. En tous cas, j'utilise ce système sans problèmes depuis plus de 2 ans, c'est très confortable et j'ai réduit ma facture électrique.
Par ailleurs, il n'y a pas de marche/arrêts de la PAC (ce qui serait le cas avec un chauffage par convecteurs), mais seulement des ajustements de consigne; donc pas de stress sur l'équipement.
Herbert
 
Messages : 31
Inscription : 16 Août 2018

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar stef31 » 16 Août 2018 14:23

Herbert a écrit:J'ai essayé d'expliquer tout ça en résumé dans les commentaires en tête de script, et j'ai aussi indiqué 3 références parmi les articles qui m'ont inspiré; comme je baigne dedans, peut-être ne suis-je pas assez explicite ?


Disons que cela fait beaucoup de lecture avant de savoir ce que peut apporter ton script mais je ne conteste pas que tu as très bien documenté ton script. ;)
EEDOMUS V2 - RFX avec RTS
En attente de l'EEDOMUS V4
stef31
 
Messages : 2947
Inscription : 20 Août 2013
Localisation : 31

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar Herbert » 16 Août 2018 16:08

Disons que cela fait beaucoup de lecture avant de savoir ce que peut apporter ton script mais je ne conteste pas que tu as très bien documenté ton script.


Merci. En résumé de résumé,
L’objectif du script est d’optimiser le réglage de la température d'ambiance pour améliorer le confort et limiter la consommation électrique.

Je conçois que la lecture puisse paraître un peu aride au premier abord, mais en fait le calcul se réduit à une simple somme de trois facteurs. Et puis c'est ce qui est rigolo avec la domotique, non ? :lol:
Herbert
 
Messages : 31
Inscription : 16 Août 2018

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar stef31 » 16 Août 2018 16:42

Herbert a écrit:Et puis c'est ce qui est rigolo avec la domotique, non ? :lol:


On trouve plein de projet sympa en domotique. Pour certains, je n'arrive pas à en comprendre l'utilité mais je trouve les concepts ludiques. :)
EEDOMUS V2 - RFX avec RTS
En attente de l'EEDOMUS V4
stef31
 
Messages : 2947
Inscription : 20 Août 2013
Localisation : 31

[V2] Régulation chauffage par climatiseurs réversibles air/a

Messagepar Herbert » 20 Déc 2018 10:54

Je reprends le clavier pour cette nouvelle version du régulateur. Le principe reste le même, à savoir :
- Le chauffage est arrêté (ou mis hors-gel) ET le régulateur stoppé en cas d’absence prolongée (par exemple : journée de travail, la nuit au rez-de-chaussée inoccupé d’une maison dont les chambres sont à l’étage etc…).
- Le régulateur est lancé AVANT le début d’occupation (prévoir le temps nécessaire pour atteindre la température de consigne), il est ensuite exécuté chaque minute.
- J’ai supprimé la détection de dégivrage, mais conservé la ré-initialisation sur changement de consigne (À ÉVITER pendant la phase d’auto-régulation décrite ci-après).

Le script PID est maintenant multi-pièces (paramètre interne « $nb_rooms ») et multi-splits (paramètre interne « $nb_splits »).
- On aura toujours un seul capteur de température par pièce (faisant office de thermostat), mais il peut y avoir plusieurs splits pour une même pièce.
- On utilise toujours des télécommandes IR z-wave (en principe une par split), avec leurs périphériques de contrôle créés à l’inclusion …
- Comme précédemment, on devra créer manuellement un périphérique « état » de type
« liste de valeurs » pour stocker la consigne de référence « $idcons ».

Les paramètres d’appel du script sont modifiés comme suit :
- &start=1 (initialisation au démarrage du régulateur) ou 0 (par la suite) => inchangé
- &idtemp1=xxxxxx …&idtempn=xxxxxx (codes API des capteurs de température)
- &aw=0.20&lag=0.75&tol=0.3 => inchangés
- &stallpso=x (par exemple 20) => limite le nombre de combinaisons PID à tester (ci-après)

Le script est maintenant « auto-régulé », c'est-à-dire que, pendant la phase initiale de réglage, différentes combinaisons des paramètres ‘kp’, ‘ki’, ‘kd’ sont automatiquement testées et évaluées par le programme (*). Le nombre de tests infructueux autorisé est inférieur ou égal à «stallpso » et le compteur est réinitialisé à chaque succès (voir « itae »). On devra créer manuellement DEUX PÉRIPHÉRIQUES « état » de type « texte » pour chaque pièce (n) :
- « PSO(n) » stocke des combinaisons PID semi-aléatoires calculées par un nouveau script (algorithme de type PSO « Particle Swarm Optimisation »), qui seront testées à l’exécution suivante du script principal. Il s’agit donc d’un processus en 2 temps.
- « PID(n) » stocke les meilleures combinaisons testées au format « kp;ki;kd;itae ». La dernière valeur « itae » est un indice de performance calculé automatiquement par le script. Après un nombre pré-défini d’essais d’optimisation exécutés SANS DIMINUTION notable (>10%) de l’indice, le script considère l’optimisation comme terminée et stoppe toute nouvelle tentative ; on pourra cependant à tout moment relancer le processus en augmentant la valeur &stallpso.

(*) Ces paramètres sont fondamentaux pour le bon fonctionnement du régulateur, mais ils sont délicats à obtenir …

SCRIPT PID :
Code : Tout sélectionner
<?
/*************************************************************************************************************************************
Ce script doit être lancé à intervalles constants (1 minute) une première fois au démarrage avec le paramètre &start=1, et ensuite 
avec le paramètre &start=0. Tous les autres paramètres sont de type "Float"
*************************************************************************************************************************************/

function sdk_pid($seterr,$lag,$int,$delT,$Tx,$Ty) {
//function sdk_pid($seterr,$chg,$int,$delT,$Tx,$der,$Ty) {
/****** Réponse Proportionnelle ********/
  $pidout1 = $seterr ;
/****** Réponse Intégrale ********/
  if($Tx != 0.0) {
     $int1 = $int * $delT ;
     $int2 = $int1 / $Tx ;
   } else {
     $int2 = 0.0 ; // Pour la mise au point des paramètres
   }
  $pidout2 = $pidout1 + $int2 ;
/****** Réponse Dérivée ********/
//  $der1 = $chg * $Ty ; // ou bien Single pole filter
  $der1 = $lag * $Ty ; // Single pole filter
  $der2 = $der1 / $delT ;
 
  $pidout3 = $pidout2 + $der2 ;
  return $pidout3 ;
}

function sdk_val($idper) {
// Lecture Valeur périphérique
  $per = getValue($idper) ;
  $id_val = $per["value"] ;
  return $id_val ;
}

function sdk_chkmaj($idper) {
  $arr1 = getValue($idper) ;
  $maj = $arr1["change"] ; // Date-heure de la dernière mise à jour
  $upd = strtotime($maj) / 60 ; // Au format UNIX (minutes)
  $ref = strtotime("now") / 60 ; // Au format UNIX (minutes)
  $delta = round($ref - $upd) ; // Temps écoulé en minutes depuis la mise à jour
  return $delta ;
}

function sdk_prop($temp,$cons,$seuil) {
/****** Réponse Proportionnelle ********/
  if($temp <= $cons + $seuil) {
     $seterror = $temp - $cons ;
  } else {
     $seterror = $seuil ; // On limite l'erreur si la pièce chauffe toute seule par les vitrages
  } 
  return $seterror ;
}

function sdk_spf($temp,$deriv,$lagcut,$lagstate) {
/****** Réponse dérivée : Single pole filter ****************/
//$lagstate = (1.0-$lagcut)*$lagstate + $lagcut*$chg ;
//$chg = $seterr - $deriv ; // Si la consigne est constante ou évolue régulièrement
  $chg = $temp - $deriv ; // Si la consigne change rapidement
  $x_lagcut = 1.0 - $lagcut ;
  $lag = $x_lagcut * $lagstate ;
  $cut = $lagcut * $chg ;
  $state = $lag + $cut ; // $lagstate
  return $state ;
}

function sdk_int($out,$max,$min,$integral,$seterr,$anti) {
//Soft Integrator Anti-Windup
  if($out >= $max or $out <= $min) {
     $seterr_2 = $anti * $seterr ;
     $int = $integral + $seterr_2 ;
  } else {
     $int = $integral + $seterr ;
  }
  return $int ;
}

function sdk_out($out,$max,$min) {
//Ajustements aux limites
  if($out >= $max) {
     $out = $max ;
  } else if ($out <= $min) {
     $out = $min ;
  } else {
     $out = $out ;
  }
  return $out ;
}

function sdk_cut($chaine) {
// Parse PID coefficients (UNNORMALIZED)
  $pd = explode(";", $chaine) ;
  $p   =  $pd[0] ; // P
  $it  =  $pd[1] ; // I
  $d   =  $pd[2] ; // D
  $fit =  $pd[3] ; // ITAE index
  return array(1=>$p, 2=>$it, 3=>$d, 4=>$fit) ;
}

function sdk_fitness($itae1, $d, $e) { // ITAE : Integral of time * absolute value of error
    $itae2 = $itae1 + ($d * abs($e)) ;
    return  $itae2 ;
}

/***************************** Paramètres *******************************/
$init = getArg('start') ; // Indicateur d'initialisation de l'intégration
/* http://www.mstarlabs.com/apeng/techniques/pidsoftw.html
Soft Integrator Anti-Windup : As it cures the windup problem, the integral clamping strategy sometimes delays desirable
integrator action. The soft anti-windup strategy reduces integrator changes rather than completely eliminating them. Select a
reduction factor in the range 0.05 to 0.25.
A reduction factor of 0 is the same as the clamping anti-windup strategy. A reduction factor of 1 is the same as no anti-windup correction.*/
$anti_w = getArg('aw') ; // Anti-windup
/* Improving Derivative Response : Single pole filter. This requires an additional lag variable for filter state,
plus an additional parameter between 0.0 and 1.0 to configure the cutoff frequency.
A lag parameter value of 0.15 to 0.35 usually works well. The lower this cutoff level, the better the high frequency
noise rejection but the more likely that effectiveness of the derivative term is reduced.*/
$lagcut = getArg('lag') ; // Single pole filter 0.75
$thresh = getArg('tol') ; // Seuil de filtrage de l'erreur positive
$stall = getArg('stallpso') ; // Nombre maximum de combinaisons PID "PSO" testées sans amélioration notable ITAE (20)

/*************************** Périphériques ******************************/
$idtemp1  = getArg('idtemp1') ; // A remplacer par le code API de la température OREGON Pièce n°1
$idtemp2  = getArg('idtemp2') ; // A remplacer par le code API de la température OREGON Pièce n°2
// $idtempx  = getArg('idtempx') ; // A remplacer par le code API de la température OREGON Pièce n°x
$idtemp = array(1=>$idtemp1, 2=>$idtemp2, ...... x=>$idtempx) ; // API températures OREGON
$idtemp_ext = xxxxxx ; // A remplacer par le code API du (des) capteur(s) OREGON (Température Extérieure)

$idsetpt1  = xxxxxx ; // A remplacer par le code API de la consigne REMOTEC ZXT-120 n°1
$idsetpt2  = xxxxxx ; // A remplacer par le code API de la consigne REMOTEC ZXT-120 n°2
// $idsetptx  = xxxxxx ; // A remplacer par le code API de la consigne REMOTEC ZXT-120 n°x
$idsetpt = array(1=>$idsetpt1, 2=>$idsetpt2, ..... x=>$idsetptx) ; // API consignes REMOTEC ZXT-120

$idpid1 = xxxxxxx ; // API PID A RENSEIGNER : pièce n°1
$idpid2 = xxxxxxx ; // API PID A RENSEIGNER : pièce n°2
// $idpidx = xxxxxxx ; // A COMPLéTER
$idpso1 = xxxxxxx ; // API PSO A RENSEIGNER : pièce n°1
$idpso2 = xxxxxxx ; // API PSO A RENSEIGNER : pièce n°2
// $idpsox = xxxxxx ; // A COMPLéTER

$idcons_pil= xxxxxx ; // A remplacer par le code API de la consigne Pilotage (Float)
$setpoint = sdk_val($idcons_pil) * 1.0 ; // Consigne Pilotage (Float)

$nb_rooms  = x ; // Nombre de pièces
$nb_splits = x ; // Nombre de splits (>= Nombre de pièces)
for ($i = 1; $i <= $nb_splits ; $i++) { 
     $maxi[$i] = x ; // Température maxi des splits
     $mini[$i] = x ; // Température mini des splits
}

switch ($init) {

    case 1 :
      /************** Initialisation des variables  ************/
      for ($i = 1; $i <= $nb_splits ; $i++) { // Index des splits 
           $int = 'int'.$i.'' ;
           $der = 'der'.$i.'' ;
           $state = 'state'.$i.'' ;
           saveVariable($int, 0.0) ; // Intégrale
           saveVariable($der, 0.0) ; // Dérivée
           saveVariable($state, 0.0) ; // Lagstate pour Single pole filter   
      }
      saveVariable('previous', time()) ;
 
      /******** Température extérieure (calculer la moyenne si plusieurs capteurs) **********/
      $temp_ext = sdk_val($idtemp_ext) ; // Température extérieure moyenne
      // Si température extérieure < 0, réajuster la consigne pilotage -1°C
      // ATTENTION : cette valeur doit être réinitialisée quotidiennement
      // (par script ou bien avec une variable interne ...)
      if ($temp_ext < 0.0) {
          if ($setpoint >= max($mini) + 1) {
              $setpoint = $setpoint - 1.0 ;
              setValue($idcons_pil, round($setpoint)) ; // Consigne Pilotage (Integer)       
          } else {
              $setpoint = max($mini) ;
              setValue($idcons_pil, round($setpoint)) ; // Consigne Pilotage (Integer)       
          }               
      }
      saveVariable('setpil',$setpoint) ; // Sauvegarde valeur pour détecter les changements
     
      /*************************** INIT PSO ********************************/   
      $itae_start = strtotime("now") / 60 ; // Au format UNIX (minutes)
      savevariable('debut', $itae_start) ;
      $idpid = array(1=>$idpid1, 2=>$idpid2, ...... x=>$idpidx) ; // API PID : A COMPLéTER JUSQU'à x ...
      $idpso = array(1=>$idpso1, 2=>$idpso2, ...... x=>$idpsox) ; // API PSO : A COMPLéTER JUSQU'à x ...

      // Ecrire les paramètres PID testés la veille
      for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce         
          $str_pid[$i] = sdk_val($idpid[$i]) ; // Lecture des dernières valeurs PID stockées pour chaque pièce
          $pid[$i] = sdk_cut($str_pid[$i]) ; // Parse PID coefficients     
          $var6 = 'pbtest'.$i.'' ;
          $psotest = loadVariable($var6) ; // Nombre de combinaisons PID "PSO" déjà testées pour cette pièce
          $str_pso[$i] = sdk_val($idpso[$i]) ; // Lecture des dernières valeurs PSO stockées pour chaque pièce

          if ($psotest <= $stall) { 
              $var2 = 'itae'.$i.'' ;
              $var3 = 'kp'.$i.'' ; 
              $var4 = 'ti'.$i.'' ;
              $var5 = 'td'.$i.'' ;
              $itae = loadVariable($var2) ; // ITAE index précédemment calculé pour la pièce i
              if ($itae == 0.0) { $itae = 999999999999.0 ; } // Première exécution du programme
             
              if ($str_pso[$i] == "STOP PSO") { // Relance si nécessaire l'exécution du programme PSO
                  $var8 = 'lastpso'.$i.'' ;
                  $lastpso = loadVariable($var8) ;
                  setValue($idpso[$i], $lastpso) ;
              }

              $tolerance = 1.10 ; // Tolerance 10 %
              if ($itae * $tolerance < $pid[$i][4]) { // L'indice ITAE calculé la veille est meilleur que celui stocké (à durées identiques)
                  $kp[$i] = loadVariable($var3) ; // Proportionnel testé la veille
                  $ti[$i] = loadVariable($var4) ; // Intégrale testé la veille
                  $td[$i] = loadVariable($var5) ; // Dérivée testé le veille
                  $pid_new = array($kp[$i], $ti[$i], $td[$i], round($itae, 3)) ;
                  $str_gb = implode(";",$pid_new) ;
                  setValue($idpid[$i], $str_gb) ; // On écrit les nouveaux paramètres
                  savevariable($var6, 0) ; // On réinitialise le compteur               
                  usleep(3000000) ; // On attend 3 secondes             
                } else { // L'indice ITAE testé la veille est moins bon : On stocke le nombre de tests sans changements
                  savevariable($var6, $psotest + 1) ; // On incrémente le compteur
              }
          } else if ($str_pso[$i] != "STOP PSO") {
              $var8 = 'lastpso'.$i.'' ;
              savevariable($var8, $str_pso[$i]) ; // Sauvegarde de la dernière combinaison PSO
              setValue($idpso[$i], "STOP PSO") ; // Bloque l'exécution du programme PSO   
          }
        }   
     
      // Calculs du jour
      for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce         
          $var3 = 'kp'.$i.'' ; 
          $var4 = 'ti'.$i.'' ;
          $var5 = 'td'.$i.'' ;
          $str_pid[$i] = sdk_val($idpid[$i]) ; // Lecture des dernières valeurs PID stockées pour chaque pièce
          $pid[$i] = sdk_cut($str_pid[$i]) ; // Parse PID coefficients     
          $var6 = 'pbtest'.$i.'' ;
          $psotest = loadVariable($var6) ; // Nombre de combinaisons PID "PSO" déjà testées pour cette pièce
          $maj_pso = sdk_chkmaj($idpso[$i]) ; // Temps écoulé en minutes depuis la mise à jour PSO pour cette pièce

          // Erreur initiale normée
          $var1 = 'norme'.$i.'' ;
          $norme[$i] = $setpoint - sdk_val($idtemp[$i]) ; // Erreur initiale algébrique (norme) pour la pièce i
          if ($norme[$i] == 0.0) { $norme[$i] = 0.001 ; }
          savevariable($var1, $norme[$i]) ;
          $setpt_err = ($setpoint - sdk_val($idtemp[$i])) / $norme[$i] ; // Erreur normée pour la pièce i
          $fitness = sdk_fitness(0.0, 1.0, $setpt_err) ; // Initialise ITAE index
          $var2 = 'itae'.$i.'' ;
          savevariable($var2, $fitness) ;   

          // On lit les paramètres PID
          $var7 = 'pbprev'.$i.'' ;
          $psoprev[$i] = loadVariable($var7) ; // PSO précédemment testé pour la pièce i

          if ($psotest <= $stall and $psoprev[$i] != $str_pso[$i]) { // On teste les PSO
              savevariable($var7, $str_pso[$i]) ; // On sauvegarde le dernier PSO testé
              $pso[$i] = sdk_cut($str_pso[$i]) ; // Parse PID coefficients                   
              savevariable($var3, $pso[$i][1]) ; // Proportionnel
              savevariable($var4, $pso[$i][2]) ; // Intégrale
              savevariable($var5, $pso[$i][3]) ; // Dérivée
//              echo "Pour la pièce n°".$i.", Kp(pso) = ".$pso[$i][1].", Ti(pso) = ".$pso[$i][2].", Td(pso) = ".$pso[$i][3]."<br>" ;
           } else { // On utilise les PID
              savevariable($var3, $pid[$i][1]) ; // Proportionnel
              savevariable($var4, $pid[$i][2]) ; // Intégrale
              savevariable($var5, $pid[$i][3]) ; // Dérivée           
//              echo "Pour la pièce n°".$i.", Kp(pid) = ".$pid[$i][1].", Ti(pid) = ".$pid[$i][2].", Td(pid) = ".$pid[$i][3]."<br>" ;
          }
      }
       
      break ;
     
    case 0 :
     
      /*************** On recharge les derniers paramètres PID ****************/   
      for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce
          $var3 = 'kp'.$i.'' ;
          $var4 = 'ti'.$i.'' ;
          $var5 = 'td'.$i.'' ;
          $kp[$i] = loadVariable($var3) ;
          $ti[$i] = loadVariable($var4) ;
          $td[$i] = loadVariable($var5) ;
      }     

      /*********** Réinitialisation des variables en cas de changement de consigne pilotage *********/ 
      $prev_setpt = loadVariable('setpil') ;
      saveVariable('setpil',$setpoint) ; // Sauvegarde valeur pour détecter les changements
     
      if ($setpoint == $prev_setpt) {
//          setValue(xxxxxx, 0) ; // Pas de reset (OPTIONNEL : créer un périphérique "état" de type texte pour visualiser le reset)
      } else {
//          setValue(xxxxxx, 1) ; // Reset sur changement de consigne (OPTIONNEL)

          for ($i = 1; $i <= $nb_splits ; $i++) {  // Indice des splits 
              $int = 'int'.$i.'' ;
              $der = 'der'.$i.'' ;
              $state = 'state'.$i.'' ;
              saveVariable($int, 0.0) ; // Intégrale
              saveVariable($der, 0.0) ; // Dérivée
              saveVariable($state, 0.0) ; // Lagstate pour Single pole filter         
          }
          saveVariable('previous', time()) ; // Heure du reset   
      }
       
      /************************* TRAITEMENT ********************************/
      $prev = loadVariable('previous') ; // Heure du précédent calcul
      $sec = time() ; // Heure courante, mesurée en secondes depuis le début de l'époque UNIX
      $dur_sec = $sec - $prev ; // Durée en secondes depuis le précédent calcul
      $dur = $dur_sec / 60.0 ; // Durée en minutes (float)
      if ($dur == 0) { $dur = 1.0 ; }

      /***************************** PSO ***********************************/
      $itae_start = loadVariable('debut') ; // Début du calcul ITAE
      $itae_now = strtotime("now") / 60 ; // Au format UNIX (minutes)
      $itae_dur = $itae_now - $itae_start ; // Durée du calcul ITAE
      $itae_max = 6 * 60 ; // En minutes

      if ($itae_dur <= $itae_max) {           
          for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce       
              $temp[$i] = sdk_val($idtemp[$i]) ; // Température de la pièce
              $var1 = 'norme'.$i.'' ;
              $norme = loadVariable($var1) ; // Erreur initiale algébrique (norme) pour la pièce i     
              $setpt_err = ($setpoint - sdk_val($idtemp[$i])) / $norme ; // Erreur normée pour la pièce i
         
              $var2 = 'itae'.$i.'' ;
              $itae_old = loadVariable($var2) ; // ITAE index précédent pour la pièce i         
              $fitness = sdk_fitness($itae_old, $dur, $setpt_err) ; // Nouvel ITAE index pour la pièce i
              savevariable($var2, $fitness) ;
          }
      }
     
      /***************************** PID ***********************************/
      for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice des pièces   
           $temp[$i] = sdk_val($idtemp[$i]) ; // Température des pièces

           // On recharge les valeurs précédentes
           $int   = 'int'.$i.'' ;
           $der   = 'der'.$i.'' ;
           $state = 'state'.$i.'' ;
           $integral[$i] = loadVariable($int) ; // Précédente intégration
           $deriv[$i]    = loadVariable($der) ; // Erreur Précédente
           $lagstate[$i] = loadVariable($state) ; // Single pole filter

           /* u  =  -Kp * ( err + integral(err)/Ti + deriv(err)*Td )
           When approximating the integral using rectangular rule integration, the integral adjustment at each time step becomes the integrand
           (setpoint error) times the update time interval. When approximating the derivative using a first difference, the derivative estimate is the
           difference in successive setpoint error values divided by the update time interval.*/
           
           /****************** Réponse Proportionnelle *****************/
           $seterr[$i] = sdk_prop($temp[$i],$setpoint,$thresh) ;
           /*********** Réponse dérivée : Single pole filter ***********/
           $state[$i] = sdk_spf($temp[$i],$deriv[$i],$lagcut,$lagstate[$i]) ;
           saveVariable($state, $state[$i]) ;
           /************************ Résultats *************************/
           $pidout[$i] = round(-$kp[$i] * sdk_pid($seterr[$i],$state[$i],$integral[$i],$dur,$ti[$i],$td[$i])) ;
//           $pidout_liv = round($pidout_l) ;
           saveVariable($der, $temp[$i]) ; // to avoid the setpoint level spikes
           /****** Soft Integrator Anti-Windup (if outside limits) ******/
           $new_int[$i] = sdk_int($pidout[$i],$maxi[$i],$mini[$i],$integral[$i],$seterr[$i],$anti_w) ;
           saveVariable($int, $new_int[$i]) ;
           /******  Ajustements aux limites ******/
           $pidout[$i] = sdk_out($pidout[$i],$maxi[$i],$mini[$i]) ;
//           echo "PIDOUT ".$i." = ".$pidout[$i]."<br>" ;
      }
      saveVariable('previous', $sec) ; // Heure du calcul
     
      /********************* Réglages périphériques *************************/
      $j = 1 ;
      $repeat = 3 ; // On répète l'opération au cas où le split ne l'ait pas enregistrée
      while ($j <= $repeat):
          for ($i = 1; $i <= $nb_splits ; $i++) {  // Indice des splits   
              if ($i == 1) { $k = $i ; } else { $k = $i - 1 ; } // Dans ce exemple il y a 2 splits pour la pièce n°1 (ADAPTER A CHAQUE CONFIGURATION !!!)
              $IR[$i] = sdk_val($idsetpt[$i]) ; // Consignes des splits
              if($IR[$i] != $pidout[$k]) {
                  setValue($idsetpt[$i], $pidout[$k]) ; // Consigne
              }           
          }
          // echo " Consigne pièce n°1 = ".$IR[1].",  Consigne pièce n°2 = ".$IR[2].",  Consigne pièce n°3 = ".$IR[3]."....."<br>" ;
          if ($j < $repeat) { usleep(5000000) ; } // Attend 5 secondes
//          for ($i = 1; $i <= $nb_splits ; $i++) {  // Indices des splits
//               $IR[$i] = sdk_val($idsetpt[$i]) ; // Consignes des splits
//          }
      $j++ ;       
      endwhile ;

      break;
}

?>


Le script PSO ci-après calcule par itérations de nouvelles combinaisons (semi-aléatoires) des paramètres ‘kp’, ‘ki’, et ‘kd’, à l’intérieur d’un domaine régi par une fonction mathématique d’optimisation et des bornes fournies au programme (**).
Ce script doit être lancé avec une fréquence suffisante pour pouvoir générer des combinaisons acceptables, mais en évitant de saturer la box (1/2 heure à 1 heure semblent corrects). Les nouvelles combinaisons obtenues ne sont renouvelées qu’après 24 heures.

Ces combinaisons « PSO(n) » sont stockées pour être ensuite évaluées (sur une durée CONSTANTE de 6 heures) par le script principal (PID) : si leur indice « itae » est notablement inférieur à celui des valeurs « PID(n) » précédemment enregistrées, elles remplacent ces dernières à l’initialisation suivante du script PID.
Lorsque les valeurs sont stabilisées, on peut suspendre l’exécution du script PSO.

(**) Les six variables d’appel &mini[1-2-3] et &maxi[1-2-3] s’appliquent respectivement aux 3 paramètres PID. On devra ajuster leurs valeurs pour obtenir des combinaisons adaptées (essayer par exemple : 50<kp<100 et 50<ki<150 et 0.1<kd<1.0, que l’on pourra affiner par la suite en jouant sur &stallpso du script principal).


SCRIPT PSO :
Code : Tout sélectionner
<?
// ###########################################################################
// # Ce programme calcule des variantes des paramètres PID pour chaque pièce.
// # Il doit être lancé AVANT le programme de régulation PID du chauffage.
// # Le programme PID lui-même évaluera les nouveaux paramètres en les comparant
// # avec les anciens, qui seront remplacés si les variantes sont meilleures.
// ###########################################################################
// &maxi1=100&maxi2=150&maxi3=1&mini1=50&mini2=50&mini3=0.05
// https://www.researchgate.net/publication/46165785_A_particle_swarm_optimization_approach_for_tuning_of_SISO_PID_control_loops

// Méthode de Ziegler-Nichols
// ku = gain maximal; tu = période d'oscillation du signal
// PID : 0.6 * ku; tu / 2; tu / 8
// PIR (Pessen Integral Rule) :  0.7 * ku; tu / 2.5; 3 * tu / 20
// Léger dépassement : 0.33 * ku; tu / 2; tu / 3
// Aucun dépassement : 0.2 * ku; tu / 2; tu / 3

function sdk_val($idper) {
//Lecture Valeur périphérique
  $per = getValue($idper) ;
  $id_val = $per["value"] ;
  return $id_val ;
}

function sdk_chkmaj($idper) {
  $arr1 = getValue($idper) ;
  $maj = $arr1["change"] ; // Date-heure de la dernière mise à jour
  $upd = strtotime($maj) / 60 ; // Au format UNIX (minutes)
  $ref = strtotime("now") / 60 ; // Au format UNIX (minutes)
  $delta = round($ref - $upd) ; // Temps écoulé en minutes depuis la mise à jour
  return $delta ;
}

function sdk_div($x, $max) { // Retourne le diviseur de x <= max
    $n = count($x) ;
    for ($i = 1 ; $i <= $n ; $i++) {
      $div[$i] = 1 ;
      while ($x[$i] > $max) {
        $x[$i] = $x[$i] / 10 ;
        $div[$i] = $div[$i] * 10 ;
      }
    }
    return $div ;
}
 
function sdk_speed($const, $vit1, $cog, $pb, $pos1, $soc, $gb, $vm) {
// v[j,n](iter+1) = chi * [v[j,n](iter) + c1*rand1*(pbest[j,n] - s[j,n](iter)) + c2*rand2*(gbest[j,n] - s[j,n](iter))] Equation (6.1)
  $rnd1 = $cog * rand(-$vm * 100.0, $vm * 100.0) / 100.0 ;
  $rnd2 = $soc * rand(-$vm * 100.0, $vm * 100.0) / 100.0 ;
  $fact1 = $rnd1 * ($pb - $pos1) ;
  $fact2 = $rnd2 * ($gb - $pos1) ;
  $vit2 = $const * ($vit1 + $fact1 + $fact2) ;
  return $vit2 ;
}

function sdk_pos($pos1, $vit2) {
// s[j,n](iter+1) = S[j,n](iter) + v[j,n](iter+1) Equation (6.2)*/
   $pos2 = $pos1 + $vit2 ;
   return $pos2 ;
}

// ######################## FITNESS FUNCTIONS ###################################
// http://www-optima.amp.i.kyoto-u.ac.jp/member/student/hedar/Hedar_files/go.htm 

function sdk_michalewicz($x) { // FITNESS FUNCTION
    // https://gist.github.com/denis-bz/da697d8bc74fae4598bf
    // limites = [0, pi]
    $michalewicz = 0.5 ; // orig 10: ^20 => underflow
    $fact = 2 * $michalewicz ;
    $n = count($x) ;
    $s = 0.0 ;
    for ($i = 1 ; $i < $n + 1 ; $i++) {
        $j = $i  * 1.0 ; // De 1.0 à $n.0
        $s = $s + (sin($x[$j]) * sin($j * pow($x[$j], 2) / pi())) ;       
    }
    return - pow($s, $fact ) ;
}

// ######################## END OF FITNESS FUNCTIONS #############################

function sdk_display_array($arr, $level = 0) {
    if(gettype($arr) == 'array') {
        foreach($arr as $key => $value) {
            $chaine = str_repeat(". .", $level) ;
            echo "<i>$chaine</i> Niveau <i>$level</i> Clé <i>$key</i> : valeur = <i>$arr</i><br>" ;
            sdk_display_array($value, $level + 1) ;
        }
    } else {
        $chaine = str_repeat(". .", $level) ;
        echo "<i>$chaine</i> Niveau <i>$level</i> : valeur = <i>$arr</i><br><br>" ;
    }
}

function sdk_contraintes($par, $min, $max) {
    // Contraintes
    $n = count($par) ;
    for ($k = 1 ; $k <= $n + 1 ; $k++) { $test[$k] = 0.0 ; }
    for ($k = 1 ; $k <= $n ; $k++) {
        if ($par[$k] < $min[$k] or $par[$k] > $max[$k]) { $test[$n+1] = $test[$n+1] + 1 ; } // Echec
    }
    return $test ;
}

// Paramètres
$maxi1 = getArg('maxi1') ; // Valeur maximum inclusive coefficient proportionnel kp
$maxi2 = getArg('maxi2') ; // Valeur maximum inclusive coefficient intégrale ti
$maxi3 = getArg('maxi3') ; // Valeur maximum inclusive coefficient dérivée td
$maxi = array(1=>$maxi1, 2=>$maxi2, 3=>$maxi3) ;
$mini1 = getArg('mini1') ; // Valeur minimum inclusive coefficient proportionnel
$mini2 = getArg('mini2') ; // Valeur minimum inclusive coefficient intégrale ti
$mini3 = getArg('mini3') ; // Valeur minimum inclusive coefficient dérivée td
$mini = array(1=>$mini1, 2=>$mini2, 3=>$mini3) ;

$nb_rooms = x ; // Nombre de pièces A RENSEIGNER
$swarm = array() ; // Swarm
$gbest = array() ; // Global Best
$pbest = array() ; // Particle Best

$dimensions = 3 ; // PID
$swarm_size = 20 ; // Nombre de particules
$chi = 0.73 ; // Constriction factor  [2*kappa/ABS(2-phi-SQRT(phi^2-4*phi)) avec kappa=1; phi=ph1+phi2=2.05+2.05]
$c1 = 2.05 ; // Cognitive acceleration
$c2 = 2.05 ; // Social acceleration
$vmax = 1.0 ;

$max_iter = 1000 ; // Durée de l'algorithme
$stall = $max_iter / 2 ; // Si les résultats de l'algorithme PSO n'évoluent plus, il est interrompu
$mval = pi() ; // Valeur maximum de la fonction d'optimisation michalewicz (3D) -> minimum = 0

// Le programme ne tourne que si nécessaire pour une pièce déterminée
$idpso1 = xxxxxxx ; // API GBEST A RENSEIGNER : pièce n°1
$idpso2 = xxxxxxx ; // API GBEST A RENSEIGNER : pièce n°2
// $idpsox = xxxxxx ; // A COMPLéTER
$idpso = array(1=>$idpso1, 2=>$idpso2, ..... ) ; // A COMPLéTER JUSQU'à x ...

$step = 24 * 60 ; // Update possible après 24 heures
$pso = array() ;
$pso_old = array() ;
$maj_pso = array() ;
$req = array() ; // Test si mise à jour requise
for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce 
    $var = 'pso'.$i."" ;
    $pso_old[$i] = loadVariable($var) ; // PSO précédent
    $pso[$i] = sdk_val($idpso[$i]) ;
    saveVariable($var, $pso[$i]) ; // PSO actuel
    $maj_pso[$i] = sdk_chkmaj($idpso[$i]) ; // Temps écoulé en minutes depuis la mise à jour
    if ($pso[$i] == $pso_old[$i] and $maj_pso[$i] >= $step and $pso[$i] != "STOP PSO") {$req[$i] = 1 ; } // Mise à jour requise
}
//for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce
    //echo "Req de ".$i." = ".$req[$i]."<br>" ;
//    if (array_key_exists($i, $req)) { echo "Mise à jour requise pièce n°".$i."<br>" ;}
//}

// PSO
$norm = sdk_div($maxi, $mval) ; // Multiplicateurs des paramètres PID calculés
for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce       
    if (array_key_exists($i, $req)) {

      // Initialisation des particules       
      for ($j = 1; $j <= $swarm_size ; $j++) { // Indice de la particule
          for ($k = 1; $k <= $dimensions ; $k++) { // Indice du paramètre
              $velocity = rand(-$vmax * 100.0, $vmax * 100.0) / 100.0 ; // Soit un nombre aléatoire entre -1 et +1
              $position = rand(0.0, $mval * 100.0) / 100.0 ; // Paramètre aléatoire (positif) entre 0 et maximum de la fonction d'optimisation
              $swarm[$i][$j]["VELOCITY"][$k] = $velocity ;
              $swarm[$i][$j]["POSITION"][$k] = $position ;
          }
                     
          // Evaluation de la particule courante
          $particle[$i] = $swarm[$i][$j] ;
          $test =  $particle[$i]["POSITION"] ; // $test["POSITION"][paramètre] = gbest     
          $fitness = sdk_michalewicz($test) ;
          // On remplace "best fitness", "particle best" et "global best"
          $bestfit[$i] = $fitness ;
          $bestfit2[$i] = $bestfit[$i] ;
          $swarm[$i][$j]["P_BEST"] = $swarm[$i][$j]["POSITION"] ;
          $grp_best[$i] = $particle[$i] ;
      }
    }
}

$iter = 1 ;
while ($iter != $max_iter) {
    for ($i = 1; $i <= $nb_rooms ; $i++) { // Indice de la pièce
        if (array_key_exists($i, $req)) {
           
            // Boucle principale des particules
            $fail = 0 ; // Réinitialisé à zéro à chaque succés         
            for ($j = 1; $j <= $swarm_size ; $j++) { // Indice de la particule
                for ($k = 1; $k <= $dimensions ; $k++) { // Indice du paramètre PID
                    //Calculs de "vitesse"
                    $vel1 = $swarm[$i][$j]["VELOCITY"][$k] ;
                    $pbest = $swarm[$i][$j]["P_BEST"][$k] ;
                    $pos1 = $swarm[$i][$j]["POSITION"][$k] ;
                    $gbest = $grp_best[$i]["POSITION"][$k] ;
                    $vit2 = sdk_speed($chi, $vel1, $c1, $pbest, $pos1, $c2, $gbest, $vmax) ;
                    $swarm[$i][$j]["VELOCITY"][$k] = $vit2 ;

                    //Calculs de "position"
                    $pos2 = sdk_pos($pos1, $vit2) ;
                    If ($pos2 > $mval) {
                        $pos2 = $mval ;
                    } else if ($pos2 < 0.0) {
                        $pos2 = 0.0 ;
                    }
                    $swarm[$i][$j]["POSITION"][$k] = $pos2 ;
                }

                // Evaluation de la particule courante
                $particle[$i] = $swarm[$i][$j] ;
                $test = $particle[$i]["POSITION"] ;
                $fitness = sdk_michalewicz($test) ;
               
                // On teste la cohérence de la particule
                $pid[$i] = $swarm[$i][$j]["POSITION"] ;
                $kp[$i] = $pid[$i][1] * $norm[1] ;
                $ti[$i] = $pid[$i][2] * $norm[2] ;
                $td[$i] = $pid[$i][3] * $norm[3] ;
                $params = array(1=>$kp[$i], 2=>$ti[$i], 3=>$td[$i]) ;
                $good = sdk_contraintes($params, $mini, $maxi) ; // Teste si les contraintes sont satisfaites
               
                // Si la particule courante est la meilleure
                $m = count($params) ;
                if ($fitness < $bestfit[$i] and $good[$m+1] == 0) {
                    // On remplace "best fitness", "particle best" et "global best"
                    $bestfit[$i] = $fitness ;
                    $swarm[$i][$j]["P_BEST"] = $swarm[$i][$j]["POSITION"] ;
                    $grp_best[$i] = $swarm[$i][$j]["P_BEST"] ;
                }
                if ($fitness < $bestfit2[$i])  { // Particules hors limites
                    $bestfit2[$i] = $fitness ;
                    $pro[$i] = $kp[$i] ;
                    $int[$i] = $ti[$i] ;
                    $der[$i] = $td[$i] ;
                }
                $fail++ ;   
                // Si les résultats n'évoluent plus
                if ($fail >= $stall) {
                    $j = $swarm_size + 1 ; // On sort de la boucle des particules
                    echo "Pour la pièce n°<i>$i<i> et la particule n°<i>$j<i> : sortie de la boucle après <i>$iter<i> itérations<br>" ;           
                }
            }
        }
    }
    $iter++ ; 
}

// Sauvegarde des GBESTS ..
for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce 
    if (array_key_exists($i, $req)) {

        // On teste la cohérence de la particule
        $kp[$i] = round($grp_best[$i][1] * $norm[1] , 0) ;
        $ti[$i] = round($grp_best[$i][2] * $norm[2] , 0) ;
        $td[$i] = round($grp_best[$i][3] * $norm[3] , 3) ;
        $params = array(1=>$kp[$i], 2=>$ti[$i], 3=>$td[$i]) ;
        $good = sdk_contraintes($params, $mini, $maxi) ; // Teste si les contraintes sont satisfaites
   
        $m = count($params) ;         
        if ($good[$m+1] == 0) { // Succès
            $arr_gbest = array($kp[$i], $ti[$i], $td[$i]) ;         
            $str_gbest = implode(";", $arr_gbest) ;
            setValue($idpso[$i], $str_gbest) ;
            $code[$i] = 0 ; // La valeur obtenue est correcte
        } else { // On vérifie si le programme a tourné, et on enregistre les résultats => OPTIONNEL(*)
            $params = array(1=>$pro[$i], 2=>$int[$i], 3=>$der[$i]) ;
            $good = sdk_contraintes($params, $mini, $maxi) ; 
            $echecs = " = ".strval(round($good[$m+1], 0))." Echecs" ;
            $arr_test = array($i, round($pro[$i], 0), round($int[$i], 0), round($der[$i], 3), $echecs) ;
            $str_test = implode(";", $arr_test) ;
//            setValue(xxxxxxx, $str_test) ; // (*) Créer un périphérique "état" de type texte. RENSEIGNER SON API
            $code[$i] = 1 ; // La valeur obtenue est incorrecte
        }
    }
}

$result = 0 ;
for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indice de la pièce 
     $result = $result + $code[$i] ; // Nombre de calculs incorrects
}
// ****************************** Pour relancer le programme PSO si nécessaire (** RECOMMANDé) **********************************
// if ($result == 0) { setValue(xxxxxxx, 0) ; } else { setValue(xxxxxxx, $result) ; } //       
// (**) Créer un périphérique "état" de type texte, et une règle : réenclenchement autorisé après 5 minutes, horaire = toutes les
//      minutes, critère = différent de 0, action = lancer le programme PSO (via macro avec délai 5 minutes). RENSEIGNER SON API
// *****************************************************  OPTIONNEL *************************************************************

?>
Dernière édition par Herbert le 05 Jan 2019 19:38, édité 2 fois.
Herbert
 
Messages : 31
Inscription : 16 Août 2018

Re: Régulation chauffage par climatiseurs réversibles air/ai

Messagepar Herbert » 21 Déc 2018 15:56

... Rajouté quelques lignes dans le code PID précédent pour permettre la relance du processus PSO ...
Herbert
 
Messages : 31
Inscription : 16 Août 2018

[Script] ADAPTATION RADIATEURS « COZYTOUCH »

Messagepar Herbert » 27 Déc 2018 18:33

J’ai adapté le script PID pour des radiateurs électriques « Cozytouch », et cela fonctionne plutôt bien. Il n’y a pas de stress inutile pour les équipements, puisque le seul réglage modifié par le script est la consigne du radiateur (pas de commutation ON/OFF).

La différence de principe avec l’application aux pompes à chaleur est qu’un radiateur chauffera de manière identique (ni plus, ni moins) tant que sa consigne sera supérieure à la température mesurée par le radiateur lui-même (PAS le thermostat d’ambiance), et ce quelle que soit la valeur relative de cette consigne. Il faudra donc :
- que le réglage des paramètres P-I-D du script soit tel que les radiateurs chauffent tant que la température d’ambiance de la pièce (thermostat) n’est pas atteinte, en évitant les dépassements et les oscillations (voir le principe général des régulateurs PID).
- Que les radiateurs « Cozytouch » NE SOIENT PAS LIÉS ENTRE EUX !

Code : Tout sélectionner
<?
/************************************************ PID Pour CozyTouch ********************************************
 Ce script doit être lancé à intervalles réguliers. Les paramtères ki et kd sont réglés pour un échantillonnage
de 1 minute, mais ils changent si <> 1 minute. Tous les paramètres (sauf $init) sont de type "Float".
**** Les paramètres kp/ki/kd suivants sont donnés à titre d'exemple (les autres sont utilisables tels quels) ****
PID_START : &start=1&kp=70.0&ki=120.0&aw=0.20&kd=0.09&lag=0.75&tol=0.3
PID : &start=0&kp=70.0&ki=120.0&aw=0.20&kd=0.09&lag=0.75&tol=0.3
*****************************************************************************************************************/

function sdk_val($idper) {
//Lecture Valeur périphérique
  $per = getValue($idper) ;
  $id_val = $per["value"] ;
  return $id_val ;
}

function sdk_pid($seterr,$lag,$int,$delT,$Tx,$Ty) {
/****** Réponse Proportionnelle ********/
  $pidout1 = $seterr ;
/****** Réponse Intégrale ********/
  if($Tx != 0.0) {
     $int1 = $int * $delT ;
     $int2 = $int1 / $Tx ;
   } else {
     $int2 = 0.0 ; // Pour la mise au point des paramètres*/
   }
  $pidout2 = $pidout1 + $int2 ;
/****** Réponse Dérivée ********/
  $der1 = $lag * $Ty ; // Single pole filter
  $der2 = $der1 / $delT ;
 
  $pidout3 = $pidout2 + $der2 ;
  return $pidout3 ;
}

function sdk_prop($temp,$cons,$seuil) {
/****** Réponse Proportionnelle ********/
  if($temp <= $cons + $seuil) {
     $seterror = $temp - $cons ;
  } else {
     $seterror = $seuil ; // On limite l'erreur si la pièce chauffe toute seule par les vitrages
  } 
  return $seterror ;
}

function sdk_spf($temp,$deriv,$lagcut,$lagstate) {
/****** Réponse dérivée : Single pole filter ****************/
  $chg = $temp - $deriv ; // Si la consigne change rapidement
  $x_lagcut = 1.0 - $lagcut ;
  $lag = $x_lagcut * $lagstate ;
  $cut = $lagcut * $chg ;
  $state = $lag + $cut ; // $lagstate
  return $state ;
}

function sdk_int($out,$max,$min,$integral,$seterr,$anti) {
//Soft Integrator Anti-Windup
  if($out >= $max or $out <= $min) {
     $seterr_2 = $anti * $seterr ;
     $int = $integral + $seterr_2 ;
  } else {
     $int = $integral + $seterr ;
  }
  return $int ;
}

function sdk_out($out,$max,$min) {
//Ajustements aux limites
  if($out >= $max) {
     $out = $max ;
  } else if ($out <= $min) {
     $out = $min ;
  } else {
     $out = $out ;
  }
  return $out ;
}

//***************************** Paramètres *******************************
$init = getArg('start') ; // Indicateur d'initialisation de l'intégration
$Kgain = getArg('kp') ; // Coefficient de proportionnalité de l'erreur
$Ti = getArg('ki') ; // Coefficient de proportionnalité de la somme des erreurs (échantillonnage = 1 minute)
$Td = getArg('kd') ; // Coefficient de proportionnalité de la variation de l'erreur (échantillonnage = 1 minute)

// Soft Integrator Anti-Windup : As it cures the windup problem, the integral clamping strategy sometimes delays desirable
// integrator action. The soft anti-windup strategy reduces integrator changes rather than completely eliminating them. Select a
// reduction factor in the range 0.05 to 0.25.
// A reduction factor of 0 is the same as the clamping anti-windup strategy. A reduction factor of 1 is the same as no anti-windup correction.
$anti_w = getArg('aw') ; // Anti-windup

// Improving Derivative Response : Single pole filter. This requires an additional lag variable for filter state,
// plus an additional parameter between 0.0 and 1.0 to configure the cutoff frequency.
// A lag parameter value of 0.15 to 0.35 usually works well. The lower this cutoff level, the better the high frequency
// noise rejection but the more likely that effectiveness of the derivative term is reduced.
$lagcut = getArg('lag') ; // Single pole filter (ex: 0.75)
$thresh = getArg('tol') ; // Seuil de filtrage de l'erreur positive

// *********** Périphériques *********************************************
// *********** Les radiateurs Cozytouch NE SONT PAS LIéS entre eux !!! ***
$idcons = XXXXXXX ; // A REMPLACER par le code API de la consigne Pilotage Globale (Float)

// Pièces et radiateurs
$nb_rooms  = x ; // Nombre de pièces
$nb_rad = x ; // Nombre de radiateurs (>= Nombre de pièces)

$idsetpt1 = xxxxxxx  ; // A REMPLACER par le code API de la consigne Cozytouch #1
$idsetpt2 = xxxxxxx  ; // A REMPLACER par le code API de la consigne Cozytouch #2
//$idsetptx = xxxxxxx  ; // A REMPLACER par le code API de la consigne Cozytouch #x
$idsetpt = array(1=>$idsetpt1, 2=>$idsetpt2, ..... x=>$idsetptx) ; // API consignes

$idtemp1 = xxxxxxx ; // A REMPLACER par le code API de la température d'ambiance de la pièce n°1
//$idtempx = xxxxxxx ; // A REMPLACER par le code API de la température d'ambiance de la pièce n°x
$idtemp = array(1=>$idtemp1, 2=>$idtemp2, ...... x=>$idtempx) ; // API températures

$idmod1 = xxxxxxx ; // A REMPLACER par le code API Mode Radiateur Cozytouch #1
$idmod2 = xxxxxxx ; // A REMPLACER par le code API Mode Radiateur Cozytouch #2
//$idmodx = xxxxxxx ; // A REMPLACER par le code API Mode Radiateur Cozytouch #x
$idmod = array(1=>$idmod1, 2=>$idmod2, ...... x=>$idmodx) ; // API Mode Radiateurs

for ($i = 1; $i <= $nb_rad ; $i++) { 
     $maxi[$i] = x ; // Température maxi des radiateurs
     $mini[$i] = x ; // Température mini des radiateurs
}

switch ($init) {
    case 1 :
      /************** Initialisation des variables  ************/
      for ($i = 1; $i <= $nb_rad ; $i++) { // Indices des radiateurs 
           $int = 'int'.$i.'' ;
           $der = 'der'.$i.'' ;
           $state = 'state'.$i.'' ;
           saveVariable($int, 0.0) ; // Intégrale
           saveVariable($der, 0.0) ; // Dérivée
           saveVariable($state, 0.0) ; // Lagstate pour Single pole filter   
      }
      saveVariable('previous', time()) ;
      $setpoint = sdk_val($idcons) * 1.0 ; // Consigne Pilotage (Float)
      saveVariable('setpil',$setpoint) ; // Sauvegarde valeur pour détecter les changements

      break ;
   
    case 0 :

      $setpoint = sdk_val($idcons) * 1.0 ; // Consigne Pilotage Globale (Float)

      for ($i = 1; $i <= $nb_rad ; $i++) {  // Indices des radiateurs   
          $mod[$i] = sdk_val($idmod[$i]) ; // Mode Radiateur
          if ($mod[$i] != "basic") {  setValue($idmod[$i], "basic") ; } // Mettre tous les radiateurs en mode BASIC
      }
           
      /*********** Réinitialisation des variables en cas de changement de consigne pilotage *********/ 
      $prev_setpt = loadVariable('setpil') ;
      saveVariable('setpil',$setpoint) ; // Sauvegarde valeur pour détecter les changements
     
      if ($setpoint != $prev_setpt) {
          for ($i = 1; $i <= $nb_rad ; $i++) {  // Indices des radiateurs 
              $int = 'int'.$i.'' ;
              $der = 'der'.$i.'' ;
              $state = 'state'.$i.'' ;
              saveVariable($int, 0.0) ; // Intégrale
              saveVariable($der, 0.0) ; // Dérivée
              saveVariable($state, 0.0) ; // Lagstate pour Single pole filter         
          }
          saveVariable('previous', time()) ; // Heure du reset   
      }

      /************** TRAITEMENT **********************************************/
      $prev = loadVariable('previous') ; // Heure du précédent calcul
      $sec = time() ; // Heure courante, mesurée en secondes depuis le début de l'époque UNIX
      $dur_sec = ($sec - $prev) * 1.0 ; // Durée en secondes depuis le précédent calcul
      $dur = $dur_sec / 60.0 ; // Durée en minutes (float)
      if ($dur == 0) { $dur = 1.0 ; }

      /***************************** PID ***********************************/
      for ($i = 1; $i <= $nb_rooms ; $i++) {  // Indices des pièces   
           $temp[$i] = sdk_val($idtemp[$i]) ; // Températures des pièces

           // On recharge les valeurs précédentes
           $int   = 'int'.$i.'' ;
           $der   = 'der'.$i.'' ;
           $state = 'state'.$i.'' ;
           $integral[$i] = loadVariable($int) ; // Précédente intégration
           $deriv[$i]    = loadVariable($der) ; // Erreur Précédente
           $lagstate[$i] = loadVariable($state) ; // Single pole filter

           /* u  =  -Kp * ( err + integral(err)/Ti + deriv(err)*Td )
           When approximating the integral using rectangular rule integration, the integral adjustment at each time step becomes the integrand
           (setpoint error) times the update time interval. When approximating the derivative using a first difference, the derivative estimate is the
           difference in successive setpoint error values divided by the update time interval.*/
           
           /****************** Réponse Proportionnelle *****************/
           $seterr[$i] = sdk_prop($temp[$i],$setpoint,$thresh) ;
           /*********** Réponse dérivée : Single pole filter ***********/
           $state[$i] = sdk_spf($temp[$i],$deriv[$i],$lagcut,$lagstate[$i]) ;
           saveVariable($state, $state[$i]) ;
           /************************ Résultats *************************/
           $pidout[$i] = -$kp[$i] * sdk_pid($seterr[$i],$state[$i],$integral[$i],$dur,$ti[$i],$td[$i]) ;
           $pidout[$i] = round($pidout[$i] * 2.0) / 2.0 ; // Round to nearest half
           saveVariable($der, $temp[$i]) ; // to avoid the setpoint level spikes
           /****** Soft Integrator Anti-Windup (if outside limits) ******/
           $new_int[$i] = sdk_int($pidout[$i],$maxi[$i],$mini[$i],$integral[$i],$seterr[$i],$anti_w) ;
           saveVariable($int, $new_int[$i]) ;
           /******  Ajustements aux limites ******/
           $pidout[$i] = sdk_out($pidout[$i],$maxi[$i],$mini[$i]) ;
//           echo "PIDOUT ".$i." = ".$pidout[$i]."<br>" ;
      }
      saveVariable('previous', $sec) ; // Heure du calcul
     
     
      /********************* Réglages radiateurs *************************/
        for ($i = 1; $i <= $nb_rad ; $i++) {  // Indices des radiateurs   
            if ($i == 1) { $k = $i ; } else { $k = $i - 1 ; } // Dans cet exemple il y a 2 radiateurs pour la pièce n°1 (ADAPTER A CHAQUE CONFIGURATION !!!)
            $CR[$i] = sdk_val($idsetpt[$i]) ; // Consignes des radiateurs
            if ($CR[$i] != $pidout[$k]) { setValue($idsetpt[$i], $pidout[$k]) ; } // Consigne       
        }

      break;
}

?>
Herbert
 
Messages : 31
Inscription : 16 Août 2018

Re: [Script] Régulation PID d'un chauffage par pompes à chal

Messagepar jeremy51 » 10 Avr 2019 19:55

c'est moi ou le script n'est pas sur le store ??
jeremy51
 
Messages : 47
Inscription : 09 Mars 2019

Re: [Script] Régulation PID d'un chauffage par pompes à chal

Messagepar RAR69 » 11 Avr 2019 09:43

jeremy51 a écrit:c'est moi ou le script n'est pas sur le store ??

Le script est juste au dessus de ton post. Regarde mieux dans la fenêtre avec ascenseur...
Roland
J'en ai rêvé, Thibautg16, Havok, P@t, merguez07, Xeos, influman et MaDomotic les ont fait !!!
RAR69
 
Messages : 539
Inscription : 30 Août 2018
Localisation : Rhône


Retour vers Scripts & Périphériques du store

Qui est en ligne ?

Utilisateurs parcourant ce forum : Aucun utilisateur inscrit et 68 invité(s)