SML Zählerdaten im Arduino UNO verarbeiten möglich?

Hallo arduino - Profis,

bei meinem Projekt komme ich seit ca. 3 Monaten nicht weiter. Vielleicht gebt ihr mir einen entscheidenden Hinweis, sonst wird das SML Arduino-Projekt vermutlich bald beendet.

Ich bin seit 20 Jahren Elektroniker, daher ist die Hardware und das Verkabeln nicht das Problem.

Ausgangssituation:
Ein elektronischer Haushaltszähler ISKRA671 soll mittels Lesekopf per Arduino UNO ausgelesen werden. Die vorhandene Photovoltaikanlage speist ggf. überschüssigen Strom ins Netz ein. Der Arduino soll einfach nur die SML Daten auslesen, und per USB die GERADE bezogene / eingespeiste Leistung im Klartext per z.B. HTerm auf dem PC angezeigt werden. (Im nächsten Schritt dann bei bestimmten Einspeisemengen die digitalen Ausgänge mit Relais angesteuert werden, um einen höheren Eigenbedarf zu generieren).

Bisheriger Aufbau:
-IR-Lesekopf ist am eHz per Magnet befestigt

  • der Arduino UNO wurde mit dem IR-Lesekopf verbunden (+ und - für den Lesekopf, und die Sendediode auf den RX Anschluss)
  • ein kleines Sketch wurde auf den Arduino Uno aufgespielt, welches die empfangenen Daten auf dem PC in HTerm anzeigt

Sketch:

#include <IRremote.h>

byte IRPin = 11;
IRrecv irrecv(IRPin); //Objekt initialisieren für die IR Übertragung
decode_results results;

void setup(){
 Serial.begin(9600); 
 pinMode(IRPin, INPUT);
 irrecv.enableIRIn(); //Den IR Pin aktivieren 
}

void loop(){

  if (irrecv.decode(&results)) { //Wenn etwas gelesen wurde dann...
    //Ausgabe des Wertes auf die Serielle Schnittstelle.
    Serial.println(results.value,HEX);
    irrecv.resume(); // auf den nächsten Wert warten
  }
 
}

Das empfangene SML Protokoll ist einwandfrei vorhanden.
Die Sequenzen 1B 1B 1B 1B|Escape Sequenz zum Start /
01 01 01 01|SML Start| sind einwandfrei da, und ich kann per HEX Code die gewünschten Werte erkennen und sie ins Dezimalsystem mit Taschenrechner umrechnen.

Mein Problem:
Wie kann ich "einfach nur" die gerade erzeugte/eingespeiste Energie daraus "extrahieren" und z.B. so anzeigen lassen:


  •                                                                           *
    
  •   Momentane Wirkleistung: XXXX Watt      *
    
  •                                                                           *
    

Ich habe mich viel eingelesen... Array, zwischenspeichern auf einer SD-Karte usw.
Mir fehlt der Sprung vom Einlesen der Daten zur Auswertung. Ich brauche keine Grafik, keine Erfassung der Daten über Jahre, kein Senden der Daten an irgendwelche Server. Nur der aktuelle Verbrauch / Bezug interessiert mich. Eine Speicherung der Daten per FHEM, IOBroker etc. brauch ich nicht.

Über eure Hilfe würde ich mich sehr freuen.

Das geht ziemlich sicher auf einem Uno, ein Nano kann das auch :grinning:

Ich habe das mal für einen anderen SML-Zähler geschrieben - vielleicht kannst Du es verwenden. Der "Zähler-Nano" ist über I²C mit einem zweiten verbunden, der als Modbus RTU-Server läuft.

#include <Arduino.h>
#include <AltSoftSerial.h>
#include <Wire.h>
#include <FastCRC.h>

// Built-in LED
#define LED 13

// Bit switch to activate debug output on pin 6
#define DEBUG_ON 6

// HOTWIRE connected to HOTWIRE pin 2 on Nano MODBUS
#define HOTWIRE 2

// States for the loop() state machine
enum STATES : uint8_t { INIT=0, FIND_PAUSE, WAIT_DATA, IN_PACKET, TRAILER, READ_DATA, WRITE_REFINED, FINISH_WRITE };
STATES state = INIT;

// Serial interface to optical readout
AltSoftSerial D0IN;

// Library to calculate CRC values
FastCRC16 CRC16;

// Input buffer for packets arriving on D0IN
const uint16_t BUFFERSIZE(400);
uint8_t data[BUFFERSIZE];
uint16_t PACKETLENGTH;       // EMH meter: 388 bytes to be exoected

// Two blocks of data memory to hold values for Nano MODBUS.
// activeBlock toggles between both, one is "active" for Nano MODBUS I²C requests,
// the other is "inactive" to write changes received from D0IN.
const uint16_t MEMSIZE(256);
uint8_t memoryBlock[2][MEMSIZE];
uint8_t activeBlock = 0;

#define activeMem memoryBlock[activeBlock&1]
#define inactiveMem memoryBlock[(activeBlock^1)&1]
#define switchMem { activeBlock &= 1; activeBlock ^= 1; }

// Variables to hold requested address and length (in bytes) on I²C
uint16_t memAddr = 0;
uint16_t memLen = 0;

