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