In diesem Beitrag zeige ich wie ich meine Öltanküberwachung mit Hilfe von Ultraschall und dem Mikrocontroller ESP8266 umgesetzt habe. Ultraschall wird genutzt, um den Füllstand des Öltanks zu messen, während der ESP8266 die Daten erfasst und via MQTT (Message Queuing Telemetry Transport) an einen Empfänger überträgt.
Durch die Verwendung von MQTT können die Daten problemlos an einen Empfänger, Server oder IoT Device übertragen werden, wo die Daten in Echtzeit überwacht und / oder gespeichert werden können.
Achtung: Der Umgang mit brennbaren Flüssigkeiten ist gefährlich. Ich weise auf den Haftungsausschluss am Ende meines Berichts hin. Die ist ein rein informativer Beitrag. Keine Nachbauanleitung.
Prinzip
Ultraschallsensor
Das Prinzip der Messung mit Ultraschall im Öltank beruht auf der Laufzeitmessung von Schallwellen. Der Ultraschallsensor AJ-SR04M sendet dabei Schallwellen aus, die an der Oberfläche des Öls reflektiert werden. Die reflektierten Schallwellen werden vom Sensor erfasst und die Laufzeit der Schallwellen wird gemessen. Aus dieser Laufzeit kann dann der Abstand zwischen Sensor und Öl-Oberfläche berechnet werden. Da sich der Füllstand im Tank ändert, ändert sich auch der Abstand zwischen Sensor und Öl-Oberfläche, was zur Folge hat, dass sich die Laufzeit der Schallwellen verändert. Daraus ergibt sich eine Änderung des Abstandes und somit des Füllstandes im Tank.
Mikrocontroller ESP8266 und MQTT
Der ESP8266 ist ein Mikrocontroller, der die Daten des Ultraschallsensors empfangen und verarbeiten kann. Dazu wird der Mikrocontroller mit dem Ultraschallsensor verbunden und es wird ein Programm erstellt, das die Daten des Sensors auswertet und in eine für den Empfänger lesbare Form bringt. Hierbei können zum Beispiel Messwerte oder ein Status-Update übertragen werden.
Die Auswertung der Daten im ESP8266 erfolgt dann in der Regel über ein Programm, das auf dem Mikrocontroller ausgeführt wird. In diesem Programm werden die Daten des Sensors ausgelesen, verarbeitet und ggf. auch gespeichert. Hierbei können verschiedene Methoden zur Datenverarbeitung genutzt werden, wie zum Beispiel die Berechnung von Durchschnittswerten oder die Überwachung von Schwellwerten. Anschließend können die Daten über das Wi-Fi-Modul des ESP8266 an einen Empfänger via MQTT übertragen werden, um sie zu visualisieren oder weitere Aktionen auszulösen.
Umsetzung Ultraschall Öltanküberwachung
Verwendete Komponenten
- ESP8266 Microcontroller WEMOS D1 Mini AliExpress
- Ultraschallsensor AJ-SR04M AliExpress
- 470 Ohm Widerstand AliExpress
- 5V Stromversorgung L7805 AliExpress
- Pin Header
- Platine Aisler
Schaltplan ESP8266 AJ-SR04M Ultraschall Sensor
Der Schaltplan zeigt die verwendeten Komponenten. Hauptsächlich ist der ESP8266 und der Ultraschallsensor AJ-SR04M wichtig. Der ESP ist durch die Pin Header J1 und J2 dargestellt. Die Pin Header J3 stellen den AJ-SR04M dar.
Ich erstelle meine Platinen und Schaltpläne gerne mit Pin Headern, da ich auf den fertigen Platinen, dann die Komponenten auswechseln oder wiederverwenden kann.
Zusätzlich verwende ich hier einen 5V Spannungsregler U1, da die Energieversorgung und deren Spannung zum Zeitpunkt der konzeption noch nicht fest lag. Die Spannungsversorgung (Batteriefach, Netzteil) wird hier an die Pin Header J5 angeschlossen.
Der Widerstand R1 470 Ohm dient der Spannungsüberwachung der Energieversorgung an J5. Je nach Energiequelle muss dieser gegebenenfalls angepasst werden. Die Spannungsüberwachung kann auch vernachlässigt werden, wenn das Projekt an einer festen Spannungsquelle wie einem Netzteil versorgt wird oder beim Ausbleiben der Messwerte die Öltanküberwachung überprüft wird. In meinem Fall verwende ich später möglicherweise eine Batteriebox. Da ist es für mich interessant die sinkende Spannung der Batterien zu überwachen um abzuschätzen, wann die Öltanküberwachung ausfallen wird.
Über den Pin Header J4 steuere ich, dass der ESP8266 aus dem Deep Sleep wieder erwachen kann. Soll die Software angepasst werden, muss hier nur ein USB Stecker für das Update eingesteckt werden und der Jumper vom Pin Header J4 entfernt werden.
Platine Ultraschall Sensor mit ESP8266
Aus dem Schaltplan, den ich mithilfe von Fritzing erstellt habe, layoutete ich meine Platine. Dabei versuche ich auf eine platzsparendes Layout zu setzten. Dies ist nicht immer leicht, da die Bedienbarkeit oder Erreichbarkeit der Schnittstellen, Jumper oder Buttons für mich auch eine wichtige Rolle spielt. Kreuzungen der Leiterbahnen sind nur durch weitere Schichten auf der Leiterplatte PCB möglich. Im heutigen Produktionsprozess nicht mehr unbedingt ein Kostenfaktor, dennoch möchte ich gerne darauf verzichten. Zu komplex ist das Projekt nun aus meiner Sicht auch nicht.
So wurde hier der Ultraschallsensor AJ-SR04M, der mit einem abgewinkelten Pin Header angeliefert wurde, senkrecht auf die Platine gestellt. Die Besonderheit bei der Platine des Ultraschallsensors ist, dass die Komponenten der Platine beidseitig angebracht sind. Der Anschluss des Ultraschallmesskopfs ist auf der Unterseite.
Beim ESP8366 war es mir wichtig den USB Anschluss und den Reset-Button gut zugänglich zu haben.
Hergestellt wurde meine Platine von Aisler, die praktischerweise direkt in der Fritzing Software angeknüpft sind.
Meine Platine habe ich veröffentlicht unter https://aisler.net/p/FBCDSPNR
Source Code ESP8266 MQTT
Ultraschallsensor AJ-SR04M Entfernung messen
Zuerst erstellte ich mit Hilfe von Foren und anderen Bloggern eine Funktion um die Messung der Entfernung über den Ultraschallsensor AJ-SR04M zu bewerkstelligung. Ich baue eine Serielle Kommunikation zwischen ESP8266 und dem Ultraschallmodul auf.
#include <SoftwareSerial.h> #define rxPin 13 // D6 // send //rxPin // trigger #define txPin 12 // D7 // receive //txPin // echo SoftwareSerial jsnSerial; void setup() { pinMode(rxPin, INPUT); pinMode(txPin, OUTPUT); Serial.begin(115200); jsnSerial.begin(9600, SWSERIAL_8N1, rxPin, txPin, false); Serial.println(); int value = getMeasurment(); }
Das oben beschriebene Messprinzip wird mit folgenden Funktionen durchgeführt. Ich wiederhole meine Messungen und berechne den Mittelwert der plausiblen Messwerte, um Schwankungen oder Messfehlern vorzubeugen. Es ist darauf zu achten, dass die Laufzeiten der Schallwellen jeder Messung sich nicht überschneiden.
int getMeasurment() { int sumValues = 0; int messurementsNotNull = 0; for(int i=0; i < MEASURMENTS; i++){ if(jsnSerial.available() > 0){ int inByte = jsnSerial.read(); } jsnSerial.write(0x01); delay(50); if(jsnSerial.available() > 0){ int dist = getDistance(); if(dist > 0){ sumValues = sumValues + dist; messurementsNotNull = messurementsNotNull + 1; } } } return sumValues / messurementsNotNull; } int getDistance(){ Serial.println("Start messung"); unsigned int distance; byte startByte, h_data, l_data, sum = 0; byte buf[4]; jsnSerial.readBytes(buf, 4); startByte = buf[0]; if(startByte != 255){ return -1; } h_data = buf[1]; l_data = buf[2]; sum = buf[3]; distance = (h_data<<8) + l_data; if((( h_data + l_data)&0xFF) -1 != sum){ Serial.printf("Invalid result: %d %d (%d)", buf[1], buf[2], buf[3]); Serial.println(); return -1; } Serial.print("Distance [mm]: "); Serial.println(distance); return distance; }
Verbindungsaufbau ESP8266 mit MQTT zum Server
Wenn die Entfernung zwischen Ultraschallsensor und Öloberfläche bestimmt wurde, sende ich die Daten mittels ESP8266 -> MQTT -> WIFI an meinen Server. Dieser nimmt die Daten entgegen, speichert diese ab und errechnet Füllmengen.
#include <ESP8266WiFi.h> #include <Ticker.h> #include <AsyncMqttClient.h> //WIFI configuration #define wifi_ssid "ChrisBue_Blog" #define wifi_password "ChrisBue_geheim_Blog" //MQTT configuration #define mqtt_host "192.168.1.77" #define mqtt_port 1883 #define mqtt_user "ChrisBue_Blog_mqtt_user" #define mqtt_pass "ChrisBue_geheim_Blog_mqtt_pass" #define MEASURMENTS 12 // Measurement for calc avg value // #define DEEPSLEEP 10000000 // 10 sek #define DEEPSLEEP 30 * 60000000 // 30 min const char* topic = "ChrisBue/Blog/oil_tank"; #define rxPin 13 // D6 // send //rxPin // trigger #define txPin 12 // D7 // receive //txPin // echo const int analogInPin = A0; // ESP8266 Analog Pin ADC0 = A0 AsyncMqttClient mqttClient; Ticker mqttReconnectTimer; WiFiEventHandler wifiConnectHandler; WiFiEventHandler wifiDisconnectHandler; Ticker wifiReconnectTimer; SoftwareSerial jsnSerial; void connectToWifi() { delay(10); Serial.print("Connecting to "); Serial.print(wifi_ssid); WiFi.begin(wifi_ssid, wifi_password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } } void onWifiConnect(const WiFiEventStationModeGotIP& event) { Serial.print("IP-Adresse: "); Serial.println(WiFi.localIP()); Serial.print("MAC-Adresse: "); Serial.println(WiFi.macAddress()); Serial.print("Hostname: "); Serial.println(WiFi.getHostname()); connectToMqtt(); } void connectToMqtt() { Serial.println("Connecting to MQTT..."); mqttClient.connect(); } void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { Serial.println("Disconnected from Wi-Fi."); mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while wifiReconnectTimer.once(2, connectToWifi);// reconnecting to Wi-Fi } void onMqttConnect(bool sessionPresent) { Serial.println("Connected to MQTT."); Serial.print("Session present: "); Serial.println(sessionPresent); } void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { Serial.println("Disconnected from MQTT."); if (WiFi.isConnected()) { mqttReconnectTimer.once(2, connectToMqtt); } } void onMqttSubscribe(uint16_t packetId, uint8_t qos) { Serial.println("Subscribe acknowledged."); Serial.print(" packetId: "); Serial.println(packetId); Serial.print(" qos: "); Serial.println(qos); } void onMqttUnsubscribe(uint16_t packetId) { Serial.println("Unsubscribe acknowledged."); Serial.print(" packetId: "); Serial.println(packetId); } void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { Serial.println("Publish received."); Serial.print(" topic: "); Serial.println(topic); Serial.print(" qos: "); Serial.println(properties.qos); Serial.print(" dup: "); Serial.println(properties.dup); Serial.print(" retain: "); Serial.println(properties.retain); Serial.print(" len: "); Serial.println(len); Serial.print(" index: "); Serial.println(index); Serial.print(" total: "); Serial.println(total); } void onMqttPublish(uint16_t packetId) { Serial.println("Publish acknowledged."); Serial.println("DeepSleep"); Serial.println(""); ESP.deepSleep(DEEPSLEEP); } void setup() { pinMode(rxPin, INPUT); pinMode(txPin, OUTPUT); Serial.begin(115200); jsnSerial.begin(9600, SWSERIAL_8N1, rxPin, txPin, false); Serial.println(); Serial.println(); wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); mqttClient.onConnect(onMqttConnect); mqttClient.onDisconnect(onMqttDisconnect); mqttClient.onSubscribe(onMqttSubscribe); mqttClient.onUnsubscribe(onMqttUnsubscribe); mqttClient.onMessage(onMqttMessage); mqttClient.onPublish(onMqttPublish); mqttClient.setServer(mqtt_host, mqtt_port); mqttClient.setCredentials(mqtt_user, mqtt_pass); connectToWifi(); int value = getMeasurment(); float sensorVolt = analogRead(analogInPin) * 0.0077; String messages = String("{ \"volt\" : \"") + String( sensorVolt ) + String("\", \"level\" : \"") + String( value ) + String( "\" }" ); // Publish an MQTT message on topic uint16_t packetIdPub1 = mqttClient.publish(topic, 1, true, String( messages ).c_str() ); Serial.printf("Publishing on topic %s at QoS 1, packetId: %i, value: %s ", topic, packetIdPub1, String( messages ).c_str() ); //Serial.printf("value: %s", String( messages ).c_str()); Serial.println(""); } int getMeasurment() { int sumValues = 0; int messurementsNotNull = 0; for(int i=0; i < MEASURMENTS; i++){ if(jsnSerial.available() > 0){ int inByte = jsnSerial.read(); } jsnSerial.write(0x01); delay(50); if(jsnSerial.available() > 0){ int dist = getDistance(); if(dist > 0){ sumValues = sumValues + dist; messurementsNotNull = messurementsNotNull + 1; } } } return sumValues / messurementsNotNull; } int getDistance(){ Serial.println("Start messung"); unsigned int distance; byte startByte, h_data, l_data, sum = 0; byte buf[4]; jsnSerial.readBytes(buf, 4); startByte = buf[0]; if(startByte != 255){ return -1; } h_data = buf[1]; l_data = buf[2]; sum = buf[3]; distance = (h_data<<8) + l_data; if((( h_data + l_data)&0xFF) -1 != sum){ Serial.printf("Invalid result: %d %d (%d)", buf[1], buf[2], buf[3]); Serial.println(); return -1; } Serial.print("Distance [mm]: "); Serial.println(distance); return distance; } void loop() { }
Die Ermittlung der Daten und das übertragen der Daten ist für mich ausreichend als Aufgabe für den ESP8266. Diese Aufgabe überlasse ich absichtlich dem Server. Der ESP8266 kann natürlich deutlich mehr. Es wäre auch möglich die Füllmengen direkt ausrechnen zu lassen oder die Daten über einen installierten Webserver direkt im Lan oder WAN anzuzeigen.
In meinem Fall wollte ich wenig energieraubende Aufgaben an den ESP8266 abgeben. Dieser ist schon mit WLAN und Ultraschallsensor beschäftigt. Nach erfolgreicher Messung und übertragung der Daten mittels MQTT geht der ESP8266 für 30 Minuten in den Deep Sleep um Energie zu sparen. Die Loop-Funktion bleibt in diesem Fall leer.
Zusammengebaut
Zusammengebaut und mit einem Batteriefach als Spannungsversorgung habe ich das Projekt bei mir im Heizraum aufgebaut. Bei der Anbringung des Sensors sollte die Beschreibung des Herstellers berücksichtigt werden. Es kann Sein, dass Reflektionen der Tankwände die Messwerte verfälschen oder unbrauchbar machen. In meinem Fall ist der Sensor z.B. für kurze Distanzen ungeeignet und misst erst ab einer Entfernung von 20 cm mit einer Genauigkeit von 1-2 cm. In meinem Fall aber ausreichend.
Resümee
Ich muss hier anmerken, dass der Öltank schon älter ist und auch diese Öffnung sowie die „Sensorbefestigung“ nicht optimal sind. Dennoch sind die Messergebnisse doch sehr beeindruckend.
Die Energieversorgung möchte ich noch einmal überarbeiten und auch ein dauerhaft geschlossenes Gehäuse wäre sinnvoll.
Ein weiteres Projekt, bei dem der Ultraschallsensor AJ-SR04M und ein ESP8266 zum Einsatz kommen habe ich hier verlinkt. Beide Projekte sind vergleichbar. Jedes hatte aber kleine, unterschiedliche Herausforderungen.
Achtung: Ich weise hier ausdrücklich darauf hin, dass der Nachbau und/oder Einbau auf eigene Gefahr geschieht. Ich weise ebenfalls ausdrücklich darauf hin, dass ich für durch den Nach- und/oder Einbau der beschriebenen Projekte entstandene Personen oder Sachschäden keine Haftung übernehme.