// Class to hold OBIS byte sequences to process
enum O_TYPE : uint8_t { O_BYTE=0, RES1, RES2, RES3, O_BOOL, O_INT, O_UINT, O_LIST, O_FLOAT=0x7F };
const int SEQLEN(12);
class Sequence
{
public:
  bool complete;        // While parsing, set to true if the value was found
  bool inactive;        // Flag to premanently disable this sequence
  bool S_once;          // If found once, this sequence will be switched to inactive
  uint8_t S_ptr;        // While parsing, index in seq successfully recognized
  uint8_t S_len;        // Length of sequence
  uint16_t S_addr;      // Target address when writing I²C
  uint16_t S_tlen;      // maximum length of target data
  O_TYPE S_typ;         // Target data type
  uint8_t S_seq[SEQLEN];    // Sequence of bytes to be recognized

  Sequence(uint8_t l, uint16_t a, bool o1, O_TYPE t, uint16_t tl, uint8_t s[])
  {
    reset();
    inactive = false;
    S_len = (l>SEQLEN) ? SEQLEN : l;
    S_addr = a;
    S_once = o1;
    S_typ = t;
    S_tlen = tl;
    memset(S_seq, 0, SEQLEN);
    memcpy(S_seq, s, S_len);
  }
  ~Sequence() {}

  inline void reset() { S_ptr = 0; complete = false; }

  inline uint8_t check(uint8_t b)
  {
    if(b==S_seq[S_ptr])
    {
      if(S_ptr<S_len) S_ptr++;
    }
    else
    {
      S_ptr = 0;
    }
    return S_len-S_ptr;
  }
};

const uint8_t SEQUENCES(9);
Sequence sequences[SEQUENCES] =
{
  //       len, addr,  once,    type, tl, sequence
  Sequence(  8,    2, false, O_FLOAT,  4, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x10, 0x07, 0x00, 0xFF } ),
  Sequence(  8,    6, false, O_FLOAT,  4, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x01, 0x08, 0x00, 0xFF } ),
  Sequence(  8,   10, false, O_FLOAT,  4, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x02, 0x08, 0x00, 0xFF } ),
  Sequence(  8,   14, false, O_FLOAT,  4, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x01, 0x08, 0x01, 0xFF } ),
  Sequence(  8,   18, false, O_FLOAT,  4, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x02, 0x08, 0x01, 0xFF } ),
  Sequence(  8,   22, false, O_FLOAT,  4, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x01, 0x08, 0x02, 0xFF } ),
  Sequence(  8,   26, false, O_FLOAT,  4, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x02, 0x08, 0x02, 0xFF } ),
  Sequence(  8,   30,  true,  O_BYTE,  4, (uint8_t []){0x77, 0x07, 0x81, 0x81, 0xC7, 0x82, 0x03, 0xFF } ),
  Sequence(  8,   34,  true,  O_BYTE, 12, (uint8_t []){0x77, 0x07, 0x01, 0x00, 0x00, 0x00, 0x09, 0xFF } ),
};

// Header bytes to detect a packet on D0IN
const unsigned char header[] = { 0x1B, 0x1B, 0x1B, 0x1B, 0x01, 0x01, 0x01, 0x01, 0x00 };
// Trailer bytes to find the end of a D0IN packet
const unsigned char trailer[] = { 0x1B, 0x1B, 0x1B, 0x1B, 0x1A, 0x00 };

#ifdef TESTING
const uint16_t BUFLEN(64);
char buffer[BUFLEN];

// Helper function to generate a printable hexadecimal dump
// head: printed first
// sep:  separator char printed between each byte. Omitted, if ==0
// data: pointer to data to be dumped
// length: number of bytes in data
// buffer: target to be printed into
// max_buffer_length: obvious... ;)
uint16_t hexDump(const char *head, const char sep, uint8_t *data, uint16_t length, char *buffer, uint16_t max_buffer_length)
{
  uint16_t maxByte = 0;               // maximum byte to fit in buffer
  uint16_t outLen = 0;                // used length in buffer
  uint8_t byteLen = (sep?3:2);        // length of a single byte dumped
  uint16_t headLen = strlen(head);    // length of header
  const char *PRINTABLES = "0123456789ABCDEF";

  // if not even the header will fit into buffer, bail out.
  if(headLen>max_buffer_length) return 0;

  // if we have a header, print it.
  if(headLen) strcpy(buffer, head);
  outLen = headLen;

  // Calculate number of bytes fitting into remainder of buffer
  maxByte = (max_buffer_length - headLen) / byteLen;
  // If more than neede, reduce accordingly
  if(length<maxByte) maxByte = length;

  // May we print at least a single byte?
  if(maxByte>0)
  {
    // Yes. Start behind the header
    char *cp = buffer + headLen;
    // For each byte...
    for(uint16_t i=0; i<maxByte; ++i)
    {
      // ...print it, plus...
      *cp++ = PRINTABLES[(data[i]>>4)&0x0F];
      *cp++ = PRINTABLES[data[i]&0x0F];
      outLen += 2;
      // .. a separator, if it is defined and another byte is to follow
      if(sep && i<maxByte-1)
      {
        *cp++ = sep;
        outLen++;
      }
    }
    *cp = 0;
  } 
  return outLen;
}
#endif

