Pegelsonde – Messwertübertragung an FHEM

Sie befinden sich hier:
Smart Home Lösungen
»
Füllstandsmessung mit einer Pegelsonde
»
Messwertübertragung an FHEM

Messwertübertragung per MQTT an FHEM

Bei der Übertragung des Messwertes vom Wemos D1 Mini zu FHEM habe ich mich für das MQTT-Protokoll entschieden. Es ist für solche IoT-Anwendungen ideal.

Den passenden Sketch für den Wemos D1 Mini und die Gerätedefinition in FHEM finden Sie hier.

Schritte zu einer Füllstandsmessung mit einer Pegelsonde

  1. Auswahl der Pegelsonde
  2. Pegelsonde - Messwerterfassung
  3. Messwertübertragung an FHEM
  4. Füllstandsanzeige auf FHEM Tablet-UI

Wemos D1 Mini und MQTT

Damit der Wemos D1 Mini in der Lage ist, Informationen über das MQTT-Protokoll zu senden, muss natürlich ein entsprechender Sketch programmiert und auf den Wemos D1 Mini geladen werden. Ich möchte jetzt nicht explizit beschreiben, wie man ein Sketch erstellt und auf den Wemos D1 Mini hoch lädt. Hierfür gibt es genügend Anleitungen im Netz. Nur so viel: Ich verwendete die Arduino IDE zur Implementierung.

Ich möchte hier die Gedanken darlegen, die meinem Sketch zu Grunde lagen (die Logik).


Anforderungen – Sketch für Pegelsonde

Folgende Anforderungen habe ich an den Sketch gestellt:

  • Ich benötige keine permanente Pegelmessung. Mir genügt es vor dem Gießens zu wissen, wieviel Wasser in der Zisterne zur Verfügung steht und ob es außerhalb dieser Zeit signifikante Änderungen am Pegelstand gab, wodurch sich evtl. das Gießen erübrigt. Signifikante Änderungen sind bei meiner Anlage ein Delta von 0,01V, was in etwa 33 Liter entspricht.
  • Insofern sehe ich es als völlig ausreichend an, alle 15 Minuten kurz zu schauen, ob sich am Pegelstand etwas geändert hat. Wenn ja, soll er den neuen Wert an FHEM senden.
  • Die restliche Zeit soll sich der Wemos D1 Mini im DeepSleep-Modus befinden, da im DeepSleep-Modus der Stromverbrauch nur einen Bruchteil beträgt (Normalbetrieb: 0,75mA vs. DeepSleep: 0,04mA).
  • Außerhalb der Gießsaison ist bei mir die Anlage übrigens ganz ausgeschaltet (Netzstecker gezogen). Dann liegt der Stromverbrauch bei 0,0mA.

Randbedingungen:

  • Damit der Wemos D1 Mini den letzten Messwert über die DeepSleep Phase behält, wird der RTC-Memory zur Ablage genutzt.
  • Damit der Wemos D1 Mini aus dem DeepSleep-Modus automatisch wieder erwachen kann, müssen die Pins D0 und RST mit einer Brücke verbunden sein.

Letztendlich ergibt sich daraus folgende Programmlogik:

  • Lesen und Aufbereitung eines Messwertes
  • Vergleich mit dem vorhandenen Messwert im RTC-Memory
  • Wenn das Delta größer ist als der Schwellwert (hier 0,01V = ca. 33 Liter), dann:
    1. starte WiFi
    2. öffne eine Verbindung zum MQTT-Server
    3. publishing des neuen Wertes zum MQTT-Server (payload als JSON)
    4. schreibe den neuen Wert in den RTC-Memory
    5. lege den Wemos D1 Mini wieder für 15 Minuten schlafen
  • Wenn kein oder ein zu geringes Delta zum letzten Messwert vorhanden war, dann tue nichts, sondern schlafe für weitere 15 Minuten.

Sketch: Messwertübertragung Pegelsonde ⇒ FHEM für Wemos D1 Mini


extern "C" {
#include "user_interface.h"                     // for the RTC memory read/write functions
}
#include <ESP8266WiFi.h>                  		// WiFi library
#include <PubSubClient.h>                 		// MQTT library
#include <Adafruit_ADS1X15.h>             		// ADS1115 library

