Dies ist eine alte Version des Dokuments!


WWW: ESP32 - IR-Schnittstelle auslesen

ESP32

Warum B und nicht A?

  • A: GPIO15 ist ein Strapping-Pin (beeinflusst den Boot), GPIO34 ist nur Eingang.
  • Als Lesekopf-RX geht 34, aber A ist insgesamt unflexibler.
  • B: 14, 33, 27 und 25 sind unkritisch.
  • GPIO14 gibt beim Booten kurz ein PWM-Signal aus, was hier nicht stört.

Anschlüsse

A B
1 15 PWM 1 14
2 34 analog 2 33 TX (zum Adapter-Tx)
3 22 SCL 3 22
4 3.3 V + 4 3.3 V +
5 GND - 5 GND -
6 21 SDA 6 21
7 26 interupt 7 27 RX (vom Adapter-Rx)
8 32 PWM 8 25

Lesekopf

  • Versorgung: 3,3–5 V, der Logikpegel entspricht Vcc. Mit 3,3 V am ESP32 passt das, am Nano gehen 5 V.
  • Pegel: Der Adapter invertiert intern so, dass am Rx-Pin normale UART-Polarität anliegt (Ruhe = HIGH).
  • In der Software ist keine Invertierung nötig.
  • Maximale Baudrate: 9600. Für den Stromzähler (SML, 9600) reicht das genau.
  • Beim Wärmezähler später muss das Protokoll mit ≤ 9600 Baud laufen.
  • Anschlussreihenfolge: Die Platine ist von links nach rechts mit GND, Tx, Rx, Vcc beschriftet.
  • Der RJ22-Stecker hat die Reihenfolge Vcc, Tx, Rx, GND.
  • Beim Verdrahten also nach der Beschriftung gehen, nicht nach der Reihenfolge.
  • Halterung: Der Adapter hält magnetisch.
  • In der Kompatibilitätsliste steht nur der Iskra MT691, der E320 ist dort nicht aufgeführt.
  • Teste also, ob der Magnet am E320 hält und der Kopf exakt über der optischen Schnittstelle sitzt, notfalls mit etwas Klebeband.

Zähler

BILD vom ZÄHLER:

Stromzähler auslesen (E320, SML)

Verdrahtung (Variante B)

Lesekopf ESP32
Vcc 3,3 V
GND GND
Rx (Daten vom Zähler) GPIO27 (RX)
Tx (Senden zum Zähler) GPIO33 (TX)
OLED SDA GPIO21
OLED SCL GPIO22

Im Sketch:

Serial1.begin(9600, SERIAL_8N1, 27, 33);   // RX=27, TX=33

Stromzähler E320 (SML)

Daten

  • Der E320 sendet jede Sekunde ein SML-Telegramm mit 9600 Baud, 8N1. Der Lesekopf muss nicht senden.
  • Ohne PIN-Freischaltung kommen nur Bezug und Einspeisung in ganzen kWh (Telegramm ca. 252 Bytes). Mit freigeschaltetem erweitertem Datensatz kommt zusätzlich die Wirkleistung und die Telegramme sind länger.
OBIS Bedeutung Einheit im Telegramm
1.8.0 Bezug (Zählerstand) Wh, Skalierer beachten
2.8.0 Einspeisung (Zählerstand) Wh, Skalierer beachten
16.7.0 Momentanleistung (negativ bei Einspeisung) W

PIN-Freischaltung (einmalig) Die PIN erhält man vom Messstellenbetreiber. Sie gehört nicht ins Wiki und nicht in den Quellcode.

  1. Lesekopf abnehmen (er deckt die optische Taste ab).
  2. Mit Handy-Taschenlampe kurz auf die optische Taste leuchten: Displaytest, dann Abfrage „PIn“.
  3. Erneut leuchten, die Anzeige beginnt bei 0. Jeder kurze Puls (unter 2 s) zählt die Ziffer um 1 hoch. Nach ca. 3 s ohne Puls wird die Ziffer übernommen und die nächste beginnt.
  4. Nach der PIN mit kurzen Pulsen durch die Liste blättern bis „inF“. Dort langer Puls (ca. 5 s), bis „inF on“ erscheint. Damit sendet die Schnittstelle den erweiterten Datensatz.
  5. Weiter zu „PIn on“, wieder ca. 5 s leuchten, bis „PIn oFF“ erscheint. Sonst verlangt der Zähler nach einem Stromausfall die PIN neu.
  6. Nach 120 s ohne Bedienung kehrt der Zähler zur Standardanzeige zurück. Dann PIN neu eingeben.
