SML Daten lesen mit ESP8266 klappt nicht

Hallo, dies ist mein erster Post im Forum.

Seit Tagen versuche ich, Daten vom SML Smartmeter (Iskra MT175) über den Hichi-Lesekopf zu lesen. Es kommt zwar etwas an, aber nicht die erwarteten Daten vom Smartmeter.

Deshalb habe ich zunächst einen Sketch geschrieben, der nur die seriallen Roh-Daten einlesen und als Hex-Daten ausgeben soll.

Die Ausgabe erfolgt über WebSerial auf einer Web-Seite, da Zähler und Computer weit auseinander sind.

Was ich bisher versucht habe:

  • Um sicher zu stellen, dass es kein HW-Problem ist (Lesekopf oder ESP8266) habe ich Tasmota mit dem SML-script auf dem ESP8266 installiert. Damit bekomme ich korrekte Werte angezeigt.

  • Im eigenen Sketch habe ich die "Standard" SoftwareSerial library verwendet, um über Pin D1 die Daten zu lesen, aber da kommen wie gesagt nur unsinnige Daten . Wenn ich allerdings die Pins D1 und D2 verbinde, und einen Text eintippe (siehe Sketch), erhalte ich die korrekten Hex-Codes.

  • Da das nicht richtig funktionierte habe ich die TasmotaSerial library eingebunden (mit Tasmota funktionierte es ja). Hier bekomme ich auch unsinnige Daten, allerdings andere (und mehr) als mit der SoftwareSerial lib. Hier funktioniert das Schreiben von Pin D2 auf D1 allerdings nicht.

Hier nun der Sketch. Ich habe ihn mit PlatformIO entwickelt. Die "Bedienung" ist im Code erläutert.

/******************************************
 * This is a demo program to test Serial libratries on the ESP8266.
 * Used libraries are standard "SoftwareSerial" amd "TasotaSerial"
 * 
 * using this program:
 *   first fill in the values for ssid, password, STATIC_IP, GATEWAY, SUBNET, and DNS for the ESP8266
 *   UPload to a ESP8266 (e. g. wemos d1 mini)
 *   In a browser open the given STATIC_IP
 *   Click the link to "Console"
 * 
 *   In the input-box of the console you can now enter a 't' for the TasmotaSerial-lib, 
 *   or an 's' for the SoftwareSerial-lib
 *   
 *   Now there are 2 scenarios:
 *   1. connect D1 of the Wemos with the output of a Hichi Smartmeter IR-adapter
 *   2. connect D1 of the Wemos with D2
 * 
 *   Use case 1:
 *    With either TasmotaSerial or SoftwareSerial the same data coming from the SML meter should 
 *    shown in the console
 * 
 *   Use case 2:
 *     Type a text in the input-window of the console that does not start with 't' or 's'.
 *     The hex-codes of the chars should be displayed in the console
 *     
 *****/ 

#include <SoftwareSerial.h>
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
#include <WebSerialLite.h>
#include <TasmotaSerial.h>


const char* ssid = "<ENTER SSID OF YOUR WIFI HERE>";
const char* password = "<ENTER PASSWORD OF YOUR WIFI HERE>";

IPAddress STATIC_IP(192,168,0,100);  // <- enter the IP-address your ESP8266 should get
                                                              // !!! note that the numbers are separated with commas, 
                                                              // not points !!
IPAddress GATEWAY(192, 168, 0, 1);  // <- enter the gateway of your network here
IPAddress SUBNET(255, 255, 255, 0); // <- enter the subnet mask here
IPAddress DNS(8, 8, 8, 8);            // <- enter the ip of the DNS-server here

AsyncWebServer server(80);

bool useTasmota = true; // allow to use either the standard SoftwareSerial lib, or the serial-lib from Tasmota
SoftwareSerial *ssSerial = useTasmota ? NULL : new SoftwareSerial(D1, D2);
TasmotaSerial *tsSerial = useTasmota ? new TasmotaSerial(D1, D2) : NULL;

