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.
Deze reactie is verwijderd door de auteur.
BeantwoordenVerwijderenHoi Marcel,
BeantwoordenVerwijderenHoe heb je uiteindelijk de p1 en de arduino verbonden?
gr
Johan
Hallo Johan,
VerwijderenIk heb in de post het schematje toegevoegd hoe ik de Arduino heb aangesloten.
Hoop dat het duidelijk is.
gr Marcel
Hoi Marcel,
BeantwoordenVerwijderenMooie 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?)
Hallo Jeroen,
Verwijderenben 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.
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.
BeantwoordenVerwijderenOplossing is een spanningsregelaar die de 5 Volt omzet naar 3,3 Volt (zoals de AMS1117). Dan werkt de ENC28J60 voorbeeldig.
Hope this helps....
Hi Compu, bedankt voor de tip!
VerwijderenInderdaad, 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.
Ik had inmiddels al een tijdje de voeding aangepast, maar dat had nog immer geen resultaat.
VerwijderenNa 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...
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...
BeantwoordenVerwijderenHallo Marcel,
BeantwoordenVerwijderenIk 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