// Helper function to get type and (extended) length information
// bytePtr: pointer to TL byte in byte stream
// type: variable reference to return type information
// length: variable reference to return length information
// advanceBytes: number of bytes to skip to get to the value proper
// Function returns 0 if okay or !=0 to denote an error or unlikely data
//   0: OK
//   1: reserved data type - unlikely
//   2: impossible length
//   3: length bigger than expected
uint16_t getLengthType(uint8_t *bytePtr, O_TYPE& type, uint16_t& length, uint16_t& advanceBytes, uint16_t expectedLength)
{
  uint8_t *ptr = bytePtr;

  // At least one TL byte
  advanceBytes = 1;

  // Get data type
  type = (O_TYPE)(((*ptr)>>4) & 0x07);

  // Reserved? Unlikely...
  if(type==RES1 || type==RES2 || type==RES3) return 1;

  // Get (first) length nibble
  length = (*ptr)&0x0F;

  while(*ptr&0x80) // extended length byte
  {
    ptr++;
    length <<= 4;
    length |= (*ptr)&0x0F;
    advanceBytes++;
  }

  // length pointing behind complete packet? Impossible!
  if((bytePtr-data)+length>PACKETLENGTH-8) return 2;

  length -= advanceBytes;

  // Length bigger than anticipated?
  if(length>expectedLength) return 3;

  // Looks okay.
  return 0;
}

// onRequest handler function for I²C
// Writes requested memLen bytes out of the "active" memory block, starting from address memAddr
void I2C_send()
{
  Wire.write(activeMem+memAddr, memLen);
}

// onReceive handler function for I²C
// Expects 4-byte packets: addrHI, addrLO, lenHI, lenLO
void I2C_addr(int cnt)
{
  if(cnt==4)
  {
    memAddr = Wire.read();
    memAddr <<= 8;
    memAddr |= Wire.read();
    memAddr -= 1;                     // MODBUS address starts at 1
    memAddr *= 2;                     // MODBUS units are 16 bit, not bytes. So *2 is required
    if(memAddr>MEMSIZE) memAddr = 0;  // Overflow?

    memLen = Wire.read();
    memLen <<= 8;
    memLen |= Wire.read();
    memLen *= 2;                     // MODBUS units are 16 bit, not bytes. So *2 is required
    if(memAddr+memLen>MEMSIZE) memAddr = 0;  // Overflow?
  }
  else
  {
    // unexpected packet length. Fall back to safe values
    memAddr = 0;
    memLen = 2;
    while(Wire.available()) Wire.read();
  }
}

void setup() {
  // Set up LED
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW); 

  // Set up data write enable pin
  pinMode(HOTWIRE, INPUT_PULLUP);

  // Set up raw data/refined data jumper pin
  pinMode(DEBUG_ON, INPUT_PULLUP);

  // Start Serial interface for photo sensor
  D0IN.begin(9600);

  // Start I²C as Slave #2
  Wire.begin(2);
  Wire.setClock(400000L);
  Wire.onReceive(I2C_addr);
  Wire.onRequest(I2C_send);

  // Init read buffer
  memset(data, 0, BUFFERSIZE);
  state = INIT;

  // Init data memory - both blocks
  memset(memoryBlock, 0, 2*MEMSIZE);

#ifdef TESTING
  // Init Serial - in case we are doing test outputs
  Serial.begin(115200);
  Serial.println("");
#endif
}

