Vechten met de kattenchip

Dit is een verslag van mijn pogingen zelf een op de Arduino gebaseerde chip-detector te bouwen voor de chips die in mijn katten zijn geïmplanteerd. En om maar meteen met de deur in huis te vallen: dat valt niet mee.

Ik ben een type dat eerder leert van proberen dan van vlijtig studeren en dan is de eerste voorwaarde dat je zelf een chip hebt om mee te experimenteren, want mijn katten zijn niet van het soort dat rustig blijft zitten als je met allerlei apparaten op ze af komt. Zou ik ook niet doen.

Als je een automatisch kattenluik koopt, van Sureflap bijvoorbeeld, dan krijg je daar een paar penningen bij die je aan de halsband van de kat kunt hangen. Jammer genoeg werken die penningen op een andere golflengte en een andere code dan de geïmplanteerde chips en zijn dus waardeloos voor de experimenten. Dus wandelde ik bij de plaatselijke dierenarts naar binnen en vroeg of zij zo'n implantatie-chip voor me had. Ze vertelde me echter dat die dingen nogal streng geregistreerd worden en dat ze er niet zomaar eentje kon meegeven. Misschien... misschien..., als er ooit eentje bij het implanteren op de grond viel... Dat was ongeveer een jaar geleden.

En toen werd ik een paar weken geleden opeens gebeld: ja, er was er eentje over en die mocht ik op komen halen! Dank je wel, dierenartsen en medewerkers van de Eenhoorn in Panningen!

het eerste wat ik deed: de kleine kattenchip in een stukje plakband vouwen en aan een sleutelhanger vastmaken tegen het kwijtraken.

Op jacht naar de data

Het leuke van Arduino is dat een heleboel mysterieuze dingen die je vroeger met soldeerbouten, transistors en condensators op moest lossen, nu beschikbaar zijn als modules die met een eenvoudige programmeertaal aan te spreken zijn. Neem bijvoorbeeld het bouwen van een thermostaat die onder de 20 graden de verwarming laat aanslaan. Vroeger was dat een hoop getrut, nu kun je een programma schrijven als
	    if (temperature<20) then zet_verwarming_aan();
          
Okee, okee, programmeren is misschien ook niet ieders roeping en het kan nog knap lastig worden, maar het is een stuk eenvoudiger dan solderen. En dank zij de grote aantallen hobbyisten en professionals die met Arduinos in de weer ziijn, kun je voor de gekste dingen zulke modules kopen: temperatuur, bewegingsmelders, licht, geluid, noem maar op... Geen van die dingen kost meer dan een paar Euro en een Arduino-kloon is ook beneden te tien Euro te krijgen. Een dure hobby is het dus niet.

...Alle soorten modules zijn dus te krijgen, behalve onze kattenchips...

Kattenchips (laten we ze zo maar noemen) zijn zogenaamde RFID chips. RFID, ofwel Radio-Frequency IDentification, dat is de technologie om met een chipkaart deuren te openen, de technologie die bij de uitgang piept als je iets vergeet af te rekenen, de technologie voor een heleboel dingen. In principe heeft iedere RFID chip zijn eigen unieke code. Bij de eenvoudiger types is dat gewoon een nummer. Bij zaken als het identificeren van dieren is het echter een stuk ingewikkelder. Met name in de USA zijn er een dozijn verschillende manieren om die code te interpreteren en ook in andere werelddelen is het een bonte verzameling standaarden.

In Europa hebben we ons gelukkig op een vaste standaard vastgelegd en dat is de zogenaamde ISO-11784/5. Op de volgende Wikipedia pagina is die standaard tot in details uitgelegd. Maar dat het een standaard is, maakt het niet gemakkelijker om hem uit te lezen.

Het irritante is dat er zoals ik al zei voor de zotste dingen Arduino modules en voorbeeldprogrammas zijn, maar voor onze kattenchip is op het hele internet niets, maar dan ook helemaal niets te vinden. Ja, roependen in de woestijn op allerlei forums die zo'n ding willen bouwen zijn er genoeg, maar oplossingen, ho maar! Ook RFID modules zijn in overvloed te koop, maar voordat je iets vindt dat zelfs qua techniek compatibel is heb je het hele project al bijna opgegeven.

De hardware
De goedkoopste RFID systemen voor de Arduino kosten een of twee Euro en bestaan uit een module en een paar blanco kaarten en sleutelhangers. Werkt prima, maar niet voor kattenchips. Dat heeft te maken met de frequentie. En 13.56 MHz komt helaas niet eens in de buurt.

De volgende groep is die van 125 Khz zoals de RDM600 en aanverwante modules. Iets duurder en veelbelovend, vooral omdat sommige dierenchips daar inderdaad mee kunnen worden gelezen. Helaas. Niet die van mijn kat. Een waarschuwing is hier op zijn plaats: sommige van die modules beweren inderdaad de ISO standaard op 125 Khz te kunnen lezen, maar voorlopig is me dat niet gelukt, waarschijnlijk omdat de chips in de katten echt op 134 Khz zijn ingesteld.

En dan tenslotte de frequentie die we moeten hebben: 135 Khz. Met 30 Euro behoorlijk aan de prijs, voor een Arduino module tenminste, vooral als je niet weet of het ook zal werken, maar vooruit. Ik heb me uit China een "134.2K Long distance RFID AGV Animal Tag Reader Module TTL Interface ISO11784/85 FDX-B" laten komen, compleet met antenne en 'oormerk' om mee te spelen. En inderdaad: een blauw lichtje vertelt me dat de kattenchip nu in ieder geval wordt herkend en ik kan data op de Arduino uitlezen. Hoera!

