Je slimme meter te slim af met Scala en Akka

Ongeveer twee jaar geleden kreeg ik een nieuwe energiemeter; een slimme. Helaas bleek bij de kennismaking dat die slimmigheid nogal tegenviel. In feite stuurt het apparaat elke twee maanden de gegevens over je energiegebruik op. Een beste tegenvaller! En daarbij, hoe zal het apparaat die gegevens versturen? Is dat veilig? Ik heb de automatische verzending uitgezet. Maar ik wou wél graag m’n energieverbruik kunnen zien. De meter heeft een klein schermpje, maar dat toont niet erg veel informatie. Op zoek naar een betere oplossing! Op diverse forums las ik, dat het mogelijk is om je slimme meter aan te sluiten op een computer en zo data uit te lezen. Tijd om de Raspberry Pi onder ’t stof vandaan te halen en aan de slag te gaan.

Maarten Mulders

Aansluiten

De eerste stap is natuurlijk het maken van een fysieke connectie tussen je computer en je meter. Mijn meter is een Kaifa MA105 (Afbeelding 1) die voorzien is van een RJ11-aansluiting aan de voorkant. RJ11 is de technische aanduiding voor een gewone, ouderwetse telefoonlijn.

 

Afbeelding 1 Slimme meter van het merk Kaifa

 

Mijn Raspberry heeft dat natuurlijk niet, maar wel een paar USB-poorten. Daarom kocht ik op eBay voor een paar euro een USB-naar-RJ11 converter en een USB-kabel om de aansluiting “om te draaien” (Afbeelding 2). De specificaties waren nogal minimalistisch, maar goed, wat kan er fout gaan?

Afbeelding 2 Zelfbedachte aansluiting

 

Na een paar weken geduld werden de kabels bezorgd. Ik sloot ze snel op elkaar aan, de RJ11-plug in de slimme meter en de USB-plug in de Raspberry. Maar helaas… er gebeurde helemaal niets. Op Tweakers vroeg ik om hulp, en al snel bleek mijn aanpak een Slecht IdeeTM. Het had zomaar het einde van de Raspberry en/of de meter kunnen betekenen! Het “niet werken” was dus eigenlijk het minst slechte resultaat…

Tijd voor plan B. Netbeheer heeft specificaties opgesteld voor hoe de slimme meters moeten werken: de DSMR[i] (Dutch Smart Meter Requirements). Daarin staat de precieze pin layout om gegevens uit een slimme meter te lezen (Afbeelding 3).

 

Afbeelding 3 Pin layout voor de RJ11-aansluiting

 

Geschrokken door de waarschuwingen op het forum, en wetend dat ik niet handig ben met de soldeerbout, besloot ik dit keer op safe te spelen en een prefab kabel te kopen (Afbeelding 4). Toen die geleverd werd en ik ‘m aansloot kreeg ik in dmesg op mijn Raspberry de uitvoer van Listing 1. Gelukt!

 

Afbeelding 4 Kant-en-klare kabel

 

[    1.979124] usb 1-1.2: new full-speed USB device number 4 using dwc_otg
[    2.105808] usb 1-1.2: New USB device found, idVendor=0403, idProduct=6001
[    2.107861] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[    2.109856] usb 1-1.2: Product: FT232R USB UART
[    2.111719] usb 1-1.2: Manufacturer: FTDI
[    2.113519] usb 1-1.2: SerialNumber: A5YXDBF7

Listing 1 Uitvoer van dmesg

Gegevens lezen

De tweede uitdaging was het daadwerkelijk lezen van gegevens over deze verbinding. Volgens de DSMR moet je je verbinding instellen op 115200 bps. De verbinding is read-only: door stroom te zetten op de tweede pin krijg je op de vijfde pin gegevens binnen. De kant-en-klare kabel zorgt ervoor dat dit op de goede manier gebeurt, dus daar hoefde ik me niet druk meer om te maken.

Met het Linux commando cu kun je een seriele verbinding zoals deze daadwerkelijk gebruiken. Bij het starten geef je op welke bitrate je wilt gebruiken en of je een pariteitsbit nodig hebt. Een succesvolle verbinding herken je aan de uitvoer “Connected”. Binnen tien seconden verschijnt er dan data in beeld (Listing 2).

 

/KFM5KAIFA-METER

1-3:0.2.8(42)

0-0:1.0.0(160416112854S)