void loop() 
{
  static uint8_t *cp = data;
  static uint8_t Startseq = 0;
  static uint8_t Endseq = 0;
  static uint8_t Trailerbytes = 0;
  static bool byteWaiting = false;
  static uint8_t byteRead = 0;
  const uint32_t PAUSETIME(200);
  static uint32_t pause = millis();
  static uint16_t cnt = 0;
  static uint8_t rc = 0;
  static uint8_t field = 0;
  static uint32_t packetOK = 0;
  static uint32_t packetERR = 0;

#ifdef TESTING
  static bool TRACE_ON = false;

  // Is trace output required?
  if(digitalRead(DEBUG_ON)==LOW)
  {
    TRACE_ON = true;
  }
  else
  {
    TRACE_ON = false;
  }
#endif
  
  // May we read another byte?
  if(!byteWaiting && D0IN.available())
  {
    // Yes. Do it.
    byteRead = D0IN.read();
    byteWaiting = true;
  }
 
  switch(state)
  {
  case INIT:
    state = FIND_PAUSE; 
    break;
  case FIND_PAUSE:
    // Detect pause
    if(!byteWaiting)
    {
      // Did we pass the pause time?
      if(millis()-pause>=PAUSETIME)
      {
        // Yes. Go on catch a packet.
        Startseq = 0;
        cp = data;
        cnt = 0;
        state = WAIT_DATA;
      }
    }
    else
    {
      byteWaiting = false;
      pause = millis();
    }
    break;
  case WAIT_DATA:
    // Pause found. Now find data packet
    if(byteWaiting)
    {
      // Is it the next in the header sequence? 
      if(byteRead==header[Startseq])
      {
        // Yes. collect it and wait for the next
        *cp++ = byteRead;
        cnt++;
        // Overflow?
        if(cnt>=BUFFERSIZE)
        {
          // Yes. Reset data and start over
          state = FIND_PAUSE;
        }
        else
        {
          Startseq++;
          // ...unless it was the 4th. Then we have found the start sequence and may go on
          if(header[Startseq]==0x00)
          {
            Endseq = 0;
            state = IN_PACKET;
            digitalWrite(LED, HIGH);
          }
        }
      }
      else
      {
        // No - garbage or defective packet. Reset to begin the search again
        cnt = 0;
        cp = data;
        Startseq = 0;
      }
      byteWaiting = false;
    }
    break;
  case IN_PACKET:
    // Start sequence was there - read on
    if(byteWaiting)
    {
      *cp++ = byteRead;
      cnt++;
      // Overflow?
      if(cnt>=BUFFERSIZE)
      {
        // Yes. Reset data and start over
        digitalWrite(LED, LOW);
        state = FIND_PAUSE;
      }
      else
      {
        // Is it part of the ending sequence?
        if(byteRead==trailer[Endseq])
        {
          // Yes. count it.
          Endseq++;
          // If we had 4 in a row, proceed to catch the trailer
          if(trailer[Endseq]==0x00)
          {
            Trailerbytes = 0;
            state = TRAILER;
          }
        }
        else
        {
          // No - something else - discard count 
          Endseq = 0;
        }
      }
      byteWaiting = false;
    }
    break;
  case TRAILER:
    // Get the final 3 bytes
    if(byteWaiting)
    {
      // Catch it.
      *cp++ = byteRead;
      cnt++;
      // Overflow?
      if(cnt>=BUFFERSIZE)
      {
        // Yes. Reset data and start over
        digitalWrite(LED, LOW);
        state = FIND_PAUSE;
      }
      else
      {
        Trailerbytes++;
        if(Trailerbytes==3)
        {
          state = READ_DATA;
        }
      }
      byteWaiting = false;
    }
    break;
  case READ_DATA:
    // Did we get some?
    if(cnt)
    {
      PACKETLENGTH = cnt;
      // Calculate CRC
      uint16_t crc = CRC16.x25(data, PACKETLENGTH-2);
      // Get CRC from packet
      uint16_t packetCRC = (data[PACKETLENGTH-1]<<8)|data[PACKETLENGTH-2];
#ifdef TESTING
      if(TRACE_ON)
      {
        uint16_t pos = 0;
        while(pos<cnt)
        {
          hexDump("  ", ' ', data+pos, (cnt-pos)<16 ? (cnt-pos) : 16, buffer, BUFLEN);
          Serial.println(buffer);
          pos += 16;
        }
        Serial.flush();
      }
#endif
      // Matching CRC? If so, proceed to processing. Else discard packet.
      if(packetCRC==crc) 
      {
        packetOK++;
        state = WRITE_REFINED;
      }
      else
      {
#ifdef TESTING
        if(TRACE_ON)
        {
          Serial.println("CRC err");
          Serial.flush();
        }
#endif
        packetERR++;
        digitalWrite(LED, LOW);
        state = FIND_PAUSE;
      }
    }
    else
    {
      // No. Wait for another packet
      digitalWrite(LED, LOW);
      state = FIND_PAUSE;
    }
    break;
  case WRITE_REFINED:
    {
      // Yes.
      cp = data;
      // Data proper
      uint16_t lcnt = 0;
      for(uint8_t i=0; i<SEQUENCES; i++)
      {
        sequences[i].reset();
      }
      while(rc==0 && lcnt<cnt)
      {
        // Check all sequences for a match
        for(uint8_t i=0; i<SEQUENCES; ++i)
        {
          if(sequences[i].complete || sequences[i].inactive) continue;
          // Sequence completely matching?
          if(sequences[i].check(*(cp+lcnt))==0)
          {
            // Yes, found it. 
            // We will have a sequence of data fields to process:
            // - status
            // - valTime
            // - unit code
            // - scaler
            // - value
            uint8_t *cq = cp + lcnt + 1;
            uint16_t skipLen = 0;
            int scaler = 0;
            O_TYPE type;
            uint16_t length;
            uint16_t stepV;
            
            rc = 0;
            field = 0;

#ifdef TESTING
            if(TRACE_ON)
            {
              hexDump("S:", ' ', sequences[i].S_seq, sequences[i].S_len, buffer, BUFLEN);
              Serial.println(buffer);
              Serial.flush();
            }
#endif

            // status: skip
            if((rc = getLengthType(cq, type, length, stepV, 4)))
            {
              field = 1;
              break;
            }
            cq += stepV + length;
            skipLen += stepV + length;

            // valTime: skip
            if((rc = getLengthType(cq, type, length, stepV, 8)))
            {
              field = 2;
              break;
            }
            cq += stepV + length;
            skipLen += stepV + length;

            // unit code: skip
            if((rc = getLengthType(cq, type, length, stepV, 2)))
            {
              field = 3;
              break;
            }
            cq += stepV + length;
            skipLen += stepV + length;

            // scaler: we will need it for O_FLOAT target values
            if((rc = getLengthType(cq, type, length, stepV, 2)))
            {
              field = 4;
              break;
            }
            if(length==1) scaler = (signed char)*(cq+stepV);
            cq += stepV + length;
            skipLen += stepV + length;

            // value: depending on type, different treatments necessary
            if((rc = getLengthType(cq, type, length, stepV, 100)))
            {
              field = 5;
              break;
            }

#ifdef TESTING
            if(TRACE_ON)
            {
              hexDump("V:", ' ', cq, stepV+length, buffer, BUFLEN);
              Serial.println(buffer);
              Serial.flush();
            }
#endif

            cq += stepV;
            // Just in case - we must have at least one byte
            if(length>0)
            {
              bool sign = false;
              switch(type)
              {
              case O_BYTE:
                if(length>sequences[i].S_tlen)
                {
                  // Double writes to both memory blocks, since we may be interrupted by I²C any time
                  // Write data to inactive memory first...
                  memcpy(inactiveMem+sequences[i].S_addr, cq, sequences[i].S_tlen);
                  // Switch memory blocks to have a valid value in the active block...
                  switchMem
                  // Write again to have both blocks synchronized
                  memcpy(inactiveMem+sequences[i].S_addr, cq, sequences[i].S_tlen);
                }
                else
                {
                  memcpy(inactiveMem+sequences[i].S_addr, cq, length);
                  switchMem
                  memcpy(inactiveMem+sequences[i].S_addr, cq, length);
                }
                break;
              case O_BOOL:
                inactiveMem[sequences[i].S_addr] = *cq;
                switchMem
                inactiveMem[sequences[i].S_addr] = *cq;
                break;
              case O_INT:
                sign = true;
              case O_UINT:
                // Conversion into float required?
                if(sequences[i].S_typ==O_FLOAT)
                {
                  // Yes. Beware: Arduino floats and longs are 32 bits maximum!
                  // OBIS values can be longer, so we need to truncate or scale down.
                  // First we try to get the sign of the number
                  float Minus = 1.0;
                  // Can we expect negative values (type O_INT)?
                  if(sign)
                  {
                    // Yes. is the very first bit set?
                    if((*cq&0x80))
                    {
                      // Yes, we have a negative number here
                      Minus = -1.0;
                    }
                    else
                    {
                      // No, positive - we may treat it like an unsigned.
                      sign = false;
                    }
                  }
                  float f = 0.0;
                  for(uint8_t j=0; j<length; ++j)
                  {
                    f *=256.0;
                    if(sign)
                    {
                      f += (~(*(cq+j)))&0xFF;
                    }
                    else
                    {
                      f += *(cq+j);
                    }
                  }
                  if(sign) f += 1.0;
                  f *= Minus;
                  // apply the scaler
                  if(scaler>0)
                  {
                    for(int j=scaler;j;--j) f *= 10.0;
                  }
                  else if(scaler<0)
                  {
                    for(int j=scaler;j;++j) f /= 10.0;
                  }
                  // Write 4 bytes of float in MSB order
                  uint32_t uf = *(uint32_t *)&f;  // Ugly... Not portable, but unavoidable
                  inactiveMem[sequences[i].S_addr] = (uf>>24) & 0xFF;
                  inactiveMem[sequences[i].S_addr+1] = (uf>>16) & 0xFF;
                  inactiveMem[sequences[i].S_addr+2] = (uf>>8) & 0xFF;
                  inactiveMem[sequences[i].S_addr+3] = uf & 0xFF;
                  switchMem
                  memcpy(inactiveMem+sequences[i].S_addr, activeMem+sequences[i].S_addr, 4);

#ifdef TESTING
                  if(TRACE_ON)
                  {
                    Serial.println(f);
                    Serial.flush();
                  }
#endif

                }
                else
                {
                  if(length>sequences[i].S_tlen)
                  {
                    memcpy(inactiveMem+sequences[i].S_addr, cq, sequences[i].S_tlen);
                    switchMem
                    memcpy(inactiveMem+sequences[i].S_addr, cq, sequences[i].S_tlen);
                  }
                  else
                  {
                    memcpy(inactiveMem+sequences[i].S_addr, cq, length);
                    switchMem
                    memcpy(inactiveMem+sequences[i].S_addr, cq, length);
                  }
                }
                break;
              default: // All else does not interest us
                break;
              }
            }
            cq += length;
            skipLen += stepV + length;

            // signature: skip
            if((rc = getLengthType(cq, type, length, stepV, 32)))
            {
              field = 6;
              break;
            }
            cq += stepV + length;
            skipLen += stepV + length;

            // disable found sequence for this turn
            sequences[i].complete = true;
            if(sequences[i].S_once) sequences[i].inactive = true;
            lcnt += skipLen - 1;
            break;
          }
        }
        lcnt++;
      }
      state = FINISH_WRITE;
    }
    break;
  case FINISH_WRITE:
    {
      // Write read length, to address 0x0000
      inactiveMem[0] = (PACKETLENGTH>>8)&0xFF;
      inactiveMem[1] = PACKETLENGTH&0xff;

      // Write return code, error field indicator and packet counts, to address 0x002E
      // Field indicator - >0, if field in error
      inactiveMem[46] = field&0xFF;
      // Return code from field length/type detection
      inactiveMem[47] = rc&0xff;
      // Number of intact packets processed
      inactiveMem[48] = (packetOK>>24)&0xFF;
      inactiveMem[49] = (packetOK>>16)&0xFF;
      inactiveMem[50] = (packetOK>>8)&0xFF;
      inactiveMem[51] = packetOK&0xFF;
      // Number of defective packets discarded
      inactiveMem[52] = (packetERR>>24)&0xFF;
      inactiveMem[53] = (packetERR>>16)&0xFF;
      inactiveMem[54] = (packetERR>>8)&0xFF;
      inactiveMem[55] = packetERR&0xFF;
      // Now switch blocks again and do a copy in the now inactive block
      switchMem
      inactiveMem[0] = (PACKETLENGTH>>8)&0xFF;
      inactiveMem[1] = PACKETLENGTH&0xff;
      memcpy(inactiveMem+46, activeMem+46, 10);

      digitalWrite(LED, LOW);
      // Go fetch next
      state = FIND_PAUSE;
    }
    break;
  };
}

