home castoo
chapitre electronique
Electronique PAC

Optimisation PAC
Mesure temperature et asservissement
variateur vitesse de moteur

avril 2025

Tentative d'optimisation de la souflerie d'une PAC

Je partage ce petit projet car je pense que le code C++ et la solution électronique utilisée ici peuvent apporter des idées dans plusieurs configurations.

Un ami souhaite optimiser sa pompe à chaleur en régulant le flux d’air de sortie vers les pièces de la maison. Ce flux d’air est généré par un moteur triphasé, l’idée est donc de faire varier la vitesse de celui-ci en fonction de températures mesurées à différents endroits de l’installation. Le type de PAC est une pompe à chaleur eau eau (aussi appelée pompe à chaleur géothermique car elle utilise le principe de la géothermie), la chaleur générée par la PAC est distribuée dans l’habitation par une soufflerie.

Voilà le constat que fait mon ami :
«Lorsque la température descend à 0°C environ, le fonctionnement continu de la PAC se stabilise avec une température de sortie d’eau de 33°C alors qu’elle chauffe de l’eau jusqu’à 44°C. La température d’équilibre dans l’échangeur est encore plus basse et sans doute aux alentours de 30°C. L’air chaud distribué dans toute la maison peut donc être proche de la température de consigne et avec la vitesse de diffusion, l’impression de chaleur est réduite. Plus l’écart de température est faible avec la température existante, plus le temps de chauffage est long. La PAC travaille toujours à pleine puissance car la géothermie permet d’avoir l’énergie nécessaire toujours disponible mais la diffusion de l’air chaud n’est pas optimisée et le taux de recyclage de l’air n’est pas géré.»

Mon objectif dans un premier temps va donc consister à concevoir un système capable de mesurer plusieurs températures et de commander l’asservissement du moteur triphasé. Une fois les mesures effectuées pendant quelques temps il sera alors possible de déterminer la stratégie de régulation à mettre en œuvre dans le code du processeur…

Pour mesurer les températures je vais utiliser des sondes de type DS18B20 elles ont la particularité de posséder une adresse unique et elle peuvent donc être utilisées en réseau. Elles s’alimentent en 5v, sont capable de mesurer des températures de -55°C à +125°C et elles sont très économiques.
PAC sonde de temperature DS18B20
Pour asservir le moteur je vais utiliser un petit module qui transforme une modulation de fréquence (PWM) en une tension variable entre 0 et 10v. J'ai testé deux modeles avec des résultats différents.
PAC Convertisseur PWM vers 0 a 10v PAC Convertisseur PWM vers 0 a 10v
Cette tension commande un variateur de vitesse triphasé du commerce qui utilise la PWM pour moduler le 380v du secteur vers le moteur triphasé. ce système contrairement à une variation de la tension permet de conserver du couple même à basse vitesse. Le modèle sélectionné est disponible sur le marché chinois à un prix très compétitif et il possède donc la possibilité d’être asservie par une tension analogique de 0 à 10v.
PAC Regulateur de tension PAC Regulateur de tension PAC Regulateur de tension

Un petit processeur type ESP8266-12 Wemos D1 Pro est utilisé, son antenne wifi extérieur devrait permettre de relever les mesures d’un peu plus loin que le sous-sol. Il permet de creer un petit serveur wifi en mode AP afin de gerer l'interface IHM. La mémoire et la vitesse de ce type de microprocesseur sont largement suffisants pour l’utilisation envisagée de plus le prix est très abordable. Ce circuit possède une mémoire EEPROM qui sera utile pour mémoriser certaines informations en cas de coupure de courant. Les 60 dernières mesures sont sauvegardées pour jusqu’à 8 sondes. Le délai entre deux mesures est parametrable entre 2 secondes et une heure ceci afin de pouvoir élaborer une stratégie de régulation dans un deuxième temps.
PAC ESP 8266 Wemos D1 Pro
Afin de suivre les horaires des mesures un circuit horloge est également utilisé. Le circuit fonctionne en mode I2C et est protégé par une pile qui garantie une sauvegarde de l’heure pendant environ 5 ans.
PAC circuit horloge


J’ai également prévu un petit boîtier imprimable en 3D.
PAC Boitier 3D

Les fichiers STL pour l'impression sont disponibles dans la partie "Impression3D" du site.



Le schéma du montage est celui-ci :
PAC chema electronique

Un petit jeu de LEGO en modules electronique


