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.
- Auswahl der Pegelsonde
- Pegelsonde - Messwerterfassung
- Messwertübertragung an FHEM
- Füllstandsanzeige auf FHEM Tablet-UI
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).
Folgende Anforderungen habe ich an den Sketch gestellt:
Randbedingungen:
Letztendlich ergibt sich daraus folgende Programmlogik:
extern "C" {
#include "user_interface.h" // for the RTC memory read/write functions
}
const char* SSID = "<<SSID>>"; // WiFi SID
const char* PASSWORD = "<<Password>>"; // WiFi Password
const char* MQTT_BROKER = "xx.xx.xx.xx"; // IP address MQTT broker
const char* clientId = "IoT2"; // name of this device = unique client identifier
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(xx, xx, xx, xx); // IP address of this device (Wemos D1 mini)
IPAddress gateway(xx, xx, xx, xx); // 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...");
if (client.connect(clientId)) {
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
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.
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.
Fragen oder Anregungen nehme ich gern über die Kontaktbox entgegen oder direkt per Email.
kontakt@kaempf-nk.de