In diesem Tutorial haben wir mit dem Raspberry Pi eine Smart Home Zentrale aufgebaut. Wir können MQTT-Botschaften empfangen, sie in einer Datenbank speichern und dann in einem Grafana Dashboard darstellen. In diesem Tutorial wollen wir uns dem Erfassen der Daten widmen.
Als Hirn der Wetterstation kommt ein ESP32 (NodeMCU) zum Einsatz. Er ist günstig, leicht verfügbar und gut dokumentiert. Um den Entwicklungsprozess noch weiter zu beschleunigen, nutzen wir als Programmiersprache MicroPython. MicroPython baut auf Python 3.5 auf und bietet bereits einige Bibliotheken, die uns das Leben erleichtern. Sogar pip funktioniert auf einem ESP32! Also los geht’s!
Zur Vorbereitung sollte MicroPython bereits auf dem ESP32 installiert sein. Wie das funktioniert, habe ich hier gezeigt.
Ich fange mit der Verbindung zum Rasperry Pi an. So kann ich später meine Messergebnisse direkt sehen und bereits jetzt prüfen, ob meine WLAN-Verbindung überhaupt bis zum späteren Einsatzort reicht.
Unser MicroPython-Skript schreiben wir in die Datei main.py, die wir dann auf den ESP32 kopieren. Diese wird dann automatisch nach dem Start ausgeführt.
Wir beginnen damit, das WLAN einzuschalten und uns mit dem Netzwerk zu verbinden:
import network import time wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect("[SSID]", "[Passwort]") # warten, bis wlan verbunden ist while not wlan.isconnected(): time.sleep(1) print(wlan.ifconfig())
Diese Datei kopieren wir dann auf den ESP32 (wie genau das geht, habe ich in diesem Tutorial erklärt). Die Ausgabe sollte dann ungefähr so aussehen:
MPY: soft reboot ('192.168.178.59', '255.255.255.0', '192.168.178.1', '192.168.178.1') MicroPython v1.13 on 2020-09-02; ESP32 module with ESP32 Type "help()" for more information. >>>
In unserem Router sollte der ESP32 (Name: “espressif”) jetzt auch zu sehen sein. Weiter geht es mit der MQTT-Verbindung. Dazu erst einmal die Bibliotheken micropython-umqtt.simple und micropython-umqtt.robust installieren. Dafür tippen wir in die REPL folgendes ein:
import upip upip.install('micropython-umqtt.simple') upip.install('micropython-umqtt.robust') from umqtt.robust import MQTTClient
Wenn hier keine Fehlermeldungen erscheinen, sind die Bibliotheken installiert und der MQTTClient kann importiert werden. Die Bibliothek ist dann dauerhaft verfügbar. Damit können wir die main.py erweitern:
import machine from umqtt.robust import MQTTClient import ubinascii client = MQTTClient(ubinascii.hexlify(machine.unique_id()), "[MQTT-Broker-IP]", "[MQTT-Port]")
Jetzt sind wir mit dem MQTT-Broker verbunden und können in einer Endlosschleife Daten senden. Um das einfach mal zu testen, senden wir die WLAN-Signalstärke (RSSI, Received Signal Strength Indicator).
import ujson while True: data = { "rssi": wlan.status("rssi") } client.publish("iot/werkstatt/wetter", ujson.dumps(data))
Jetzt nur noch in Telegraf (telegraf.conf) das MQTT-Topic hinzufügen:
[[inputs.mqtt_consumer]] topics = [ "test/#", "iot/" ]
Und die Docker Container neu starten:
pi@raspberrypi:~/docker $ sudo docker-compose down pi@raspberrypi:~/docker $ sudo docker-compose up -d
Eventuell müssen wir jetzt auch noch einmal den ESP32 neu starten, weil Mosquitto durch den Neustart der Docker Container kurz weg war. Jetzt können wir uns in Grafana auch schon ein Dashboard erstellen und als erstes Signal die WLAN-Signalstärke anzeigen.
Hier nochmal die main.py, ein wenig umsortiert:
import network import time import machine from umqtt.robust import MQTTClient import ubinascii import ujson wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect("[SSID]", "[Passwort]") while not wlan.isconnected(): time.sleep(1) print(wlan.ifconfig()) client = MQTTClient(ubinascii.hexlify(machine.unique_id()), "[MQTT-Broker-IP]", "[MQTT-Port]") client.connect() while True: data = { "rssi": wlan.status("rssi") } client.publish("iot/werkstatt/wetter", ujson.dumps(data))
Okay, jetzt wird gelötet. Ich habe folgende Sensoren verwendet:
Einiges davon ist wahrscheinlich mehr Spielerei als wirklich wissenschaftliches Arbeiten, aber man muss ja irgendwie in das Thema reinkommen. Wenn euch das Thema interessiert, könnt ihr tiefer einsteigen. Ich werde im Laufe der Zeit noch einige Verbesserungen für umsetzen, aber lasst uns einfach anfangen und dann komplexer werden.
Also, was brauchen wir:
So, diese Überlegungen müssen jetzt in einer Schaltung umgesetzt werden. Ich verwende hierfür mein SolidCircuit-HV3. Wenn ihr dieses Projekt auch umsetzen wollt, wäre ich euch sehr dankbar, wenn ihr euch auch eine dieser Platinen kauft. Sie sind leider nicht ganz billig, nehmen einem aber einiges an Arbeit ab. Im Lieferumfang sind ein wasserdichtes Gehäuse, ein Netzteil (ihr braucht also nur 230V anklemmen), einige JST-XH-Stecker, damit die Kabel zu den Sensoren nicht angelötet werden müssen, und 2 Relais. Die Relais brauchen wir in diesem Projekt nicht, aber falls ihr später noch z.B. eine Lampe in der Nähe der Wetterstation per Smartphone schalten wollt, bräuchtet ihr kein weiteres Gerät. Also, überlegt es euch, ihr würdet mich damit sehr unterstützen. Aber jetzt genug Werbung. Los geht’s!
Hier der Schaltplan und ein paar Fotos von der Platine. Ich hoffe, es hilft euch beim nachbauen.
Wenn alles zusammengelötet ist, kommt die Programmierung. Wie gesagt, hier ist alles vereinfacht, ich weise aber auf die Fallstricke hin. In weiteren Tutorials erkläre ich nach und nach, wie ich diese Probleme gelöst habe. Aber erstmal die einfache Variante. Bevor wir in die Endlosschleife gehen, müssen wir die Sensoren initialisieren. Für die I2C-Sensoren gibt es Bibliotheken, die uns das Abfragen erleichtern. Ich habe diese für den BME280 und diese für den BH1750 verwendet.
Die Analogwerte für die Solarzelle, Windrichtungssensor und Mikrofon werden direkt über die ADC-Wandler des ESP32 eingelesen.
Das Zählen der Impulse setzen wir Interrupts. Diese werden ausgeführt, sobald sich der Zustand eines Pins ändert. Innerhalb dieser Interrupts erhöhen wir einfach die entsprechenden Zähler.
import bme import BH1750 i2c = machine.I2C(0, scl=machine.Pin(22), sda=machine.Pin(21)) bme = bme280.BME280(i2c=i2c) bh = BH1750.BH1750(i2c) adc_sol = machine.ADC(machine.Pin(34)) adc_vol = machine.ADC(machine.Pin(32)) adc_wind = machine.ADC(machine.Pin(35)) wind_speed = 0 rain = 0 def callback(pin): global wind_speed global rain if str(pin) == "Pin(18)": wind_speed += 1 elif str(pin) == "Pin(19)": rain += 1 pin_wind_speed = machine.Pin(18, machine.Pin.IN) pin_wind_speed.irq(trigger=machine.Pin.IRQ_RISING, handler=callback) pin_rain = machine.Pin(19, machine.Pin.IN) pin_rain.irq(trigger=machine.Pin.IRQ_RISING, handler=callback)
Für die Windrichtung brauchen wir noch eine Übersetzungsfunktion:
def get_wind_dir(adc): wind_dir_dict = { 1030: 0, 360: 22.5, 602: 45, 120: 67.5, 225: 90, 185: 112.5, 1749: 135, 1520: 157.5, 3083: 180, 2730: 202.5, 3585: 225, 3270: 247.5, 3980: 270, 2310: 292.5, 2433: 315, 849: 337.5, } for key in wind_dir_dict: if key-20 > adc > key+20: return wind_dir_dict[key]
Ok, die Sensoren sind initialisiert, weiter geht es mit dem auslesen. Das Mikrofon würde ich an dieser Stelle einmal überspringen. Das behandle ich in einem anderen Tutorial gesondert.
In der Endlosschleife können wir die Sensoren dann auslesen:
while True: bme_data = bme.read_compensated_data() v_sol = adc_sol.read()/4096.0*3.6 # Spannung der Solarzelle in V i_sol = v_sol/22.0 # Strom durch den Widerstand p_sol = v_sol*i_sol # Leistung der Solarzelle data = { "temperature": bme_data[0]/100, "humidity": bme_data[2]/1000, "pressure": bme_data[1]/256, "luminance": bh.luminance(BH1750.BH1750.ONCE_HIRES_1), "wind_dir" : get_wind_dir(adc_wind.read()), "wind_dir_raw" : adc_wind.read(), "wind_speed": wind_speed, "rain": rain, "solar": p_sol, "solar_raw": adc_sol.read(), "rssi": wlan.status("rssi") } client.publish("iot/werkstatt/wetter", ujson.dumps(data))
Damit sollte die Wetterstation laufen. Wie bereits erwähnt, einige Probleme, speziell das Prellen der Reed-Kontakte, bestehen noch. Ich beschreibe in einem weiteren Tutorial, wie man damit umgehen kann.
Eydam-Prototyping
Saccasner Straße 19
03096 Schmogrow-Fehrow
Germany