// receive message from WebSerial (Web-console)
// can be used to switch between standard Serial and Tasmota serial
void recvMsg(uint8_t* data, size_t len) {
  String d = "";
  for (unsigned int i = 0; i < len; i++) {
    d += char(data[i]);
  }
  if (d.startsWith("t")) {
    useTasmota = true;
    if (ssSerial != NULL) {
      delete ssSerial;
      ssSerial = NULL;
    }
    if (tsSerial == NULL) {
      tsSerial = new TasmotaSerial(D1, D2);
      tsSerial->begin(9600, SWSERIAL_8N1);
    }
  } else if (d.startsWith("s")) {
    useTasmota = false;
    if (tsSerial != NULL) {
      delete tsSerial;
      tsSerial = NULL;
    }
    if (ssSerial == NULL) {
      ssSerial = new SoftwareSerial(D1, D2);
      ssSerial->begin(9600, SWSERIAL_8N1);
    }
  } else if (d.length() > 0){
    if(useTasmota) {
      if (tsSerial != NULL) {
        for (int i = 0; i < d.length(); ++i) {
          tsSerial->write(d[i]);
        }
      }          
    } else {
      if (ssSerial != NULL) {
        for(int i = 0; i < d.length(); ++i) {
          ssSerial->write(d[i]);
        }
      }
    }
  }

  WebSerial.printf("\nUsing %s for serial communication\n", (useTasmota ? "TasmotaSerial" : "SoftwareSerial"));
}

void setup() {
  if (useTasmota) {
    tsSerial->begin(9600, SWSERIAL_8N1);
  } else {
    ssSerial->begin(9600, SWSERIAL_8N1);
  }

  Serial.begin(115200);   // baud serial
  WiFi.mode(WIFI_STA);
  WiFi.config(STATIC_IP, GATEWAY, SUBNET, DNS);
  while(WiFi.status() != WL_CONNECTED){
    delay(400);
    Serial.print(".");
  }
  Serial.println("IP Address: ");
  Serial.println(WiFi.localIP());

  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(200, "text/html", "<html><body><h2>Webserver running...</h2>" 
                      "<a href=\"/webserial\" target=\"_blank\">Console</a><p>"
                      "<a href=\"/update\" target=\"_blank\">Update</a></body></html>");                     
  });
  
  AsyncElegantOTA.begin(&server);
  WebSerial.begin(&server);
  WebSerial.onMessage(recvMsg);
  server.begin();
}

byte values[500];
char prtbuf[1540];

bool serialAvailable() {
  return useTasmota ? tsSerial->available() != 0 : ssSerial->available() != 0;
}

byte serialRead(){
  return useTasmota ? tsSerial->read() : ssSerial->read();
}

// wait for a pause in serial-stream of at least ms milliseconds
void waitForPause(unsigned long ms){
  long start = millis();
  while (millis() - start < ms) {
    yield();
    while(serialAvailable()) {
      serialRead(); // flush the data
      yield();
      start = millis();
    }
  }
}

void waitForData() {
  while(!serialAvailable()) {
    yield();
  }
}

// read serial data in the buffer. Allow max maxTime or maxCount bytes (whatever comes first)
int readData(byte *buf, int maxCount, unsigned long maxTime){
  unsigned long start = millis();
  byte *ptr = buf;
  while (ptr - buf < maxCount-1 && millis() - start < maxTime ){
    while(ptr - buf < maxCount-1 && millis() - start < maxTime && serialAvailable()){
      *(ptr++) = serialRead();
    }
    yield();
  }
  return ptr - buf;
}  

