Realtime Energiemeting online met COSM


Een van de redenen om voor een 'slimme meter' te kiezen was om real-time mijn energie verbuik te zien.
COSM.com is een site waarbij je eenvoudig  realtime meetdata naartoe kan sturen.


Ik gebruik hiervoor een Arduino waarmee dit op een goedkope en weinig energie verbruikende :-) manier gedaan wordt. Een grote uitdaging is om het geheel in het geheugen van een Arduino te laten passen.  Het regelmatig restarted doe ik omdat ik het anders niet betrouwbaar voor een langere tijd kon laten draaien.

Zelfde prgramma kan in principe gebruikt worden om data naar SD kaart te schrijven, echter het is me niet gelukt om dat tegelijk met de ethernet verbinding te gebruiken ivm het beschikbare geheugen op de Arduino

Ik gebruik de AltSerial library want met de reguliere SoftSerial kreeg ik geen juiste resultaten (ivm ongebruikelijke seriele setting die in P1 gebruikt wordt)

Om de verbinding te leggen met internet gebruik ik het meest goedkope boardje dat ik kon vinden ($5 USD)
http://www.geeetech.com/enc28j60-ethernet-module-p-263.html hiermee samen met de Arduino van $13 het prijskaartje van deze oplossing onder de 15 EUR.



Zie hieronder het Arduino programmatje dat ik gebruik om de meter uit te lezen en naar COSM te schrijven.


#include <AltSoftSerial.h>
#define maxRunTime 600000L

#define UPDATE_COSM 1
#define SDLOG 0
#define STATIC 1  // set to 1 to disable DHCP (adjust myip/gwip values below)
// if you don't want to use DNS (and reduce your sketch size)
// use the numeric IP instead of the name for the server:
#define FIXEDSERVER 1  // set to 1 to to use the numeric server
#define etherPin 10 //pin to be used for the ethernet card

#if UPDATE_COSM
#include <EtherCard.h>
#endif

#if SDLOG
#include <SD.h>
#endif

// AltSoftSerial always uses these pins:
//
// Board          Transmit  Receive   PWM Unusable
// -----          --------  -------   ------------
// Teensy 2.0         9        10       (none)
// Teensy++ 2.0      25         4       26, 27
// Arduino Uno        9         8         10
// Arduino Mega      46        48       44, 45
// Wiring-S           5         6          4
// Sanguino          13        14         12

AltSoftSerial altSerial;
//int pMode = 0;

#define BUFSIZE 75
char buffer[BUFSIZE];
int bufpos = 0;

int Pos = 0;
#define StartChar = '/';
#define StopChar = '!';

long   PowerUsage = 0;
long   PowerSolar = 0;
float   GasMeter = 0;
float   GasUsage = 0;
long m1 = 0;
long m2 = 0;
long m3 = 0;
long m4 = 0;

bool  readnextLine = false;
#define MeterInterval  60000  // Send the meter values only once per 60s
unsigned long previousMillis;
bool sendMeter = true;


#if UPDATE_COSM
// change these settings to match your own setup

#define APIKEY         "ENTER YOUR KEY HERE" // your cosm api key
#define FEED         "ENTER FEED" // your feed ID
#define USERAGENT      "Cosm Arduino Example (62918)" // user agent is the project name

// ethernet interface mac address
static byte mymac[] = {
  0x74,0x69,0x69,0x2D,0x30,0x36 };
#if STATIC
// ethernet interface ip address NB DEZE VERVANGEN MET EIGEN IP
static byte myip[] = {
  192,168,3,160 };
// gateway ip address NB DEZE VERVANGEN MET EIGEN GW
static byte gwip[] = {
  192,168,3,1 };
//dns server
static byte dnsip[] = {
  8,8,8,8 };
#endif

#if FIXEDSERVER
static byte hisip[] = {
  216,52,233,122 };
#else
char website[] PROGMEM = "api.pachube.com";
#endif

byte Ethernet::buffer[265];
//byte Ethernet::buffer[250];
//uint32_t timer;
Stash stash;
#endif



#include <MemoryFree.h>

void setup() {
  Serial.begin(9600);
  Serial.println(F("Energie reading Begins...")); //memory friendly
  altSerial.begin(9600);

#if SDLOG
  Serial.println(F("Initializing SD card..."));
  // On the Ethernet Shield, CS is pin 4. It's set as an output by default.
  // Note that even if it's not used as the CS pin, the hardware SS pin
  // (10 on most Arduino boards, 53 on the Mega) must be left as an output
  // or the SD library functions will not work.
  //  pinMode(10, OUTPUT);
  //  digitalWrite(10, HIGH);
  //  digitalWrite(4, HIGH);

  if (!SD.begin(4)) {
    Serial.println(F("initialization failed!"));
    return;
  }
#endif
  checkmem();
#if UPDATE_COSM
  netsetup();
#endif
  checkmem();
}