Moin,

das geht auch ohne programmieren fertig.

Nimm einen ESP8266 und flashe tasmota.
Da kannst du dann due Date per MQTT versenden oder per http webrequest anfordern.

..das kostet dann keine Nerven :wink:

...wozu das Rad nochmal erfinden?

Beste Grüße,
Chris

Wenn Du schon den Wert hast, dann hast Du davor den OBIS-Code. Auf den parsen und den flgenden Wert übernehmen.
Wir hatten hier schon mehrere solcher Fälle.

@Miq1 war damals mit seinem Parser beim Nano schon recht weit - ich hab das aber aus den Augen verloren...

WOW. VIELEN DANK für die schnelle Hilfe

@Miq1 da muss ich mich erstmal durcharbeiten. Komme mir bei dem großen Sketch vor wie ein Erstklässler auf der Uni. Da brauche ich etwas Zeit :astonished:

@themanfrommoon Zuhause läuft bei mir als "Smart Home" eine Siemens Logo!
MQTT und webrequest sind für mich leider chinesische Worte.

@my_xy_projekt Vielen lieben Dank für die Links. Den Bereich "Stromzähler auslesen" habe ich in meiner Suche gar nicht gefunden. Da hänge ich mich jetzt rein :ok_hand:

Dann poste doch einfach mal zwei Hex-Zahlenkolonnen und beschreibe wo sich in den Hexzahlen dann die momentane Wirkleistung befindet.

