S-Electricity : connecter son compteur d’électricité en Wifi dans Constellation – le remake d’S-Energy avec un ESP8266
Présentée en Février 2015 lors des Microsoft Techdays, S-Energy est une solution de monitoring des ressources énergétiques (eau, électricité et gaz) connectée dans Constellation conçue fin 2014 suite à une fuite d’eau sur ma chaudière.
Avec Constellation, la plateforme d’interconnexion des objets connectés, applications et services, cela me permet d’afficher les consommations en temps sur des dashboards comme S-Panel en quelques lignes de code, de faire des analyses statiques avec Excel, Cacti ou même Kibana/ElasticSearch comme présenté ici, ou encore de l’alerting sur Smartphone ou via le système sonore de la maison aussi expliqué ici et j’en passe 😉
Pour lire ou relire l’article complet dédié à S-Energy afin connaitre tous les détails techniques du projet ou encore les interactions dans Constellation rendez-vous sur la page : S-Energy : la solution de monitoring des ressources énergétiques de la maison – Geek Is In Da House 2015 – quand la maison vous fera sortir de votre douche, de gré ou de force !
Vous pouvez aussi revivre la présentation du projet donnée lors de la “Geek Is In Da House” des Microsoft Techdays 2015 de Paris :
Dans cet article nous allons découvrir comment réaliser un capteur avec un ESP8266 pour connecter son compteur d’électricité électromécanique à Constellation directement par Wifi.
Rappel : mesure de l’électricité d’un compteur électromécanique avec un Arduino connecté à Constellation
La partie la plus importante et compliquée du projet S-Energy fut la mesure du compteur d’eau réalisée par un mécanisme de reconnaissance optique (OCR) du compteur d’eau via un Rapsberry et sa caméra “Pi Cam” (relire).
Pour la gaz, il s’agit d’un simple compteur d’impulsion attaché au compteur et pour l’électricité : un opto-interrupteur ! En effet, ayant un vieux compteur électromécanique, il n’y a qu’un seul moyen de comptabiliser la consommation électrique : compter le nombre de tour du disque !
Pour rappel, un opto-interrupteur est composé d’une partie émetteur (une LED infrarouge) et une partie récepteur pour “recevoir” ce signal IR. J’ai donc coupé en deux l’opto-interrupteur et l’ai collé côté à cote :
Il est ensuite fixé face au disque métallique du compteur électrique :
De cette façon, le faisceau IR est dirigé vers le disque métallique du compteur. Comme celui-ci est métallique, le faisceau IR est réfléchit comme vous pouvez le voir sur la photo ci-dessus (en violet). De ce fait la partie “récepteur” de l’opto-interrupteur, lui aussi face au disque, est capable de le “voir”. Branché sur une entrée analogique, on mesure la quantité de lumière IR reçue par l’émetteur.
Sur ces vieux compteurs électromécaniques, il y a sur le disque métallique une bande “noire” d’un centimètre environ, une sorte de marqueur qui nous permet de savoir quand le disque fait une révolution complète.
Ainsi lorsque que cette bande noire passe devant le faisceau, celui-ci n’est plus réfléchit donc le récepteur ne reçoit plus de lumière ce qui se traduit par une baisse soudaine de la mesure. On peut donc en déduire que le disque à fait une révolution. Dans mon cas, une révolution correspond à 4Wh consommés.
On peut ainsi compter le nombre de révolution pour connaitre la consommation cumulée mais aussi chronométrer le temps d’une révolution pour en déduire la « consommation courante » (la puissance active demandée).
Dans l’architecture réalisée fin 2014, l’opto-interrupteur etait piloté par un Arduino Mini qui se chargait de compter et chronométrer les révolutions du disque.
A chaque révolution il envoyait ces deux informations (le n° du tour et le temps de révolution à la millième de seconde) au Raspberry “S-Energy” connecté à Constellation.
La communication entre l’Arduino et le Raspberry se basait sur des modules nRF24L01+ : communication sans-fil par radio-fréquence en 2,4 Ghz.
Les raisons du “remake”
Installé fin 2014, tout fonctionnait bien jusqu’au mois de Juin 2016, soit 1 an 1/2 sans problème. Seulement à partir de cette date, j’ai commencé à avoir des soucis dans la réception des données capturées par l’Arduino.
Sans que je puisse l’expliquer, l’Arduino fonctionnait très bien et continuait à détecter chaque révolution du compteur sans aucune faille (vérifiable grâce à la LED rouge sur le boitier qui s’allume à chaque révolution détectée). Seulement côté Raspberry les données étaient reçues avec beaucoup de perte.
Comme les StateObjects de S-Energy sont envoyés dans ElasticSearch (relire l’article ici), il possible d’utiliser Kibana pour visualiser le nombre de mesure dans le temps.
Vous constatez qu’il y a beaucoup de perte depuis Juin 2016 jusqu’en Octobre 2016, date à laquelle j’ai remplacé mon Arduino par un ESP8266 que je vous presente ci-après.
Si on zoom d’avantage sur cette période, disons entre le 11 Juin 2016 7h au lendemain, le 12 Juin à 20h, voici le nombre de mesures reçues par le Raspberry :
Bien entendu, le Raspberry fonctionne très bien, aucun problème de connexion à Constellation ni aucun problème dans l’exécution du package S-Energy. Les StateObjects pour le compteur d’eau ou de gaz sont corrects.
Seulement sur les données reçues du compteur électrique par l’Arduino, on a quelques trames mais la plupart du temps, on ne reçoit rien ! Ci-dessus on constate que le 11 Juin vers 21h, on a quelques minutes où tout semblait parfaitement fonctionner à en croire le nombre événements, puis une très longue pause entre 23h et 10h le lendemain où aucune trame ne fut reçues, puis à nouveau quelques trames entre 10h et 11h30 avant un nouveau silence radio !
J’ai longtemps cherché à identifier des changements de configuration dans l’environnement (nouveau meuble, nouvel équipement électronique, ou autre), j’ai aussi remplacé les modules nRF24, j’ai également changé l’orientation des antennes et même utilisé des nRF24 avec antenne externe RP-SMA (et non celles incluses sur le PCB).
Mais rien n’y fait, malgré toutes mes tentatives, je n’ai jamais compris pourquoi depuis le mois de Juin de l’an passé, la communication entre l’Arduino et le Raspberry pour la remontée des mesures du capteur électrique sont devenues stochastique avec une fiabilité très faible.
Depuis la réalisation d’S-Energy fin 2014, j’ai découvert les ESP8266 comme j’ai déjà pu vous les présenter ici ou ici, et donc courant Octobre 2016 j’ai pris la décision de remplacer l’Arduino par un ESP8266.
L’idée étant d’avoir un compteur électrique directement connecté à Constellation par le Wifi en supprimant la communication nRF24 vers le Raspberry qui opérait en tant que passerelle vers Constellation.
De là est né S-Electricity !
Mesure de l’électricité d’un compteur électromécanique avec un ESP8266 connecté en Wifi à Constellation
Jusqu’à présent le capteur ressemblait à cela :
On retrouve un Arduino Mini à droite et le module nRF24L01+ qui nous servait d’interface de communication vers le Raspberry. Ce dernier étant alimenté en 3.3V, on trouvait un régulateur LD33V avec des condensateurs :
De ce fait la migration se trouve grandement simplifiée ! Explications !
J’ai commencé par découper une plaque époxy de la même dimension pour conserver le même boitier et minimiser l’effort et les coûts.
Et j’ai simplement dessouder/ressouder les composants vers la nouvelle version “ESP” :
Voilà la nouvelle carte :
On retrouve toujours le régulateur LD33V avec les deux condensateurs 10uF pour l’alimentation de l’ESP8266, la LED rouge témoin avec sa résistance, et une résistance pour la sortie qui servira à alimenter la LED IR de l’opto-interrupteur ainsi qu’un résistance pour l’entrée analogique de l’opto-interrupteur.
Il ne reste plus qu’a repositionner la nouvelle carte dans le boitier d’origine :
Et pour finir, nous pouvons déposer un ESP8266 sur la carte :
Pour ce projet j’ai utilisé un ESP-12 que j’ai soudé sur une plaque d’adaptation. Si les ESP8266 sont nouveaux pour vous, je vous invites à lire mon dossier de présentation ici. Les ESP12 ont l’avantage d’avoir un port ADC pour exploiter une entrée analogique comme notre opto-interrupteur.
On a donc quasiment aucune perte hormis l’utilisation d’une nouvelle plaque epoxy et quelques headers pour y déposer l’ESP.
Il ne reste plus qu’à « flasher » notre firmware à l’aide d’une interface FTDI vers notre ordinateur.
Et voilà, ni vu ni connu, tout est comme avant en apparence ! La seule différence (et pas des moindre) est que maintenant ce n’est plus un Arduino qui compte le nombre de révolution et publie cette mesure via une communication RF vers un Raspberry lui même connecté à Constellation, mais un ESP8266 directement connecté sur le serveur Constellation via une connexion Wifi intégrée assurant ainsi un fonctionnement autonome.
Programmation : compter le nombre de tour et publier la mesure dans Constellation
Pour découvrir la programmation des ESP8266, je vous invite une nouvelle fois sur mon dossier de présentation ici.
Le code est relativement simple et ne fait que 150 lignes au total !
On commence par inclure les librairies Wifi et Constellation puis on déclare dans des variables le SSID et mot de passe de notre réseau Wifi et pour finir on crée le client de communication vers Constellation en spécifiant l’URI de votre Constellation, le nom de votre sentinelle et package virtuel (identifiant) et sa clé d’accès.
1 2 3 4 5 6 7 | #include <ESP8266WiFi.h> const char* ssid = "MON_SSID"; const char* password = "mon_wifi_access_key"; // Constellation client #include <Constellation.h> Constellation<WiFiClient> constellation("constellation.ajsinfo.loc", 8088, "esp-senergy", "SElectricity", "my_constellation_secret_key"); |
Vous pouvez découvrir tout cela dans le guide : Connecter un Arduino ou un ESP8266 à Constellation
Dans notre code, on va commencer par écrire le code de démarrage : la méthode « setup() ». Premièrement on configure nos deux sorties : celle pour la LED rouge (éteinte par défaut) et celle pour la LED IR (qu’on allume) :
1 2 3 4 5 | // Configure I/O pinMode(emitterPin, OUTPUT); pinMode(ledPin, OUTPUT); digitalWrite(emitterPin, HIGH); digitalWrite(ledPin, LOW); |
Ensuite on configure notre interface Wifi pour se connecter en tant que « Station » (mode client) sur notre réseau Wifi :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Set Wifi mode if (WiFi.getMode() != WIFI_STA) { WiFi.mode(WIFI_STA); delay(10); } // Connecting to Wifi Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFi connected. IP: "); Serial.println(WiFi.localIP()); |
Une fois connecté, on peut interroger Constellation pour récupérer les settings de notre package qu’on affecte à des variables de notre code :
1 2 3 4 5 6 7 | // Get package settings JsonObject& settings = constellation.getSettings(); wattPerRevolution = settings["WattPerRevolution"].as<int>(); stateObjectLifeTime = settings["StateObjectLifeTime"].as<int>(); edgeThreshold = settings["EdgeThreshold"].as<int>(); normalThreshold = settings["NormalThreshold"].as<int>(); interval = settings["Interval"].as<int>(); |
Les settings sont des paramètres pour vos packages (= services, applications ou objets) centralisés sur le serveur Constellation.
Ainsi pour changer un paramètre de configuration, il suffit d’utiliser l’API Constellation ou la Console Constellation pour les modifier en un point unique.
Dans notre cas on a défini dans Constellation les settings représentant le nombre de Watt par révolution, les seuils de détection des révolutions par notre capteur, etc…
Ainsi je peux par exemple changer ce seuil de détection depuis mon navigateur sans avoir à reprogrammer le firmware de mon ESP.
Pour finir on va décrire le StateObject que notre ESP publiera.
Les StateObjects sont des objets de données produits et publiés par des packages d’une Constellation. […] Les StateObjects sont tous stockés sur le serveur Constellation et peuvent être interrogés par n’importe quels packages ou consommateurs de la Constellation.
Dans notre cas, voici la structure de notre StateObject :
1 2 3 4 5 6 7 8 | // Describe StateObject constellation.addStateObjectType("SEnergy.Electricity", TypeDescriptor() .setDescription("S-Energy Electricity data") .addProperty<long>("Counter", "Number of revolution") .addProperty<long>("Timestamp", "Internal timestamp of the last revolution") .addProperty<double>("RevolutionTime", "The time (in ms) of the last revolution") .addProperty<int>("WattPerHour", "Energy consumed") .addProperty<long>("Cumul", "Total of KWh consumed")); |
L’intérêt de décrire le StateObject est que cette description est partagée par tous, permettant aux autres programmes, objets, services connectés dans une Constellation de pouvoir se découvrir plus facilement :
Voilà notre ESP est initialisé, passons pour finir à la boucle principale : la méthode loop().
Le principe est de mesurer l’entrée analogique « A0 » (variable analogSensorPin) sur laquelle on trouve le récepteur de l’opto-interrupteur. Sur un ESP8266, l’unique entrée analogique (un ADC pour être exact) mesure une tension comprise entre 200mA et 1V sur 10bits (soit une valeur entre 0 et 1023).
Plus il y a de lumière plus la valeur sera élevée. Le principe étant donc mesurer une première fois puis d’attendre une certain intervalle (setting dans Constellation défini à 20ms dans mon cas) avant de refaire une nouvelle mesure et de comparer cette valeur à un seuil. Au dessus de 150, on est dans la partie métallique, en dessous de 90 on est sur la bande noire.
Les seuils (normal > 150 et bande noir < 90) sont définis dans Constellation me permettant d’affiner le paramétrage facilement lors de l’installation.
Lorsque l’on détecte une révolution on peut calculer la différence de temps avec la révolution précédente (et donc en déduire la vitesse de rotation) et incrémenter un compteur. On allumera une LED temoin pour le feedback visuel et surtout on publiera l’information dans Constellation sous forme d’un StateObject.
Le code complet :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | void loop(void) { if (analogRead(analogSendorPin) < edgeThreshold) { delay(interval); int sensorValue = analogRead(analogSendorPin); if (sensorValue < edgeThreshold) { if (trigger == 1) { // LED Power On digitalWrite(ledPin, HIGH); // Increment counter counter++; trigger = 0; bool firstRevolution = (lastTimestamp == 0); unsigned long ts = millis(); if(ts > lastTimestamp) { // Compute unsigned long timeDiff = ts - lastTimestamp; unsigned int counterDiff = counter - lastCounter; double timePerRevolution = timeDiff / counterDiff; unsigned int wattPerHour = (millisecondsPerHour / timePerRevolution) * wattPerRevolution; unsigned long cumul = counter * wattPerRevolution; // Push StateObject if(firstRevolution) { constellation.writeInfo("[ELEC] First revolution detected @ %lu (C=%lu)", ts, counter); } else { StaticJsonBuffer<JSON_OBJECT_SIZE(5)> jsonBuffer; JsonObject& myStateObject = jsonBuffer.createObject(); myStateObject["Counter"] = counter; myStateObject["Timestamp"] = ts; myStateObject["RevolutionTime"] = timePerRevolution; myStateObject["WattPerHour"] = wattPerHour; myStateObject["Cumul"] = cumul; constellation.pushStateObject("Electricity", myStateObject, "SEnergy.Electricity", stateObjectLifeTime); } } else { constellation.writeInfo("[ELEC] Reset internal timestamp - New:%lu Last:%lu (C=%lu)", ts, lastTimestamp, counter); } // History lastTimestamp = ts; lastCounter = counter; // LED Power Off digitalWrite(ledPin, LOW); } } } // perform debounce check if state changed and only then add count if (analogRead(analogSendorPin) > normalThreshold) { delay(interval); if (analogRead(analogSendorPin) > normalThreshold) { trigger = 1; } } } |
La partie la plus important étant la publication vers Constellation d’un StateObject contenant 5 propriétés :
- le numéro du tour
- le timestamp de l’ESP au moment de la révolution
- le temps mesurée de la révolution en milliseconde
- l’estimation de la puissance active (basée sur le temps de la révolution)
- le cumul de l’énergie consommée (le produit du nombre de tour par le Wh/révolution du compteur)
1 2 3 4 5 6 7 | JsonObject& myStateObject = jsonBuffer.createObject(); myStateObject["Counter"] = counter; myStateObject["Timestamp"] = ts; myStateObject["RevolutionTime"] = timePerRevolution; myStateObject["WattPerHour"] = wattPerHour; myStateObject["Cumul"] = cumul; constellation.pushStateObject("Electricity", myStateObject, "SEnergy.Electricity", stateObjectLifeTime); |
Ainsi n’importe quelle page Web, objet connecté, service ou application, qu’il soit écrit en .NET, en Python, en Arduino, en Javascript ou autre peuvent intégrer cette donnée en temps réel !
Prenons un exemple simple : intégrons la consommation actuelle (en Wh) dans mon dashboard S-Panel.
On vient simplement s’abonner aux StateObjects produit par le package « SElectricity ». En Javascript/Angular on écrira :
1 | constellation.requestSubscribeStateObjects("*", "SElectricity", "*", "*"); |
Dans le code dans le page, chaque StateObject est stocké dans une variable du scope Angular :
1 2 3 4 5 | constellation.onUpdateStateObject(function (message) { $scope.$apply(function () { $scope[message.PackageName][message.Name] = message; }); }); |
Je n’ai donc aucune autre ligne de code à écrire! Je peux tout simplement, dans mon modèle HTML, afficher ma propriété « WattPerHour » du StateObject « Electricity » produit par mon ESP :
1 | <bold>{{SElectricity.Electricity.Value.WattPerHour}}</bold> W |
Et voilà mon interface est opérationnelle et affichera en temps réel la mesure de notre ESP !
Facile non !?
On peut bien entendu « brancher » sur Constellation n’importe quel système pour venir exploiter/traiter nos StateObjects.
On a déjà parlé du package Graylog/Kibana, on verra également dans un prochain article comment « aspirer » des StateObjects dans Cacti pour générer des graphiques très facilement sans aucune ligne de code :
Pour découvrir Constellation rendez-vous sur le portail développeur. Vous trouverez toutes les documentations pour comprendre, déployer et développer vos propres solutions interconnectées avec Constellation.
BEAUDOIN Laurent
Bonjour Sébastien,
merci pour ce nouveau post et cette mise à jour avec un ESP !
Je projette de faire la même chose avec un Wemos alimenté en 5 volts.
Dans ce cas pas besoin du LD33V et des 2 condensateurs n’est ce pas ?
Peux tu me donner la valeur des 2 résistances par contre ?
Merci beaucoup, et longue vie à Constellation
Sebastien
Salut Laurent,
J’aurais pu mettre à jour mon schéma en effet, c’est le même que ma 1ère version avec l’Arduino dispo ici : https://sebastien.warin.fr/wp-content/uploads/2015/05/senergy_scma3.png !
Il y a 3 resistances :
1/ une pour la LED rouge de 51 ohm
2/ une pour la LED IR de l’opto-interrupteur de 51ohm également
3/ une pour la sortie analogique de l’opto-interrupteur de 1kOhm
Bien à toi,
(ps : oui en effet pas besoin de LD33 et condo car déjà intégré dans le Wemos)
Trackback: S-DoorBell : connecter sa sonnette à Constellation avec un ESP8266 ou comment protéger le sommeil de son enfant, la sonnette propulsée dans Constellation ! - Sebastien.warin.fr
Trackback: S-Watch : pilotez votre domotique et objets connectés depuis une montre Samsung Gear S2 ou comment développer des applications Tizen connectées à Constellation - Sebastien.warin.fr