void loop() {
 
  // forced realod after 10 min run time impl
 if(millis() > maxRunTime ) {
     softReset();
 }

 
#if UPDATE_COSM
ether.packetLoop(ether.packetReceive());
//  int tt = ether.packetLoop(ether.packetReceive());
//  if (tt)  Serial.println (tt);
#endif

  if (millis() > (previousMillis + MeterInterval) ) {
    sendMeter = true ;
    previousMillis = millis();
  }
  char c;

  if (Serial.available()) {
    c = Serial.read();
  }

  if (altSerial.available()) {

    c = altSerial.peek();
    if (c == '/' ){ //StartChar){
      checkmem();
    }

    // we've loaded a line. Now parse the values
    if (read2Line()){

          Serial.print(buffer);
long tl = 0;
long tld =0;

      if (sendMeter) {
        //Meter //1-0:1.8.1(00391.000*kWh)
  if (sscanf(buffer,"1-0:1.8.1(%ld%.%ld%*s" , &tl, &tld) >0  )   //)
  { m1 = tl * 1000 + tld;}

  if (sscanf(buffer,"1-0:1.8.2(%ld%.%ld%*s" , &tl, &tld) >0  )   //)
  { m2 = tl * 1000 + tld;}

  if (sscanf(buffer,"1-0:2.8.1(%ld%.%ld%*s" , &tl, &tld) >0  )   //)
  { m3 = tl * 1000 + tld;}

  if (sscanf(buffer,"1-0:2.8.2(%ld%.%ld%*s" , &tl, &tld) >0  )   //)
  { m4 = tl * 1000 + tld;}

      } //sendmeter

      if (readnextLine){
         if (sscanf(buffer,"(%ld.%ld%*s" , &tl, &tld) >0  ) {  //)  //(00127.969)
      GasMeter =  float ( tl * 1000 + tld )  / 1000 ;   //(00127.969)
        readnextLine = false;
      }}
      //gas   0-1:24.3.0
       if (sscanf(buffer,"0-1:24.3.0(%6ld%4ld%*s" , &tl, &tld) > 0  ) {  //)
        Serial.print (F("Gasreading time "));
        Serial.print (tl) ;
        Serial.print (F("-"));
        Serial.println (tld) ;
        readnextLine = true;
      }

  // Consumption
  if (sscanf(buffer,"1-0:1.7.0(%ld.%ld%*s" , &tl , &tld) >0  )   //)
  { PowerUsage = tl * 1000 + tld * 10;}

      // Supply       //       1-0:2.7.0(0000.00*kW)
  if (sscanf(buffer,"1-0:2.7.0(%ld.%ld%*s" , &tl , &tld) >0  )   //)
  { PowerSolar = tl * 1000 + tld * 10;}

      //we're done
      if (buffer[0] == '!' ) {
      checkmem();
         GasUsage = GasMeter;
         printData();
        checkmem();

#if UPDATE_COSM
  //      SendData (GasUsage,PowerUsage,PowerSolar);
        SendData();
#endif

        if (sendMeter){
#if UPDATE_COSM
       //        SendDataM (GasMeter,m1,m2,m3,m4);
               SendDataM ();
#endif
          sendMeter = false;
        }
        checkmem();

#if SDLOG
        // SaveData(GasUsage,PowerUsage,PowerSolar);
        SaveData();
#endif
        checkmem();
      }


    }


  }

}

void printData(){
         Serial.print(F("Power : "));
        Serial.println(PowerUsage);
        Serial.print(F("PowerSolar : "));
        Serial.println(PowerSolar);
        Serial.print(F("GAS : "));
        Serial.println(GasUsage,3 );
        Serial.println(F("*Meters*"));
        Serial.println(m1);
        Serial.println(m2);
        Serial.println(m3);
        Serial.println(m4);
}

void checkmem(){
   Serial.print(F("FreeMemory()="));
  Serial.println(freeMemory());
}

/*
float  ConvertStr(String TheNumber){
  char bufx[TheNumber.length()+2];
  TheNumber.toCharArray(bufx,TheNumber.length()+1);
  return float(atof(bufx));
}
*/

bool read2Line() {
  char c;
  //  bufpos = 0;
  //  buffer[0] = '\0';

  c = altSerial.read();
  if (bufpos + 1 < BUFSIZE) {
    buffer[bufpos] = c&127;
    bufpos++;
  }
  else
  { // return the partial line
    Serial.println(F("************************"));
    Serial.println(F("**Readline Buffer Full**"));
    Serial.println(F("************************"));

    buffer[bufpos] = '\0';
    bufpos = 0;
    return true;
  }

  if (c == '\n'){
    buffer[bufpos] = '\0';
    bufpos = 0;
    return true;
  }
  else return false;
}