Wenn es keine Umstände macht auch gerne 5 Zahlenkolonnen um zu prüfen ob das ein feststehendes Muster ist und ob es einzelne bytes gibt die immer den gleichen Wert haben.

Wenn ich das richtig verstehe dann kommt am Anfang

 1B 1B 1B 1B|Escape

und wann genau kommt dann

01 01 01 01|SML Start|

Was ist dabei "SML Start" für ein Bytewert??

Gibt es ein Byte mit immer dem gleichen Wert ganz am Ende einer Sequenz?

Gibt es ein Byte mit immer dem gleichen Wert nach den Bytes die dich interessieren?

Je nachdem was für ein Muster / was für einen Aufbau diese Telegramme haben, wartet man beim Empfang auf ein Byte mit einem bestimmten Wert

erstes "01"
Wenn "01" empfangen wurde eine Flagvariable auf true setzen

zweites "01"
Wenn als nächstes wieder "01" empfangen wurde eine Zahlvariable um eins erhöhen wenn andere Zahl dann flagvariable zurücksetzen

drittes "01"
Wenn als nächstes wieder "01" empfangen wurde eine Zahlvariable um eins erhöhen wenn andere Zahl dann flagvariable zurücksetzen

viertes "01"
Wenn als nächstes wieder "01" empfangen wurde eine Zahlvariable um eins erhöhen wenn andere Zahl dann flagvariable zurücksetzen

prüfen ob Zähler auf 3 steht wenn ja neue flag-variable "StartSequenz" auf true setzen

Wenn StartSequenz == true gewisse Anzahl Bytes in einen Array einlesen

aus dem array die Elemente herausziehen die die Momentanleistung bedeuten und die Rechnung die du auf dem Taschenrechner machst im Arduino programmieren.

Ob diese Beschreibung funktioniert hängt vom Inhalt der Telegramme ab
deswegen werden ja Beispiele von ganzen Telegrammen gebraucht

vgs

Nein.
Dein ganzes Post hättest Du Dir sparen können.
SML ist eine Sprache und es gibt dazu eine Referenz. Die ist auch versteckt in den Links oben.
Wichtig: Ausser der Start-Sequenz dem "Header" und dem Ende + CRC16 ist nichts zwingend enthalten.
Jedes Element kann - nichts muss.
Auch ist die Größe der Elemente in den Elementen selbst versteckt. Somit ist ein einfaches parsen auf eine Position im Stream gar nicht zielführend.
@beeebeee solltest Du ein ganzes Telegram zur Verfügung stellen wollen, achte drauf das die Id's für Deinen Zähler da nicht mit online gestellt werden.

Nein konnte ich nicht.
a.) Weil du jetzt ansatzweise erklärt hast wie die Telegramme aufgebaut sind
b.) Es ja doch ein wie auch immer geartetes Muster geben muss das eine Software auswerten kann.

Warum die per Infrarot gesendeten IDs der Öffentlichkeit verschwiegen werden müssen erschließt sich mir noch nicht.

Gibt es seit neuestem Minidronen die man durch ein gekipptes Fenster fliegen lassen kann um dann die Zähler zu manipulieren?
Haben diese Zähler grundsätzlich auch noch WLAN, drahtlosen Lon-Bus usw.?
Ich bin sicher du kannst mich aufklären.

Warum sollte ich das auch tun?
Gib mir doch einfach Deine Telefonnummer, dann können wir ne Runde schnacken.

Warum hast Du nicht selbst vorher nachgeschaut?

Ja. Habe ich erklärt. Und wenn Du nachschaust, dann bekommst Du das sogar ganz ausführlich, bis hin zur Feldbeschreibung und den enthaltenen Elementen sowie deren Gültigkeitsbereich.

Und weil Dein Google heute kaputt ist mal eine externe Linkempfehlung SML Arduino Parser.

Das mit dem SML Parser war mir bisher nicht bekannt. Vielen Dank.

Wie schon erwähnt bin ich kein Profi am Arduino. Grundlegende Kenntnisse liegen vor, jedoch schwindet mein Verständnis schon beim Einsatz eines Parsers...

Ich brauche hier kein vorgekautes Sketch. Aber eine für einen "Einsteiger" verständliche Erklärung wäre super

Das ist gar nicht schwer.
Und ich denke für Dich gibt es da auch eine feine Lösung mit dem hier: GitHub - olliiiver/sml_parser: Low memory C++ library to parse Smart Message Language (SML) data from smart meters.

Das was unter Usage steht, lässt sich sicher schön umschreiben, das Du es direkt verwenden kannst.

Ich habe mir den Abschnitt "usage" in der olliiiver lib angeschaut.
Na von einer guten Doku ist das aber noch kilometerweit weg.#

