/* Ce programme permet la gestion d'un radiateur à travers le logiciel Domoticz Il assure également le suivi de la consommation et de la temperature. Il permet aussi un pilotage autonome du radiateur en fonction de la temperature: Fenetre ouverte ou apport de chaleur dans la pièce. ecrit en février 2017 par P.BUSSIERE Modification Consommation octobre 2017 V2 Ameliorations V3 Novembre 2017 - Detection fenetre ouverte - Envoi temperature et puissance toutes les 5 minutes - Sauvegarde kwh dans EEPROM Decembre 2018 Portage vers plateforme ESP8266 Ajout affichage OLED Ajout page HTM Avril 2019 ajout Autoconnect et OTA Mai 2020 Utilisation du protocole MQTT */ #include // Inclusion de la librairie OneWire #include #include #include #include #include //Serveur pour pouvoir recevoir les données #include #include #include #include #include //*******************parametres communs************* const char* ssid = "your_SSID"; const char* password = "Your_password"; const char* mqtt_server = "192.168.1.19"; //adresse Ip du server Mosquito //#define mqtt_user "guest" //s'il a été configuré sur Mosquitto //#define mqtt_password "guest" //idem const char *topicin = "domoticz/in"; const char *topicout = "domoticz/out"; char msgToPublish[MQTT_MAX_PACKET_SIZE+1]; //long lastMsg = 0; //Horodatage du dernier message publié sur MQTT //long lastRecu = 0; //*******************parametres a modifier pour chaque carte************* const char* myhost ="cuisine";//Nom sur le réseau String domonane ="Cuisine"; // Nom du selecteur Domoticz il faut utiliser le meme nom que le selecteur dans Domoticz const int idxl = 40; //idx selecteur radiateur const int idxt = 39; //idx temper const int idxp = 41; //idx puissance String piece ="Radiateur Cuisine"; #define DS18B20 0x28 // Adresse 1-Wire du DS18B20 #define MY_PROBE A0 //Capteur de courant #define OPTO_1 D6 // Pilotage des optocoupleurs GPIO12 et 13 #define OPTO_2 D7 #define PIN_ONEWIRE D4 //Gpio 2 String version = "2.0"; uint32_t montemps; int Level; // Niveau de chauffage 4 niveaux sont gérés par Domoticz: 0 = arret, 10 = hors gel, 20 = éco, 30 = confort static int consigne = 22; // passe en mode réduit si la temperature depasse ce maximum float oldtemp = 20; // temperature de depart float seuil; int sensorValue; // valeur restituée par le capteur de courant int sensorMax = 0; int sensorMin = 700; // valeur de l'offset int timer; int mytimer; int timer_30; int puissance; String jtemp; String jpwr; String jkwh; String jlevel; String jmessage; String mystate; //String mapiece(myhost); //convertit le nom en string static float conso; static float cumul_temp ; static int cumul_puissance; float kwh; float temp =20.00; float delta ; int variation = 0; long rssi; boolean flagTemp= false; boolean flag = false; boolean flag_win = false; boolean chauffe = false; byte data[9], addr[8]; // data :scratchpad,addr: adresse du DS 18B20 //****************************Page HTML***************************** String getPage(){ String page = ""; page += ""; page += piece; page += ""; page += ""; page += "

"; page += piece; page += "

DS18B20

"; page += "
  • Temperature : "; page += temp; page += "ºC
  • "; page += "

Consommation

"; page += "
  • Puissance instantanée :
  • "; page += puissance; page += " W
  • Puissance consommée : "; page += kwh; page += " KWh
"; page += "

Niveau

"; page += "
"; page += "
  • (etat: "; page += mystate; page += ")"; page += "Arret"; page += "Hors Gel"; page += "Eco"; page += "Confort
"; page += ""; page += "

www.projetsdiy.fr