Bei LED-Handylicht erkennt der Sensor die Pulse manchmal schlecht. Hilfreich ist eine saubere Lichtquelle, direkt an der Taste, zwischen den Pulsen wirklich wegnehmen. Alternativ die IR-Diode des Lesekopfs über den ESP32 schalten, solange die BOOT-Taste gedrückt ist (Sketch unten). Das hat bei uns funktioniert.
const int PIN_LED   = 33;   // Adapter-Tx: LOW = IR-LED an
const int PIN_TASTE = 0;    // BOOT-Taste (gedrückt = LOW)
 
void setup() {
  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, HIGH);          // LED aus
  pinMode(PIN_TASTE, INPUT_PULLUP);
}
 
void loop() {
  digitalWrite(PIN_LED, digitalRead(PIN_TASTE) == LOW ? LOW : HIGH);
}

Sketch

  • Lesen: SML-Telegramm erkennen (Start 1B 1B 1B 1B 01 01 01 01, Ende 1B 1B 1B 1B 1A), danach OBIS-Werte suchen.
  • Senden: alle 20 s per HTTP-POST an emonCMS in einem eigenen Task, damit das Lesen nicht blockiert.
  • OLED: Bezug, Einspeisung, Leistung, WLAN-Status, Telegrammzähler, HTTP-Code.
  • Flag VERBINDUNGSTEST: true sendet nur einen Testwert (1 Versuch), false die Zählerdaten.
  • Bibliothek: U8g2 (Arduino-Bibliotheksverwalter). Board: ESP32.
/********************************************************************************************
 *  Stromzähler E320 (SML) -> OLED + EmonCMS    ESP32, statische IP, selbstheilend
 *
 *                                    2026-11-07
 *  Features:
 *   - NUR HIER ÄNDERN: SENSOR_ID, NODE, VERBINDUNGSTEST, Netzwerk-Auswahl (Bereich A/B/C)
 *   - VERBINDUNGSTEST true  = nur Testwert senden, 1 Versuch, Port-Test (80/443/8080)
 *     VERBINDUNGSTEST false = Zählerdaten (B_kWh, E_kWh, P_kWh) werden gesendet
 *   - Statische IP (kein DHCP), robuster WLAN-Start mit 3 Versuchen + Funk-Reset
 *   - Lesen (SML, 9600 Baud) in loop(), Senden in eigenem Task (blockiert das Lesen nicht)
 *   - Server ist nach dem Start erst nach ca. 5 min erreichbar:
 *       • in den ersten 10 min keine Funk-Resets
 *       • Boot-Watchdog erst nach 15 min
 *   - Auto-Neustart, falls:
 *       • beim Boot 15 min lang kein erfolgreicher Upload (nur im Normalbetrieb mit Zählerdaten)
 *       • WLAN im Betrieb > 2 min am Stück weg
 *
 *  Pinbelegung ESP32:  Lesekopf Rx -> GPIO27 (RX), Lesekopf Tx -> GPIO33 (TX),
 *                      Vcc -> 3,3 V, GND, OLED SDA = GPIO21, SCL = GPIO22
 ********************************************************************************************/
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClient.h>
#include <U8g2lib.h>
 
/****************************************  NUR HIER ÄNDERUNGEN VORNEHMEN ******************************/
//
//                                        1) Geräte-Identität
#define SENSOR_ID 241                    // letzte IP-Stelle: 192.168.1.SENSOR_ID
const char* NODE = "241-ESP";            // Node-Name in EmonCMS
//
//                                        2) Verbindungstest
#define VERBINDUNGSTEST false            // true = nur Testwert senden, false = Zählerdaten senden
//
//                                        3) Netzwerk-Auswahl siehe weiter unten (Bereich A/B/C)
//
/**************************************** AB HIER KEINE ÄNDERUNGEN VORNEHMEN *****************************************************/
 