#define SSID "<<WiFi-SID>>"						// WiFi SID
#define PASSWORD "*********"					// WiFi Password
#define MQTT_BROKER "x.x.x.x"					// IP address MQTT broker

typedef struct {								// prepare rtc memory
  int16_t Voltage;
} rtcStore;

rtcStore rtcMem;

int16_t adc0;
int16_t volts0;

// Set your Static IP address, gateway, subnet
IPAddress ip(x, x, x, x);						// IP address of this device (Wemos D1 mini)
IPAddress gateway(x, x, x, x);					// IP address gateway (e.g. router)
IPAddress subnet(255, 255, 255, 0);

WiFiClient espClient;
PubSubClient client(espClient);
Adafruit_ADS1115 ads;

void setup_wifi() {								// try to start WiFi

  Serial.println("Connecting to WiFi");

  WiFi.mode(WIFI_STA);							// station mode = connect to existing access point (e.g. router)
  WiFi.begin(SSID, PASSWORD);

												// Configures static IP address
  if (!WiFi.config(ip, gateway, subnet)) {
    Serial.println("STA failed to configure");
  }

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void setup_mqtt() {								// try to connect to the MQTT broker
  client.setServer(MQTT_BROKER, 1883);

  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    String clientId = "IoT2";					// name of this device = unique client identifier

    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      
      delay(5000);                                // Wait 5 seconds before retrying
    }
  }
}

void setup_ads1115() {                            // setup ADS1115 (set input range to GAIN_ONE)
  ads.setGain(GAIN_ONE);                          // 1x gain   +/- 4.096V  1 bit = 0.125mV
  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    delay(1000);
    while (1);
  }
  Serial.println("ADS connected");
}

void setup() {

  pinMode(BUILTIN_LED, OUTPUT);                   // Initialize the BUILTIN_LED pin as an output
  Serial.begin(9600);
  Serial.println("Start");
 
  system_rtc_mem_read(64, &rtcMem, 2);            // read last value from RTC
  Serial.print("lastValue: ");
  Serial.println(rtcMem.Voltage);

  setup_ads1115();

  adc0 = ads.readADC_SingleEnded(0);              // get current value via ADS1115
  volts0 = ads.computeVolts(adc0)*10000;          // prepare current value for comparison 
  
  if (abs(rtcMem.Voltage-volts0)>100) {           // if delta > 100 (0,01V) => start wifi + mqtt and publish new value
    setup_wifi();
    setup_mqtt();
    delay(1000);
                                                  // prepare payload to publish (payload = JSON array)
    String payload = "{\"Voltage\":" + String(float(volts0)/10000,4) + ",";
    payload += "\"RSSI\":" + String(WiFi.RSSI()) + "}";
  
                                                  // publish values (topic, payload)
    client.publish("/aussen/levelsensor", payload.c_str());
    delay(1000);

    rtcMem.Voltage = volts0;                      // write new value to RTC
    system_rtc_mem_write(64, &rtcMem, 2);
    Serial.print("newValue: ");
    Serial.println(rtcMem.Voltage);
 }

  Serial.println("go to sleep");

//  ESP.deepSleep(5 * 1000000, WAKE_NO_RFCAL);      // go back to deep sleep for 5 sec (test)
  ESP.deepSleep(15 * 60 * 1000000, WAKE_NO_RFCAL);  // go back to deep sleep for 15 min (prod)
}

void loop() {}										// no loop needed in this sketch


Voraussetzung in FHEM für MQTT = MQTT-Server (Broker)

Damit FHEM Informationen über das MQTT-Protokoll empfangen kann, muss ein MQTT-Server (ein sogenannter Broker) vorhanden sein, der den Messwert von einem MQTT-Device entgegen nehmen und ggf. an andere IoT-Geräte weitergeben kann (was hier aber nicht der Fall ist). Das kann z.B. ein öffentlicher MQTT-Broker sein, wie z.B. Mosquitto. Ich bevorzuge lokale Lösungen, um die Abhängigkeiten von externen Systemen zu minimieren. Aus diesem Grund habe ich den in FHEM verfügbaren internen MQTT-Server verwendet.