"; page += ""; return page; } OneWire ds(PIN_ONEWIRE); // Création de l'objet OneWire ds //HTTPClient http; WiFiClient espClient; PubSubClient client(espClient); ESP8266WebServer server (80);//ouvre le port de communication pour recevoir //Declaration des fonctions void fenetre(); void SetLed (int Level); float lecprom(); void clearprom(); void saveprom(float kwhvalue); void sendDomoticz(String myjson); void initTemp(); void startTemp(); float readTemp(); void affichage(String niveau, float mytemp, int mypwr) ; void handleRoot(); void callback(char* topic, byte* payload, unsigned int length); void reconnect(); //**************************Setup*********************************** void setup() { EEPROM.begin(5); //clearprom(); //Reinitialise le compteur de KWH pinMode(OPTO_1, OUTPUT); //commande fil pilote pinMode(OPTO_2, OUTPUT); Serial.begin(115200); //Serial.setDebugOutput(true); WiFi.mode(WIFI_STA); Serial.println("Connecting Wifi..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.print(WiFi.localIP()); // Affiche la qualié du signal reçu rssi = WiFi.RSSI(); Serial.print("RSSI:"); Serial.println(rssi); delay(2000); //WiFi.hostname(myhost); ArduinoOTA.setHostname(myhost); //OTA ArduinoOTA.begin(); client.setServer(mqtt_server, 1883); //Configuration de la connexion au serveur MQTT client.setCallback(callback); //La fonction de callback qui est executée à chaque réception de message reconnect();// connexion MQTT Level = EEPROM.read(1);//recupere l'etat du radiateur SetLed(Level); //{"command": "switchlight", "idx": 45, "switchcmd": "Set Level", "level": 0 } jlevel ="{\"command\": \"switchlight\", \"idx\":" +String(idxl)+ ", \"switchcmd\": \"Set Level\", \"level\":" +String(Level)+"}"; Serial.println(jlevel); sendDomoticz(jlevel); float oldvalue = lecprom(); //Lit la derniere valeur des KWh Serial.print("Ancienne valeur kwh "); Serial.println (oldvalue); kwh = oldvalue; initTemp();//Recherche et initialise le capteur de temperature server.on ( "/status", handleRoot ); //affiche la page d'info server.on("/reset",[]() { server.send(200,"text/plain","Reset systeme..."); delay(1000); ESP.restart(); }); server.begin(); montemps = millis(); } //***********************Boucle princpale*************************** void loop() { server.handleClient(); ArduinoOTA.handle(); // Surveillance des demandes de mise à jour en OTA if(!client.connected()) //Connexion au MQTT { reconnect(); } client.loop();//**********************boucle pour le MQTT********************** if((millis() - montemps) > 1000) //Si une seconde s'est écoulée { if(flagTemp==true) //Si une mesure de temperature est en cours { readTemp(); // On lit et on acquite flagTemp =false; } montemps = millis(); //On recharge le compteur une seconde uint32_t start_time = millis(); //on charge les compteurs d'échantillonage while ((millis() - start_time) < 100) //On echantillone sur 100 ms { sensorValue = analogRead(MY_PROBE); if (sensorValue > sensorMax) sensorMax = sensorValue;//enregistre la valeur maximale du capteur if (sensorValue < sensorMin) sensorMin = sensorValue;//enregistre la valeur minimmale du capteur } float courant; if ((sensorMax - sensorMin) < 25) {sensorMax = sensorMin;}// On elimine le bruit pour un tres faible courant courant = (((sensorMax - sensorMin) * 0.0063) / 0.282);// Calcul de la valeur efficace (valeurmax -valeurmin) * pas de conversion)/ 2*racine(2))*10 if (courant>=1.00) {chauffe=true;} //Affichage logo chauffe else {chauffe=false;} sensorMax = 0; //on remet à zéro les valeurs sensorMin = 700; puissance = (230 * courant); // P=UI On calcule la puissance et la conso instantanée conso += puissance; // On cumule dans conso sur une minute timer++; if (timer == 30) //on mesure la temperature toutesles 30 secondes { startTemp();//mesure de la Temperature fenetre(); Serial.print("courant "); Serial.println(courant); } if (timer == 60) // au bout d'une minute { //cumul_puissance = cumul_puissance + (conso / 60); cumul_puissance = (cumul_puissance + puissance); kwh = kwh + (conso / 3600000);//On enregistre des Kw startTemp();//mesure de la Temperature cumul_temp = (cumul_temp + temp); mytimer++; // compteur pour 5 minutes if (mytimer == 5) // on envoie temp et puissance a 5mn { timer_30++; float temp_moy = (cumul_temp / 5); int puissance_moy = ( cumul_puissance / 5); rssi = WiFi.RSSI(); int signal = ((rssi+100)*2/10);// calcule la qualité du signal WIFI -40 = 12, -100 = 0 signal = signal>=12 ? 12 :signal; // Si signal sup ou égal à 12 il vaut 11 sinon il garde sa valeur //envoi la temperature au logiciel sous la forme {"idx" : 48,"nvalue" : 0,"svalue" :"25.3","RSSI" : 10,} jtemp = "{\"idx\" : "+ String(idxt) + ",\"nvalue\" : 0, \"svalue\" : \"" +String(temp_moy) + "\",\"RSSI\" :" +String(signal)+",}"; sendDomoticz(jtemp); // envoi puissance_moy et kwh au logiciel sous la forme {"idx" : 47,"nvalue" : 0,"svalue" :"850;512.34"} float domokwh= (kwh*1000); //Multiplie par 1000 pour etre compatible DOMOTICZ jpwr = "{\"idx\" : "+ String(idxp) + ",\"nvalue\" : 0, \"svalue\" : \"" +String(puissance_moy)+";"+String(domokwh) + "\"}"; sendDomoticz(jpwr); cumul_puissance = 0; cumul_temp = 0; mytimer = 0; //reset du timer } if (timer_30 == 6) //au bout de 30 minutes (6* 5mn) { //sauvegarde dans l'EEPROM float oldpwr = lecprom(); //on lit la dernière valeur Serial.print("Lecprom "); Serial.println(oldpwr); if (int(oldpwr) < int(kwh)) saveprom(kwh); // Si valeur entiere differente on sauve timer_30 = 0; //on reset timer 30 mn } fenetre(); //On remet a zero la conso et le timer timer = 0; conso = 0; } //fin timer 60 }//fin seconde } //fin de loop //*********************Gestion fenetre ouverte********************** void fenetre() { // pilotage autonome par la temperature et gestion fenetre ouverte delta = (oldtemp - temp); //Calcule la variation de temperature Serial.print("Temperature de reference : "); Serial.println(oldtemp); Serial.print(" delta "); Serial.println (delta); if (delta > 0 && delta > 0.8) {variation++;} else {variation = 0;} oldtemp = temp; Serial.print(" variation "); Serial.println (variation); // on étudie la variation de temperature si on est a la baisse // plus de trois mesure la fenetre est ouverte if ((variation >= 2) && Level == 30 && temp < 18) // Fenetre ouverte { flag_win = true; Level = 10; //Mode hors gel seuil = oldtemp; Serial.println("Passage en reduit fenetre"); //************************Message dans les logs********************************************************* jmessage= "{\"command\" : \"addlogmessage\", \"message\" : \""+ domonane +" passage en reduit fenetre a :"+String(temp)+" °C\" }"; sendDomoticz(jmessage); //************************************************Informe la passerelle*********************************** jlevel ="{\"command\": \"switchlight\", \"idx\":" +String(idxl)+ ", \"switchcmd\": \"Set Level\", \"level\":" +String(Level)+"}"; sendDomoticz(jlevel); SetLed(Level); //Envoi de la commande Fil pilote } if ((variation == 0) && flag_win == true) { if (temp >= seuil) //On attend de repasser au dessus d'un seuil { flag_win = false; Level = 30; Serial.println("Passage en normal fenetre "); //************************Message dans les logs********************************************************** jmessage= "{\"command\" : \"addlogmessage\", \"message\" : \""+ domonane +" passage en normal fenetre a :"+String(temp)+" °C\" }"; sendDomoticz(jmessage); //************************************************Informe la passerelle*********************************** jlevel ="{\"command\": \"switchlight\", \"idx\":" +String(idxl)+ ", \"switchcmd\": \"Set Level\", \"level\":" +String(Level)+"}"; sendDomoticz(jlevel); SetLed(Level); //Envoi de la commande Fil pilote } } //Si la temperature de la pièce dépasse la consigne on passe en réduit. if (temp > consigne && Level == 30) { // surchauffe piéce flag = true; Level = 20; Serial.println("Passage en reduit surchauffe"); //************************Message dans les logs********************************************************* jmessage= "{\"command\" : \"addlogmessage\", \"message\" : \""+ domonane +" passage en reduit surchauffe a :"+String(temp)+" °C\" }"; sendDomoticz(jmessage); //************************************************Informe la passerelle*********************************** jlevel ="{\"command\": \"switchlight\", \"idx\":" +String(idxl)+ ", \"switchcmd\": \"Set Level\", \"level\":" +String(Level)+"}"; sendDomoticz(jlevel); SetLed(Level); //Envoi de la commande Fil pilote } if (temp < consigne && flag == true) { flag = false; Level = 30; Serial.println("Passage en normal surchauffe "); //************************Message dans les logs*********************************************************** jmessage= "{\"command\" : \"addlogmessage\", \"message\" : \""+ domonane +" passage en normal surchauffe a :"+String(temp)+" °C\" }"; sendDomoticz(jmessage); //************************************************Informe la passerelle*********************************** jlevel ="{\"command\": \"switchlight\", \"idx\":" +String(idxl)+ ", \"switchcmd\": \"Set Level\", \"level\":" +String(Level)+"}"; sendDomoticz(jlevel); SetLed(Level); //Envoi de la commande Fil pilote } } //**********************Pilotage des optocoupleurs****************** void SetLed (int Level) { Serial.println("reglage led "); Serial.println(Level); if (Level == 0) //Mode arret: demi alternance positive { digitalWrite(OPTO_1, HIGH); digitalWrite(OPTO_2, LOW); mystate="Arret"; } if (Level == 10) //Mode hors gel: demi alternance négative { digitalWrite(OPTO_1, LOW); digitalWrite(OPTO_2, HIGH); mystate="Hors Gel"; } if (Level == 20) // Mode éco: pleine alternance { digitalWrite(OPTO_1, LOW); digitalWrite(OPTO_2, LOW); mystate = "Eco"; } if (Level == 30) //Mode confort: pas de signal { digitalWrite(OPTO_1, HIGH); digitalWrite(OPTO_2, HIGH); mystate = "Confort"; } EEPROM.write (1,Level); //Enregistre le dernier état dans l'EEPROM EEPROM.commit(); jmessage= "{\"command\" : \"addlogmessage\", \"message\" : \""+ domonane +" passage en mode "+ mystate +"\" }"; sendDomoticz(jmessage);//************************et informe Domoticz******************** } //***********************Lecture EEPROM***************************** float lecprom() { //On lit les 3 octets int byte1 = EEPROM.read(2); int byte2 = EEPROM.read(3); float decim = EEPROM.read(4); float decim2 = (decim / 100); //on deplace les décimales //on recrée la valeur avec les décimales float oldkwh = (((byte1 << 8) | byte2) + decim2); return oldkwh; } //***********************Effacement EEPROM************************** void clearprom() { for (int i = 1; i < 5 ; i++) //initialisation a zéro EEPROM { EEPROM.write (i, 0); EEPROM.commit(); } } //************************Ecriture EEPROM*************************** void saveprom(float kwhvalue) { int entier = int(kwhvalue); //On extrait la partie entiere // on extrait les décimales et on les multiplie par 100 float decim = ((kwhvalue - entier) * 100); //On separe en deux octets //on ne tient pas compte des 2 octets poids fort du float int Byte2 = byte(entier); int Byte1 = byte(entier >> 8); int Byte3 = byte(decim); EEPROM.write (2, Byte1); EEPROM.write (3, Byte2); EEPROM.write (4, Byte3); EEPROM.commit(); } //*************************envoi a Domoticz************************* void sendDomoticz(String myjson) { Serial.print("envoi de: "); myjson.toCharArray( msgToPublish,myjson.length()+1 ); // tranforme la chaine char array Serial.println(msgToPublish); Serial.print(" Publié dans domoticz/in. Etat= "); if ( client.publish(topicin, msgToPublish) ) Serial.println("OK message reçu"); else Serial.print("erreur : "); } //************************initialise DS18B20-*********************** void initTemp() { //initialisation DS18B20 ds.search(addr); // Recherche un module 1-Wire ds.reset(); // On reset le bus 1-Wire ds.select(addr); // On sélectionne le DS18B20 ds.write(0x4E); //for (byte i = 0; i < 3; i++) // On ecrit dans le scratchpad //ds.write(0x1F // Resolution 0.5 °C Temps conversion/8 //ds.write(0x3F);// Resolution 0.25°C Temps conversion/4 ds.write(0x0F); //On ecrit dans les deux premiers scratchpad ds.write(0x0F); //ds.write(0x5F);// Resolution 0.125°C Temps conversion/2 ds.write(0x7F); // Resolution 0.0625°C } //************************start temperature************************* void startTemp() { //ds.search(addr); // Recherche un module 1-Wire ds.reset(); // On reset le bus 1-Wire ds.select(addr); // On sélectionne le DS18B20 ds.write(0x44, 1); // On lance une prise de mesure de température flagTemp = true; } //***********************lecture temperature************************ float readTemp() { ds.reset(); // On reset le bus 1-Wire ds.select(addr); // On sélectionne le DS18B20 ds.write(0xBE); // On envoie une demande de lecture du scratchpad for (byte i = 0; i < 9; i++) // On lit le scratchpad data[i] = ds.read(); // Et on stock les octets reçus temp = ((data[1] << 8) | data[0]) * 0.0625;// Calcul de la température en degré Celsius Serial.print("Temperature : "); Serial.println(temp); return temp; } //****************reponse a une requete HTTP************************* void handleRoot(){ Serial.print("Envoi page html"); server.send ( 200, "text/html", getPage() ); } //****Déclenche les actions à la réception d'un message************** void callback(char* topic, byte* payload, unsigned int length) { String message = ""; for ( unsigned int i = 0; i < length; i++) message += (char)payload[i];//on lit la totalité du message //*************************mode Json Parser***************************************************** //Serial.print(message); StaticJsonDocument<1024> doc; DeserializationError error = deserializeJson(doc, message); // Test if parsing succeeds. if (error) { Serial.print(F("deserializeJson() failed: ")); Serial.println(error.c_str()); return; } // decode la valeur String nom = doc["name"]; int valeur =doc["svalue1"]; if (nom ==domonane) // si c'est ce qu'on attend { Level=valeur; SetLed(Level); //******************************Envoi la commande au radiateur********* } }// fin de callback //********************************Reconnexion************************ void reconnect() { while (!client.connected()) { Serial.print("Tentative de connexion MQTT..."); if (client.connect(myhost)) { jmessage= "{\"command\" : \"addlogmessage\", \"message\" : \"connexion de "+ domonane +" version :"+ version + "\" }"; Serial.print("Connexion au serveur MQTT..."); sendDomoticz(jmessage); client.subscribe("domoticz/out"); } else { Serial.print("KO, erreur : "); Serial.print(client.state()); Serial.println(" On attend 5 secondes avant de recommencer"); delay(5000); } } }