Elmåler Arduino kode

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("");
}