Smart Meter - SML HEX Code zerlegen

Hallo zusammen,

ich habe mich bereits durch die bestehenden Threads zu dem Thema Smart Meter und SML (Smart Meter Language) gelesen, komme aber leider nicht weiter - ich verstehe nicht, wie ich mein Vorhaben in Arduino Code umsetzen kann.

Situation: ISKRA MT175 Smart Meter mit IR-Ausgang -> empfangen wird über Phototransistor und 10KOhm Pull-Up Widerstand - aktuell über ein UART (für Funktionstest).

Ich erhalte mit HTerm ohne Probleme vollständige SML-Telegramme, welche ich nach diesen Informationen auch manuell dekodieren kann.

Gestartet wird ein Telegramm immer mit "1B 1B 1B 1B 01 01 01 01", enden tut es immer auf "1B 1B 1B 1B 1A" + CRC-Bits.

Ich möchte genau zwei Werte aus dem Salat extrahieren:

1) Die aktuelle Wirkleistung: Diese hat folgendes Format : 07 01 00 10 07 00 FF 01 01 62 1B 52 00 55 XX XX XX XX 01

Beginnt immer mit 07 01 00 10 07 00 FF 01 01 62 1B 52 00 55
dann kommt der Wert (z.B. wie oben): 00 00 01 1B: Ergibt Dezimal 238 (Watt)
dann wird immer mit "01" abgeschlossen

2) Den Gesamtverbrauch: Dieser hat folgendes Format : 07 01 00 01 08 00 FF 65 00 00 01 82 01 62 1E 52 FF 59 XX XX XX XX XX XX XX XX 01

Beginnt immer mit 07 01 00 01 08 00 FF 65 00 00 01 82 01 62 1E 52 FF 59
dann kommt der Wert (z.B. wie oben): 00 00 00 00 02 6E F4 91: Ergibt Dezimal 40826001 -> 4082,6001 kWH
dann wird immer mit "01" abgeschlossen

Theoretisch ist mein Ziel ganz einfach formuliert: Aus dem eingelesenen HEX-Telegramm sollen ein Integer (Wirkleistung) und ein Float (Gesamtverbrauch) werden.

Ich muss ja sicher irgendwie die fixen Startsequenzen nutzen, um das gesamte Telegramm zu durchsuchen und dann die darauf entsprechenden Stellen zu extrahieren. Aber wie macht man so etwas?

Vielen Dank im Voraus!

PS: Realisiert werden soll alles letztendlich auf einem ESP8266 ESP-12E, welcher die beiden Werte per UDP oder MTTQ -da bin ich noch unschlüssig - an meinen Home Assistant Server sendet.

Hallo,

entweder liest du alles in großes Array ein und ziehst dir dann die entsprechenden Werte von den entsprechenden Stellen raus und setzt den Endwert zusammen oder um Speicher zu sparen zählst du am Anfang mit und speicherst erst ins Array wenn die erste relevante Ziffernfolge für dich kommt und ziehst ab da alle anderen raus. Zur Einfachheit würde ich erstmal alles einlesen. Optimieren kann man später immer noch.

Hallo,
Stehen die relevanten Werte denn innerhalb des telegramms immer an den selben Stellen
Dann kann man ja byteweise einlesen , zählen und ab der relevanten Position speichern .

Hi

So weit wäre ich gerne :o
Mit welchen Parametern hast Du HTerm gefüttert?
Ich hatte mittels der Photo-Diode eines CNY70 (Reflex-Lichtschranke) nur Müll empfangen, war allerdings auch 'nur' mit einem Arduino vor Ort (irgendwie war ich zu faul, das Oszi vor den Zählerschrank zu packen ... auf Laptop bin ich gar nicht gekommen :confused: nutze Diese aber auch nur äußerst selten - bin meinem PC treu geblieben und dadurch wenig mobil :wink: )

Selber würde ich, glaube, die Daten parsen - sofern zwischen den Einzelzeichen genügend Platz für Berechnungen sind.
Allerdings interessieren mich eher alle Daten, Die das Ding ausspuckt.

MfG

Hallo,

beschäftige dich bitte erstmal mit den seriellen Funktionen und den Bsp. der IDE, alles weitere baut darauf auf. Erstmal musst du alles einlesen können bevor es verarbeitet werden kann. Die Verarbeitung ist nachgelagert, nicht davor.

Hallo,

der Autor lässt den Anfang des Datenpaketes an Hand der Pausenzeit erkennen. Ist ein Weg von mehreren.
Wie gesagt, du solltest dich erstmal um das Verständnis der seriellen Funktionen kümmern. Meine Empfehlung.
Dann kannste testen ob du bestimmte Dinge schalten und walten kannst mit Buchstabeneingabe im seriellen Monitor.
Dann kannste dich entscheiden ob du die Pausenerkennung übernimmst oder die Anfangserkennung wählst.
Wenn das klar ist könnten wir weitermachen mit der Starterkennung vom Frame. Dann käme aus meiner Sicht switch case oder ein String Vergleich ins Spiel.

