SML Protokoll verstehen


Allgemeine Informationen

Im ersten Teil unseres Stromzähler-Projekts haben wir den Drehstromzähler Itron 3.HZ-AC-D1-A1 mithilfe eines Mikrocontrollers und eines IR-Lesekopfs erfolgreich ausgelesen. Die erfassten Daten wurden über einen längeren Zeitraum in eine Datenbank übertragen und dort gespeichert.
Doch alles hat irgendwann ein Ende – so auch unser treuer Itron-Zähler. Der neue Stromzähler, ein ISKA MT691, war mit dem bisherigen Arduino-Quellcode leider nicht kompatibel. Daher habe ich das Projekt neu aufgesetzt.
Zwar musste ich nicht komplett von vorne beginnen, doch ich habe mich erneut durch zahlreiche Internetseiten gelesen, um mich in das Thema einzuarbeiten. Mittlerweile verstehe ich das SML-Protokoll deutlich besser und möchte hier meine Erfahrungen sowie die einzelnen Schritte meiner erfolgreichen Umsetzung mit euch teilen.


Inhaltsverzeichnis

Die Hardware

Hier dreht sich alles um die ersten Schritte zum Auslesen und Verstehen des SML-Protokolls deines Stromzählers – mit einfacher Hardware und einem Mikrocontroller wie dem Arduino.

Was brauchst du?
Für den Einstieg benötigst du im Grunde drei Dinge:

  • Einen SML-fähigen Stromzähler
    Moderne Stromzähler unterstützen das SML-Protokoll (Smart Message Language), über das sie ihre Messwerte seriell übertragen.
  • Einen Mikrocontroller (z.B. Arduino)
    Dieser dient als Schnittstelle, um die vom Stromzähler kommenden Daten zu empfangen und auszuwerten.
  • Einen Lesekopf (Infrarot-Sensor oder optische Schnittstelle)
    Der Lesekopf wird direkt auf den IR-Ausgang deines Stromzählers gesetzt und verbindet sich mit dem Mikrocontroller.
Hardwareaufbau
Der Lesekopf wird fest auf den IR-Port des Stromzählers angebracht und mit dem Mikrocontroller verbunden. Wie genau das aussieht, zeigt unser Schaltplan weiter unten. Dort findest du auch Tipps zum richtigen Anschluss und zur Spannungsversorgung.

Platzierung und Installation
Ob du den Mikrocontroller direkt im Zählerschrank, im Sicherungskasten oder extern anbringst, ist flexibel und hängt von deinem individuellen Setup ab. Wichtig ist nur, dass die Verbindung zwischen Lesekopf und Zähler stabil bleibt und die Verkabelung sicher erfolgt.

Stromzähler

TYP: ISKRA MT691

Mikrocontroller

Typ: Wemos D1 Mini Pro

Platine