IPAddress ip(192, 168, 1, SENSOR_ID);    // feste IP aus SENSOR_ID
 
IPAddress SERVER_IP(192, 168, 1, 2);     // EmonCMS-Server (gilt für alle drei Netzwerkprofile)
const int   httpPort  = 80;
const char* EMON_PATH = "/emoncms/input/post.json";
 
// ============================================================
//  NETZWERK-AUSWAHL - genau EINE der drei Zeilen auf "true" setzen
// ============================================================
#define NETZ_A_MOBIL  false   // SAS-ARDUINO-mobil (persönliches Testnetz)
#define NETZ_B_TEST   true    // SAS-ARDUINO, Schul-Testbetrieb (TEST-API-Key)
#define NETZ_C_LIVE   false   // SAS-ARDUINO, Schul-Livebetrieb (LIVE-API-Key)
 
#if (NETZ_A_MOBIL + NETZ_B_TEST + NETZ_C_LIVE) != 1
  #error "Bitte genau EINE der drei NETZ_-Zeilen oben auf true setzen!"
#endif
 
// ---- BEREICH A: SAS-ARDUINO-mobil (persönliches Testnetz) -------------
// Vor Weitergabe des Codes an andere Schulen DIESEN BLOCK LÖSCHEN!
#if NETZ_A_MOBIL
  const char* ssid     = "SAS-ARDUINO-mobil";
  const char* password = "...";
  const char* apikey   = "...";
#endif
 
// ---- BEREICH B: SAS-ARDUINO, Schul-Testbetrieb -------------------------
#if NETZ_B_TEST
  const char* ssid     = "SAS-ARDUINO";
  const char* password = "...";
  const char* apikey   = "...";
#endif
 
// ---- BEREICH C: SAS-ARDUINO, Schul-Livebetrieb --------------------------
#if NETZ_C_LIVE
  const char* ssid     = "SAS-ARDUINO";
  const char* password = "...";
  const char* apikey   = "...";
#endif
 
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet (255, 255, 255, 0);
IPAddress dns    (0,   0,   0,   0);
 
/*** Messintervall *************************************************************************/
const int Schreibpause = NETZ_A_MOBIL ? 10000 : 20000;  // A (mobil/Test) = 10 s, B/C (Schulnetz) = 20 s
 
/*** Hardware ******************************************************************************/
const int PIN_RX = 27;                   // Adapter-Rx (Daten vom Zähler)
const int PIN_TX = 33;                   // Adapter-Tx (wird nicht benutzt)
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);   // SDA=21, SCL=22
 
/*** Selbstheilungs-Parameter **************************************************************/
const uint32_t BOOT_UPLOAD_DEADLINE_MS = 900000;   // 15 min: Server ist erst nach ca. 5 min erreichbar
const uint32_t NO_RESET_AFTER_BOOT_MS  = 600000;   // 10 min: in dieser Zeit keine Funk-Resets
const uint32_t WIFI_DOWN_REBOOT_MS     = 120000;   // 2 min am Stück offline -> Neustart
const uint8_t  WIFI_BOOT_TRIES         = 3;
 
/*** Zustände ******************************************************************************/
static volatile bool     g_lastSendFailed = false;
static volatile bool     g_uploadedOnce   = false;
static volatile uint32_t g_wifiDownSince  = 0;
static volatile int      g_httpCode       = 0;
static uint32_t          g_bootMs         = 0;
 
struct Messwerte { double bezug, einsp, leistung; bool gueltig; uint32_t nr; };
Messwerte g_mw = {0, 0, 0, false, 0};
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
 
Messwerte holeMesswerte() {
  Messwerte c;
  portENTER_CRITICAL(&mux); c = g_mw; portEXIT_CRITICAL(&mux);
  return c;
}
 
/*** SML-Parser ****************************************************************************/
const uint8_t OBIS_BEZUG[]    = {0x77,0x07,0x01,0x00,0x01,0x08,0x00,0xFF};
const uint8_t OBIS_EINSPEIS[] = {0x77,0x07,0x01,0x00,0x02,0x08,0x00,0xFF};
const uint8_t OBIS_LEISTUNG[] = {0x77,0x07,0x01,0x00,0x10,0x07,0x00,0xFF};
const uint8_t START[8] = {0x1B,0x1B,0x1B,0x1B,0x01,0x01,0x01,0x01};
const uint8_t ENDM[5]  = {0x1B,0x1B,0x1B,0x1B,0x1A};
 
