Lo scorso articolo hai letto del progetto di Stefano Gelmini Energy Monitor le premesse che lo hanno portato alle scelte tecniche.
Stefano non si è limitato a prendere ed utilizzare il progetto openenergy monitor così come fornito e descritto on-line ma ha provveduto, da vero maker, a personalizzarlo ed adattarlo.
In questa seconda parte del progetto energy monitor leggerai lo sketch e la raccolta dati vera e propria con tanto di dati on-line, raccontato da Stefano stesso:
Lo sketch dell’Energy Monitor
Passo a descrivere la parte di codice che ho modificato prendendo spunto da altri codici:
- la parte di connessione Ethernet
- la parte di acquisizione dati usando Emon
- la parte di sincronizzazione NTP
- la parte di server web con le funzioni JSON
Ecco il codice:
#define DEBUGIRMS true #define ENABLESERVERWEB true #include <SPI.h> #include <Ethernet.h> #include <EmonLib.h> // Include Emon Library #include "EthernetUdp.h" #include "EmonLib.h" // Include Emon Library #include "Time.h" /* DEFINITIONS */ #define BUFSIZE 255 /* CONFIGURATION PARAMETERS */ byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0xEE, 0xAB}; // the dns server ip IPAddress dnServer(192, 168, 1, 2); // the router's gateway address: IPAddress gateway(192, 168, 1, 2); // the subnet: IPAddress subnet(255, 255, 255, 0); //the IP address is dependent on your network IPAddress ip(192, 168, 1, 211); // define usable pins on the board int digitalPinRed = 9; EnergyMonitor emon1, emon2; // Create an instance float IConsumata = 0; float IProdotta = 0; float WConsumata = 0; float WProdotta = 0; /* Initialize Webserver on Port 80 */ EthernetServer server(80); // NTP ZONE IPAddress timeServer(193,204,114,232); const long timeZoneOffset = 7200L; // Syncs to NTP server every 15 seconds for testing, // set to 1 hour or more to be reasonable unsigned int ntpSyncTime = 60; // ALTER THESE VARIABLES AT YOUR OWN RISK // // local port to listen for UDP packets unsigned int localPort = 8888; // NTP time stamp is in the first 48 bytes of the // message const int NTP_PACKET_SIZE= 48; // Buffer to hold incoming and outgoing packets // byte packetBuffer[NTP_PACKET_SIZE]; // A UDP instance to let us send and receive packets // over UDP EthernetUDP Udp; // Keeps track of how long ago we updated the // NTP server unsigned long ntpLastUpdate = 0; // Check last time clock displayed (Not in Production) // time_t prevDisplay = 0; // ********* NTP ZONE /* PROGRAMM */ void setup() { // turn on serial (for debugging) Serial.begin(9600); //emon1.current(1, 66); // Current: input pin, calibration. //emon2.current(2, 60); // Current: input pin, calibration. emon1.voltage(0, 234.26, 1.7); // Voltage: input pin, // calibration, phase_shift emon2.voltage(0, 234.26, 1.7); // Voltage: input pin, // calibration, phase_shift emon1.current(1, 66); // Current: input pin, calibration. emon2.current(2, 60); // Current: input pin, calibration. Ethernet.begin(mac, ip, dnServer, gateway, subnet); server.begin(); Serial.print("IP = "); Serial.println(Ethernet.localIP()); } void loop() { // SET NTP if(now()-ntpLastUpdate > ntpSyncTime) { int trys=0; while(!getTimeAndDate() && trys<10){ trys++; } if(trys<10){ Serial.println("NTP server update success"); } else{ Serial.println("NTP server update failed"); } } // Display the time if it has changed by more than a second. if( now() != prevDisplay){ prevDisplay = now(); //clockDisplay(); } // SET NTP char clientline[BUFSIZE]; int index = 0; emon1.calcVI(20,2000); // Calculate all. No.of wavelengths, time-out emon2.calcVI(20,2000); // Calculate all. No.of wavelengths, time-out double Irms = emon1.calcIrms(1480); // Calculate Irms only double Vrms = emon1.calcVrms(1480); // Calculate Vrms only Serial.print(Irms); // Irms Serial.print(","); // Serial.println(Vrms); // Vrms IConsumata= emon1.Irms; IProdotta = emon2.Irms; WConsumata=emon1.apparentPower; WProdotta = emon2.apparentPower; //IProdotta = emon2.calcIrms(1480); // Calculate Irms only // Se corrente Prelevata superiore quella prodotta accendo il led pinMode(digitalPinRed, OUTPUT); if (IConsumata > IProdotta) { digitalWrite(digitalPinRed, HIGH); } else { digitalWrite(digitalPinRed, LOW); } delay(10); #if DEBUGIRMS //Serial.print(getTimeDisplay()); Serial.print(" - Watt Prelevata= "); Serial.print(WConsumata); Serial.print(" ("); Serial.print(IConsumata); Serial.print(" A) - Watt Prodotta= "); Serial.print(WProdotta); Serial.print(" ("); Serial.print(IProdotta); Serial.println(" A)"); #endif #if ENABLESERVERWEB EthernetClient client = server.available(); if (client) { index = 0; while (client.connected()) { if (client.available()) { char c = client.read(); if (c != '\n' && c != '\r') { clientline[index] = c; index++; if (index >= BUFSIZE) { index = BUFSIZE - 1; } continue; } String urlString = String(clientline); String op = urlString.substring(0, urlString.indexOf(' ')); urlString = urlString.substring(urlString.indexOf('/'), urlString.indexOf(' ', urlString.indexOf('/'))); urlString.toCharArray(clientline, BUFSIZE); char *pin = strtok(clientline, "/"); char *value = strtok(NULL, "/"); char outValue[10] = "MU"; String jsonOut = String(); if (pin != NULL) { // determine analog or digital if (pin[0] == 'a' || pin[0] == 'A') { // analog int selectedPin = pin[1] - '0'; // Modificato da Stefano Per fare output personalizzato String strValue = "0"; char buf[32]; // needs to be at least large // enough to fit the formatted text switch (selectedPin) { case 1: sprintf(buf,"%2.6f\n",IConsumata); //strValue = String(emon1.Irms); break; case 2: sprintf(buf,"%2.6f\n",IProdotta); //strValue = String(IProdotta); break; } //printOutput(200, client, jsonOutput(pin, output)); // Serial.println(strValue); strValue = buf; printOutput(200, client, jsonOutput(pin, strValue)); // } } else { printOutput(100, client, ""); } break; } } client.stop(); delay(1); // while (client.status() != 0) { // delay(10); // } } #endif } /* FUNCTIONS */ String jsonOutput(String pin, String value) { String output = String(); output += "{\""; output += "time\":\""; output += getTimeUTC(); output += "\",\r\n \""; output += "pin\":\""; output += pin; output += "\",\r\n \""; output += "value\":\""; output += value; output += "\"}"; Serial.println(output); return output; } void printOutput(int status, EthernetClient client, String message) { switch (status) { case 200: client.println("HTTP/1.1 200 OK"); client.println("Content-Type: application/json"); client.println("Access-Control-Allow-Origin: *"); client.println(); client.println(message); break; case 100: client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<head>"); client.println("<title>Gelmini Stefano - Arduino Energy Monitor</title>"); client.println("<style type=""text/css"">"); if (IConsumata > IProdotta) { client.println("body {background-color:red;}"); } else { client.println("body {background-color:green;}"); } client.println("</style>"); client.println("</head>"); client.println("<body>"); // output the value of each analog input pin client.print("<h1>Stefano Gelmini - Arduino Monitor</h1>"); client.print("<h2>"); client.print(getTimeDisplay()); client.print("</h2><br>"); client.print("<table border=0>"); client.print("<tr>"); client.print("<th></th>"); client.print("<th>Consumata</th>"); client.print("<th>Prodotta</th>"); client.print("</tr>"); client.print("<tr>"); client.print("<td>Apparent Power</td>"); client.print("<td>"); client.print(WConsumata); client.print("</td>"); client.print("<td>"); client.print(WProdotta); client.print("</td>"); client.print("</tr>"); client.print("<tr>"); client.print("<td>Irms</td>"); client.print("<td>"); client.print(IConsumata); client.print("</td>"); client.print("<td>"); client.print(IProdotta); client.print("</td>"); client.print("</tr>"); client.print("</table>"); client.println("</body>"); client.println("</html>"); break; default: client.println("HTTP/1.1 200 OK"); client.println("Content-Type: application/json"); client.println("Access-Control-Allow-Origin: *"); client.println(); break; } } // Do not alter this function, it is used by the system int getTimeAndDate() { int flag=0; Udp.begin(localPort); Serial.print("sendNTPpacket :"); Serial.println(timeServer); sendNTPpacket(timeServer); Serial.println("sendNTPpacket OK"); delay(1000); if (Udp.parsePacket()){ Udp.read(packetBuffer,NTP_PACKET_SIZE); // read the packet // into the buffer unsigned long highWord, lowWord, epoch; highWord = word(packetBuffer[40], packetBuffer[41]); lowWord = word(packetBuffer[42], packetBuffer[43]); epoch = highWord << 16 | lowWord; epoch = epoch - 2208988800 + timeZoneOffset; flag=1; setTime(epoch); ntpLastUpdate = now(); } return flag; } // send an NTP request to the time server at the given address unsigned long sendNTPpacket(IPAddress& address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer,NTP_PACKET_SIZE); Udp.endPacket(); } // Utility function for clock display: prints preceding colon and leading 0 void printDigits(int digits){ Serial.print(":"); if(digits < 10) Serial.print('0'); Serial.print(digits); } String getTimeDisplay() { String myTimer= String(""); if (hour()<10) { myTimer += "0"; myTimer += hour(); } else { myTimer += minute(); } myTimer += ":"; if (minute()<10) { myTimer += "0"; myTimer += hour(); } else { myTimer += minute(); } myTimer += ":"; if (second()<10) { myTimer += "0"; myTimer += second(); } else { myTimer += second(); } myTimer += " "; myTimer += day(); myTimer += "/"; myTimer += month(); myTimer += "/"; myTimer += year(); return myTimer; } String getTimeUTC() { String myTimer= String(year()); if (month()<10) { myTimer += "0"; myTimer += month(); } else { myTimer += month(); } if (day()<10) { myTimer += "0"; myTimer += day(); } else { myTimer += day(); } if (hour()<10) { myTimer += "0"; myTimer += hour(); } else { myTimer += hour(); } if (minute()<10) { myTimer += "0"; myTimer += minute(); } else { myTimer += minute(); } if (second()<10) { myTimer += "0"; myTimer += second(); } else { myTimer += second(); } return myTimer; }
Innanzitutto dovete scaricare le seguenti librerie: “EmonLib.h” e “Time.h” configurare e referenziare gli oggetti di EmonLib:
EnergyMonitor emon1, emon2; // Create an instance * PROGRAMM */ void setup() { emon1.voltage(0, 234.26, 1.7); // Voltage: input pin, calibration, phase_shift emon2.voltage(0, 234.26, 1.7); // Voltage: input pin, calibration, phase_shift emon1.current(1, 66); // Current: input pin, calibration. emon2.current(2, 60); // Current: input pin, calibration. Ethernet.begin(mac, ip, dnServer, gateway, subnet); server.begin(); Serial.print("IP = "); Serial.println(Ethernet.localIP()); }
leggo i valori di corrente dai sensori :
double Irms = emon1.calcIrms(1480); // Calculate Irms only double Vrms = emon1.calcVrms(1480); // Calculate Vrms only Serial.print(Irms); // Irms Serial.print(","); // Serial.println(Vrms); // Vrms
per la parte di Server Web ho modificato lo sket trovato a questo indirizzo https://github.com/jjg/RESTduino e fatto in modo che quando il browser o un altro client chiamano il mio server arduino (http://192.168.1.211/a1 ) anziché ritornare il valore del Pin A1 sostituisco il valore analogico del pin con il risultato della lettura della pinza amperometrica:
… IConsumata= emon1.Irms; IProdotta = emon2.Irms; … switch (selectedPin) { case 1: sprintf(buf,"%2.6f\n",IConsumata); break; case 2: sprintf(buf,"%2.6f\n",IProdotta); break; } …
Ecco il risultato della chiamata via web al Arduino:
{"time":"20140830120900","a1":"4.32"}
Nella stringa JSON oltre al valore del A1 ho messo il valore del TIMESTAMP in modo da rendere il dato più tracciabile.
La parte del codice relativa al NTP si commenta da sola, basta dare un occhiata ai vari esempi presenti in rete e adattarli come ho fatto io alle proprie esigenze, io per esempio ho fatto in modo che la sincronizzazione del TIME avvenga ogni 60 secondi.
Monitoring dati Consumi / Produzione
Per la parte di analisi dati e logging dei dati mi sono affidato a un sistema gratuito chiamato PVOutput dove, registrandosi gratuitamente, puoi inviare i dati di produzione e di consumo utilizzando client che supportano protocollo JSON.
Nel mio caso usando un piccolo server linux , ho usato il comando CURL e ho prelevato i dati da Arduino e li ho trasmessi al sistema PVOutput ed ecco il risultato in produzione:
Come puoi vedere dal sito , ci sono giornate in cui non trasmetto i dati di consumo perché il sistema non è ancora ben affinato e quindi alcuni dati capita che vanno persi.
La mia idea sarà la futura evoluzione di quanto descritto in questo documento è sfruttare arduino con un sistema MQTT.
In sostanza Arduino dovrebbe acquisire i dati:
- DataOra
- Corrente Prodotta
- Corrente Consumata
e salvarli in una EEProm all’interno di una Coda, dopodiché se il servizio Broker di code è disponibile, li manda e svuota la coda.
Cosi facendo anche se la mia linea internet dovesse cadere ho sempre la sicurezza che i dati sono stati acquisiti.
Ci sto lavorando…
Se vuoi aggiornamenti o hai dubbi sullo sketch che hai letto puoi usare i commenti per rivolgermi le tue richieste ed i tuoi dubbi.
5 commenti
Vai al modulo dei commenti
ottimo esempio molto utile (come tutti i Vostri esempi)
una domanda tecnica pero’, nello sketch si imposta il valore di phase_shift a 1.7,valore non calcolato e dipendente dal carico ( un motore di una lavatrice ha un valore diverso da una lampadina). il tutto alla fine che precisione ha ? se consideriamo un “semplicewattmetro” da 15 e che mi legge i kw reali con uscita ad impulsi tipo contatore dell Enel non e’ piu preciso?
chiedo solo come utilizzatore finale perche’ mi interessa il sensore e sto valutando i pro e contro edeventuali altre possibilita’ a parita’di costi.
Autore
Lascio a Stefano la risposta essendo un suo progetto 🙂
Ciao Stefano, vorrei chiederti un chiarimento, ho scaricato lo sketch e le librerie da te indicate, ma sia su IDE 1.5.2 che su 1.6.5 ho l’errore calcVrms non definito, mi puoi aiutare? Grazie
Autore
Ciao Marco,
approvo la tua richiesta lasciando che Stefano la veda e ti risponda.
Buongiorno ragazzi.
Mauro come sempre, ottimo lavoro.
Vorrei invece chiedere a Stefano maggiori informazioni su Monitoring dati Consumi / Produzione cliente ecc.
Buon fine settimana