This is an old revision of the document!
IR-øje til Kamstrup og andre målere
De fleste Kamstrup målere (og visse andre fabrikater) kan aflæses via infrarød kommunikation. Det kræver dog et optisk læseøje, der konverterer lysglimtene til serielt data. Her er beskrivelsen og samlevejledning til sådan et øje.
Vi sælger et kit med de nødvendige dele (dog ikke kabel); kontakt os for bestilling. Kittet koster DKK 120 (inklusiv porto) for 1 stk (udland DKK 150), med mulighed for rabat ved større styktal. Kvittering for køb koster yderligere 50 kr. Kittet indeholder de dele der vises på billedet nedenfor.
English: We sell a kit containing all necessary parts (excluding cable); contact us to order. The price is DKK 150 including postage if you pay by PayPal (bank transfers usually require an exorbitant fee, so we do not recommend using those). The kit contains all the parts depicted on the image below.
Komponenter
- Et printkort (diagram), fra http://www.thingiverse.com/thing:264268
- Et plastic-hylster, fra https://www.thingiverse.com/thing:2652216
- 1 x IR-modtager, f.x. http://www.el-supply.dk/?Vnr=455.IRR (klar)
- 1 x IR-sender, f.x. http://www.el-supply.dk/?Vnr=455.IRT (sort)
- 2 x BC547
- 1 x BC557
- 15 k Ohm modstand (grøn-blå-orange-guld)
- 39 k Ohm modstand (orange-hvid-orange-guld)
- 2 x 10 k Ohm modstand (brun-sort-sort-rød-brun)
- 220 Ohm modstand (rød-rød-brun-guld)
- Kabel med 4 ledere, eller 4-pins pinheader
- Runde super-magneter, 6 mm * Ø 6 mm, f.x. http://www.supermagnete.de/eng/S-06-06-N
Se også projektets git.
Samle-vejledning
Det enkeltsidede print er lidt besværligt at lave første gang: Alle komponenter (undtaget de 2 dioder) skal nemlig sidde på BAGSIDEN, hvor print-banerne og lodningerne også er. Dette er et mindre problem med det dobbeltsidede print (som er i vores kit).
Layoutet er set fra forsiden (dvs. fra siden uden printbaner):

Opad kan findes ved at der er 4 huller på række.
Lod dioderne på forsiden
Transmit skal sidde til venstre, receive til højre (set forfra)
Bor de 2 midterste huller af hylsteret ud til 5mm, og check at dioderne passer i hullerne:
Lod 39k ohm på mellem de 2 dioder
Lod de resterende komponenter på
Pas meget på at tænke fremad, så du ikke får blokeret for at et andet komponent kan loddes på bagefter.
Sæt magneter fast i hylsteret
Bor hullerne ud til 6mm, og vær sikker på at bore dem dybt nok til magneterne. Klem en magnet så meget i som muligt, og brug en gummihammer til at slå den helt i:
PAS PÅ! Magneter kan nemt splintre, hvis man slår på dem med en metalhammer!
Sæt printet i hylsteret, og forbind til Arduino
Benene er (fra venstre mod højre):
- Receive ⇒ Arduino ben 9
- Transmit ⇒ Arduino ben 10
- VCC (5V) ⇒ Arduino 5V
- Ground ⇒ Arduino GND
Hvis din måler er påtrykt “DLMS” (se billede nedenfor) kan du bruge nedenstående Arduino sketch eller (hvis du forbinder øjet til en PC, Raspberry Pi eller lignende) PyKamstrup. Hvis din måler ikke er mærket DLMS, bruger den formodentlig en anden protokol ved navn IEC 62056 - i dette tilfælde skal der bruges noget andet software.
Til Kamstrup MULTICAL 66 kan du bruge denne software, som Kenan Vilic har lavet.
Læg Arduino SerialKamstrupReadout programmet på Arduino'en:
#include <SoftwareSerial.h>
//Kamstrup setup
// 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_KAMSER_RX 9 // Kamstrup IR interface RX
#define PIN_KAMSER_TX 10 // Kamstrup IR interface TX
#define PIN_LED 13 // Standard Arduino LED
// Kamstrup optical IR serial
#define KAMTIMEOUT 300 // Kamstrup timeout after transmit
SoftwareSerial kamSer(PIN_KAMSER_RX, PIN_KAMSER_TX, false); // Initialize serial
void setup () {
Serial.begin(57600);
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, 0);
// setup kamstrup serial
pinMode(PIN_KAMSER_RX,INPUT);
pinMode(PIN_KAMSER_TX,OUTPUT);
kamSer.begin(KAMBAUD);
delay(200);
//Serial.println("\n[testKamstrup]");
// poll the Kamstrup registers for data
for (int kreg = 0; kreg < NUMREGS; kreg++) {
kamReadReg(kreg);
delay(100);
}
}
void loop () {
// poll the Kamstrup registers for data
for (int kreg = 0; kreg < NUMREGS; kreg++) {
kamReadReg(kreg);
delay(100);
}
digitalWrite(PIN_LED, digitalRead(PIN_KAMSER_RX));
delay(1000);
}
// kamReadReg - read a Kamstrup register
float kamReadReg(unsigned short kreg) {
byte recvmsg[40]; // 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();
return 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;
}
Test
Hvis alt er samlet rigtigt virker IR-øjet nu. Det er dog en god ide at lave et par tests, da det er nemt at lave fejl.
Transmit test
Den første test er at transmit øjet blinker. Find en mobil-telefon og brug dens kamera til at kigge på transmit øjet, mens Arduino'en kører. Nogle digital-kameraer kan se infrarødt lys -vi har fået at vide at nogle mobiler, f.eks. iPhone 6 og Samsung Galaxy 4, ikke kan se infrarødt lys (men for iPhone er det tilsyneladende sådan, at frontkameraet kan se infrarødt lys, mens kameraet på bagsiden ikke kan). Så det kan være nødvendigt at prøve mere end en - find evt. en fjernbetjening og kontrollér at du kan se signalet fra den med dit kamera.
Den skulle gerne blinke i en lilla farve:
Hvis det ikke virker er der en fejl på printet. Check alt igennem igen, og check for kortslutninger mellem baner — også under komponenter!
Receive test
På Arduino'en sidder der en LED på ben 13. Test-programmet lyser med LED på ben 13 hvis IR-recieveren er i mørke, og slukker hvis IR-receiveren er i lys.
Lys på IR-receiveren. LED på Arduino ben 13 skal slukke, indenfor 2 sekunder.
Hvis det ikke virker er der en fejl på printet. Check alt igennem igen, og check for kortslutninger mellem baner — også under komponenter!
Kamstrup test
Sæt læseøjet fast på Kamstrup måleren. På Arduino seriel konsollen (som skal sættes til 57600 baud) burde der komme data:
Hvis der i stedet står Timed out listening for data er der ikke forbindelse. Prøv at dreje IR-hovedet 180 grader. Hvis det ikke virker, så drej det langsomt hele vejen rundt.
Hvis der stadig ikke er forbindelse: Lav de to forrige tests, check alt igennem, specielt for kortslutninger!
Bemærk: På nogle målere aktiveres IR-forbindelsen af de magneter der sidder på plasthuset. Så hvis du tester uden, kan du risikere at du ikke får noget signal.