Kleine Platine mit Anschlüsse für den Lesekopf und 2 Status Led`s

IR Lesekopf

Typ: Hichi IR v1.1

Die ersten HEX-Werte

Wenn alles korrekt angeschlossen und verbunden ist, können wir mit dem Arduino-Code beginnen.

Wichtige Hinweise:

  • Es kann sein, dass du an deinem Stromzähler einen PIN eingeben musst, um alle Daten über die optische Schnittstelle (z. B. D0) auszulesen. Zusätzlich habe ich bei meinem Gerät die Funktion INFO ON aktiviert. Diese sorgt offenbar nur dafür, dass mehr Informationen auf dem Display des Stromzählers angezeigt werden – sie beeinflusst jedoch nicht die optische Schnittstelle.
  • Baudrate des Mikrocontrollers:
    Die Baudrate ist entscheidend für eine zuverlässige Datenübertragung. Über die D0-Schnittstelle senden viele Stromzähler ihre Daten mit 9600 Baud – das solltest du in deinem Programm entsprechend einstellen.
    Ein häufiger Fehler (den auch ich gemacht habe): Die Baudrate für die serielle Verbindung vom Mikrocontroller zum PC war bei mir anfangs ebenfalls auf 9600 Baud eingestellt – also gleich schnell wie die Schnittstelle zum Zähler. Dadurch kam es zu Datenverlusten, da der Mikrocontroller während des Sendens an den PC keine neuen Daten vom Zähler verarbeiten konnte.
    Tipp: Wähle die serielle Baudrate zum PC deutlich höher als die zum Zähler – z. B. 115200 Baud – damit der Mikrocontroller genug Zeit hat, alle Daten ohne Verluste zu übertragen.
  • Mikrocontroller:
    Achte darauf, dass dein Mikrocontroller ausreichend leistungsfähig ist. Ein zu schwach dimensionierter Controller kann zu Datenverlust führen – insbesondere wenn mehrere Aufgaben gleichzeitig verarbeitet werden müssen (z. B. Datenempfang, -verarbeitung und serielle Weiterleitung).
    Bei mir hat sich der Wemos D1 Mini Pro als zuverlässige und leistungsfähige Lösung erwiesen. Er bietet ausreichend Rechenleistung und Speicher, um die Daten des Stromzählers stabil auszulesen und weiterzuleiten – auch bei höheren Baudraten.
			#include <SoftwareSerial.h>;

// SoftwareSerial nutzen, um Daten von der optischen Schnittstelle des Stromzählers zu lesen
// GPIO4 = RX (Empfang vom Zähler), GPIO5 = TX (nicht benutzt in diesem Fall)
SoftwareSerial irSerial(4, 5);

// Puffer zum Zwischenspeichern der empfangenen Bytes
#define BUFFER_SIZE 256
byte buffer[BUFFER_SIZE];
int idx = 0;  // Aktuelle Position im Puffer (Index)

void setup() {
  Serial.begin(115200);    // Serielle Verbindung zum PC – hohe Baudrate empfohlen
  irSerial.begin(9600);    // Serielle Verbindung zum Stromzähler (z. B. über die D0-Schnittstelle)
}

void loop() {
  // Solange Daten vom Stromzähler verfügbar sind und der Puffer nicht voll ist:
  while (irSerial.available() && idx < BUFFER_SIZE) {
	buffer[idx++] = irSerial.read();  // Nächstes Byte lesen und im Puffer speichern
  }

  // Wenn der Puffer voll ist, geben wir alle gesammelten Bytes als HEX-Werte aus
  if (idx >= BUFFER_SIZE) {
	for (int i = 0; i < idx; i++) {
	  // Optional: eine führende Null anzeigen, damit alles zweistellig ist (z. B. "0A" statt "A")
	  if (buffer[i] < 0x10) Serial.print("0");

	  // Byte als HEX-Wert ausgeben (z. B. "1B", "7E", etc.)
	  Serial.print(buffer[i], HEX);
	  Serial.print(" ");
	}

	Serial.println();  // Neue Zeile nach der Ausgabe
	idx = 0;           // Puffer zurücksetzen für den nächsten Durchlauf
  }
}
			
		

Mit diesem Code erhalten wir vollständige und zusammenhängende SML-Protokolle im seriellen Monitor.
Die einzelnen Protokolle werden durch spezielle Start- und Endsequenzen voneinander abgegrenzt:

  • Startsequenz: 1B 1B 1B 1B 01 01 01 01
  • Endsequenz: 1B 1B 1B 1B 1A

Diese Sequenzen sind im SML-Standard definiert und ermöglichen es, einzelne Telegramme bei kontinuierlichem Datenstrom zuverlässig zu erkennen und zu verarbeiten.

Hier ein einzelnes Protokoll von meinem ISKRA MT691:
			
1B 1B 1B 1B 01 01 01 01 76 05 03 F8 1F BD 62 00 62 00 72 63 01 01 76 01 01 05 01 52 B5 3F 0B 0A 01 48 53 4C 00 05 69 4E BA 72 62 01 65 01 52 B3 9D 62 01 63 BA CF 00 76 05 03 F8 1F BE 62 00 62 00 72 63 07 01 77 01 0B 0A 01 48 53 4C 00 05 69 4E BA 07 01 00 62 0A FF FF 72 62 01 65 01 52 B3 9D 75 77 07 01 00 60 32 01 01 01 01 01 01 04 49 53 4B 01 77 07 01 00 60 01 00 FF 01 01 01 01 0B 0A 01 48 53 4C 00 05 69 4E BA 01 77 07 01 00 01 08 00 FF 65 00 1C 01 04 01 62 1E 52 FF 65 02 EF 85 5E 01 77 07 01 00 02 08 00 FF 01 01 62 1E 52 FF 65 00 1E 6B AA 01 77 07 01 00 10 07 00 FF 01 01 62 1B 52 00 53 0B 10 01 01 01 63 1F 7C 00 76 05 03 F8 1F BF 62 00 62 00 72 63 02 01 71 01 63 FA 7C 00 00 1B 1B 1B 1B 1A 01 54 68 
			
		

Aufteilung und Auswertung

Nachfolgend sehen Sie eine Tabelle, in der die zuvor empfangenen Daten in einzelne Segmente unterteilt werden. Für uns sind die fett markierten Werte interessant. Die mit * markierten Werte werden unten genauer beschrieben.

📡 SML-Telegramm
1B 1B 1B 1B 01 01 01 01Startsequenz
76 05 03 F8 1F BDZeitstempel / Message-ID
62 00Public-Key Info (leer)
62 00Public-Key Info (leer)
72 63 01 01Transaktionsdaten
76 01 01 05 01 52 B5 3F 0BMessage-Header
0A 01 48 53 4C 00 05 69 4E BAServer-ID: ISKRA MT691 (verändert)
🔁 Wiederholung der Server-ID & Steuerdaten
72 62 01 65 01 52 B3 9DMessage-Body
62 01 63 BA CF 00Steuerdaten
76 05 03 F8 1F BEZeitstempel
62 00 62 00Public-Key Info (leer)
72 63 07 01 77 01 0BDatenblock
0A 01 48 53 4C 00 05 69 4E BAServer-ID erneut (verändert)
⚙️ OBIS-Setup & Konfiguration
07 01 00 62 0A FF FFOBIS Setup Info
72 62 01 65 01 52 B3 9D 75Steuerdaten
77 07 01 00 60 32 01 01 01 01OBIS-Definitionen
01 01 04 49 53 4B 01OBIS-Definitionen (Fortsetzung)
77 07 01 00 60 01 00 FF 01 01OBIS-Definitionen
01 01 0BEnde OBIS-Definitionen
0A 01 48 53 4C 00 05 69 4E BAServer-ID erneut (verändert)
01Ende Block
⚡ Verbrauchswerte
77 07 01 00 01 08 00 FFOBIS 1.8.0 (Bezug)
65 00 1C 01 04 01Struktur-Header
62 1E 52 FFDatentyp: Integer /10000
65* Marker für SEQUENCE (5 Byte)
02 EF 85 5E** Zählerstand: 49251678 → 4925.1678 kWh
01Endblock
🔋 Einspeisung
77 07 01 00 02 08 00 FFOBIS 2.8.0 (Einspeisung)
01 01 62 1E 52 FFDatentyp: Integer /10000
65* Marker für SEQUENCE (5 Byte)
00 1E 6B AA** Zählerstand: 1995690 → 199.5690 kWh
01Endblock
⚙️ Momentanwert (Bezug)
77 07 01 00 10 07 00 FFOBIS 10.7.0
01 01 62 1B 52 00Datentyp: Integer
53* Unsigned Integer, 3 Byte
0B 10*** Bezug oder Einspeisung: 2832 Watt Bezug
01Endblock
✅ Abschluss
01 01 63 1F 7C 00Steuerdaten / CRC / Padding
76 05 03 F8 1F BFTimestamp
62 00 62 00Public-Key Info (leer)
72 63 02 01 71 01 63 FA 7CAbschlussdaten
00 00Padding
1B 1B 1B 1B 1A 01 54 68Endsequenz

Details zu den * :

*: Ein Hexwert besteht aus einem Byte. Die rechten vier Bit des Bytes zeigen die Anzahl der nachfolgenden Bytes, die den Zählerwert beinhalten. Dabei ist zu beachten, dass das letzte Byte (HEX 0x01) zu dieser Anzahl dazugehört, aber nicht im Zählerwert enthalten ist.
**: Dieser Datensatz beinhalten den Zählerwert (1.8.0 oder 2.8.0), der später im Dezimal durch 10.000 geteilt werden muss, um den Zählerstand in kWh zu erhalten. Die Anzahl der Bytes ergibt sich aus der zuvor ermittelten Länge minus einem Byte für den Endblock.
***: Dieser Datensatz beinhaltet den Zählerwert für OBIS 10.07.0. Nach der Umrechnung in Dezimalzahl gibt dieser den aktuellen Bezug oder die Einspeisung in Watt an. Die Anzahl der Bytes ergibt sich aus der zuvor ermittelten Länge minus einem Byte für den Endblock. Ein negativer Wert bedeutet Einspeisung, ein positiver Wert Bezug.

Umrechnung der HEX- und Dezimalwerte online

Als Hilfe könnt Ihr diesen kleinen HEX ↔ DEC Umrechner verwenden. Die Umrechnung erfolgt in beide Richtung. Einfach im jeweiligen Feld einen Wert eintragen.
Hierbei ggf. die führende Null nicht vergessen.

Umrechnung Zählerstände aus HEX-Werten mit dem Arduino

Umwandlung der Zählerstände aus dem Stromzähler 1.8.0 und 2.8.0

Die folgenden Informationen beschreiben die Funktion, die dazu dient, die Hex-Werte aus den Stromzählern 1.8.0 und 2.8.0 in eine lesbare, ganzzahlige Zahl umzuwandeln. Diese Zählerstände werden häufig als Hex-Werte übertragen, und die Funktion parseUnsignedValue verarbeitet diese, um den entsprechenden unsigned long Wert zu berechnen.

Der aufruf der Funktion kann wie folgt geschehen.

				// Beispiel 1: 2 Byte (0x12 0x34 = 4660)
byte data1[] = {0x12, 0x34};
unsigned long val1 = parseUnsignedValue(data1, 2);

// Beispiel 2: 4 Byte (0x00 0x00 0x27 0x10 = 10000)
byte data2[] = {0x00, 0x00, 0x27, 0x10};
unsigned long val2 = parseUnsignedValue(data2, 4);
				
			

Arduino Quellcode

			/* Parse Zählerstände 1.8.0 und 2.8.0 (immer positiv)
   Diese Funktion liest die Zählerstände aus einem Array von Bytes und 
   gibt den entsprechenden unsigned long Wert zurück. Der Wert wird 
   durch eine Kombination von 1 bis 4 Bytes gebildet. */

unsigned long parseUnsignedValue(byte* data, int length) {
    // Überprüfen, ob die Länge des Arrays im gültigen Bereich (1 bis 4) liegt.
    // Wenn die Länge ungültig ist, wird 0 zurückgegeben.
    if (length < 1 || length > 4) return 0;
    
    unsigned long result = 0;  // Initialisiere den Ergebniswert auf 0
    
    // Iteriere über jedes Byte im Array, um den finalen Wert zu berechnen
    for (int i = 0; i < length; i++) {
        result <<= 8;        // Verschiebe das Ergebnis um 8 Bits nach links (entspricht *256)
        result |= data[i];   // Füge das aktuelle Byte `data[i]` mit einer OR-Operation hinzu
    }
    
    // Gib den berechneten unsigned long Wert zurück
    return result;
}
			
		

Funktionsweise der Umwandlung

Die Funktion parseUnsignedValue hat die Aufgabe, Byte-Werte aus den Zählern zu lesen und diese zu einer Zahl zusammenzuführen. Diese Funktion unterstützt Längen von 1 bis 4 Bytes, sodass sie flexibel für verschiedene Formate von Zähler-Daten eingesetzt werden kann.

Wie funktioniert die Umwandlung?

  • Eingabeparameter: Die Funktion erwartet zwei Eingabewerte:
    • data: Ein Array von Bytes, das die Zählerwerte in Hex-Form enthält.
    • length: Die Länge des Arrays, die die Anzahl der verwendeten Bytes beschreibt. Diese muss zwischen 1 und 4 liegen.
  • Verarbeitung:

    Für jedes Byte im Array wird das aktuelle Ergebnis um 8 Bits nach links verschoben, um Platz für das nächste Byte zu schaffen. Anschließend wird das Byte mit einer OR-Operation an das Ergebnis angehängt. Dieser Vorgang wird für alle Bytes im Array wiederholt, bis die endgültige Zahl gebildet ist.

  • Rückgabe: Die Funktion gibt den berechneten Wert als unsigned long zurück, der die kombinierte Zahl der Hex-Werte repräsentiert.

Beispiel der Anwendung

Diese Umwandlung ist besonders nützlich, wenn du die Zählerstände aus den Modulen 1.8.0 und 2.8.0 korrekt auslesen und verarbeiten möchtest. Die Funktion ermöglicht es, die Hex-Werte in eine standardisierte Zahl zu konvertieren, die dann weiterverarbeitet oder angezeigt werden kann.

Umrechnung Wirkleistung aus HEX-Werten mit dem Arduino

Die Messwerte des Stromzählers werden in einem speziellen Format, oft als Hexadezimalwert (HEX), übertragen. Diese Werte können sowohl für den aktuellen Bezug (Stromverbrauch) als auch für die Einspeisung von Strom (z. B. aus einer Solaranlage) verwendet werden. Die Herausforderung besteht darin, dass der HEX-Wert sowohl positive als auch negative Zahlen darstellen kann. Genau hier setzt die Funktion parseWattValue an.

Der aufruf der Funktion kann wie folgt geschehen.

				// Beispiel: negativer Wert (Zweierkomplement, z. B. 0xFF 0x9C = -100)
byte data2[] = {0xFF, 0x9C};  
long watt2 = parseWattValue(data2, 2);

// Beispiel: 4 Byte Wert
byte data3[] = {0x00, 0x01, 0x86, 0xA0}; // = 100000 dezimal
long watt3 = parseWattValue(data3, 4);
				
			

Arduino Quellcode

			// Funktion zur Umrechnung des Stromwertes (Wirkleistung) aus einem HEX-Wert
/* Diese Funktion nimmt ein Array von Bytes (data) und die Länge dieser Bytes und berechnet den entsprechenden 
   Wert der Wirkleistung (in Watt), der als positiver oder negativer Wert vorliegen kann. */
long parseWattValue(byte* data, int length) {
    if (length < 1 || length > 4) return 0; // Ungültige Länge, nur 1 bis 4 Bytes sind erlaubt

    long result = 0;

    // Die einzelnen Bytes werden hier zu einer Zahl zusammengesetzt.
    // Das erfolgt im Big-Endian-Format, wobei das erste Byte den höchsten Wert darstellt.
    for (int i = 0; i < length; i++) {
        result <<= 8;           // Verschiebe das bisherige Ergebnis um 8 Bits nach links
        result |= data[i];      // Füge das aktuelle Byte zum Ergebnis hinzu
    }

    // Prüfen, ob das erste Byte ein "Vorzeichenbit" gesetzt hat (höchstes Bit, 0x80)
    if (data[0] & 0x80) {
        // Der Wert ist negativ (Zweierkomplement)
        // Berechnung der Anzahl der ungenutzten Bits
        // (die Anzahl der Bits, die im Wert nicht genutzt werden, abhängig von der Länge der Eingabedaten)
        int shift = (4 - length) * 8; // Berechne, wie viele Bits wir für das Vorzeichen anpassen müssen
        result = (result << shift) >> shift; // Vorzeichen wird durch Links- und Rechtsschieben erweitert
    }

    // Rückgabe des berechneten Wertes
    return result;
}
			
		

Was macht die Funktion?

Die Funktion nimmt ein Array von Bytes (die Hex-Werte) und berechnet daraus eine Ganzzahl, die den Stromverbrauch oder die Einspeisung in Watt angibt. Dabei wird berücksichtigt, ob der Wert positiv oder negativ ist, was durch das Zweierkomplement des HEX-Werts ermittelt wird.

Wie funktioniert das?

  1. Zusammenfügen der Bytes
    Die Eingabewerte bestehen aus 1 bis 4 Bytes. Diese Bytes werden zu einer einzigen Zahl zusammengefügt. Dabei wird das Big-Endian-Format verwendet, was bedeutet, dass das erste Byte den höchsten Wert darstellt.
  2. Erkennen von negativen Werten
    Stromzähler-Daten können negative Werte enthalten, wenn z. B. Strom ins Netz eingespeist wird. Diese negativen Werte sind im Zweierkomplement kodiert. Das bedeutet, dass das höchste Bit des ersten Bytes (0x80) auf „1“ gesetzt sein kann, um anzuzeigen, dass der Wert negativ ist.
  3. Korrektur negativer Werte
    Wenn das höchste Bit des ersten Bytes gesetzt ist, erkennt die Funktion, dass der Wert negativ ist. Um dies korrekt darzustellen, wird die Zahl durch eine spezielle Technik (Links- und Rechtsschieben der Bits) ins richtige Format umgerechnet.
  4. Rückgabe des Ergebnisses
    Nach der Umrechnung gibt die Funktion den berechneten Wert zurück – entweder als positiven oder negativen Wert in Watt, je nachdem, ob Strom verbraucht oder eingespeist wird.

Fazit

Diese Funktion stellt sicher, dass die Hexadezimalwerte, die vom Stromzähler geliefert werden, korrekt als tatsächliche Wirkleistung in Watt interpretiert werden. So kann der tatsächliche Stromverbrauch oder die Einspeisung zuverlässig ermittelt werden, unabhängig davon, ob der Wert positiv oder negativ ist.

Donation

Euch hat es bei uns gefallen oder die Beiträge haben euch weitergeholfen, dann könnt uns gerne mit einer kleinen Spende für weitere Projekte unterstützen. Vielen Dank

Letzte Änderung: 28.10.2025