Reading IrDA data from an Elster A100C electricity meter

Hi

I've recently installed solar panels and wanted to read the data from the generation meter, an Elster A100C. This project uses an Arduino sending data via USB serial to a server. I hope it is of use.

http://www.rotwang.co.uk/projects/meter.html

Dave

TrystanLea's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Thankyou for sharing this daveb, I have added a link from the community builds section

DaveLloyd's picture

Re: Reading IrDA data from an Elster A100C electricity meter

I ported the code to run on an emonTX:

Network ID is set to 11 as I am using another emonTX for live energy monitoring.

Dave

 

 

/*
 EmonTx Pulse example combined with Arduino code to read data from an Elster A100C electricity meter.
 
 An example sketch for the emontx module for
 CT only electricity monitoring.
 
 Part of the openenergymonitor.org project
 Licence: GNU GPL V3
 
 Authors: Glyn Hudson, Trystan Lea
 Builds upon JeeLabs RF12 library and Arduino
 
 Arduino code to read data from an Elster A100C electricity meter.
 
 Copyright (C) 2012 Dave Berkeley solar@rotwang.co.uk
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 USA
 
 Modified for emonTX by Dave Lloyd
 Added temperature monitor 
 
 
*/
 
#define freq RF12_868MHZ                                                // Frequency of RF12B module can be RF12_433MHZ, RF12_868MHZ or RF12_915MHZ. You should use the one matching the module you have.433MHZ, RF12_868MHZ or RF12_915MHZ. You should use the one matching the module you have.
const int nodeID = 11;                                                  // emonTx RFM12B node ID
const int networkGroup = 210;                                           // emonTx RFM12B wireless network group - needs to be same as emonBase and emonGLCD needs to be same as emonBase and emonGLCD
 
const int UNO = 1;                                                      // Set to 0 if your not using the UNO bootloader (i.e using Duemilanove) - All Atmega's shipped from OpenEnergyMonitor come with Arduino Uno bootloader
#include <avr/wdt.h>                                                    // the UNO bootloader 
 
#include <OneWire.h>    // http://www.pjrc.com/teensy/td_libs_OneWire.html
#include <DallasTemperature.h>      // http://download.milesburton.com/Arduino/MaximTemperature/ (3.7.2 Beta needed for Arduino 1.0)
 
#include <JeeLib.h>                                                     // Download JeeLib: http://github.com/jcw/jeelib
ISR(WDT_vect) { Sleepy::watchdogEvent(); }
  
typedef struct { unsigned long meter; int temperature;} PayloadTX;
PayloadTX emontx;                                                     // neat way of packaging data for RF comms
 
// set pin numbers:
const int ledPin = 9;    // remapped
const int intPin = 3;    // remapped
#define ONE_WIRE_BUS 4              // temperature sensor connection - hard wired 
#define BIT_PERIOD 416 // us
#define BIT_MARGIN 10 // us
 
//--------------------------------------------------------------------------------------------
// DS18B20 temperature setup - onboard sensor 
//--------------------------------------------------------------------------------------------
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
double temp;
 
 
 
  /*
   *  Buffer
   */
 
#define BUFF_SIZE 128
 
struct Buffer
{
  int data[BUFF_SIZE];
  int in;
  int out;
};
 
static void buff_init(struct Buffer* b)
{
  b->in = b->out = 0;
}
 
static int buff_full(struct Buffer* b)
{
  int next = b->in + 1;
  if (next >= BUFF_SIZE)
    next = 0;
  return next == b->out;
}
 
static int buff_add(struct Buffer* b, int d)
{
  int next = b->in + 1;
  if (next >= BUFF_SIZE)
    next = 0;
  if (next == b->out)
    return -1; // overfow error
 
  b->data[b->in] = d;
  b->in = next;  
  return 0;  
}
 
static int buff_get(struct Buffer* b, int* d)
{
  if (b->in == b->out)
    return -1;
  int next = b->out + 1;
  if (next >= BUFF_SIZE)
    next = 0;
  *d = b->data[b->out];
  b->out = next;
  return 0;  
}
 
  /*
   *  Called on every InfraRed pulse, ie. every '1' bit.
   */
 
typedef unsigned long stamp;
 
static stamp last_us;
 
#define BITS(t) (((t) + (BIT_PERIOD/2)) / BIT_PERIOD)
 
static struct Buffer bits;
 
void on_change(void)
{
  //  push the number of bit periods since the last interrupt to a buffer.
  const stamp us = micros();
  const int diff = us - last_us;
  last_us = us;
 
  const int bit_periods = BITS(diff);
  buff_add(& bits, bit_periods);
}
 
  /*
   *  Decode bit stream
   */
 
static struct Buffer bytes;
 
static int bit_data;
static int bit_index;
 