void bytesToString(char *strbuf, byte *valbuf, int count){
    char *sPtr = strbuf;
    byte *vPtr = valbuf;
    const static char nibble[] = "0123456789ABCDEF";
    for (int i = 0; i < count; ++i) {
      *(sPtr++) = nibble[*vPtr >> 4];
      *(sPtr++) = nibble[*vPtr & 0x0F];
      *(sPtr++) = ' ';
      *sPtr = 0;
      ++vPtr;
    } 
}


void loop() {
  waitForPause(200);
  waitForData();
  int count = readData(values, 500, 400);
  bytesToString(prtbuf, values, count);
  WebSerial.printf("\n%s\n", prtbuf);
  // delay(3000);
}

... und die platformio.ini sieht so aus:

[common]
platform = espressif8266@^2.4.2
monitor_speed=115200
lib_deps = 
    git+https://github.com/volkszaehler/libsml
    ; EspSoftwareSerial
    ; MicroDebug
    ; ArduinoJson
    ESPAsyncTCP
    ESP Async WebServer
    WebSerialLite
    AsyncElegantOTA

lib_ldf_mode = deep+

[env:wemos_d1_mini_lite]
platform = ${common.platform}
board = d1_mini
framework = arduino
lib_deps = ${common.lib_deps}
monitor_speed = ${common.monitor_speed}
lib_ldf_mode = ${common.lib_ldf_mode}

Die Ausgabe des Programms in der Console sieht etwa so aus (Zeilenumbrüche sind angepasst):

[WebSerial] Connecting...
[WebSerial] Connected...

Using SoftwareSerial for serial communication
64 80 77 20 12 BC 64 76 20 FE FF 60 C8 80 00 00 86 00 FE FE 
20 FE 00 FE 40 EE 60 FE FE 00 FE FE FE FE 00 0C 76 FE FE 00 
2C 76 FE FE FE 02 00 00 86 00 FE 02 F0 20 90 CE 70 C4 28 00 
C2 81 98 B0 60 00 F2 00 20 80 1C 0E 60 02 02 70 00 

80 70 20 00 00 19 01 20 FE FF C8 81 70 76 86 00 FE FE 20 76 
FE 00 FE 00 CE FE 40 FE 00 CE 60 76 FE FE 66 FE FE 00 0C FE 
FE 00 0C FE FE 76 FE 12 76 00 00 86 00 FE 02 F0 20 90 EE C0 
38 C4 00 C2 09 70 72 D8 30 00 F2 00 20 60 80 1C E6 60 40 60 
E6 92 

Using SoftwareSerial for serial communication
60 80 60 76 20 40 40 FE FF C8 86 00 FE FE 20 76 FE 00 FE 00 
EE 60 FE 40 FE 00 CE FE FE 76 FE FE 00 2C 76 FE FE 00 86 FE 
FE FE 18 00 87 00 FE 02 F0 20 90 EE 70 C4 00 C2 80 72 D8 70 
F2 00 20 80 1C 42 C2 09 64 60 60 00 20 

Using TasmotaSerial for serial communication
13 12 12 12 00 00 00 00 04 00 00 02 00 00 00 00 00 00 00 08 
00 00 04 00 00 00 04 02 00 10 08 00 00 00 08 00 00 00 04 00 
00 00 00 02 08 00 00 04 00 00 02 00 00 00 00 00 00 00 08 18 
00 06 00 02 00 00 00 08 00 00 00 04 00 00 18 00 00 00 00 1E 
1E 00 00 00 00 00 10 08 00 10 18 18 00 00 06 00 02 1E 00 00 
00 00 00 00 08 00 00 06 18 00 00 00 00 00 1E 00 00 00 00 02 
00 00 00 08 00 00 00 04 00 00 00 06 18 00 00 00 00 00 1E 00 
00 00 00 00 00 00 10 00 00 10 00 00 00 00 00 00 0E 04 00 06 
18 00 00 00 00 00 1E 00 00 00 10 00 00 10 00 00 00 00 00 00 
0E 04 00 06 18 00 00 00 00 00 1E 00 00 00 10 00 00 10 00 00 
00 00 00 00 00 00 00 06 18 00 00 00 00 00 1E 00 00 00 10 00 
00 10 00 00 00 00 10 00 06 0C 00 06 18 00 00 00 00 00 1E 00 
00 00 10 00 00 10 00 00 00 00 10 00 06 0C 00 06 18 00 00 00 
00 00 1E 00 00 00 10 00 00 10 00 00 00 00 00 00 00 00 00 06 
18 00 00 00 06 00 1E 00 00 00 08 00 00 00 00 00 00 00 00 06 
18 00 00 06 00 00 1E 00 00 00 00 02 00 00 08 00 00 00 1E 02 
06 00 00 00 0E 02 02 04 18 00 00 1C 00 02 08 18 00 06 00 00 
00 10 10 08 18 10 18 00 02 00 02 00 12 00 00 02 02 00 06 00 
00 06 1C 00 00 00 02 18 04 00 04 00 00 02 00 10 00 00 00 00 
00 08 00 00 00 00 