uint8_t buf[1024];
int  n = 0;
int  rest = -1;
bool imFrame = false;
 
bool finde(const uint8_t *obis, double &out) {
  for (int i = 0; i < n - 30; i++) {
    if (memcmp(buf + i, obis, 8) != 0) continue;
    for (int j = i + 8; j < i + 24 && j < n - 2; j++) {
      if (buf[j] == 0x52 && (buf[j - 1] == 0x1E || buf[j - 1] == 0x1B)) {
        int8_t scaler = (int8_t)buf[j + 1];
        uint8_t typ = buf[j + 2];
        int len = (typ & 0x0F) - 1;
        if (len < 1 || len > 8 || j + 3 + len > n) return false;
        int64_t v = (buf[j + 3] & 0x80) ? -1 : 0;
        for (int k = 0; k < len; k++) v = (v << 8) | buf[j + 3 + k];
        out = (double)v;
        for (int s = scaler; s < 0; s++) out /= 10.0;
        for (int s = scaler; s > 0; s--) out *= 10.0;
        return true;
      }
    }
  }
  return false;
}
 
void anzeigen() {
  Messwerte m = holeMesswerte();
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_6x12_tf);
    u8g2.setCursor(0, 12); u8g2.printf("Bezug: %.1f kWh", m.bezug);
    u8g2.setCursor(0, 28); u8g2.printf("Einsp: %.1f kWh", m.einsp);
    u8g2.setCursor(0, 44); u8g2.printf("Leist: %.0f W", m.leistung);
    u8g2.setCursor(0, 60); u8g2.printf("%s #%lu H%d%s",
                                       WiFi.status() == WL_CONNECTED ? "WLAN" : "w/o",
                                       (unsigned long)m.nr, (int)g_httpCode,
                                       VERBINDUNGSTEST ? " TEST" : "");
  } while (u8g2.nextPage());
}
 
void auswerten() {
  double v;
  bool ok = false;
  Messwerte c = holeMesswerte();
  if (finde(OBIS_BEZUG, v))    { c.bezug    = v / 1000.0; ok = true; }
  if (finde(OBIS_EINSPEIS, v)) { c.einsp    = v / 1000.0; }
  if (finde(OBIS_LEISTUNG, v)) { c.leistung = v; }
  if (ok) c.gueltig = true;
  c.nr++;
  portENTER_CRITICAL(&mux); g_mw = c; portEXIT_CRITICAL(&mux);
  Serial.printf("#%lu (%d Bytes) Bezug %.3f | Einsp %.3f | %.0f W\n",
                (unsigned long)c.nr, n, c.bezug, c.einsp, c.leistung);
  anzeigen();
}
 
void byteEmpfangen(uint8_t b) {
  if (n < (int)sizeof(buf)) buf[n++] = b;
  else { n = 0; rest = -1; imFrame = false; return; }
 
  if (n >= 8 && memcmp(buf + n - 8, START, 8) == 0) {   // Telegrammanfang
    memmove(buf, buf + n - 8, 8);
    n = 8; rest = -1; imFrame = true;
    return;
  }
  if (!imFrame) { if (n > 16) { memmove(buf, buf + n - 8, 8); n = 8; } return; }
  if (rest > 0) {                                        // 3 Bytes nach dem Endmarker
    if (--rest == 0) { auswerten(); n = 0; rest = -1; imFrame = false; }
    return;
  }
  if (n >= 5 && memcmp(buf + n - 5, ENDM, 5) == 0) rest = 3;
}
 
/*** WLAN-Helfer ***************************************************************************/
bool ensureWifi(uint32_t timeoutMs = 15000) {
  if (WiFi.status() == WL_CONNECTED) { g_wifiDownSince = 0; return true; }
  Serial.print("[WIFI] Verbinde");
  uint32_t start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < timeoutMs) {
    delay(250);
    Serial.print(".");
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\n[WIFI] OK: %s\n", WiFi.localIP().toString().c_str());
    g_wifiDownSince = 0;
    return true;
  }
  Serial.println("\n[WIFI] Timeout");
  if (!g_wifiDownSince) g_wifiDownSince = millis();
  return false;
}
 