#if UPDATE_COSM
void netsetup(){
  Serial.println(F("\n[webClient]"));

  if (ether.begin(sizeof Ethernet::buffer, mymac,etherPin) == 0)
    Serial.println(F("Failed to access Ethernet controller"));

#if STATIC
  ether.staticSetup(myip, gwip);
  //  use the GoogleDNS
  ether.copyIp(ether.dnsip, dnsip);
#else
  if (!ether.dhcpSetup())
    Serial.println(F("DHCP failed"));
#endif

  ether.printIp("IP:  ", ether.myip);
  ether.printIp("GW:  ", ether.gwip); 
  ether.printIp("DNS: ", ether.dnsip); 

#if FIXEDSERVER
  ether.copyIp(ether.hisip, hisip);
#else
  if (!ether.dnsLookup(website))
    Serial.println(F("DNS failed"));
#endif
  ether.printIp("SRV: ", ether.hisip);
}
#endif

#if UPDATE_COSM
void SendData(){
  // by using a separate stash,
  // we can determine the size of the generated message ahead of time
  byte sd = stash.create();
  stash.print("G,");
  stash.println(GasUsage,3);
  stash.print("P,");
  stash.println(PowerUsage);
  stash.print("S,");
  stash.println(PowerSolar);
  stash.save();
      checkmem();
  // generate the header with payload - note that the stash size is used,
  // and that a "stash descriptor" is passed in as argument using "$H"
  // Stash::prepare(PSTR("PUT http://$F/v2/feeds/$F.csv HTTP/1.0" "\r\n"
  Stash::prepare(PSTR("PUT /v2/feeds/$F.csv HTTP/1.0" "\r\n"
    "Host: api.pachube.com" "\r\n"
    "X-PachubeApiKey: $F" "\r\n"
    "Content-Length: $D" "\r\n"
    "\r\n"
    "$H"),
  // website, PSTR(FEED), website, PSTR(APIKEY), stash.size(), sd);
  PSTR(FEED), PSTR(APIKEY), stash.size(), sd);
      checkmem();
  // send the packet - this also releases all stash buffers once done
  ether.tcpSend();
      checkmem();
}
#endif

#if UPDATE_COSM
void SendDataM(){
  // by using a separate stash,
  // we can determine the size of the generated message ahead of time
 stash.cleanup();
  byte sd = stash.create();

  if (GasMeter != 0){
    stash.print("GM,");
    stash.println(GasMeter,3);
  }
  m1 = m1+2;
  stash.print("m1,");
  stash.println(float (m1/1000),3);
  stash.print("m2,");
  stash.println(float (m2/1000),3);
  stash.print("m3,");
  stash.println(float (m3/1000),3);
  stash.print("m4,");
  stash.println(float (m4/1000),3);
  stash.save();

  // generate the header with payload - note that the stash size is used,
  // and that a "stash descriptor" is passed in as argument using "$H"
  // Stash::prepare(PSTR("PUT http://$F/v2/feeds/$F.csv HTTP/1.0" "\r\n"
  Stash::prepare(PSTR("PUT /v2/feeds/$F.csv HTTP/1.0" "\r\n"
    "Host: api.pachube.com" "\r\n"
    "X-PachubeApiKey: $F" "\r\n"
    "Content-Length: $D" "\r\n"
    "\r\n"
    "$H"),
  // website, PSTR(FEED), website, PSTR(APIKEY), stash.size(), sd);
  PSTR(FEED), PSTR(APIKEY), stash.size(), sd);

  // send the packet - this also releases all stash buffers once done
//  Serial.print("sending Size : ");
//  Serial.print(stash.size());
  if ((    stash.size() <= 0) || (stash.size() > 400 )){
  }
  else{
    ether.tcpSend();
  }
}
#endif


#if SDLOG
//void SaveData(float value1,  long value2  ,long value3){
void SaveData(){
  char dataString[30]="";
  checkmem();
  sprintf (dataString,"%ld,%ld,%ld,%ld",millis(),long(GasMeter/1000),PowerUsage,PowerSolar);

  /*
  // open the file. note that only one file can be open at a time,
   // so you have to close this one before opening another.
   */
  checkmem();
  Serial.println(dataString);
  checkmem();

 
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  checkmem();
  // if the file is available, write to it:

  if (dataFile) {
       dataFile.println(dataString);

    dataFile.close();
    // print to the serial port too:

  } 

  // if the file isn't open, pop up an error:
  else {
    Serial.println(F("error opening datalog.txt"));
  }

  checkmem();
}

#endif


