Energy Monitor Stefano Gelmini – seconda parte

Lo scorso articolo hai letto del progetto di Stefano Gelmini Energy Monitor le premesse che lo hanno portato alle scelte tecniche.

energy monitor pvoutput home

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.

Cortesemente, prima di inserire i commenti leggi il regolamento

Permanent link to this article: http://www.mauroalfieri.it/elettronica/energy-monitor-seconda-parte.html

5 comments

Skip to comment form

    • francesco on 15 gennaio 2015 at 22:43
    • Reply

    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.

    1. Lascio a Stefano la risposta essendo un suo progetto 🙂

    • Marco on 5 ottobre 2015 at 14:45
    • Reply

    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

    1. Ciao Marco,
      approvo la tua richiesta lasciando che Stefano la veda e ti risponda.

  1. Buongiorno ragazzi.
    Mauro come sempre, ottimo lavoro.
    Vorrei invece chiedere a Stefano maggiori informazioni su Monitoring dati Consumi / Produzione cliente ecc.

    Buon fine settimana

Lascia un commento

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.