void funkNeustart() {
  WiFi.disconnect(true);
  delay(200);
  WiFi.mode(WIFI_OFF);
  delay(200);
  WiFi.mode(WIFI_STA);
  delay(200);
  WiFi.config(ip, gateway, subnet, dns);
  WiFi.begin(ssid, password);
}
 
bool wifiBootConnect() {
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.setSleep(false);
  WiFi.setTxPower(WIFI_POWER_19_5dBm);
 
  for (uint8_t attempt = 1; attempt <= WIFI_BOOT_TRIES; ++attempt) {
    Serial.printf("[BOOT] WLAN-Versuch %u/%u\n", attempt, WIFI_BOOT_TRIES);
    funkNeustart();
    if (ensureWifi(20000)) {
      Serial.printf("[WIFI] OK: %s  RSSI:%d dBm\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
      return true;
    }
    int backoff = 1000 + random(0, 2000);
    Serial.printf("[BOOT] fehlgeschlagen -> Backoff %d ms\n", backoff);
    delay(backoff);
  }
  return false;
}
 
bool netSoftReset(uint32_t connectTimeoutMs = 15000) {
  Serial.println("[NET] Soft-Reset: Funk aus/an");
  funkNeustart();
  return ensureWifi(connectTimeoutMs);
}
 
bool tcpPreflight(uint16_t port = 80, uint16_t graceMs = 50) {
  WiFiClient poke;
  if (!poke.connect(SERVER_IP, port)) return false;
  delay(graceMs);
  poke.stop();
  return true;
}
 
// Nur im VERBINDUNGSTEST: Ports des Servers und des Gateways prüfen
void portTest() {
  Serial.printf("[TEST] Eigene IP %s, Gateway %s, Subnetz %s, RSSI %d dBm\n",
                WiFi.localIP().toString().c_str(), WiFi.gatewayIP().toString().c_str(),
                WiFi.subnetMask().toString().c_str(), WiFi.RSSI());
  const uint16_t ports[] = {80, 443, 8080};
  for (uint16_t p : ports) {
    WiFiClient c;
    bool ok = c.connect(SERVER_IP, p, 3000);
    Serial.printf("[TEST] Server %s Port %u : %s\n", SERVER_IP.toString().c_str(), p,
                  ok ? "OFFEN" : "zu / keine Antwort");
    c.stop();
  }
  WiFiClient g;
  bool ok = g.connect(gateway, 80, 3000);
  Serial.printf("[TEST] Gateway %s Port 80 : %s\n", gateway.toString().c_str(),
                ok ? "OFFEN" : "zu / keine Antwort");
  g.stop();
}
 
// Nur im VERBINDUNGSTEST: misst, nach wie vielen Sekunden der Server nach dem Start antwortet
void warteBisServerAntwortet() {
  uint32_t t0 = millis();
  while (millis() - t0 < 600000) {                       // max. 10 min
    WiFiClient g, s;
    bool gw = g.connect(gateway, 80, 2000);   g.stop();
    bool sv = s.connect(SERVER_IP, 80, 2000); s.stop();
    Serial.printf("[DIAG] %lu s  Gateway:%s  Server:%s\n",
                  (millis() - t0) / 1000, gw ? "ok" : "-", sv ? "ok" : "-");
    if (sv) break;
    delay(3000);
  }
}
 
/*** SENDEN der Daten **********************************************************************/
void sendData(const Messwerte &m) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("[HTTP] offline - Upload uebersprungen");
    g_lastSendFailed = true;
    return;
  }
 
  String path  = String(EMON_PATH);
  String query = "?node=" + String(NODE) + "&apikey=" + String(apikey);
  String body;
  if (VERBINDUNGSTEST) {
    body = "json={\"Test\":" + String(millis() / 1000) + "}";
  } else {
    body = "json={\"B_kWh\":" + String(m.bezug, 3) +
           ",\"E_kWh\":"      + String(m.einsp, 3) +
           ",\"P_kWh\":"      + String(m.leistung, 0) + "}";
  }
  Serial.printf("[HTTP] POST http://%s%s  node=%s  body=%s\n",
                SERVER_IP.toString().c_str(), path.c_str(), NODE, body.c_str());
 
  if (g_lastSendFailed && !VERBINDUNGSTEST) {
    bool ok = tcpPreflight(httpPort, 80);
    Serial.printf("[NET] Preflight %s\n", ok ? "OK" : "FAIL");
  }
 
  String hostStr = SERVER_IP.toString();
 
  auto doPostOnce = [&](int attempt) -> int {
    if (WiFi.status() != WL_CONNECTED) {
      ensureWifi(8000);
      if (WiFi.status() != WL_CONNECTED) {
        Serial.printf("[HTTP] WLAN down (Try %d) - skip\n", attempt);
        return -10000;
      }
    }
    WiFiClient client;
    client.setNoDelay(true);
 
    HTTPClient http;
    http.setConnectTimeout(5000);
    http.setTimeout(15000);                  // ms
    http.setReuse(false);
 
    if (!http.begin(client, hostStr, httpPort, path + query, false)) {
      Serial.println("[HTTP] begin() failed");
      return -2;
    }
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    http.addHeader("Connection", "close");
 
    int code = http.POST(body);
    String err = http.errorToString(code);
    String payload = (code > 0) ? http.getString() : String();
    http.end();
 
    g_httpCode = code;
    Serial.printf("[HTTP] Try %d -> code=%d (%s)\n", attempt, code, err.c_str());
    if (payload.length()) { Serial.print("[HTTP] resp: "); Serial.println(payload); }
    if (code == 200) {
      Serial.println(VERBINDUNGSTEST ? "Sent: Testwert" : "Sent: Zaehlerdaten");
      g_uploadedOnce   = true;
      g_lastSendFailed = false;
    }
    return code;
  };
 
  int code = -9999;
  for (int attempt = 1; attempt <= 3; ++attempt) {          // 3 normale Versuche
    code = doPostOnce(attempt);
    if (code == 200) return;
    if (VERBINDUNGSTEST) return;                             // im Test nur 1 Versuch
    delay(250 * attempt);
  }
 
  // Server ist nach dem Start erst nach ca. 5 min erreichbar: in den ersten 10 min keine Funk-Resets
  if (millis() < NO_RESET_AFTER_BOOT_MS) { g_lastSendFailed = true; return; }
 
  if (netSoftReset(15000)) {                                 // Soft-Reset + 4. Versuch
    code = doPostOnce(4);
    if (code == 200) return;
  }
  Serial.println("[NET] Soft-Reset #2 ...");                  // zweiter Soft-Reset + 5. Versuch
  if (netSoftReset(15000)) {
    code = doPostOnce(5);
    if (code == 200) return;
  }
  g_lastSendFailed = true;
  Serial.printf("[HTTP] FAILED after retries | Node=%s\n", NODE);
}
 