int add_bit(int state)
{
    bit_data >>= 1;
 
    if (state)
        bit_data += 0x200;
 
    if (bit_data)
        bit_index += 1;
 
    if (bit_index < 10)
        return 0;
 
    if (bit_data & 1) // start bit
    {
        if (!state) // stop bit
        {
            const int d = (bit_data >> 1) & 0xFF;
            // high represents '0', so invert the bits
            buff_add(& bytes, (d ^ 0xFF));
            bit_data = bit_index = 0;
            return 1;
        }
    }
 
    //  bad frame
    bit_data = bit_index = 0;
    return 0;
}
 
static void on_timeout()
{
  // too long since last bits count
  
  if (bit_index)
    while (bit_data) // flush with trailing '0' bits
      add_bit(0);  
  bit_data = bit_index = 0;
}
 
static void on_bits(int bits)
{
  // called on each bits count.
  // the bit periods represent a '1' followed by N '0's.
 
  if (bits < 1)
  {
    on_timeout();
    return;
  }
 
  // the elapsed bit periods since last interrupt
  // represents a '1' followed by 0 or more '0's.  
  add_bit(1);
  for (; bits > 1; bits--)
    add_bit(0);
}
 
  /*
   *
   */
   
static int decode_bit_stream(void)
{
  // look for a pause in the bit stream
  const stamp us = micros();
  const int diff = us - last_us;
  if (BITS(diff) > 20)
    on_timeout();
 
  // read the bit stream  
  int bit_count;
  if (buff_get(& bits, & bit_count) < 0)
    return -1;
 
  on_bits(bit_count);
 
  // read the byte stream decoded above
  int byte_data;
  if (buff_get(& bytes, & byte_data) < 0)
    return -1;
 
  return byte_data;
}
 
  /*
   *
   */
 
unsigned long bcdtol(const unsigned char* data, int bytes)
{
    unsigned long result = 0;
    for (int i = 0;  i < bytes; i++)
    {
        const unsigned char digit = *data++;
        result *= 100;
        result += 10 * (digit >> 4);
        result += digit & 0xF;
    }
    return result;
}
 
typedef void (*on_reading)(unsigned long reading);
 
class ElsterA100C
{
    // see Appendix B of the product manual
    struct info {
        char product[12]; // eg. "Elster A100C..."
        char firmware[9];
        unsigned char mfg_serial[3];
        unsigned char config_serial[2];
        char utility_serial[16];
        unsigned char meter_definition[3];
        unsigned char rate_1_import_kWh[5]; // the reading we are interested in!
        unsigned char rate_1_reserved[5];
        unsigned char rate_1_reverse_kWh[5];
        unsigned char rate_2_import_kWh[5];
        unsigned char rate_2_reserved_kWh[5];
        unsigned char rate_2_reverse_kWh[5];
        unsigned char reserved_01[1];
        unsigned char status;
        unsigned char error;
        unsigned char anti_creep[3];
        unsigned char rate_1_time[3];
        unsigned char rate_2_time[3];
        unsigned char power_up[3];
        unsigned char power_fail[2];
        unsigned char watchdog;
        unsigned char reverse_warning;
        unsigned char reserved_02[10];
    };
 
    unsigned char data[sizeof(info)];
    unsigned int idx;
    int reading;
    unsigned char last_4[4];
    on_reading handler;
public:
    ElsterA100C(on_reading cb)
    : idx(0), reading(0), handler(cb)
    {
    }
 
    int good_cs(unsigned char cs, unsigned char check)
    {
      // note : should be 'cs == check', but I'm seeing
      // systematic 1-bit errors which I can't track down.      
 
      /*
      Serial.print("bcc=");
      Serial.print(check, HEX);
      Serial.print(" cs=");
      Serial.print(cs, HEX);
      Serial.print(" xor=");
      Serial.print(cs ^ check, HEX);
      Serial.print("\r\n");
      */
 
      int bits = 0;
      int delta = cs ^ check;
      for (; delta; delta >>= 1)
        if (delta & 0x01)
          bits += 1;
 
      return bits < 2;
    }
 
    void good_packet()
    {
        struct info* info = (struct info*) data;        
        handler(bcdtol(info->rate_1_import_kWh, 5));
    }
 
    unsigned char bcc(unsigned char cs, const unsigned char* data, int count)
    {
        for (int i = 0; i < count; ++i)
            cs += *data++;
        return cs;
    }
 
    void on_data(unsigned char c)
    {
        // match the packet header
        const unsigned char match[] = { 0x01, 0x00, sizeof(info), 0x02 };
 
        if (!reading)
        {
            // keep a log of the last 4 chars
            last_4[0] = last_4[1];
            last_4[1] = last_4[2];
            last_4[2] = last_4[3];
            last_4[3] = c;
 
            if (memcmp(match, last_4, sizeof(match)) == 0)
            {
                idx = 0;
                reading = 1;
                return;
            }
 
            return;
        }
 
        if (idx < sizeof(info))
        {
            data[idx++] = c;
            return;
        }
 
        const unsigned char etx = 0x03;
        if (idx == sizeof(info))
        {
            if (c != etx)
            {
                printf("etx=%02X\n", c);
                reading = idx = 0;
                return;
            }
            idx++;
            return;
        }                    
 
        unsigned char cs = 0x00; // why 0x40?
        cs = bcc(cs, match, sizeof(match));
        cs = bcc(cs, data, sizeof(data));
        cs = bcc(cs, & etx, 1);
            
        if (good_cs(cs, c))
          good_packet();
 
        reading = 0;
    }
};
 
  /*
   *
   */
 