Das ist eine so was von typisch zu kurz Doku wie sie leider zu 95% auf GitHub zu finden ist. Wenn du jetzt anderer Meinung bist dann fasse es doch mal eben kurz in 3 bis 5 Sätzen zusammen.

Ich wette das geht nicht so kurz.

Hahaha. Schreib doch mal schnell um. Damit bist du doch in einem halben Stündchen fertig oder?

Warst Du nicht derjenige, der mir gerade erklärt hat, das er Spaghetticode ganz einfach und schnell auf nonBlockingcode umschreiben kann? Bist Du nicht im selben Atemzug am erklären gewesen, in Funktionen auszulagern?
Da ist doch soviel fertig.
Jetzt wo es ernst wird kannst Du doch punkten.

Du darfst gerne weitermachen: Das while(true) kann noch raus udn dafür eine Klammerebene weniger.
Aber bereits so sollte auf dem SerMon bei @beeebeee irgendwas erscheinen....

// Forensketch SML-Parser für EHZ
// https://forum.arduino.cc/t/sml-zahlerdaten-im-arduino-uno-verarbeiten-moglich/1066572
// basierd auf https://github.com/olliiiver/sml_parser und dem dortigen Example-Code
// Im OBISHandler muss festelegt werden, welche Messages auszuwerten sind!

#include <Streaming.h>     // https://github.com/janelia-arduino/Streaming
/*
   Die ehz_bin.h liegt im Verzeichnis ~\examples/Arduino/src -
   Diese umkopieren ins library-Directory
*/
#include "ehz_bin.h"
#include "sml.h"
#include <Arduino.h>

double T1Wh = -2, SumWh = -2;

typedef struct
{
  const unsigned char OBIS[6];
  void (*Handler)();
} OBISHandler;

void PowerT1() { smlOBISWh(T1Wh); }

void PowerSum() { smlOBISWh(SumWh); }

// clang-format off
OBISHandler OBISHandlers[] =
{
  {{ 0x01, 0x00, 0x01, 0x08, 0x01, 0xff }, &PowerT1},      /*   1-  0:  1.  8.1*255 (T1) */
  {{ 0x01, 0x00, 0x01, 0x08, 0x00, 0xff }, &PowerSum},     /*   1-  0:  1.  8.0*255 (T1 + T2) */
  {{ 0, 0 }}
};
// clang-format on


#define SML_DEBUG

//#define DEBUG              // Wenn aktiviert, werden Zwischenwerte ausgegeben

#ifdef DEBUG
#define DBG_PRINTLN(...) Serial << __VA_ARGS__ << endl

#else
#define DBG_PRINTLN(...)
#endif

void setup()
{
  Serial.begin(115200);
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2650)) // https://github.com/arduino/Arduino/issues/10764
  delay(300);
#endif
  Serial << (F("\r\nStart...\r\n")) << endl;
  DBG_PRINTLN(__FILE__);
  DBG_PRINTLN( __TIMESTAMP__);
}

// Continuously loops through a static message from RAM and outputs information
// to serial

void loop()
{
  char floatBuffer[20];
  unsigned int i = 0, iHandler = 0;
  unsigned char c;
  sml_states_t s;
  while (true)
  {
    for (i = 0; i < ehz_bin_len; ++i)
    {
      c = ehz_bin[i];
      s = smlState(c);
      if (s == SML_START)
      {
        /* reset local vars */
        T1Wh = -3;
        SumWh = -3;
      }
      if (s == SML_LISTEND)
      {
        /* check handlers on last received list */
        for (iHandler = 0; OBISHandlers[iHandler].Handler != 0 &&
             !(smlOBISCheck(OBISHandlers[iHandler].OBIS));
             iHandler++)
          ;
        if (OBISHandlers[iHandler].Handler != 0)
        {
          OBISHandlers[iHandler].Handler();
        }
      }
      if (s == SML_UNEXPECTED)
      {
        Serial << F(">>> Unexpected byte! <<<\n") << endl;
      }
      if (s == SML_FINAL)
      {
        dtostrf(T1Wh, 10, 3, floatBuffer);
        Serial << F("Power T1    (1-0:1.8.1)..: ") << floatBuffer << endl;
        Serial.println();
        dtostrf(SumWh, 10, 3, floatBuffer);
        Serial << F("Power T1+T2 (1-0:1.8.0)..: ") << floatBuffer << endl;
        Serial.println();
      }
    }
  }
}

Wenn der Code läuft, ist nur noch das einlesen zu lösen und die ehz_bin.h damit zu ersetzen.
Da ich nichts zum einlesen habe, bin ich da raus.

Das da viel fertig ist heißt aber nicht automatisch, dass man die Anpassungen die noch gemacht werden müssen auch schnell hinbekommt.
Die "D_k_m_nt_tion" die bei dem sml_parser auf GitHub von olliiiver Merge mitgeliefert wird ist so umfangreich wie das Wort in Anführungszeichen. Ganz viele Lücken.

Man müsste es eher "Dkon" schreiben um den Umfang der Erklärung zu charakterisieren. Aber dann ist es so verstümmelt, dass man mit seinem Allgemeinwissen nicht mehr darauf kommt.

Es fehlt dass, was in "Get the best out of this forum" eingefordert wird:
Eine detaillierte funktionelle Beschreibung was der Code macht.

vgs

Was erwartest Du? Fertig entwickelte Codes für alles?
Schreib mir einen Funktionsblock, der den IR-Pin parst und mir jedes Zeichen zurück gibt.
Unsinn, braucht es nicht. Der Parser nimmt die HEX-Werte.