13 12 12 12 00 00 00 00 04 00 00 02 00 18 00 00 00 00 00 08 
00 00 04 00 00 00 04 02 00 12 08 00 00 00 08 00 00 00 04 00 
00 00 00 02 00 0C 00 04 00 00 02 00 00 00 00 00 00 00 08 18 
00 06 00 02 00 00 00 08 00 00 00 04 00 00 18 00 00 00 00 1E 
1E 00 00 00 00 00 10 08 02 10 18 18 00 00 06 00 02 1E 00 00 
00 00 00 00 08 00 00 06 18 00 00 00 00 00 1E 00 00 00 00 02 
00 00 00 08 00 00 00 04 00 00 00 06 18 00 00 00 00 00 1E 00 
00 00 00 00 00 00 10 00 00 10 00 00 00 00 00 00 0E 10 00 06 
18 00 00 00 00 00 1E 00 00 00 10 00 00 10 00 00 00 00 00 00 
0E 10 00 06 18 00 00 00 00 00 1E 00 00 00 10 00 00 10 00 00 
00 00 00 00 00 00 00 06 18 00 00 00 00 00 1E 00 00 00 10 00 
00 10 00 00 00 00 10 00 06 0C 00 06 18 00 00 00 00 00 1E 00 
00 00 10 00 00 10 00 00 00 00 10 00 06 0C 00 06 18 00 00 00 
00 00 1E 00 00 00 10 00 00 10 00 00 00 00 00 00 00 00 00 06 
18 00 00 00 06 00 1E 00 00 00 08 00 00 00 00 00 00 1E 00 06 
18 00 00 06 00 00 1E 00 00 00 00 02 00 00 08 00 00 00 1E 02 
06 00 00 00 0E 02 02 04 18 00 00 1C 00 02 08 18 00 06 00 00 
00 10 10 08 18 10 18 00 02 00 02 00 12 00 00 02 02 00 06 00 
00 06 1C 00 00 00 02 00 00 00 04 00 00 02 00 00 00 00 00 00 
00 08 00 00 00 00 

Hat jemand eine Idee, was hier falsch laufen könnte, und natürlich vor allem, wie man das fixen kann?

Wenn Infos fehlen, bitte nachfragen.

@agmue wird da aushelfen können.
Ich duck mich schon mal weg....

Ich hatte hier mal was gebaut. Das hat auch funktioniert. Evtl. hilft es ja.

Hallo,
schau mal in hier hier das ist der gleiche Post wie unter #2
Der Sketch läuft bei mir und ich habe ihn mit 3 verschieden Hex Dumps getestet.

Heinz

Nachtrag
bist Du sicher das Du den Anfang immer richtig erkennst ?

Hallo,
den Thread bin ich jetzt mal komplett durch gegangen (immerhin 414 Posts).