PAC Détail electronique
Les fils des sondes (jusqu'à 8) sont rassemblés sur le connecteur ainsi qu'un petit cable qui permettra l'asservissement du variateur de vitesse du moteur.

L'interface accessible en html est simple.
Selectionner le SSID Wifi : CHAUFFAGE-AP
Passe : 12345678
Adresse : http://192.168.4.1


PAC Menu HTML

Aprés avoir saisie l'adresse dans un navigateur le menu principal suivant apparait.



PAC Menu HTML EEPROM

En cas de coupure secteur il est important que l'asservissement puisse redémarer dans de bonnes conditions. C'est pourquoi des données essentiels sont sauvegardées dans une mémoire non volatile. Le nom des différentes sondes et le délai entre deux mesures. La valeur des seuils de la PWM sera sauvegardée dans une prochaine version...



PAC Menu HTML délai entre les mesures

Il est possible de régler le délai entre deux mesures entre 2 secondes et 1 heure. Ceci afin de donner le maximum de possibilité pour déterminer l'utilité de chaque sonde. Le délai est exprimé en seconde.



PAC Menu HTML Sonde

Cet écran donne l'adresse réseau des sondes et il permet d'attribuer un nom à chacune des sondes.



PAC Menu HTML mesures

Cet écran est trés important dans la premiere phase de la recherche d'optimisation, c'est lui qui va permettre d'élaborer les différents parametres pour gerer l'asservissement de la soufflerie.
Il est possible de suivre la date et l'heure des mesures ainsi que les valeurs de 8 sondes.



PAC Menu HTML Graphique

Le graphique donne une vue générale des valeurs des sondes aux horaires de captures.



PAC Menu HTML Mise à l'heure

Il est important de connaitre l'horaire des mesures, en principe l'horloge interne est à jour, mais avec les changement d'horaire été/hiver il est possible ici de se resynchroniser.



PAC Menu HTML Modif PWM

Il est possible ici de tester la conversion PWM vers la tension de commande du variateur 0 à 10v. Cette variation sera gerer aprés la formulation de l'optimisation par une formule qui tiendra compte des différentes mesures des sondes et le processeur l'appliquera automatiquement au variateur du moteur triphasé de la soufflerie.




Le code de base qui doit vous permettre de démarrer pour vos propres projets...
Il y a des choses à modifier pour l'optimiser mais il sera revu lorsque l'optimisation sera effective...


        #include <Arduino.h>
        #include <Wire.h>
        #include <OneWire.h>
        #include <DallasTemperature.h>
        #include <ESP8266WiFi.h>
        #include <ESP8266WebServer.h>
        #include <RTClib.h>
        #include "ESP_EEPROM.h"
        
        RTC_DS3231 rtc;
        DateTime timeinfo;
        unsigned long temps_qui_passe; // Mesure le delai depuis la derniere mesure
        int nb_sec_inter_mesure = 10; // temps en seconde entre deux mesure
        unsigned long temp_t; // nb_sec_inter_mesure * 10000 pour résultat en milliseconde (évite un warning de compilation)
        
        #define MAXTABTEMP 60
        struct gr_temp { // Info du graph
          uint8_t v_j; // jour (1 - 31)
          uint8_t v_h; // heure (0 - 23)
          uint8_t v_m; // minute (0 - 60)
          uint8_t v_s; // seconde (0 - 60)
          float v_t1; // temp1
          float v_t2; // temp2
          float v_t3; // temp2
          float v_t4; // temp4
          float v_t5; // temp5
          float v_t6; // temp6
          float v_t7; // temp7
          float v_t8; // temp8
        };
        gr_temp tgr_temp[MAXTABTEMP];
        
        float val_mesure[8];
        
        #define PinPWM D5 // Gpio PWM commande moteur convertion 0-10v
        int val_PWM = 255; // PWM au max
        //int i2c_sda = D2; // I2C 
        //int i2c_scl = D1; // I2C
        #define ONE_WIRE_BUS D6 // Gpio capteur temperature
        #define TEMPERATURE_PRECISION 12 // (avant ct 9) resolution
        OneWire oneWire(ONE_WIRE_BUS);
        DallasTemperature sensors(&oneWire);
        int nb_capteur;
        DeviceAddress tempDeviceAddress;
        bool capteur_HS = false;
        
        #define MAXTCAPTEUR 8
        struct t_capt { // tableau des capteurs
          uint8_t v_n;      // num
          uint8_t v_ad[8];  // adresse
        //  char v_nom[11];     // nom 10 car max
          String v_coul;    // couleur
        };
        t_capt tb_capteurs[MAXTCAPTEUR];
        
        // Définir les informations du point d'accès
        const char* ap_ssid = "CHAUFFAGE-AP"; // Nom du réseau Wi-Fi de l'ESP8266
        const char* ap_password = "12345678"; // Mot de passe du réseau Wi-Fi
        
        // Créer une instance du serveur web
        ESP8266WebServer server(80);
        
        // Variables pour les valeurs des capteurs
        float temperature;
        float humidity;
        
        String const v_css_body = "body { background-color: grey; font-family: Sans-Serif; Color: orange; text-align: center; display: flex; justify-content: center; align-items: center; height: 100vh;}";
        String const v_css_lien = "a.btn { text-decoration: none; padding: 10px; font-family: arial; font-size: 1em; color: #FFFFFF; background-color: #b2a2c7; border-radius: 24px;-webkit-border-radius: 24px; -moz-border-radius: 24px; border: 4px solid #ffffff; box-shadow: 3px 3px 8px #444444; -webkit-box-shadow: 3px 3px 8px #444444; -moz-box-shadow: 3px 3px 8px #444444; }";
        String const v_css_lien_vol = "a.btn:hover { padding: 10px; color: #ffff00; background-color: #5f497a; border: 4px solid #fbd5b5; box-shadow: 1px 1px 4px #777777; -webkit-box-shadow: 1px 1px 4px #777777; -moz-box-shadow: 1px 1px 4px #777777; }";
        
        // gestion de l'eeprom
        struct s_memo_rom {
            int delai_inter_mesure; // Delai entre deux mesures de temperature 
          int seuil_min_pwm; // Seuil mini pour la PWM du moteur
          int seuil_max_pwm; // Seuil maxi pour la PWM du moteur
            char nom_s[8][10]; // Nom des sondes
        } memo_rom, v_ram;
        int adr_deb_memo_rom = 0;
        
        // Affiche compte-rendu enregistrement eeprom
        String aff_eeprom(){
          String html_s = "<!DOCTYPE html>";
          html_s += "<html lang='fr'><head> <meta charset='UTF-8'><head>";
          html_s += "<style> " + v_css_lien + v_css_lien_vol + " </style>";
          html_s += "<title>Lecture EEPROM</title></head>";
          html_s += "<body  style='background-color: grey; font-family: Sans-Serif; Color: orange; text-align: center;'><br/><br/><br/>";
          html_s += "<table align='center' border='2'>";
          html_s += "<caption><h1>Liste des données mémorisées (en cas de coupure secteur)</h1></caption>";
          html_s += "<tr><td>Délai entre les mesures de température : </td><td>" + String(v_ram.delai_inter_mesure) + "</td></tr>";
          html_s += "<tr><td>Seuil minimum PWM commande moteur : </td><td>" + String(v_ram.seuil_min_pwm) + "</td></tr>";
          html_s += "<tr><td>Seuil maximum PWM commande moteur : </td><td>" + String(v_ram.seuil_max_pwm) + "</td></tr>";
          for(int i=0; i<8; i++)  html_s += "<tr><td>Nom capteur 0 : </td><td>" + String(v_ram.nom_s[i]) + "</td></tr>";
          html_s += "</table><br/><br/><br/>";
          html_s += "<p align='center'><br/><br/><a class='btn' href='/' target='_self'>Retour Accueil</a></p>";
          html_s += "<p align='center'><br/>Ces données doivent permettre un redémarrage autonome suite à une coupure secteur, les autres données peuvent être recalculées.</p>";
          html_s += "</body></html>";
          return html_s;
        }
        
        // Lecture eeprom
        void lecture_eeprom(){
            EEPROM.begin(sizeof(s_memo_rom));
            delay(300);
            EEPROM.get(adr_deb_memo_rom, memo_rom);
            delay(300);
            v_ram.delai_inter_mesure = memo_rom.delai_inter_mesure;
            v_ram.seuil_min_pwm = memo_rom.seuil_min_pwm;
            v_ram.seuil_max_pwm = memo_rom.seuil_max_pwm;
          for(int i=0; i<8; i++) strcpy(v_ram.nom_s[i], memo_rom.nom_s[i]);
            EEPROM.end();
            delay(300);
            aff_eeprom();
        }
        
        // Ecriture eeprom
        void ecriture_eeprom(){
            EEPROM.begin(sizeof(s_memo_rom));
            delay(300);
            memo_rom.delai_inter_mesure = v_ram.delai_inter_mesure;
            memo_rom.seuil_min_pwm = v_ram.seuil_min_pwm;
            memo_rom.seuil_max_pwm = v_ram.seuil_max_pwm;
          for(int i=0; i<8; i++) strcpy(memo_rom.nom_s[i], v_ram.nom_s[i]);
            EEPROM.put(adr_deb_memo_rom, memo_rom);
            delay(300);
            EEPROM.commit();
            delay(300);
            aff_eeprom();
            EEPROM.end();
            delay(300);
        }
        
        // -----------------------------------------------------------------------------------------------------------------------------------
        // Initialise la totalité des tableaux de variables
        void vinit_tab_val(){
          for(int v_adr = 0; v_adr <= MAXTABTEMP-1; v_adr++){
            tgr_temp[v_adr].v_j = 0;
            tgr_temp[v_adr].v_h = 0;
            tgr_temp[v_adr].v_m = 0;
            tgr_temp[v_adr].v_s = 0;
            tgr_temp[v_adr].v_t1 = 0;
            tgr_temp[v_adr].v_t2 = 0;
            tgr_temp[v_adr].v_t3 = 0;
            tgr_temp[v_adr].v_t4 = 0;
            tgr_temp[v_adr].v_t5 = 0;
            tgr_temp[v_adr].v_t6 = 0;
            tgr_temp[v_adr].v_t7 = 0;
            tgr_temp[v_adr].v_t8 = 0;
          }
        }
        
        String htmldateheure() {
          String html_s = "<!DOCTYPE html>";
          html_s += "<html lang='fr'><head> <meta charset='UTF-8'><head>";
          html_s += "<style> " + v_css_lien + v_css_lien_vol + " </style>";
          html_s += "<title>Initialisation Date / Heure</title></head>";
          html_s += "<body  style='background-color: grey; font-family: Sans-Serif; Color: orange; text-align: center;'><br/><br/><br/>";
          html_s += "<table align='center'>";
          html_s += "<caption><h1>Définir la date et l'heure</h1></caption><tr><td>";
          html_s += "<form action='/setDateTime' method='POST'>";
          html_s += "<br/>Date et Heure (YYYY-MM-DDTHH:MM):";
          html_s += "<input type='datetime-local' name='datetime'></br><br/><br/><br/></td></tr>";
          html_s += "<tr><td><br/><br/><br/><button>Valider la modification</button> <input type='reset' value='Annuler la modification' /></td></tr>";
          html_s += "</table><br/><br/><br/><br/></form>";
          html_s += "<p align='center'><br/><br/><a class='btn' href='/' target='_self'>Retour Accueil</a></p>";
          html_s += "</body></html>";
          return html_s;
        }
        
        String modif_delai_m(){
          String html_s = "<html lang='fr'><head> <meta charset='UTF-8'><head>";
          html_s += "<title>Modif delai 2 dates</title>";
          html_s += "<style> " + v_css_lien + v_css_lien_vol + " </style></head>";
          html_s += "<body style='background-color: grey; font-family: Sans-Serif; Color: orange; text-align: center;'><br/><br/><br/>";
          html_s += "<table width='80%' align='center'><caption><h1>Définir le délai entre deux mesures</h1></caption><tr><td>";
          html_s += "<form action='/setDelaiMesure' method='POST'>";
          html_s += "Délai en seconde (mini 2 secondes - maxi 3600):";
          html_s += "<input type='number' name='v_d_mesure' min='2' max='3600' value='" + String(nb_sec_inter_mesure) + "'/></br><br/><br/><br/>";
          html_s += "</td></tr><tr><td><button>Valider la modification</button> <input type='reset' value='Annuler la modification' /></td></tr>";
          html_s += "</form></table><br/><br/><br/><br/>";
          html_s += "<p align='center'><br/><br/><a class='btn' href='/' target='_self'>Retour Accueil</a></p>";
          html_s += "</body></html>";
          return html_s;
        }
        
        String modif_PWM(){
          String html_s = "<html lang='fr'><head> <meta charset='UTF-8'><head>";
          html_s += "<title>Modif PWM</title>";
          html_s += "<style> " + v_css_lien + v_css_lien_vol + " </style></head>";
          html_s += "<body style='background-color: grey; font-family: Sans-Serif; Color: orange; text-align: center;'><br/>";
          html_s += "<table width='80%' align='center'><caption><h1>Modification de la vitesse du moteur</h1></caption>";
          html_s += "<tr><td>Valeur PWM : " + String(val_PWM) + "</br><br/><br/><br/></td></tr>";
          html_s += "<tr><td align='center'><form action='/inc_vit'><button><h1>+</h1></button> Augmenter la vitesse.</form></td></tr> ";
          html_s += "<tr><td align='center'><form action='/dec_vit'><button><h1>-</h1></button> Diminuer la vitesse.</form></td></tr><br/><br/>";
          html_s += "</table><br/><br/><br/><br/>";
          html_s += "<p align='center'><br/><br/>La vitesse du moteur est commandée par une tension qui varie de 0 à 10 volts (10 volts -> Vitesse max).</p>";
          html_s += "<br/><br/><p align='center'><br/><br/><a class='btn' href='/' target='_self'>Retour Accueil</a></p>";
          html_s += "</body></html>";
          return html_s;
        }
        
        // --------- Demande de page inexistante ------
        void page_inexistante() {
            String page_inexist = "Page inexistante";
            page_inexist += "<br/>URL: ";
            page_inexist += server.uri();
            page_inexist += "<br/>Method: ";
            page_inexist += (server.method() == HTTP_GET) ? "GET" : "POST";
            page_inexist += "<br/>Arguments: ";
            page_inexist += server.args();
            page_inexist += "<br/>";
            for (uint8_t i = 0; i < server.args(); i++) { page_inexist += " " + server.argName(i) + ": " + server.arg(i) + "<br/>"; }
            server.send(404, "text/plain", page_inexist);
            #ifdef debug_sur_usb
                Serial.println("Envoi : page_inexistante");
            #endif
        }
        
        void printAddress(DeviceAddress deviceAddress)
        {
          // exemple : 28FF5246601705AF
          for (uint8_t i = 0; i < 8; i++)
          {
            if (deviceAddress[i] < 16) Serial.print("0");
            Serial.print(deviceAddress[i], HEX);
          }
        }
        
        String adr_string(DeviceAddress deviceAddress)
        {
          // exemple : 28FF5246601705AF
          char ch_temp[20];
          sprintf(ch_temp, "0x%02x%02x%02x%02x%02x%02x%02x%02x",
                  deviceAddress[0],
                  deviceAddress[1],
                  deviceAddress[2],
                  deviceAddress[3],
                  deviceAddress[4],
                  deviceAddress[5],
                  deviceAddress[6],
                  deviceAddress[7]
                );
                Serial.print(ch_temp);
                return String(ch_temp);
        }
        
        String page_capteurs() {
          // Page de description des capteurs
          String html_s = "<html lang='fr'><head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'><title>PAC Chauffage</title>";
          html_s += "<style> " + v_css_body + v_css_lien + v_css_lien_vol + " </style>";
          html_s += "</head><body>";
          html_s += "<table width='80%' border=2>";
          html_s += "<caption><h1>Identification des Capteurs</h1></caption>";
          html_s += "<tr><th align='center'>Numero</th>";
          html_s += "<th align='center'>Adresse</th>";
          html_s += "<th align='center'>Nom</th>";
          html_s += "<th align='center'>Modif.</th></tr>";
          html_s += "<form action='/setNomCapt' method='POST'>";
          for (uint8_t i = 0; i < 8; i++){
            html_s += "<tr><td align='center'>";
            if(nb_capteur > i) html_s += String(tb_capteurs[i].v_n);
            html_s += "</td><td align='center'>" +  adr_string(tb_capteurs[i].v_ad) + "</td>";
            html_s += "<td align='center'>" + String(v_ram.nom_s[i]) + "</td>";
            html_s += "<td align='center'>";
            String v_temp;
            if(nb_capteur > i){  v_temp = String(v_ram.nom_s[i]); html_s += "(de 4 à 10 car.) <input type='text' id='nom_capt_" + String(i) + "' name='nom_capt_" + String(i) + "' minlength='4' maxlength='10' value='" + v_temp + "'/>";}
            html_s += "</td></tr>";
          }
          html_s += "<tr><td align='center' colspan='4'><br/><p align='center'><a class='btn' href='/' target='_self'>Retour Accueil</a> <button>Valider les modifications</button> <input type='reset' value='Annuler les modifications' /><br/><br/></p></td></tr>";
          html_s += "</form>";
          html_s += "</table>";
          html_s += "</body></html>";
          return html_s;
        }
        
        String page_valeurs(){
          // Page de description des capteurs
          String html_s = "<html lang='fr'><head> <meta charset='UTF-8'><title>PAC Chauffage</title>";
          html_s += "<style> " + v_css_lien + v_css_lien_vol + "</style>";
          html_s += "</head><body style='background-color: grey; font-family: Sans-Serif; Color: orange; text-align: center;'>";
          html_s += "<div align='center'><table width='80%' border=2>";
          html_s += "<caption><h1>Valeurs des Capteurs</h1></caption>";
          html_s += "<tr>";
          html_s += "<th width='10%' align='center'>Jour</th>";
          html_s += "<th width='10%' align='center'>Heure</th>";
          html_s += "<th width='10%' align='center'>Minute</th>";
          html_s += "<th width='10%' align='center'>Seconde</th>";
          for(int i=0; i<8; i++)  html_s += "<th width='5%' align='center'>" + String(v_ram.nom_s[i]) + "</th>";
          html_s += "</tr>";
          for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
            if(tgr_temp[vadr].v_j){
                html_s += "<tr>";
                html_s += "<td width='10%' align='center'>" + String(tgr_temp[vadr].v_j) + "</td>";
                html_s += "<td width='10%' align='center'>" + String(tgr_temp[vadr].v_h) + "</td>";
                html_s += "<td width='10%' align='center'>" + String(tgr_temp[vadr].v_m) + "</td>";
                html_s += "<td width='10%' align='center'>" + String(tgr_temp[vadr].v_s) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t1) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t2) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t3) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t4) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t5) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t6) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t7) + "</td>";
                html_s += "<td width='5%' align='center'>" + String(tgr_temp[vadr].v_t8) + "</td>";
                html_s += "</tr>";
            }
          }
          html_s += "</table></div><p align='center'><br/><br/><a class='btn' href='/' target='_self'>Retour Accueil</a></p>";
          html_s += "</body></html>";
          return html_s;
        }
        
        String page_graph() {
          // Page graph
          String html_s = "<html lang='fr'><head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'><title>PAC Chauffage</title>";
          html_s += "<style>";
          html_s += ".chart-container { width: 600px; height: 400px; margin: 0 auto;}";
          html_s += ".chart {position:relative; width: 600px; height: 500px; background-color: #fff; border: 1px solid #ccc; margin:0 auto;}";
          html_s += ".line-chart {width: 100%; height: calc(100% - 80px);}";
          html_s += ".y-axis {position: absolute; top: 20px; bottom: 30px; left: 20; width: 40px; height: 390; display: flex; flex-direction: column; justify-content: space-between;}";
          html_s += ".y-tick {text-align: right; font-size: 12px; padding-right: 5px; font-weight: bold; color: #999;}";
          html_s += ".x-axis {position: absolute; bottom: 5px; left: 70px; right: 40px; height: 20px; display: flex; justify-content: space-between;}";
          html_s += ".x-axis2 {position: absolute; bottom: 20px; left: 70px; right: 40px; height: 20px; display: flex; justify-content: space-between;}";
          html_s += ".x-axis3 {position: absolute; bottom: 35px; left: 70px; right: 40px; height: 20px; display: flex; justify-content: space-between;}";
          html_s += ".x-axis4 {position: absolute; bottom: 50px; left: 70px; right: 40px; height: 20px; display: flex; justify-content: space-between;}";
          html_s += ".x-tick {font-size: 12px; text-align: center; font-weight: bold; color: #999;}";
          html_s += "</style>";
          html_s += "</head><body style='background-color: grey;'>";
          html_s += "<div class='chart-container'><div class='chart'>";
          html_s += "<svg viewBox='0 0 150 120' preserveAspectRatio='none' class='line-chart'>";
          html_s += "<line x1='0' y1='7' x2='150' y2='7' stroke='#f1f1f1' stroke-width='0.5'/><line x1='0' y1='25.5' x2='150' y2='25.5' stroke='#f1f1f1' stroke-width='0.5'/><line x1='0' y1='44' x2='150' y2='44' stroke='#f1f1f1' stroke-width='0.5'/>";
          html_s += "<line x1='0' y1='62.5' x2='150' y2='62.5' stroke='#f1f1f1' stroke-width='0.5'/><line x1='0' y1='81' x2='150' y2='81' stroke='#f1f1f1' stroke-width='0.5'/><line x1='0' y1='99.5' x2='150' y2='99.5' stroke='#f1f1f1' stroke-width='0.5'/><line x1='0' y1='118' x2='150' y2='118' stroke='#f1f1f1' stroke-width='0.5'/>";
          html_s += "<polyline fill='none' stroke='" + tb_capteurs[0].v_coul + "' troke-width='0.7' points='";
          for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
            if(tgr_temp[vadr].v_j){
                html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t1);
            }
          }
          html_s += "'/>";
          if(nb_capteur > 1){
            html_s += "<polyline fill='none' stroke='" + tb_capteurs[1].v_coul + "' troke-width='0.7' points='";
            for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
              if(tgr_temp[vadr].v_j){
                  html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t2);
              }
            }
            html_s += "'/>";
          }
          if(nb_capteur > 2){
            html_s += "<polyline fill='none' stroke='" + tb_capteurs[2].v_coul + "' troke-width='0.7' points='";
            for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
              if(tgr_temp[vadr].v_j){
                  html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t3);
              }
            }
            html_s += "'/>";
          }
          if(nb_capteur > 3){
            html_s += "<polyline fill='none' stroke='" + tb_capteurs[3].v_coul + "' troke-width='0.7' points='";
            for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
              if(tgr_temp[vadr].v_j){
                  html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t4);
              }
            }
            html_s += "'/>";
          }
          if(nb_capteur > 4){
            html_s += "<polyline fill='none' stroke='" + tb_capteurs[4].v_coul + "' troke-width='0.7' points='";
            for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
              if(tgr_temp[vadr].v_j){
                  html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t5);
              }
            }
            html_s += "'/>";
          }
          if(nb_capteur > 5){
            html_s += "<polyline fill='none' stroke='" + tb_capteurs[5].v_coul + "' troke-width='0.7' points='";
            for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
              if(tgr_temp[vadr].v_j){
                  html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t6);
              }
            }
            html_s += "'/>";
          }
          if(nb_capteur > 6){
            html_s += "<polyline fill='none' stroke='" + tb_capteurs[6].v_coul + "' troke-width='0.7' points='";
            for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
              if(tgr_temp[vadr].v_j){
                  html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t7);
              }
            }
            html_s += "'/>";
          }
          if(nb_capteur > 7){
            html_s += "<polyline fill='none' stroke='" + tb_capteurs[7].v_coul + "' troke-width='0.7' points='";
            for(int vadr=0; vadr <= MAXTABTEMP-1; vadr++){
              if(tgr_temp[vadr].v_j){
                  html_s += " " + String(20 + (vadr*2)) + "," + String(100-tgr_temp[vadr].v_t8);
              }
            }
            html_s += "'/>";
          }
          html_s += "</svg>";
          html_s += "<div class='y-axis'><div class='y-tick'>100</div><div class='y-tick'>80</div><div class='y-tick'>60</div><div class='y-tick'>40</div><div class='y-tick'>20</div><div class='y-tick'>0</div><div class='y-tick'>-20</div></div>";
          html_s += "<div class='x-axis'>";
          for(int vadr=0; vadr <= MAXTABTEMP-1; vadr+=10){
            html_s += "<div class='x-tick'>S:" + String(tgr_temp[vadr].v_s) + "</div>";
          }
          html_s += "</div><div class='x-axis2'>";
          for(int vadr=0; vadr <= MAXTABTEMP-1; vadr+=10){
            html_s += "<div class='x-tick'>M:" + String(tgr_temp[vadr].v_m) + "</div>";
          }
          html_s += "</div><div class='x-axis3'>";
          for(int vadr=0; vadr <= MAXTABTEMP-1; vadr+=10){
            html_s += "<div class='x-tick'>H:" + String(tgr_temp[vadr].v_h) + "</div>";
          }
          html_s += "</div><div class='x-axis4'>";
          for(int vadr=0; vadr <= MAXTABTEMP-1; vadr+=10){
            html_s += "<div class='x-tick'>J:" + String(tgr_temp[vadr].v_j) + "</div>";
          }
          html_s += "</div></div>";
          html_s += "</div><br><br><br><br><br><br><br><br>";
          html_s += "<table align='center'><tr>";
          for(uint8_t i = 0; i < 8; i++){
            html_s += "<td>" + String(v_ram.nom_s[i]) + " : <font color='" + tb_capteurs[i].v_coul + "'>=====</font></td>";
            if(i==3) html_s += "</tr><tr>";
          }
          html_s += "</tr></table>";
          html_s += "<br><br><p align='center'><a class='btn' href='/' target='_self'>Retour Accueil</a>";
          html_s += "</body></html>";
          return html_s;
        }
        
        String handleRoot() {
          // Serve la page principale (HTML)
          String html_s = "<html lang='fr'><head> <meta charset='UTF-8'><title>PAC Chauffage</title>";
          html_s += "<style> " + v_css_body +  v_css_lien + v_css_lien_vol + "</style>";
          html_s += "</head><body><table width='100%'>";
          html_s += "<caption><h1>Optimisation PAC</h1></caption>";
          if(capteur_HS){
            html_s += "<tr><td align='center'><br/><h1>PROBLEME INIT CIRCUIT HORLOGE ===>>>> FAIRE UN MARCHE ARRET !</h1><br/></td></tr>";
          }
          html_s += "<tr><td align='center' width='100%'>";
          html_s += "Nous sommes le " + String(timeinfo.day()) + "/" + String(timeinfo.month()) + "/" +  String(timeinfo.year());
          html_s += " il est " + String(timeinfo.hour()) + " H " + String(timeinfo.minute());
          html_s += "</td></tr>"; 
          html_s += "<tr><td align='center'><br/><p><a class='btn' href='/dh' target='_self'>Modif date / heure</a></p><br/></td></tr>";
          html_s += "<tr><td align='center'><br/><p><a class='btn' href='/aff_eeprom' target='_self'>Affichage données EEPROM</a></p><br/></td></tr>";
          html_s += "<tr><td align='center'>Nb maximum valeurs mémorisées : " + String(MAXTABTEMP) + "</td></tr>";
          html_s += "<tr><td align='center'>Délai entre deux mesures : " + String(nb_sec_inter_mesure) + " (secondes)</td></tr>";
          html_s += "<tr><td align='center'><br><p align='center'><a class='btn' href='/modif_delai_m' target='_self'>Modifier délai entre 2 mesures</a></p></td></tr> ";
          html_s += "<tr><td align='center'><br><p align='center'><a class='btn' href='/init_tab_val' target='_self'>Init tableau mesures</a></p></td></tr> ";
          html_s += "<tr><td align='center'><br><br>Nb de capteur detecté : " + String(nb_capteur) + "</td></tr>";
          html_s += "<tr><td align='center'><br><p align='center'><a class='btn' href='/capteur' target='_self'>Liste des capteurs</a></p></td></tr>";
          html_s += "<tr><td align='center'>";
            html_s += "<br><br><a class='btn' href='/page_graph' target='_self'>Graphique</a>  ";
            html_s += "<a class='btn' href='/page_valeurs' target='_self'>Liste des valeurs</a>";
          html_s += "</td></tr>";
          html_s += "<tr><td align='center'>";
            html_s += "<br><br><a class='btn' href='/modif_PWM' target='_self'>Modif. vitesse moteur</a>  ";
          html_s += "</td></tr></table>";
          html_s += "</body></html>";
          return html_s;
        }
        
        void printTemperature(DeviceAddress deviceAddress)
        {
          float tempC = sensors.getTempC(deviceAddress);
          if (tempC == DEVICE_DISCONNECTED_C)
          {
            Serial.println("Capteur en erreur");
            return;
          }
          Serial.print(" => ");
          Serial.print(tempC);
          Serial.println("°");
        }
        
        void memo_mesure(){
          uint8_t  vjour, vheure, vminute, vseconde;
          // décallage vers le bas de tout le tableau
          for(int vadr = MAXTABTEMP - 2; vadr >= 0; vadr--){
            tgr_temp[vadr+1].v_j = tgr_temp[vadr].v_j;
            tgr_temp[vadr+1].v_h = tgr_temp[vadr].v_h;
            tgr_temp[vadr+1].v_m = tgr_temp[vadr].v_m;
            tgr_temp[vadr+1].v_s = tgr_temp[vadr].v_s;
            tgr_temp[vadr+1].v_t1 = tgr_temp[vadr].v_t1;
            tgr_temp[vadr+1].v_t2 = tgr_temp[vadr].v_t2;
            tgr_temp[vadr+1].v_t3 = tgr_temp[vadr].v_t3;
            tgr_temp[vadr+1].v_t4 = tgr_temp[vadr].v_t4;
            tgr_temp[vadr+1].v_t5 = tgr_temp[vadr].v_t5;
            tgr_temp[vadr+1].v_t6 = tgr_temp[vadr].v_t6;
            tgr_temp[vadr+1].v_t7 = tgr_temp[vadr].v_t7;
            tgr_temp[vadr+1].v_t8 = tgr_temp[vadr].v_t8;
          }
          // Memo derniere valeur
          vjour = timeinfo.day(); 
          vheure = timeinfo.hour();
          vminute = timeinfo.minute();
          vseconde = timeinfo.second();
          tgr_temp[0].v_j = vjour;
          tgr_temp[0].v_h = vheure;
          tgr_temp[0].v_m = vminute;
          tgr_temp[0].v_s = vseconde;
          tgr_temp[0].v_t1 = val_mesure[0];
          tgr_temp[0].v_t2 = val_mesure[1];
          tgr_temp[0].v_t3 = val_mesure[2];
          tgr_temp[0].v_t4 = val_mesure[3];
          tgr_temp[0].v_t5 = val_mesure[4];
          tgr_temp[0].v_t6 = val_mesure[5];
          tgr_temp[0].v_t7 = val_mesure[6];
          tgr_temp[0].v_t8 = val_mesure[7];
        }
        
        void modif_param_post() {
          if (server.hasArg("datetime")) {
              String dateTime = server.arg("datetime"); // Récupère la valeur du formulaire
              int year, month, day, hour, minute;
              sscanf(dateTime.c_str(), "%d-%d-%dT%d:%d", &year, &month, &day, &hour, &minute);
              // Met à jour l'horloge RTC
              rtc.adjust(DateTime((uint16_t)year, (uint8_t)month, (uint8_t)day, (uint8_t)hour, (uint8_t)minute, 0));
              timeinfo = rtc.now();
        
          }
          server.send(200, "text/html", handleRoot());
        }
        
        void setDelaiMesure() {
          if (server.hasArg("v_d_mesure")) {
              int v_d_mesure = server.arg("v_d_mesure").toInt(); // Récupère la valeur du formulaire
              nb_sec_inter_mesure = v_d_mesure;
              v_ram.delai_inter_mesure = nb_sec_inter_mesure;
              temp_t = nb_sec_inter_mesure * 1000;
              ecriture_eeprom();
          }
          server.send(200, "text/html", handleRoot());
        }
        
        void inc_PWM() {
          if(val_PWM<255){
            val_PWM += 10;
            if(val_PWM>255) val_PWM = 255;
            analogWrite(PinPWM, val_PWM);
          }
          server.send(200, "text/html", modif_PWM());
        }
        
        void dec_PWM() {
          if(val_PWM>1){
            val_PWM -= 10;
            if(val_PWM<1) val_PWM = 1;
            analogWrite(PinPWM, val_PWM);
          }
          server.send(200, "text/html", modif_PWM());
        }
        
        
        void setNomCapt() {
          String v_nom;
          if(server.hasArg("nom_capt_0")) {v_nom = server.arg("nom_capt_0"); v_nom.toCharArray(v_ram.nom_s[0], v_nom.length()+1);}
          if(server.hasArg("nom_capt_1")) {v_nom = server.arg("nom_capt_1"); v_nom.toCharArray(v_ram.nom_s[1], v_nom.length()+1);}
          if(server.hasArg("nom_capt_2")) {v_nom = server.arg("nom_capt_2"); v_nom.toCharArray(v_ram.nom_s[2], v_nom.length()+1);}
          if(server.hasArg("nom_capt_3")) {v_nom = server.arg("nom_capt_3"); v_nom.toCharArray(v_ram.nom_s[3], v_nom.length()+1);}
          if(server.hasArg("nom_capt_4")) {v_nom = server.arg("nom_capt_4"); v_nom.toCharArray(v_ram.nom_s[4], v_nom.length()+1);}
          if(server.hasArg("nom_capt_5")) {v_nom = server.arg("nom_capt_5"); v_nom.toCharArray(v_ram.nom_s[5], v_nom.length()+1);}
          if(server.hasArg("nom_capt_6")) {v_nom = server.arg("nom_capt_6"); v_nom.toCharArray(v_ram.nom_s[6], v_nom.length()+1);}
          if(server.hasArg("nom_capt_7")) {v_nom = server.arg("nom_capt_7"); v_nom.toCharArray(v_ram.nom_s[7], v_nom.length()+1);}
          ecriture_eeprom();
          server.send(200, "text/html", handleRoot());
        }
        
        void setup() {
            Serial.begin(115200);
            #ifndef ESP8266
              while (!Serial);
            #endif
            delay(500);
            if (! rtc.begin()) {
              Serial.println("On ne trouve pas le circuit Horloge RTC");
              capteur_HS = true;
              delay(2000);
              if (! rtc.begin()) {
                Serial.println("On ne trouve toujours pas le circuit Horloge RTC");
                capteur_HS = true;
                delay(1000);
              }else{
                capteur_HS = false;
              }
            }
            Serial.println("Circuit Horloge RTC OK");
            lecture_eeprom();
            delay(1000);
            analogWriteFreq(2000); // Frequence de PWM à 2 Khz
            analogWrite(PinPWM, 255); // Moteur lancé à fond
            //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Date et heure compilation mettre en comment
            Wire.begin();
            WiFi.softAP(ap_ssid, ap_password); // ESP8266 en mode Point d'Accès
            Serial.println("Point d'Accès démarré");
            Serial.print("Adresse IP: ");
            Serial.println(WiFi.softAPIP()); // L'adresse IP sera généralement 192.168.4.1
            // Configurer les routes du serveur
            server.on ( "/",  [](){ server.send(200, "text/html", handleRoot()); });
            server.on ( "/dh",  [](){ server.send(200, "text/html", htmldateheure()); });
            server.on ( "/page_valeurs",  [](){ server.send(200, "text/html", page_valeurs()); });
            server.on ( "/capteur",  [](){ server.send(200, "text/html", page_capteurs()); });
            server.on ( "/page_graph",  [](){ server.send(200, "text/html", page_graph()); });
            server.on ( "/modif_delai_m",  [](){ server.send(200, "text/html", modif_delai_m()); });
            server.on ( "/modif_PWM",  [](){ server.send(200, "text/html", modif_PWM()); });
            server.on ( "/aff_eeprom",  [](){ server.send(200, "text/html", aff_eeprom()); });
            server.on("/setDateTime", modif_param_post);
            server.on("/setNomCapt", setNomCapt);
            server.on("/setDelaiMesure", setDelaiMesure);
            server.on("/inc_vit", inc_PWM);
            server.on("/dec_vit", dec_PWM);
            server.on("/init_tab_val", vinit_tab_val);
            server.onNotFound(page_inexistante);
            server.begin(); // Démarrer le serveur web
            Serial.println("Serveur démarré");
            sensors.begin();
            nb_capteur = sensors.getDeviceCount();
            Serial.print("Locating devices...");
            Serial.print("Found ");
            Serial.print(nb_capteur, DEC);
            Serial.println(" devices.");
            Serial.print("Parasite power is: ");
            if (sensors.isParasitePowerMode()) Serial.println("ON");
            else Serial.println("OFF");
            for (int i = 0; i < nb_capteur; i++)
            {
              if (sensors.getAddress(tempDeviceAddress, i))
              {
                Serial.print("Capteur ");
                Serial.print(i, DEC);
                tb_capteurs[i].v_n = i;
                Serial.print(" adresse : ");
                printAddress(tempDeviceAddress);
                for (uint8_t n = 0; n < 8; n++){ tb_capteurs[i].v_ad[n] = tempDeviceAddress[n]; }
                Serial.println();
                Serial.print("Resolution ");
                Serial.println(TEMPERATURE_PRECISION, DEC);
                sensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION);
                Serial.print("Resolution actually set to: ");
                Serial.print(sensors.getResolution(tempDeviceAddress), DEC);
                Serial.println();
              } else {
                Serial.print("Capteur ");
                Serial.print(i, DEC);
                Serial.print(" pas d'adresse detectee.");
              }
            }
            String v_defaut = "Sonde_x"; bool v_init = false;
            for(uint8_t x=0; x<8; x++){ if(v_ram.nom_s[x][0]=='0'){ v_defaut.toCharArray(v_ram.nom_s[x], v_defaut.length()+1); v_init=true; }}
            if(v_init) ecriture_eeprom();
            tb_capteurs[0].v_coul = "Orange";    
            tb_capteurs[1].v_coul = "Yellow";    
            tb_capteurs[2].v_coul = "Green";    
            tb_capteurs[3].v_coul = "Purple";    
            tb_capteurs[4].v_coul = "Pink";    
            tb_capteurs[5].v_coul = "Blue";    
            tb_capteurs[6].v_coul = "Magenta";    
            tb_capteurs[7].v_coul = "Cyan";    
            timeinfo = rtc.now();
            temps_qui_passe = millis();
            nb_sec_inter_mesure = v_ram.delai_inter_mesure;
            temp_t = nb_sec_inter_mesure*1000;
            vinit_tab_val();
        }
        
        void loop() {
            for(int x = 0; x < 8; x++){ val_mesure[x] = 0; }
            timeinfo = rtc.now();
            if((millis() - temps_qui_passe) >= temp_t){
                sensors.requestTemperatures();
                for (int i = 0; i < nb_capteur; i++){
                  if (sensors.getAddress(tempDeviceAddress, i)){
                    val_mesure[i] = sensors.getTempC(tempDeviceAddress);
                    //printTemperature(tempDeviceAddress); 
                  }
                }
                memo_mesure();
                temps_qui_passe = millis();
              }
            server.handleClient(); // Gérer les requêtes HTTP
        }
        
    



