Elmåler Arduino kode
Herunder min Arduino kode til at aflæse elmåleren og sende måledata til pachube.com.
Hardwaren er et hjemmebygget IR øje forbundet til Arduinoens digitale port 6 & 7 (RX og TX) samt et Ethernetshield (originalt arduino ethernet m. wiznet 5100).
Derudover er en 1-wire bus forbundet på digital pin 5 – denne er pulled up med en 1k5 ohm modstand til 5V.
Jeg bruger SoftwareSerial biblioteket for at tale med IR øjet – samt ERxPachube biblioteket for at afsende data til pachube.com
Koden kan også hentes som fil her
#include <Arduino.h> #include <SoftwareSerial.h> #include <OneWire.h> #include <ERxPachube.h> #include <Ethernet.h> #include <SPI.h> #include <avr/wdt.h> // Kamstrup 382Jx3 word const kregnums[] = { 0x0001,0x03ff,0x0027,0x041e,0x041f,0x0420 }; char* kregstrings[] = { "Energy in","Current Power","Max Power","Voltage p1","Voltage p2","Voltage p3" }; #define NUMREGS 6 // Number of registers above #define KAMBAUD 9600 // Units char* units[65] = {"","Wh","kWh","MWh","GWh","j","kj","Mj", "Gj","Cal","kCal","Mcal","Gcal","varh","kvarh","Mvarh","Gvarh", "VAh","kVAh","MVAh","GVAh","kW","kW","MW","GW","kvar","kvar","Mvar", "Gvar","VA","kVA","MVA","GVA","V","A","kV","kA","C","K","l","m3", "l/h","m3/h","m3xC","ton","ton/h","h","hh:mm:ss","yy:mm:dd","yyyy:mm:dd", "mm:dd","","bar","RTC","ASCII","m3 x 10","ton xr 10","GJ x 10","minutes","Bitfield", "s","ms","days","RTC-Q","Datetime"}; // Pin definitions #define PIN_DIAG_LED 4 // Diag LED to blink with for fun #define PIN_KAMSER_RX 6 // Kamstrup IR interface RX #define PIN_KAMSER_TX 7 // Kamstrup IR interface TX #define PIN_1WIRE 5 // 1-Wire bus // Network byte mac[] = { 0xCC, 0xAC, 0xBE, 0xEF, 0x24, 0x42 }; // Fake MAC address, pick any :-) byte ip[] = { 10, 0, 0, 200 }; byte dns[] = { 10, 0, 0, 10 }; byte gateway[] = { 10, 0, 0, 1 }; byte netmask[] = { 255, 255, 255, 0 }; // 1-wire byte ts_indoor[] = {0x28,0xEF,0xB2,0xDB,0x00,0x00,0x00,0x4A}; // Indoor sensor byte ts_outdoor[] = {0x28,0x92,0xC2,0xDB,0x00,0x00,0x00,0xF5}; // Outdoor sensor OneWire ds(PIN_1WIRE); // Initialize 1-Wire // Kamstrup optical IR serial #define KAMTIMEOUT 2000 // Kamstrup timeout after transmit SoftwareSerial kamSer(PIN_KAMSER_RX, PIN_KAMSER_TX, true); // Initialize serial // Pachube definitions #define PACHUBE_API_KEY "insertyourownpachubeapikeyhere" // API key to upload data #define PACHUBE_FEED_ID 12345 // Pachube Feed ID long pachube_update_interval = 60000; // Pachube Update interval long pachube_update_last; ERxPachubeDataOut pachube(PACHUBE_API_KEY, PACHUBE_FEED_ID); // Initialize pachube void setup() { // Uncomment this to do a 1-wire bus scan to detect the serial codes of the connected devices // scan-1wire(); // setup WDT // Needed because buggy ethernet shield sometimes make Arduino freeze :-( wdt_enable(WDTO_8S); wdt_reset(); // setup ethernet shield Ethernet.begin(mac, ip, dns, gateway, netmask); // setup serial Serial.begin(9600); // setup kamstrup serial pinMode(PIN_KAMSER_RX,INPUT); pinMode(PIN_KAMSER_TX,OUTPUT); digitalWrite(PIN_KAMSER_TX,LOW); kamSer.begin(KAMBAUD); // Setup diag LED pinMode(PIN_DIAG_LED,OUTPUT); // setup pachube data points pachube.addData(0); // Energy pachube.addData(1); // Power actual pachube.addData(2); // Power max pachube.addData(3); // P1 Voltage pachube.addData(4); // P2 Voltage pachube.addData(5); // P3 Voltage pachube.addData(6); // Outdoor pachube.addData(7); // Indoor pachube.addData(8); // Reset // log the reset to pachube // this way we can track how often the board resets pachube.updateData(8, 1); pachube.updatePachube(); // start pachube update timer pachube_update_last = millis(); } // Main loop void loop() { // check if it is time to poll and send data if ((millis() - pachube_update_last) > pachube_update_interval) { // turn on the diag LED digitalWrite(PIN_DIAG_LED,HIGH); // poll the Kamstrup registers for data for (int kreg = 0; kreg < NUMREGS; kreg++) { kamReadReg(kreg); delay(100); } // poll 1-wire sensors pachube.updateData(6, gettemp_ds18b20(ts_outdoor)); pachube.updateData(7, gettemp_ds18b20(ts_indoor)); // send data to pachube pachube.updateData(8, 0); // 0 for no reset int status = pachube.updatePachube(); Serial.print("Sync status code <OK == 200> => "); Serial.println(status); pachube_update_last = millis(); // turn off diag led digitalWrite(PIN_DIAG_LED,LOW); } // reset watchdog timer wdt_reset(); // loop delay delay(500); } /* Subroutines */ // kamReadReg - read a Kamstrup register void kamReadReg(unsigned short kreg) { byte recvmsg[30]; // buffer of bytes to hold the received data float rval; // this will hold the final value // prepare message to send and send it byte sendmsg[] = { 0x3f, 0x10, 0x01, (kregnums[kreg] >> 8), (kregnums[kreg] & 0xff) }; kamSend(sendmsg, 5); // listen if we get an answer unsigned short rxnum = kamReceive(recvmsg); // check if number of received bytes > 0 if(rxnum != 0){ // decode the received message rval = kamDecode(kreg,recvmsg); // print out received value to terminal (debug) Serial.print(kregstrings[kreg]); Serial.print(": "); Serial.print(rval); Serial.print(" "); Serial.println(units[recvmsg[4]]); // store value for pachube upload pachube.updateData(kreg,rval); } } // kamSend - send data to Kamstrup meter void kamSend(byte const *msg, int msgsize) { // append checksum bytes to message byte newmsg[msgsize+2]; for (int i = 0; i < msgsize; i++) { newmsg[i] = msg[i]; } newmsg[msgsize++] = 0x00; newmsg[msgsize++] = 0x00; int c = crc_1021(newmsg, msgsize); newmsg[msgsize-2] = (c >> 8); newmsg[msgsize-1] = c & 0xff; // build final transmit message - escape various bytes byte txmsg[20] = { 0x80 }; // prefix int txsize = 1; for (int i = 0; i < msgsize; i++) { if (newmsg[i] == 0x06 or newmsg[i] == 0x0d or newmsg[i] == 0x1b or newmsg[i] == 0x40 or newmsg[i] == 0x80) { txmsg[txsize++] = 0x1b; txmsg[txsize++] = newmsg[i] ^ 0xff; } else { txmsg[txsize++] = newmsg[i]; } } txmsg[txsize++] = 0x0d; // EOF // send to serial interface for (int x = 0; x < txsize; x++) { kamSer.write(txmsg[x]); } } // kamReceive - receive bytes from Kamstrup meter unsigned short kamReceive(byte recvmsg[]) { byte rxdata[50]; // buffer to hold received data unsigned long rxindex = 0; unsigned long starttime = millis(); kamSer.flush(); // flush serial buffer - might contain noise byte r; // loop until EOL received or timeout while(r != 0x0d){ // handle rx timeout if(millis()-starttime > KAMTIMEOUT) { Serial.println("Timed out listening for data"); return 0; } // handle incoming data if (kamSer.available()) { // receive byte r = kamSer.read(); if(r != 0x40) { // don't append if we see the start marker // append data rxdata[rxindex] = r; rxindex++; } } } // remove escape markers from received data unsigned short j = 0; for (unsigned short i = 0; i < rxindex -1; i++) { if (rxdata[i] == 0x1b) { byte v = rxdata[i+1] ^ 0xff; if (v != 0x06 and v != 0x0d and v != 0x1b and v != 0x40 and v != 0x80){ Serial.print("Missing escape "); Serial.println(v,HEX); } recvmsg[j] = v; i++; // skip } else { recvmsg[j] = rxdata[i]; } j++; } // check CRC if (crc_1021(recvmsg,j)) { Serial.println("CRC error: "); return 0; } return j; } // kamDecode - decodes received data float kamDecode(unsigned short const kreg, byte const *msg) { // skip if message is not valid if (msg[0] != 0x3f or msg[1] != 0x10) { return false; } if (msg[2] != (kregnums[kreg] >> 8) or msg[3] != (kregnums[kreg] & 0xff)) { return false; } // decode the mantissa long x = 0; for (int i = 0; i < msg[5]; i++) { x <<= 8; x |= msg[i + 7]; } // decode the exponent int i = msg[6] & 0x3f; if (msg[6] & 0x40) { i = -i; }; float ifl = pow(10,i); if (msg[6] & 0x80) { ifl = -ifl; } // return final value return (float )(x * ifl); } // crc_1021 - calculate crc16 long crc_1021(byte const *inmsg, unsigned int len){ long creg = 0x0000; for(unsigned int i = 0; i < len; i++) { int mask = 0x80; while(mask > 0) { creg <<= 1; if (inmsg[i] & mask){ creg |= 1; } mask>>=1; if (creg & 0x10000) { creg &= 0xffff; creg ^= 0x1021; } } } return creg; } // Get temperature reading from 1wire sensor String gettemp_ds18b20(byte addr[8]) { byte i; byte present = 0; byte data[12]; int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract; String value = ""; ds.reset(); ds.select(addr); ds.write(0x44,1); delay(1000); present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for ( i = 0; i < 2; i++) { // we need 2 bytes data[i] = ds.read(); } // Convert to degrees celsius LowByte = data[0]; HighByte = data[1]; TReading = (HighByte << 8) + LowByte; SignBit = TReading & 0x8000; // test most sig bit if (SignBit) // negative { TReading = (TReading ^ 0xffff) + 1; // 2's comp } Tc_100 = (6 * TReading) + TReading / 4; // multiply by (100 * 0.0625) or 6.25 Whole = Tc_100 / 100; // separate off the whole and fractional portions Fract = Tc_100 % 100; if (SignBit) // If its negative { value = "-"; } value += Whole; value += "."; if (Fract < 10) { value += "0"; } value += Fract; return value; } // Scan the 1-Wire bus void scan_1wire(void) { Serial.println("Scanning the 1-Wire bus:"); while(ds.search(addr)) { Serial.print("ID = "); for( i = 0; i < 8; i++) { Serial.print(addr[i], HEX); Serial.print(" "); } if ( OneWire::crc8( addr, 7) != addr[7]) { Serial.print("CRC is not valid!\n"); return; } switch(addr[0]) { case 0x28: Serial.println(" DS18B20-PAR"); break; default: Serial.print(" Unknown (type:"); Serial.print(addr[0]); Serial.println(")"); } } ds.reset_search(); Serial.println(""); }