/*** Sende-Task (läuft getrennt vom Lesen) *************************************************/
void emonTask(void *) {
  uint32_t zyklus = 0;
  for (;;) {
    vTaskDelay(Schreibpause / portTICK_PERIOD_MS);
    ensureWifi(10000);
    if ((++zyklus % 18) == 0) Serial.printf("[WIFI] RSSI: %d dBm\n", WiFi.RSSI());
    Messwerte m = holeMesswerte();
    if (!VERBINDUNGSTEST && !m.gueltig) continue;   // im Normalbetrieb erst senden, wenn Daten da sind
    sendData(m);
  }
}
 
/*** SETUP *********************************************************************************/
void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\n=== Boot: Stromzaehler-Lesekopf startet ===");
  Serial.printf("Node = %s, IP = %s, Server = %s%s\n", NODE, ip.toString().c_str(),
                SERVER_IP.toString().c_str(), EMON_PATH);
  if (VERBINDUNGSTEST) Serial.println("*** VERBINDUNGSTEST aktiv: es wird nur ein Testwert gesendet ***");
 
  u8g2.begin();
  anzeigen();
 
  randomSeed((uint32_t)ESP.getEfuseMac());
  if (!wifiBootConnect()) {
    Serial.println("[BOOT] WLAN nicht erreichbar -> Neustart");
    delay(800);
    ESP.restart();
  }
 
  if (VERBINDUNGSTEST) { portTest(); warteBisServerAntwortet(); }
 
  Serial1.setRxBufferSize(1024);
  Serial1.begin(9600, SERIAL_8N1, PIN_RX, PIN_TX);
 
  g_bootMs = millis();
  xTaskCreate(emonTask, "emon", 8192, NULL, 1, NULL);
}
 