Der größte Teil dreht sich ja um das Auswerten der SML-Messages.
Soweit komme ich aber aktuell mit dem geposteten Sketch gar nicht. Die eingelesenen Hex-Codes unterscheiden sich je nach verwendeter SoftwareSerial Bibliothek, und enthalten nie eine Folge von 4 mal 1B.
An der Hardware (Lesekopf, Ausrichtung des Lesekopfs, Verkabelung oder ESP8266) kann es nicht liegen, da ich bei gleicher HW mit Tasmota korrekte Werte bekomme.

Ob die Blockgrenzen korrekt sind kann ich nicht 100% sagen. Falls mein Sketch das tut was wr soll, dann wird die Blocktrennung eingefügt, wenn 200ms lang keine Daten gekommen sind, bzw vor dem Starten eines Blocks wird gewartet bis 200ms lang keine Daten ankommen.

Sollte aber auch aktuell keine Rolle spielen, da die Startsequenz nirgends vorkommt.

Irgendwas amCode muss falsch sein, oder es gibt ggf Inkompatibilitäten mit den verwendeten Modulen. Ersteres halte ich für eher unwahrscheinlich, weil der relevante Teil nur aus den 2 Zeilen 'available()' und 'read()' besteht, die überall so verwendet werden. Andererseits gibt es in Tasmota auch einen Webserver und eine Console über web, und es funktioniert trotzdem.

Über Ideen, woran es liegen könnte würde ich mich freuen

Warum verwendest Du dann nicht die Daten von Tasmota?

Hast Du einen zweiten ESP32/8266?

Ich hab das Projekt zwischendurch auf Eis gelegt gehabt, da mir hier noch ein gateway über den Weg gelaufen ist und mein Ansatz nicht in Teilen funktioniert, sondern wenn dann komplett geschrieben werden muss.

Was ich aber sagen kann: Du musst die Startsequenz bekommen.
gibt denn der Kopf die Daten 1:1 seriell aus, oder werden die erst auf dem ESP verarbeitet und dann abgegeben?
Nicht das das abgebende Protokoll nicht passt... :crazy_face:

Tasmota ist eigentlich ein komplettes IoT Ökosystem. Die Idee dahinter ist, IoT Geräte durch Flashen mit Tasmota im lokalen Netzwerk einbinden zu können ohne über die Cloud gehen zu müssen.

Durch Scripting kann man auch eigene Funktionen zufügen, und ich werde nach den bisherigen Problemen meine PV-Überschuss Lösung wohl auch zunächst mit Tasmota aufsetzen.

Aber es sollte doch auch ohne den gigantischen Overhead von Tasmota gehen, und bei einer Eigenentwickling lernt man ja auch mehr.

Geht es, wurde ja in dem anderen Thema gezeigt!

Die in #1 gezeigten Daten haben allerdings nichts mit dem mir bekannten SML zu tun.

Bitte beantworte auch meine zweite Frage.

Hallo,
dann gehe doch mal direkt seriell da dran und lass das Web Ding weg. Wenn das nicht geht eine Lösung mit Puty gibts ja auch noch .
Die Startsequenz im Hex Dump muss sich finden lassen , was Du dann damit machst ist ein anderes Thema.
Einfach mal jedes Zeichen lesen und direkt auf die Serielle mit 115200 ausgeben. Oder halt mal 500 byte in ein Array einlesen und bei 500 dann einfach mal abbrechen und ausgeben. Da sollte sich der Start irgendwo finden lassen.
Doku zum Stromzähler schon studiert ? Was soll denn kommen ?

Nachtrag
Total verrückt geht aber sicher auch recht einfach
Du könntest auch das Array ins Filesysem LittleFS. des ESP auf eine Datei schreiben. Im Keller schließt Du das Ding an und ließt das Array ein. Anzeige mit der internen LED. auf dem ESP wenn voll .
Dann baust Du ab schließt das Ding an den PC an und gibst das HEX Dump aus dem File im Setup auf dem Monitor aus. :innocent:

Ich hab mal kurz quer gekesen.
Der Sensor liegt direkt am ESP GPIO3 wenn ich das richtig verstehe;
Ich kann mir nicht vorstellen, das sich dann parallel das Signal abgreifen lässt.

@slartibartfass wie kommt denn da oben die Ausgabe

Using SoftwareSerial for serial communication

Und was ist das für eine eigenartige Formatierung, das da mal 77 und mal 82 bytes kommen.
Ich seh irgendwie keine Grundlage, den Kopf direkt am Arduino zu betreiben wenn nicht klar ist, wie die Werte da zustande kommen.

Der Lesekopf ist der Hichi Kopf. Da ist keinerlei Intelligenz drin, sondern nur Fototransistor, Widerstand und ein TTL Schmitt-Trigger.

Es sollten also die Rohdaten kommen, tun sie aber nicht. Das ist ja genau mein Problem.

Wenn Tasmota das kann, dann sollte das auch gehen.
Nicht, das die (Pegel-)Logik anders ist?
@Rentner @agmue hat wer ne Ahnung, was da raus kommen muss? Ich kann im Moment nicht intensiv danach suchen.

Der Sensor hänht am GPIO5 (nicht 3). Die Ausgabe "using SoftwareSerial ..." wird ausgegeben, wenn über WebSerial eine Eingabe gemacht wird. Damit kann man zwischen dem Default SoftwareSerial und TasmotaSerial umschalten. Das habe ich allerdings nur für den Post eingebaut. Das seltsame Verhalten bekomme ich auch, wenn nur eine der Libs vorhanden ist.

Die unterschiedlichen Blocklängen finde ich auch seltsam, genau sowie das Fehlen der 1B Startfolge.

Die Blöcke sind folgendermaßen entstanden:

  • Warten, dass für 200ms kein Byte ankommt
  • Warten, dass ein Byte verfügbar ist (available())
  • Zeichen lesen solange bis 500 Bytes gelesen sind, oder 400ms vergangen ist, was immer zuerst kommt

Kennst du die Sequenz die Dein Zähler sendet?
Sprich den Inhalt der über die Schnittstelle kommt? Daran könnte man zumindest versuchen die Länge aufzudröseln.
Ich würde als erstes komplett ohne Limit solange lesen, bis iwann die ESC-Sequenz kommt. (Und dem in #15 nachgehen, damit Du wenigstens sicher sein kannst, das Dein Arduino tut, was Du von ihm willst)

Nochmal gefragt: Hast Du einen weiteren µC, wenn ja, welchen?

Es geht um Simulation.

Also ich hab den Lesekopf auch in Verwendung der geht sowohl für 3,3 als auch 5V Versorgungsspannung beachten, beim ESP an 3,3V anschließen.

bei mir sieht das dann so aus.

#define RX 13 // =D7
#define TX 15 // =D8
SoftwareSerial mySerial;
byte sml[420];

Setup
mySerial.begin(9600, SWSERIAL_8N1, RX, TX, false, 256);

Hallo,
ich hab mir mal deine beiden Funktionen readData und bytesToString angesehen. ich weiß nicht ob ich denen trauen soll.

Heinz

Ich habe nur einen Microcontroller, einen ESP8266. Angeschlossen ist der Lesekopf. Es geht hier nicht um Simulation.

Der Zähler ist ein Iskra MT175, der SML aussgibt.

Wenn ich (mit SoftwareSerial) RX und TX verbinde und über TX etwas ausgebe wird das korrekt in Hex angezeigt.

Was an dem Code findest Du nicht vertrauenswürdig?

Genau, da det Microcontroller ein ESP8266 ist, wird der Kopf mit 3,3 V versorgt.
Bei SoftwareSerial kann man einstellen, dass der Input invertiert wird, aber das hilft leider auch nichts.

Was bedeutet der letzte Parameter "256" in Deinem SoftwareSerial Call?