Bonjour, ci-après mon programme, il y a certainement plein de choses qui ne serviront pas à d'autres, et des choses à améliorer (je suis dessus!), mais il fonctionne très bien depuis quelques jours déjà. Pour info, j'ai un compteur Linky avec tarif Heures pleines et Heures creuses.
Si cela peut inspirer ou servir à d'autres...
// V1.1 du 15 au 27/03/2020 (confinement!!)
// par Pierre avec les infos et la Lib de Charles pour la Téléinfo,
// et d'autres pour les autres librairies
/* Ce programme reçoit les infos de téléinfo (TIC) d'un compteur électrique
* et les renvoie par WiFi après traitement à un brocker MQTT.
* Il se connecte aussi à un serveur NTP afin d'avoir la date et l'heure.
* Il sauvegarde, au changement de jour, les index des compteurs HC et HP dans la mémoire SPIFFS
* afin de pouvoir les retrouver si reset ou coupure de courant. Ces index servent à calculer la
* consommation du jour en cours.
* Il calcule la conso du jour HC et HP, le cumul HP+HC et envoie le tout par MQTT.
*/
#include <SoftwareSerial.h>
#include <LibTeleinfo.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include <NtpClientLib.h> // attention, version 2.5.1, les supérieures ne fonctionnent pas!
#include <TimeLib.h>
#include <ESP8266WiFi.h>
#include <FS.h>
//*********************** Déclaration des constantes et variables globales utilisées *********************
//********************************************************************************************************
const char* ssid = "freeP";
const char* password = "3493493493";
//NTP config
String ServeurNTP = "ntp.unice.fr";
// MQTT Configuration
IPAddress serverIPAddress(xxx,xxx,xxx,xxx);
const int PortMQTT = xxxx;
WiFiClient espClient;
PubSubClient client(espClient);
String clientId = "Linky"; //Nom du client MQTT
const char *TopicConso = "/Exterieur/Maison/Conso"; // consommation instantanée (PAPP)
const char *TopicHCHP = "/Exterieur/Maison/HCHP"; // Index heures pleines (HCHP)
const char *TopicHCHC = "/Exterieur/Maison/HCHC"; // index heures creuse (HCHC)
const char *TopicHPJ = "/Exterieur/Maison/HPJ"; // Conso actuelle du jour heures pleines
const char *TopicHCJ = "/Exterieur/Maison/HCJ"; // Conso actuelle du jour heures creuses
const char *TopicHJTotal= "/Exterieur/Maison/HJTotal"; // Conso du jour total (HC + HP)
const char *TopicHPJT = "/Exterieur/Maison/HPJT"; // Total du jour heures pleines
const char *TopicHCJT = "/Exterieur/Maison/HCJT"; // Total du jour heures creuses
// WIFI Configuration
IPAddress MonIP(192,168,xxx,xxx);
IPAddress gateway(192, 168, xxx, xxx);
IPAddress subnet(255, 255, 255, 0);
IPAddress dns(192, 168, xxx, xxx);
//On défini un deuxième port serie (entrée RX GPIO 13, Sortie TX GPIO 15)
// seule l'entrée sert à recevoir la trame Linky
SoftwareSerial Serie2(13,15);
TInfo tinfo;
int NbLoop = 0; //compteur pour reset si perte Wifi en MQTT
String Conso="";
String JourActuel="";
char *LastHCHP="";
char *LastHCHC="";
char *DebJourHP="005614000"; // au 27/03/20
char *DebJourHC="003488000"; // idem
long HCHPj;
long HCHCj;
//********************************************* SETUP ****************************************************
//********************************************************************************************************
void setup()
{
//On arrete le serveur Wifi qui fonctionne souvent par défaut!!
WiFi.softAPdisconnect (true);
// On passe en mode station, et on se connecte au réseau existant.
// en mode DHCP afin de pouvoir se connecter au serveur NTP
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
delay(5000); // attente pour l'établissement des connexions WIFI
Serial.begin(9600); // configure la sortie série "normale" à 9600 Bauds
Serial.println("début");
// config et init NTP et jour
NTP.begin (ServeurNTP,1,true); // Pour NTP
NTP.setInterval(86400); // on interroge le serveur NTP toutes les 24h (86400 s)
delay(3000); // attente pour réponse serveur NTP
Serial.println (NTP.getTimeDateString ());
JourActuel = NTP.getDateStr ();
if (JourActuel=="01/01/1970"){ // il arrive parfois que le serveur NTP ne réponde pas,
Serial.print ("Reset par soft");// dans ce cas, la date s'initialise au 01/01/1970,
ESP.restart(); // donc, on recommence (un peu brutal, mais cela fonctionne!!)
// on pourrait aussi re-interroger le serveur, ou augmenter le delai...
}
// Config SPIFFS
SPIFFS.begin();
File f = SPIFFS.open("/config.txt", "r"); // ouverture du fichier en mémoire pour y lire les valeurs
if (!f) { // sauvegardées (DebJourHP et DebJourHC)
Serial.println("file open failed");
}
else{
while(f.available()) {
//lecture ligne par ligne du fichier
f.readStringUntil('\n').toCharArray(DebJourHP,10);
Serial.print("DebJourHP lu en mémoire = ");Serial.println(DebJourHP);
f.readStringUntil('\n').toCharArray(DebJourHC,10);
//String DebJourHCs = f.readStringUntil('\n');
Serial.print("DebJourHC lu en mémoire = ");Serial.println(DebJourHC);
f.close();
}
}
// reconfig WIFI pour IP fixe si besoin
// mais si on passe en IP fixe, le NTP ne fonctionne plus!
//WiFi.config(MonIP,gateway,subnet,dns);
//WiFi.begin(ssid, password);
//delay(5000); // attente pour l'établissement des connexions WIFI
//préparation du mode OTA pour pouvoir modifier le programme par WIFI
ArduinoOTA.setHostname("Un Nom");
ArduinoOTA.setPassword("Un Mot de passe");
ArduinoOTA.begin();
// reset à la fin de l'OTA :
ArduinoOTA.onEnd([]() {
ESP.restart();
});
// connect to the MQTT server
client.setServer(serverIPAddress, PortMQTT);
// Configure Teleinfo Soft serial pour Linky
Serie2.begin(1200);
// Init teleinfo
tinfo.init();
// Attache la procédure de callback
tinfo.attachData(DataCallback);
} // Fin de setup
//*************************************** Boucle principale **********************************************
//********************************************************************************************************
void loop()
{
//gestion de l'OTA
ArduinoOTA.handle();
if ( Serie2.available() )
tinfo.process(Serie2.read());
} // fin de loop
//*************************************** Fonctions et procédures ****************************************
//********************************************************************************************************
//procédure appelée suite à une interruption:
void DataCallback(ValueList * me, uint8_t flags)
{
// Display values
String Nom="";
Nom = me->name;
Conso = me->value;
// si le jour à changé, on publie la valeur calculée de la conso totale du jour passé
// puis on réactualise la valeur de début de journée qui sert à remettre à zéro l'index du jour,
// et on sauvegarde cette valeur en mémoire SPIFFS, pour pouvoir la retrouver en cas de reset.
// on remet les HC du jour à 0
String J = NTP.getDateStr ();
if (J != JourActuel) {
JourActuel = J; // on remet le jour à la valeur actuelle
publie(TopicHPJT,atol(LastHCHP)- atol(DebJourHP)); // Publie la conso HP totale du jour (Wh)
DebJourHP = LastHCHP;
publie(TopicHCJT,atol(LastHCHC)- atol(DebJourHC)); // Publie la conso HC totale du jour (Wh)
DebJourHC = LastHCHC;
// les 4 lignes suivantes sont nécessaires car chez moi, lorsqu'on change de jour, on est en
// Heures Pleines, donc il faut remettre à 0 les heures creuses du jour, sinon elles ne se remettent à 0
// que vers 1 h du matin, lorsqu'on passe en Heures Creuses
long valeur = atol(LastHCHC)- atol(DebJourHC); // Calcule la conso HC actuelle du jour (mise à 0)
HCHCj = valeur; // Conserve la conso du jour pour la conso totale
publie(TopicHCJ,valeur); // publie la conso actuelle HC du jour (Wh)
publie (TopicHJTotal,HCHPj+HCHCj); // publie la somme HC+HP (Wh)
//Sauvegarde (dans un fichier qui a été crée auparavant):
File f = SPIFFS.open("/config.txt", "w");
if (!f) {
Serial.println("Echec d'ouverture du fichier");
}
else{
f.println(DebJourHP);
f.println(DebJourHC);
f.close();
Serial.print("DebJourHP = ");Serial.println(DebJourHP);
Serial.print("DebJourHC = ");Serial.println(DebJourHC);
Serial.println("Valeurs sauvegardées");
}
}
// si PAPP a changé, on publie
if (Nom=="PAPP"){
Serial.print(me->name);Serial.print(" = "); Serial.print(me->value);
if (client.connect("clientId")) {
Serial.print(" Publié à "); Serial.println (NTP.getTimeStr ());
client.publish(TopicConso, me->value); //publie la conso instantanée (en Watt,ou plus exactement en VA)
} // on peut publier directement, car me->value retourne un Char array
else {
connectMQTT(me->value);
}
}
// si HCHP a changé, on publie
if (Nom=="HCHP"){
Serial.print(me->name);Serial.print(" : ");Serial.println(me->value);
if (client.connect("clientId") ) {
client.publish(TopicHCHP,me->value); // publie l'index HP
LastHCHP = me->value; // conserve la valeur
long valeur = atol(me->value)- atol(DebJourHP); // Calcule la conso actuelle du jour
HCHPj = valeur; // Conserve la conso du jour pour la conso totale
publie(TopicHPJ,valeur); // publie la conso actuelle HP du jour (Wh)
publie (TopicHJTotal,HCHPj+HCHCj); // publie la somme HC+HP (Wh)
}
}
// idem pour HCHC
if (Nom=="HCHC"){
Serial.print(me->name);Serial.print(" : ");Serial.println(me->value);
if (client.connect("clientId") ) {
client.publish(TopicHCHC,me->value); // publie l'index HC
LastHCHC = me->value; // conserve la valeur
//Calcule la conso du jour
long valeur = atol(me->value)- atol(DebJourHC); // Calcule la conso actuelle du jour
HCHCj = valeur; // Conserve la conso du jour pour la conso totale
publie(TopicHCJ,valeur); // publie la conso actuelle HC du jour (Wh)
publie (TopicHJTotal,HCHPj+HCHCj); // publie la somme HC+HP (Wh)
}
}
} // fin de DataCallback
//*********************************
// connect to MQTT server si besoin
void connectMQTT(char *PAPP) {
// Wait until we're connected
while (!client.connected()) {
if (client.connect("clientId")) {
Serial.println(" Linky reconnecté");
client.publish(TopicConso, PAPP);
}
else {
Serial.printf("MQTT failed, state %s, retrying...\n", client.state());
// Wait before retrying
delay(2000);
// reset soft car perte wifi parfois
++NbLoop ;
if (NbLoop > 10){
Serial.print ("Reset par soft");
ESP.restart();
}
}
}
//client.disconnect();
} // fin de connectMQTT
//**********************************
//transforme la valeur en Char array et publie
// les valeurs qui sont déjà en Char array sont publiées sans passer par cette procédure
void publie(const char *Topic, long valeur ){
String ValeurP = String(valeur);
Serial.print(ValeurP); Serial.print(Topic);
char PourPublier[ValeurP.length()+1];
ValeurP.toCharArray(PourPublier, ValeurP.length()+1);
client.publish(Topic, PourPublier);
Serial.print(" Publié à ");
Serial.println (NTP.getTimeDateString());
} // fin de publie