void meter_reading(unsigned long reading)
{
  Serial.print(reading);
  Serial.print(", ");
  Serial.print(temp);
  Serial.print("\r\n");
 
  if (emontx.meter != reading)
  {
    emontx.meter = reading;
    sensors.requestTemperatures();
    temp = (sensors.getTempCByIndex(0));
    emontx.temperature = (int) (temp * 100); 
 
    delay (50);
    send_rf_data();                                                       // *SEND RF DATA* - see emontx_lib
    delay (50);
  } 
  if (UNO) wdt_reset();
 
 
  static int state = 0;
  digitalWrite(ledPin, state = !state);
}
 
ElsterA100C meter(meter_reading);
 
 
void setup() 
{
  buff_init(& bits);
  buff_init(& bytes);
  
  Serial.begin(9600);
  Serial.println("Dave's emonTX meter reading");
             
  rf12_initialize(nodeID, freq, networkGroup);                          // initialize RF
  rf12_sleep(RF12_SLEEP);
 
  pinMode(ledPin, OUTPUT);
  pinMode(intPin, INPUT);
 
  sensors.begin();                         // start up the DS18B20 temp sensor onboard  
  sensors.requestTemperatures();
  temp = (sensors.getTempCByIndex(0));     // get inital temperture reading
 
 
  attachInterrupt(1, on_change, RISING);   // remap
 
 if (UNO) wdt_enable(WDTO_8S);  
}
 
void loop() 
  int byte_data = decode_bit_stream();
  if (byte_data == -1)
    return;
 
  meter.on_data(byte_data);
//  emontx_sleep(10);                                                     // sleep or delay in seconds - see emontx_lib
//  digitalWrite(LEDpin, HIGH); delay(2); digitalWrite(LEDpin, LOW);      // flash LED
}
 
jack_kelly's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Does anyone know if it's possible to read kvarh measurements (i.e. reactive power) over IrDA from an Elster A102C?

TrystanLea's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Dont know the answer im afraid, I think the only items that can be read are the ones described in the class:

        char product[12]; // eg. "Elster A100C..."
        char firmware[9];
        unsigned char mfg_serial[3];
        unsigned char config_serial[2];
        char utility_serial[16];
        unsigned char meter_definition[3];
        unsigned char rate_1_import_kWh[5]; // the reading we are interested in!
        unsigned char rate_1_reserved[5];
        unsigned char rate_1_reverse_kWh[5];
        unsigned char rate_2_import_kWh[5];
        unsigned char rate_2_reserved_kWh[5];
        unsigned char rate_2_reverse_kWh[5];
        unsigned char reserved_01[1];
        unsigned char status;
        unsigned char error;
        unsigned char anti_creep[3];
        unsigned char rate_1_time[3];
        unsigned char rate_2_time[3];
        unsigned char power_up[3];
        unsigned char power_fail[2];
        unsigned char watchdog;
        unsigned char reverse_warning;
        unsigned char reserved_02[10];

I will send Dave Berkeley who wrote the library an email to ask.

http://www.rotwang.co.uk/projects/projects.html

daveb's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Hi Kevin

Trystan is right, the data from the meter is described by the class above, so no analysis of reactive power I'm afraid.

 

jack_kelly's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Many thanks for the reply.  And thanks for emailing Dave, Trystan!

Just to confirm: are you guys talking about the Elster A100C rather than the A102C?  The Elster website suggests that the A102C does measure reactive power, although it doesn't specify whether that measurement is exposed on the IrDA port.  I've sent several emails to Elster but they haven't replied.

Have you guys tinkered with an A102C or A220?

jack_kelly's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Sorry, one more question: does anyone know where I can get hold of Elster's documentation for the output of their IrDA ports?

DaveLloyd's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Found this on Google www.meterspec.com/143.pdf

jack_kelly's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Brilliant, thank you!  (I clearly wasn't trying hard enough when I googled for the same info!)

I've also spoken to a very nice man at Elster.

The conclusion seems to be that yes, the A102C does spit out both kWh and kvarh readings over the IrDA port.  Hurray!  I'll almost certainly buy an A102C over the next few days and I'll report back.

jack_kelly's picture

Re: Reading IrDA data from an Elster A100C electricity meter

Humph.  Turns out the A102C isn't available in the UK.  The AS230 might be a possibility though...