Une petite video pour montrer les difficultés rencontrées avec le premier convertisseur PWM vers tension 0 à 10v.



accueil electronique

Bricolage Robotique Informatique Peinture Voyage
Téléc. portail Le robot "mécano" Astuces informatique Henri Bertrou Auvergne
Bat. Iphone 6S Le robot "solaire" Réseau couche app. Jean-Michel Castille Floride
Robot piscine Servo et IR" Réseau Les couches New York
Xiaomi M365 Le robot "thymio" Réseaux Outils L'Ouest américain
Mac Mini Le robot "Rovio" Unités grandeur inf. L'Ile Maurice
Putty SSH Windows L'Italie / Venise
Bases Raspberry Tunisie
Termius IPhone/IPad Grece
Le vieux ZX 81
...
Navigation La Rochelle CNC / Imp3D Electronique Programmation
Rencontre dauphins Les Minimes Construction CNC Alim. TPL 5110 Doc. programme
Analyse NMEA 0183 Le Vieux port CNC du commerce Carte ESP8266 Indent programme
graph. NMEA 0183 L'Ile de Ré Martyr CNC ESP8266 1 relai Prog. objet
Analyse trames AIS A visiter Réa. imp. 3D ESP8266 Alarme Prog. procédurale
Analyse AIS TCP-IP Cura impression 3D ESP8266 MQTT
Sortie en ketch Plateau CR10 ESP8266 Temp.
Echange GPS C80 Anémomètre.
HP Sun-Odyssey CNC / 3D en vrac MCP9808 Librairie
LCD yanmar Saisie Oled
Testeur nmea esp1 i2c