!4016

 

Listing 2 Gegevens van de meter lezen met cu

 

Die tien seconden is conform specificaties; de DSMR stelt dat er elke tien seconden gegevens opgestuurd moeten worden in een “P1 telegram”. Zo’n telegram heeft een vaste structuur.

 

Gegevens interpreteren

Op de eerste en laatste regel na volgt elke regel een vast patroon (Listing 3). De eerste en de laatste regel bevatten respectievelijk een header (met informatie over het merk en model meter) en een checksum over het bericht. Opnieuw biedt de DSMR uitkomst om dat te begrijpen.

 

<digit>-<digit>:<digit>.<digit>.<digit>(characters)

Listing 3 Structuur van een regel uit een P1 Telegram

 

De vaste structuur maakt het eenvoudig om deze gegevens te parsen. Scala heeft daar een heel krachtig mechanisme voor dat “parser combinators” heet[ii]. Als voorbeeld nemen we een regel uit het telegram, 1-0:1.8.1(001371.351*kWh). Het eerste deel, 1-0:1.8.1, is een OBIS-referentie die aangeeft welk gegeven op deze regel staat; in dit geval “Meter Reading electricity delivered to client (Tariff 1) in 0,001 kWh”. Het deel tussen haakjes is de daadwerkelijke waarde, met eenheid kWh, die we willen bewaren als BigDecimal.

Een parser is iets dat gestructureerde tekst kan omzetten in een andere representatie. In dit geval willen we een tekst die voldoet aan de reguliere expresse \d*\.?\d*  omzetten naar een BigDecimal waarde. De code daarvoor is vrij eenvoudig (Listing 4). De drievoudige quotes is een Scala-feature waardoor je geen escaping hoeft toe te passen wanneer je een String maakt; je reguliere expressie wordt daardoor een stuk leesbaarder. Vervolgens maakt de .r methode van die String een Regex. De ^^ geeft aan dat tekst die voldoet aan die Regex verwerkt moet worden door het block dat volgt; binnen dat block is _ de placeholder voor die waarde. Hierdoor hoef je voor die waarde geen variabele te declareren.

 

def number = “””\d*\.?\d*”””.r ^^ { BigDecimal(_) }

Listing 4 Eenvoudige parser

 

Een combinator is een functie die twee parsers omzet naar een nieuwe parser. Hij combineert als het ware de twee parsers. Een voorbeeld staat in Listing 5. De ~> maakt een parser die een combinatie is van de parser ervoor en die erna. Het resultaat van die gecombineerde parser is het resultaat van de parser achter de ~> – hij vergeet als het ware de tekst die de eerste parser verwerkte. De <~ werkt precies de andere kant op; hij verwerkt input die voldoet aan de reguliere expresse d*\.?\d* gevolgd door de vaste tekst “*kWh”, maar het resultaat is dat van de parser die vóór de <~ staat. De uiteindelijke elecCons1 parser is dus een parser die de hele regel 1-0:1.8.1(001371.351*kWh) omvormt naar een BigDecimal met waarde 1371,351.

 

def elecCons1 = “1-0:1.8.1(” ~>

   “””\d*\.?\d*”””.r <~ “*kWh)” ^^ { BigDecimal(_) }

Listing 5 Combinatie van meerdere parsers

 

Dit lijkt een heel complexe manier om een stukje tekst uit een regel te halen en er een getal van te maken, en dat is het ook. De kracht is echter dat je deze combinaties oneindig vaak kan herhalen. Daardoor kun je parsers maken die hele grote structuren tekst in één keer kunnen verwerken – bijvoorbeeld een P1 telegram (ongeveer 30 regels) naar een mooie objectboom die alle data netjes gestructureerd bevat. De uiteindelijke parser voor één heel telegram staat in Listing 6. Een telegram bestaat uit een header gevolgd door wat metadata, de daadwerkelijke data en een checksum. Daarbij is ~ een combinator die zowel de linker als de rechterzijde teruggeeft als resultaat. Als de input ergens niet voldoet aan de verwachte structuur vangt de | combinator dat af door een parse failure te gooien.

 

def telegram = header ~ metadata ~ data ~ checksum

def parser: Parser[P1Telegram] = telegram ^^ {

    case header ~ metadata ~ data ~ checksum =>

        P1Telegram(header, metadata, data, checksum)

    } | failure(“Not all required lines are found”)