define mq_Server MQTT2_SERVER 1883 global;
attr mq_Server autocreate simple;

Mit „autocreate simple“ wird ein neues MQTT2-Device in FHEM angelegt, sobald es erstmalig versucht, zum MQTT-Server etwas zu senden.


Verarbeitung des Messwertes in FHEM

Beim erstmaligen Publish des Wertes wird ein MQTT2-Device in FHEM erzeugt.
Dabei wird i.W. folgendes Attribut angelegt:


readingList IoT2:/aussen/levelsensor:.* { json2nameValue($EVENT) }

„IoT2“ ist meine eindeutige ClientId, „/aussen/levelsensor“ ist mein Topic und das payload JSON-Array besteht aus Voltage:<<Wert>> und RSSI:<<Wert>>. Somit werden beim erstmaligen Publish auch gleich die beiden Readings Voltage und RSSI angelegt. RSSI (WiFi Signalstärke) ist nur ein „nice to have“ Wert, da mich die Empfangssignalstärke interessiert.

Aus der übermittelten Spannung (Voltage) kann über eine einfache Verhältnisgleichung das Volumen errechnet werden. Hierzu müssen zuvor die Readings VolumeMax [Liter] (in meinem Fall 5000) und VoltageMax [V] (1,525) manuell angelegt werden. VoltageMax hatte ich bei der Installation der Pegelsonde und Kalibrierung der Elektronik bei voller Zisterne ermittelt. Mit diesen Ausgangswerten lässt sich letztendlich das aktuelle Volumen über ein Userreading wie folgt errechnen:

Volume = Voltage/ VoltageMax * VolumeMax

Falls das berechnete Volumen > VolumeMax ist (kann auftreten durch Messtoleranzen), wird Volume auf VolumeMax gesetzt. Zusätzlich merke ich mir noch das Volumen des Vortages (VolumePrev), um die Veränderung des Füllstandes über den ganzen Tag erkennen zu können.

Hier der wesentliche Teil aus der FHEM-Definition des MQTT2-Devices:


Internals:
   DEF        IoT2
   DEVICETOPIC EG_au_LS_1
   IODev      mq_Server
   NAME       EG_au_LS_1
   STATE      4180 Liter
   TYPE       MQTT2_DEVICE
   Helper:
     DBLOG:
       Volume:
         lg_DB_HS1_all:
           TIME       1669071660.56647
           VALUE      5000
   READINGS:
     2022-11-17 07:04:12   IODev           mq_Server
     2022-10-18 18:09:13   RSSI            -69
     2022-10-18 18:09:13   Voltage         1.2750
     2022-05-19 22:26:39   VoltageMax      1.525
     2022-11-22 00:00:05   Volume          4180
     2021-06-10 20:13:28   VolumeMax       5000
     2022-11-22 00:00:05   VolumePrev      4180
Attributes:
   DbLogExclude .*
   DbLogInclude Volume,VolumePrev
   IODev      mq_Server
   addLog     Volume
   alias      Levelsensor
   autocreate 0
   event-on-change-reading Volume,Voltage
   group      Giessen
   icon       well
   readingList IoT2:/aussen/levelsensor:.* { json2nameValue($EVENT) }
   stateFormat Volume Liter
   userReadings Volume {
 my $Voltage = ReadingsVal("$name","Voltage",0);
 my $VoltageMax = ReadingsVal("$name","VoltageMax",2);
 my $VolumeMax = ReadingsVal("$name","VolumeMax",5000);
 my $Volume = round($Voltage/ $VoltageMax * $VolumeMax,0);
 if ($Volume > $VolumeMax) {
   $Volume = $VolumeMax;
 }
 return $Volume;
 }

Somit fehlt nur noch eine ansehnliche Darstellung des Messwertes in FHEM Tablet-UI.


Kontakt

Senden Sie mir Ihre Fragen oder Anregungen über die Kontaktbox oder direkt per Email. Sie können mich natürlich auch über die gängigen sozialen Netze erreichen.

kontakt@kaempf-nk.de

Fragen / Anregungen?

Sicherheitsabfrage:
Datenschutzhinweis: Die eingegebenen Daten werden nicht an Dritte weitergegeben.