/*** LOOP: nur Lesen + Watchdogs ***********************************************************/
void loop() {
  while (Serial1.available()) byteEmpfangen(Serial1.read());
 
  uint32_t now = millis();
  if (!VERBINDUNGSTEST && g_mw.gueltig && !g_uploadedOnce && (now - g_bootMs > BOOT_UPLOAD_DEADLINE_MS)) {
    Serial.println("[WD] Kein erfolgreicher Upload binnen 15 min -> Reboot");
    delay(500);
    ESP.restart();
  }
  if (g_wifiDownSince && (now - g_wifiDownSince > WIFI_DOWN_REBOOT_MS)) {
    Serial.println("[WD] WLAN > 120 s weg -> Reboot");
    delay(500);
    ESP.restart();
  }
}

emonCMS

  • Node: 241-ESP, Inputs: B_kWh (Bezug), E_kWh (Einspeisung), P_kWh (enthält die Leistung in Watt, nur der Name ist irreführend).
  • Feeds mit „Log to feed“, Engine PHPFina, Intervall 20 s (muss zum Sendeintervall passen, später nicht änderbar).
  • Für Tagesverbrauch auf dem Feed „kWh/d“ oder „Delta“ verwenden.
  • Wichtig: Nach dem Start des ESP32 ist der Server ca. 5 Minuten nicht erreichbar (Ursache im Netz noch unklar, nach dieser Zeit klappt alles). Der Sketch berücksichtigt das: in den ersten 10 min keine Funk-Resets, Boot-Watchdog erst nach 15 min.

Fehlersuche

Symptom Ursache / Lösung
Kompilierfehler extended character is not valid in an identifier Unsichtbare geschützte Leerzeichen durch Kopieren-Schaltfläche. Code mit der Maus markieren statt „Kopieren“ klicken.
Keine Bytes im Serial Monitor Kopf verrutscht, Rx/Tx vertauscht oder falsche GPIOs. Mit Pin-Test prüfen.
1B 1B 1B 1B 01 01 01 01 kommt, aber nur kWh, keine Leistung PIN-Freischaltung fehlt (inF on).
Anzeige bleibt stehen, Telegramme laufen weiter Telegrammerkennung per Pause funktioniert nicht, Marker-Erkennung im Sketch nutzen.
HTTP code=-1 direkt nach dem Start Server ca. 5 min nicht erreichbar, abwarten.
HTTP code=200, Antwort ok, aber keine Inputs Falscher Node oder Write-Key.

Echo-Test des Lesekopfs (ohne Zähler): ESP32 sendet 100 Bytes 0x55, der Empfänger sieht die eigene IR-Diode und meldet ca. 100 empfangene Bytes. Das zeigt nur, dass Diode und Empfänger arbeiten, nicht die Strecke zum Zähler.

Wärmezähler Sharky 775 (in Arbeit)

  • Optische Schnittstelle: ZVEI, M-Bus-Protokoll, 2400 Baud, 8E1.
  • Ablauf: 2,2 s lang 0x55 senden (8N1, 480 bis 600 Bytes), dann umschalten auf 8E1, SND_NKE (Antwort E5), dann REQ_UD2 (langes Telegramm).
  • Der Lesekopf muss hier senden, Tx (GPIO33) ist also angeschlossen.
  • Stand: Der Zähler in der Schule hat bisher nicht geantwortet. Echo-Test lief gut. Mögliche Ursachen: Position auf der Linse, Wecksignal oder zu schwache Sendeleistung des Adapters. Alternative: M-Bus-Pegelwandler.
QR-Code
QR-Code www-messdatenerfassung-sas:esp32-ir-messstellenauslese (erstellt für aktuelle Seite)