/*

 char *ftoa(char *a, double f, int precision)
 {
 long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000};

 char *ret = a;
 long heiltal = (long)f;
 itoa(heiltal, a, 10);
 while (*a != '\0') a++;
 *a++ = '.';
 long desimal = abs((long)((f - heiltal) * p[precision]));
 itoa(desimal, a, 10);
 return ret;
 }

 */
// soft reset implementation function
void softReset() {
asm volatile ("  jmp 0");
}

Voor het aansluiten van de Arduino aan de P1 meter heb ik een kleine inverter gemaakt van 3 weerstanden en transitor. Waarschijnlijk kan je dit ook wel in de software doen, maar ik vond niet 123 de plek waar ik dat kon doen.




Reacties

  1. Deze reactie is verwijderd door de auteur.

    BeantwoordenVerwijderen
  2. Hoi Marcel,
    Hoe heb je uiteindelijk de p1 en de arduino verbonden?
    gr
    Johan

    BeantwoordenVerwijderen
    Reacties
    1. Hallo Johan,
      Ik heb in de post het schematje toegevoegd hoe ik de Arduino heb aangesloten.
      Hoop dat het duidelijk is.

      gr Marcel

      Verwijderen
  3. Hoi Marcel,

    Mooie uitleg, ik ga zoiets binnenkort ook proberen.
    Ik heb ik nog wel 2 vragen:

    - Klopt het dat de volgorde van aanslutingen van de P1 (in het schema) niet overeenkomt met de volgorde van je eerdere post? (mbt de usb convertor)

    - Waarom gebruik je de R3 weerstand richting GND? Deze is toch niet nodig voor de inverter? (en zijn de collector en de emissie van de transistor niet verkeerd om getekend?)

    BeantwoordenVerwijderen
    Reacties
    1. Hallo Jeroen,
      ben nu niet in Nederland. Zal als ik thuis kom mijn tekening ff vergelijken met mijn werkende setup om te kijken of ik tekenfouten heb gemaakt.

      R3.... ik heb maar basale elektronica kennis... wilde de transistor/arduino niet overbelasten en was niet zeker of dat goed zou gaan zonder R3. Voor de invertor zelf zou het inderdaad niet nodig zijn.

      Verwijderen
  4. Leuk project, Ik heb ook iets dergelijks gedaan met de ENC28J60. Ik zie dat je elke 10 seconden een software reset geeft. Dat is waarschijnlijk omdat ethernet zich regelmatig ophangt. Dat ligt aan de 3,3 Volt voeding. De Arduino Nano levert de 3,3 Volt via de FTDI-chip en die kan maar 50 mA leveren, terwijl de ENC28J60 tot 180 mA nodig heeft.
    Oplossing is een spanningsregelaar die de 5 Volt omzet naar 3,3 Volt (zoals de AMS1117). Dan werkt de ENC28J60 voorbeeldig.
    Hope this helps....

    BeantwoordenVerwijderen
    Reacties
    1. Hi Compu, bedankt voor de tip!

      Inderdaad, dat is de reden, kreeg het niet stabiel en het hield om onverklaarbare reden na een tijdje op met werken. Dacht dat het aan de ENC28J60 library lag en heb daar al in lopen klooien.
      Had inmiddels er een Raspberry Pi tussen gezet met simpel python scriptje om de energie meter uit te lezen.

      Zal er binnenkort nog eens proberen met een spanningsregelaar ertussen, hopelijk maakt dat het stabiel.

      Verwijderen
    2. Ik had inmiddels al een tijdje de voeding aangepast, maar dat had nog immer geen resultaat.

      Na toch weer eens erin gedoken te hebben denk ik de oplossing gevonden te hebben:

      http://blog.cuyahoga.co.uk/2012/05/theres-something-wrong-with-my-stash/
      bij snelle updates na elkaar (zoals bij de meterstand updates) lijkt de stash niet goed leeg gemaakt te worden door de ethercard library.
      Oplossing is eenvoudig: stash.cleanup(); ervoor of erna zetten...

      Verwijderen
  5. Op http://phoenixinteractive.mine.nu/websitebb/viewtopic.php?f=23&t=31 staat er ook een heel net schema getekend met de zelfde bc547 - Altsoftserial combi net als ik heb proberen te tekenen...

    BeantwoordenVerwijderen
  6. Hallo Marcel,

    Ik probeer om de P1 data in een MySQL database op m'n NAS te krijgen. Helaas wil het nog niet lukken.
    Zou jij eens een blik op m'n code kunnen werpen? De code heb ik hier geplaatst: http://gathering.tweakers.net/forum/list_messages/1601301

    BeantwoordenVerwijderen

Een reactie posten

Populaire posts van deze blog

Onkyo TX-NR656 hacking

P1 port als energiemeter voor SolarEdge omvormer

Energie meter uitlezen via P1 poort