Dann habe ich jetzt was gebaut.
Kurze Erklärung: Es gibt eine Zeile

#define DEBUG

Diese ist aktuell aktiv.
Damit bekommst Du - wenn Du meiner Anleitung im Code folgst eine finktive Ausgabe eines fiktiven Zählers.
Wenn das klappt, dann kannst Du die Zeiloe auskommentieren und automatisch wird Dein IR-Lesekopf bedient.
So die Theorie.

// Forensketch SML-Parser für EHZ
// https://forum.arduino.cc/t/sml-zahlerdaten-im-arduino-uno-verarbeiten-moglich/1066572
// basierd auf https://github.com/olliiiver/sml_parser und dem dortigen Example-Code
// Im OBISHandler muss festelegt werden, welche Messages auszuwerten sind!

#define DEBUG

#include <Streaming.h>     // https://github.com/janelia-arduino/Streaming
#ifdef DEBUG
/*
   Die ehz_bin.h liegt im Verzeichnis ~\examples/Arduino/src -
   Diese umkopieren ins library-Directory
*/
#include "ehz_bin.h"
#endif
#include "sml.h"
#include <Arduino.h>

#include <IRremote.h>

const byte IRPin = 11;
IRrecv irrecv(IRPin); //Objekt initialisieren für die IR Übertragung
decode_results results;

double T1Wh = -2, SumWh = -2;

typedef struct
{
  const unsigned char OBIS[6];
  void (*Handler)();
} OBISHandler;

void PowerT1() { smlOBISWh(T1Wh); }
void PowerSum() { smlOBISWh(SumWh); }

OBISHandler OBISHandlers[] =
{
  {{ 0x01, 0x00, 0x01, 0x08, 0x01, 0xff }, &PowerT1},      /*   1-  0:  1.  8.1*255 (T1) */
  {{ 0x01, 0x00, 0x01, 0x08, 0x00, 0xff }, &PowerSum},     /*   1-  0:  1.  8.0*255 (T1 + T2) */
  {{ 0, 0 }}
};


#ifdef DEBUG
#define DBG_PRINTLN(...) Serial << __VA_ARGS__ << endl
#else
#define DBG_PRINTLN(...)
#endif


void setup()
{
  Serial.begin(115200);
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2650)) // https://github.com/arduino/Arduino/issues/10764
  delay(300);
#endif
  Serial << (F("\r\nStart...\r\n")) << endl;
  DBG_PRINTLN(__FILE__);
  DBG_PRINTLN( __TIMESTAMP__);
  pinMode(IRPin, INPUT);
  irrecv.enableIRIn(); //Den IR Pin aktivieren
}

// Continuously loops through a static message from RAM and outputs information
// to serial

void loop()
{
  smlRead();
}


void smlRead()
{
  char floatBuffer[20];
  unsigned int iHandler = 0;
  unsigned char c;
  sml_states_t s;
#ifdef DEBUG
  static unsigned int i;
  c = ehz_bin[i];
#else
  if (irrecv.decode(&results))                        // Da kommt was
  {
    // Serial.println(results.value, HEX);
    c = results.value;
    irrecv.resume(); // auf den nächsten Wert warten
  }
#endif
  s = smlState(c);
  switch (s)
  {
    case SML_START:
      /* reset local vars */
      T1Wh = -3;
      SumWh = -3;
      break;
    case SML_LISTEND:
      /* check handlers on last received list */
      for (iHandler = 0; OBISHandlers[iHandler].Handler != 0 &&
           !(smlOBISCheck(OBISHandlers[iHandler].OBIS));
           iHandler++)
        ;
      if (OBISHandlers[iHandler].Handler != 0)
      {
        OBISHandlers[iHandler].Handler();
      }
      break;
    case SML_UNEXPECTED:
      Serial << F(">>> Unexpected byte! <<<\n") << endl;
      break;
    case SML_FINAL:
      dtostrf(T1Wh, 10, 3, floatBuffer);
      Serial << F("Power T1    (1-0:1.8.1)..: ") << floatBuffer << endl;
      Serial.println();
      dtostrf(SumWh, 10, 3, floatBuffer);
      Serial << F("Power T1+T2 (1-0:1.8.0)..: ") << floatBuffer << endl;
      Serial.println();
      break;
    default:
      break;
  }
#ifdef DEBUG
  i++;
  if (i >= ehz_bin_len)
  {
    i = 0;
  }
#endif
}

Nein das braucht es nicht aber Kommentare die beschreiben was die jeweilige function macht.
Ich habe jetzt diesen Sketch hier gefunden der hat solche beschreibenden Kommentare
und ist sehr viel leichter nachvollziehbar als der von oh iiigit merge

Der Code arduino-ehz-diag ist möglicherweise nicht so universell einsetzbar wie der sml_parser
Aber wesentlich leichter nachvollziehbar.
vgs

Hast Du schon mal bemerkt, das ich der falsche Adressat bin?
Wenn ja, dann frag ich mich was Dein ganzes Genöle mir gegenüber soll.
Wenn nein, dann gehe zum Maintainer und beschwer Dich da, das Du eine ausführlich Doku willst und Dir die Keywords.txt fehlt und was noch alles.

Ich habe aus den Codeteilen was herausgelesen. Ob es passt, kann ich noch nicht beurteilen. Aber las mich in Ruhe mit dem Gejammere über fremden Code. Du musst ihn nicht benutzen.