Hi

Hört sich doch schon ganz gut an (mein Zähler wäre im 1.ten Stock, also durchaus erreichbar).
In dem Sketch von github (Quelle) wird beim Einlesen überprüft, ob der Puffer schon voll ist - Das fehlt bei Dir - solange neue Daten vorhanden sind, werden Diese an den Puffer angehangen - egal, wie groß der Puffer ist - denke, Das führt bei Dir zu den beschriebenden Problemen.

Von dem Code gibt es eine zweite, wohl Vorversion.
In der oberen Version wird eine zusätzliche Dummy-Seriel-Schnittstelle benutzt (noch kA, warum), weiter sieht die Anfangs-Erkennung mit der Verknüpfung der Bytewerte durch && intuitiv sinniger aus, als mit || in der wohl früheren Version.

... dann werde ich Mal einem Nano neues Leben einhauchen und mich vor den Zähler stellen :wink: ...

MfG

slaven1337:
... wird die smlMessage extrem groß ...

byte smlMessage[1000]; //byte to store the parsed message 
...
      smlMessage[startIndex] = inByte; //set smlMessage element at position 0,1,2 to inByte value
      startIndex++;

Das Feld ist auf 1000 Elemente begrenzt, der Index startIndex wird aber nirgendwo auf kleiner 1000 begrenzt. Wenn die Startsequenz nicht gefunden wird, überschreibt smlMessage[startIndex] unkontrolliert den Speicher.

Versuche mal als Begenzung:

startIndex =  (startIndex +1) %1000;

Besser wäre es vermutlich, einen Ringpuffer zu implementieren.

postmaster-ino:
... wird beim Einlesen überprüft, ob der Puffer schon voll ist - Das fehlt bei Dir - ...

Ok, nur Zweiter.

Hallo,

Slaven, kannst mal mit 3 Datenpaketen zeigen wie das ganze zeitlich abläuft, damit ich einmal die Längen und Pausenzeiten einsehen kann? Log vom Wireshark sollte dafür reichen.

Hi
gibt es denn einen finalen Code der bei dir Läuft.
Ich habe heute 2 Stück MT175 bekommen und muss auch meinen alten Code umbauen (der noch kein SML war)
Gruß
Thorsten

Hallo,

ich hänge mich hier mal mit dran.

mit dem gezeigten Code habe ich mir was zusammengebastelt das mir an einem Pin ein 0-5V Signal erzeugt um einen Wechselrichter zu steuern.

Leider hab ich 0 Erfahrung mit Programmieren.

Ein Problem gild es noch in dem Code zu lösen , mal abgesehen vom Durcheinander:

Wenn keine Daten eindrudeln MUSS der Ausgang 0V haben, um zu verhindern das bei einem Fehler ggf. mit Voller LEistung eingespeist wird.

zusätzlich scheint der Code ztw zu hängen ??

evtl kann mir da jemand helfen?

byte inByte; //byte to store the serial buffer
byte smlMessage[1000];
const byte startSequence[] = { 0x1B, 0x1B, 0x1B, 0x1B, 0x01, 0x01, 0x01, 0x01 };//start sequence of SML protocol
const byte stopSequence[]  = { 0x1B, 0x1B, 0x1B, 0x1B, 0x1A };//end sequence of SML protocol
const byte powerSequence[] = { 0x07, 0x01, 0x00, 0x10, 0x07, 0x00, 0xFF, 0x01, 0x01, 0x62, 0x1B, 0x52, 0x00, 0x55 }; //sequence preceeding the current "Wirkleistung" value (4 Bytes)
int smlIndex;//index counter within smlMessage array
int startIndex;//start index for start sequence search
int stopIndex;//start index for stop sequence search
int stage;//index to maneuver through cases
int currentpower; //variable to hold translated "Wirkleistung" value
int currentpower1;
byte analogpower; //Byte für analogen Ausgang
byte power[4]; //array that holds the extracted 4 byte "Wirkleistung" value

void setup() {
  pinMode(9,OUTPUT);//setze PIN 9 für Output
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
     }