Testopstelling. Antenne, FDX-lezer en Arduino. Op de voorgrond de kattenchip aan de sleutelhanger en het meegeleverde oormerk.

De software
So far, so good. Maar data uitlezen wil nog niet zeggen dat ik het 15-cijferige, unieke nummer van mijn katten eruit kan opmaken.

Je zou hopen dat de informatie op die Wiki-pagina daar inzicht in zou geven. maar de gegevens zijn eerst bitwise gecodeerd, en de Arduino geeft bytes van acht bits. De module verzorgt de vertaling van die bitwise codering naar bytes, dus dat is geen probleem. Als je dan weet dat een tag begint met een byte met een waarde van 2 en eindigt met een byte van 3 dan heb je ook het datapakketje te pakken:

	  8003A55E030120010000000000⸮ (testchip 1)
          3181F54B404830110000000000⸮ (testchip 2)
        
Op dit ogenblik kunnen we dus wel een uniek nummer uit de chip lezen en de module gebruiken om onze twee katten te herkennen. Voerbakjes, kattenluiken en wat dies meer zij binnen mijn eigen kring zijn dus in feite geregeld.
Alleen zou het een stuk eleganter zijn als we de code konden kraken, zodat hetzelfde nummer wordt teruggegeven als op de papieren staat, het land herkend kan worden, de checksum klopt, zodat we foute lezingen kunnen herkennen enzo. Nu wordt het dus echt moeilijk. Verder snuffelen op het internet leert ons dat de cijfertjes en lettertjes die ik heb uitgelezen hexadecimale code bevatten. Voor hackers van de oude stempel zoals ik is dat een tamelijk eenvoudige code. maar nu hebben we dus een extra vertaalslag nodig.

Het programma dat we gaan schrijven doet dus het volgende:

  • De module zendt zijn data over een serieele interface. Binnen de Arduinofamilie is dat afgedekt door hardware of door software, is dus geen probleem.
  • je leest binnenkomende data tot het een byte met de waarde 2 tegenkomt. Vanaf dat ogenblik bewaart het iedere byte in een buffer tot er een byte met waarde '3' is gesignaleerd.
  • We weten van de ISO standaard dat er eerst 38 ID bits en dan een 10-bits landscode komt. De module heeft dat voorverpakt in 10 bytes voor het ID nummer en drie voor het landsnummer. Vraag me niet hoe ik dat weet, ik heb het ook maar ergens opgepikt.
  • Ik HAAT youtube met een diepe, alles verterende haat. Tegenwoordig wordt ook technische informatie, die je als tekst in een oogopslag leest. verpakt in filmpjes van tien, twintig minuten waar je eindeloos heen en terug moet springen op zoek naar relevante informatie.
  • We moeten dus de eerste 10 bytes behandelen als een groot hexadecimaal nummer en de drie volgende ook.
  • Het gaat hier om hele grote getallen, en mijn UNO kon die in eerset instantie niet behappen. Gelukkig is er een BigNumber library om dat te omzeilen.
  • Dus schrijven we een eenvoudig programma dat de voorgaande punten afhandelt.





	    
#include <SoftwareSerial.h>
#include "BigNumber.h"

#define LENGTH 35
char message[LENGTH];
char buff[LENGTH];
unsigned long lastSignal = 0;
bool transmission = false;
byte state = 1;
int pos;
SoftwareSerial Chiplezer = SoftwareSerial(4,5); 

BigNumber num = 1;

// ---------------------- setup ---------------------

void setup() {
  Serial.begin(115200);
  Chiplezer.begin(9600);
  BigNumber::begin ();  // initialize library
  Serial.println("Start");
  lastSignal = millis();
}

//------------------------- main ---------------------

void loop() {

  switch (state) {
    case 1: {

        if (Chiplezer.available() > 0) {
          lastSignal = millis();
          pos = 0;
          state = 2;
          }
        break;
        }
    case 2: {
        if (Chiplezer.available() > 0 && pos < LENGTH) {
          lastSignal = millis();
          message[pos] = Chiplezer.read();
          pos++;
          }
        if (millis() - lastSignal > 100) state = 3;
        if (pos >= LENGTH) {
          delay(300);
          state = 1;
          }
        }
      break;
    case 3: {     
        byte check = message[1];
        for (int i = 2; i < 27; i++) {
          check = check ^ message[i];
          }
        
        if (check == message[27]) 
          {
          BigNumber id;
          BigNumber countryNbr;
          id=hexInDec(message, 1, 10);
          countryNbr=hexInDec(message, 11, 4);
          
          Serial.print("CardNumber=");
          Serial.println(id);
          Serial.print("Country=");
          Serial.println(countryNbr);

          state = 4;
          }
        break;
        }
    case 4: {
        delay(2000);
        state = 1;    
        break;
        }
  } //switch
}
// -----------------------------------------------------------
BigNumber  hexInDec(char message[], int beg , int len) {
  BigNumber mult = 1;
  BigNumber nbr = 0;
  BigNumber b=0;
  BigNumber bb=0;
  int nextInt;

  for (int i = beg; i < beg + len; i++) {
    // Serial.print(i);Serial.print("\t"); Serial.print(message[i]);
    // Serial.print("\t");Serial.println(nbr);
    nextInt = message[i];
    
    if (nextInt >= 48 && nextInt <= 57) nextInt = map(nextInt, 48, 57, 0, 9);
    if (nextInt >= 65 && nextInt <= 70) nextInt =  map(nextInt, 65, 70, 10, 15);
    if (nextInt >= 97 && nextInt <= 102) nextInt = map(nextInt, 97, 102, 10, 15);
    nextInt = constrain(nextInt, 0, 15);
    bb=nextInt;
    b=mult * bb;
    nbr +=  b;
    mult *= 16;
    }
  return nbr;
}