Listing 6 Parsen van een compleet P1 Telegram

 

Gegevens verwerken met Akka

Het voert wat ver om hier uit te leggen wat Akka is en hoe het werkt; lees bijvoorbeeld dit[1] of dit[iii] artikel om genoeg te weten te komen om de rest te kunnen volgen.

Het lezen van de seriële poort met Akka is een behoorlijke klus. Er is zelfs C-code voor nodig om de seriële poort te kunnen benaderen; de JVM kan dat zelf niet. Om dit niet allemaal zelf te hoeven doen heb ik gebruik gemaakt van Jakob Odersky’s library akka-serial[iv]. Om deze library te gebruiken moet je Akka-applicatie een “managing actor” starten die zichzelf als extensie op Akka I/O registreert met IO(Serial). Deze “managing actor” zal de seriële poort openen en alle verdere interactie daarmee voor z’n rekening nemen. Hij regelt ook het aanroepen van de C-code. Als er data binnenkomt stuurt hij een Serial.Received bericht naar z’n parent actor. Zie Listing 7 voor een paar code-voorbeelden.

 

private val operator = IO(Serial)(context.system)

override def preStart = {

  // Geef de seriële poort en de baud rate op.

  operator ! Serial.Open(“/dev/ttyUSB0”, SerialSettings(115200))

}

override def receive = {

  case Serial.Received(bytes) =>
// doe iets met de ontvangen bytes, bv. lezen als UTF-8 string
val s = bytes.utf8String
log.info(s”Received bytes $s”)
// Verwerk andere binnenkomende berichten

}

override def postStop(): Unit = {

  operator ! Serial.Close

}

Listing 7 Interactie met de seriële poort vanuit Akka

 

Deze Serial.Received berichten bevatten de bytes zoals ze over de lijn zijn gekomen. Vantevoren weet je niet hoeveel bytes je in elk bericht zult ontvangen. Daarom slaan we de bytes op in een buffer totdat we zeker weten dat er één P1 Telegram in de buffer zit. We weten dat het telegram compleet is als we de regel die met een uitroepteken begint ontvangen hebben. Op dat moment roepen we de parser aan die hiervoor besproken is, die ons – hopelijk – een mooie objectstructuur teruggeeft. Als dat slaagt stuurt onze actor een TelegramReceived bericht met daarin die objectstructuur. Op deze manier hebben we elke tien seconden actuele data.

Dat bericht wordt gestuurd naar een nieuwe actor, de “distributeur”. Het is zijn taak om bij te houden welke andere actoren geïnteresseerd zijn in deze berichten, en ze allemaal een kopietje te sturen. Actoren die telegrams willen ontvangen kunnen zich bij de distributeur aanmelden, en zullen vanaf dat moment elke tien seconden nieuwe data krijgen. Zie Afbeelding 5 voor een versimpelde weergave van deze structuur.

Afbeelding 5 Distributie van binnenkomen P1 Telegrams

Aan de slag!

Als je enthousiast geworden bent en zelf aan de slag wilt gaan, dan kan dat! De code van dit project is volledig open source en te vinden op GitHub[v]. Maar pas op…

  1. Wees niet te zuinig en koop een goede kabel. Je wilt niet het risico lopen om je slimme meter en/of je computer op te blazen…
  2. Er zijn veel versies van de DSMR in omloop. Het loont de moeite om even te kijken welke versie jouw meter volgt. Over de loop der tijd zijn er bijvoorbeeld heel wat verschillende tabellen met OBIS-referenties geweest…
  3. Als je nog niet zo bekend bent met Akka kan het zinvol zijn om in elke actor een “fallback” in je receive-methode in te bouwen: case a: Any => log.debug(s”Ignoring message $a”). Als je ziet dat bepaalde berichten niet aankomen in de actor waar je ze verwacht kun je hiermee eenvoudig traceren waar ze dan wél afgeleverd zijn.

 

 

[1] https://blog.codecentric.de/en/2015/07/a-map-of-akka/

[i] http://www.netbeheernederland.nl/themas/dossier/documenten/?pageindex=3

[ii] https://github.com/scala/scala-parser-combinators

[iii] https://blog.codecentric.de/en/2015/08/introduction-to-akka-actors/

[iv] https://github.com/jodersky/akka-serial

[v] https://github.com/mthmulders/hyperion