void loop() {
  
  switch (stage) {
    case 0:
      findStartSequence();
      break;
    case 1:
      findStopSequence();
      break;
    case 2:
      findPowerSequence(); //look for power sequence and extract
      break;
    case 3:
      publishMessage();
      break;
  }
}
void findStartSequence() {
  while (Serial.available())
  {
    inByte = Serial.read();//read serial buffer into array
       if (inByte == startSequence[startIndex])//in case byte in array matches the start sequence at position 0,1,2...
    {
      smlMessage[startIndex] = inByte;//set smlMessage element at position 0,1,2 to inByte value
      startIndex++;
      if (startIndex == sizeof(startSequence))//all start sequence values have been identified
      {
        //   Serial.println("Match found");
        stage = 1;
      
      smlIndex = startIndex;
      
                startIndex = 0;
      }
    }
    else {
      startIndex = 0;
    }
  }
}
void findStopSequence() {
     while (Serial.available())
  {
    inByte = Serial.read();
    smlMessage[smlIndex] = inByte;
    smlIndex++;
    if (inByte == stopSequence[stopIndex])
    {
      stopIndex++;
      if (stopIndex == sizeof(stopSequence))
      {
                stage = 2;
        stopIndex = 0;
        // after the stop sequence, ther are sill 3 bytes to come. 
        // One for the amount of fillbytes plus two bytes for calculating CRC.
        for (int c = 0 ; c < 3 ; c++) {
          smlMessage[smlIndex++] = Serial.read();
          if (stopIndex == sizeof(stopSequence))
          {
            stage = 2;
            stopIndex = 0;
            // after the stop sequence passed, there are sill 3 bytes to come.
            // One for the amount of fillbytes plus two bytes for calculating CRC.
            delay(3); // wait for the 3 bytes to be received. 
            for (int c = 0 ; c < 3 ; c++) {
              smlMessage[smlIndex++] = Serial.read();
            }
            // smlIndex is at this point one bigger than the amount of stored inBytes because it is incremented evreytime after reading.
            // To store the real smlIndex, we have to substract the last incrementation.
            smlIndex--;
          }

  
      }
      }
    }
    else {
            stopIndex = 0;
    }
  }
}
void findPowerSequence() {
  byte temp; //temp variable to store loop search data
 startIndex = 0; //start at position 0 of exctracted SML message
 
for(int x = 0; x < sizeof(smlMessage); x++){ //for as long there are element in the exctracted SML message
    temp = smlMessage[x]; //set temp variable to 0,1,2 element in extracted SML message
    if (temp == powerSequence[startIndex]) //compare with power sequence
    {
      startIndex++;
      if (startIndex == sizeof(powerSequence)) //in complete sequence is found
      {
        for(int y = 0; y< 4; y++){ //read the next 4 bytes (the actual power value)
          power[y] = smlMessage[x+y+1]; //store into power array
        }
        stage = 3; // go to stage 3
        startIndex = 0;
      }
    }
    else {
            startIndex = 0;
    }
  }
   currentpower = (power[0] << 24 | power[1] << 16 | power[2] << 8 | power[3]); //merge 4 bytes into single variable to calculate power value
}
void publishMessage() {
  //  int arrSize = 2 * smlIndex + 1;
  //  char smlMessageAsString[arrSize];
  //  char *myPtr = &smlMessageAsString[0]; //or just myPtr=charArr; but the former described it better.
  //
  //  for (int i = 0; i < smlIndex; i++) {
  //    snprintf(myPtr, 3, "%02x", smlMessage[i]); //convert a byte to character string, and save 2 characters (+null) to charArr;
  //    myPtr += 2; //increment the pointer by two characters in charArr so that next time the null from the previous go is overwritten.
  //  }
  //Serial.print("Byte count: ");
  //Serial.println(smlIndex);
   //Serial.print('\n');
        //output 4 bytes of exctracted power bytes
//for(int x = 0; x < 4; x++){
//Serial.print(power[x],HEX);
//Serial.print(" ");
//}
      //output calculated "Wirkleistung"
Serial.print("Leistung: ");
Serial.println(currentpower);
//hier wird aus currentpower  dien Wert der nur zwischen 0 und 998
//sein kann , falls größer ist er immer 998
if(currentpower>998)currentpower=998;
//hier wird der int Currentpower in einen byte Wert gewandelt und durch 3,9 geteilt für das Verhältnis
//1000W = 100%, 255=100% entpricht 100/255=0,39 0,39*10 (10W entpricht 1% von 1000W)
byte analogpower = currentpower / 3.9;
Serial.println(analogpower);// gibt den analogwert aus zum Debug 
analogWrite(9,analogpower);
int currentpower1 = currentpower;
Serial.print('\n');
      //output 8 bytes of exctracted consumption bytes
for(int x = 0; x < 8; x++){

 }
   
   
  // clear the buffers
  smlIndex = 0;
  memset(smlMessage, 0, sizeof(smlMessage));
  memset(power, 0, sizeof(power));
  
  
  
  stage = 0; // start over
  
}