ESP32 Wetterstation

IoT-Test-Projekt: Wetterstation mit Microcontrollerboard ESP32 und Sensor BME280, LDR und I2C-Display, programmiert in Micropython. Die Datenerfassung erfolgt alle 5 Minuten und wird in Echtzeit per WLAN an IoT-Plattform ThingSpeak.com übertragen.


Überblick

Als sporadischer Hobbyprogrammierer habe ich in vielen Jahren mit verschiedensten Programmiersprachen herumexperimentiert und war nie so recht zufrieden. Ich suchte nach einer effektiveren Alternative. Insbesondere für den Unterricht im Fach Informatik. Und so stieß ich auf die Programmiersprache Python und war von Beginn an begeistert. Erste Erfahrungen bestätigten die Versprechungen aus dem Internet. Ich konnte mich relativ schnell einarbeiten und hatte nur geringe Verständnisprobleme.

Folgerichtig gelangte ich durch Erfahrungen mit Mikrocontrollern und als Hardwarebastler zu Mikropython. Mit dem ESP32 der chinesischen Firma espressif fand ich eine kostengünstige Einstiegsvariante. Leistungsstark, WLAN- und Bluetooth, jede Menge Anschlüsse, reichlich Speicher und Programmierung mit Mikropython machen ihn zum idealen Basteltyp.

Einen praktisch sinnvollen Einstieg bietet eine Wetterstation. Sensoren und Display sind schnell am ESP32 angeschlossen. Der Rest ist je nach Anspruch, Komfort und vorhandener Zeit eine Frage der Programmierung. Für wichtige Wetterdaten bietet der Sensor BME280 bereits Temperatur, Luftdruck und Luftfeuchte. Rein aus Interesse habe ich noch einen LDR zur Messung der Lichtintensität angehängt. Der ESP32 liest die Werte der Sensoren aus und überträgt diese per WLAN direkt ins Internet. Hier speziell zur IoT-Plattform (Internet of Things) ThingSpeak. Diese ist für Privatanwender kostenfrei. Parallel dazu stellt der ESP32 eine einfache Wetterwebsite im Heimnetz bereit. Das kleine Display dient der direkten Anzeigemöglichkeit von Wetterdaten und/oder Uhrzeit vor Ort an der Station.


Merkmale

  • Mikrocontroller ESP32
  • 1,3 Zoll OLED I2C 128×64 Pixel Display über I2C-Bus
  • Sensor BME280 für Temperatur, Luftdruck und Luftfeuchte über I2C-Bus
  • KY-018 Foto LDR Widerstand Diode Photo Resistor Sensor
  • vier Taster: Anzeige Messwerte im Display, Anzeige Datum/Uhrzeit im Display, Programm-Selbsttest des ESP32 (WLAN, Sensoren, Datum, Zeit) und Resettaster
  • Anzeigedauer der Diaplayanzeige über RTC-Timer gesteuert
  • Taster per Pin-Interrupt gesteuert
  • Messwerterfassung zeitgesteuert per RTC-Timer Interrupt
  • Reset synchronisiert RTC-Timer des ESP32 via NTP
  • Website vom ESP32-Webserver im lokalen Heimnetz bereitgestellt

Schaltplan

Kern der Wetterstation ist ein ESP-WROOM-32 NodeMCU Modul WLAN WiFi Development Board. Der unmittelbaren Anzeige vor Ort am Gerät dient das 1,3 Zoll OLED 128 x 64 Pixel Display, angeschlossen über I2C-Bus, wie auch der BME280 Barometrischer Sensor für Temperatur, Luftfeuchtigkeit und Luftdruck. Helligkeitsmessungen erfolgen über einen LDR, angeschlossen an GPIO9 (G32, ADC_CH4). Weiterhin sind vier Schalter angeschlossen. Drei davon über Pull-Down Widerstande für verschiedene Anzeigen im Display. Ausgelöst durch Interrupt zeigen die zugehörigen Interruptroutinen für einige Sekunden die aktuellen Messwerte, Datum und Uhrzeit bzw. eine Selbsttest-Status von WLAN, Sensoren, Datum und Zeitvergleich zur NTP-Zeit. Der vierte Schalter löst im Problemfall einen RESET des ESP32 aus.


Platine, Gehäuse und Display


Beispiel Website ESP32-Webserver im heimischen WLAN



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 import network import urequests from machine import * # Pin, I2C, ADC import machine from time import sleep, sleep_ms import utime import sh1106 # Modul für I2C-Display SH1106, 128x64 Pixel import bme280 # Modul für Sensor Bosch BME280 import esp esp.osdebug(None) import gc gc.collect() try: import usocket as socket except: import socket try: import ustruct as struct except: import struct def web_page(): messwerte = sensorenLesen() ... return html def sensorenLesen(): messwerte = [] bme = bme280.BME280(i2c) temperature = 0 pressure = 0 humidity = 0 ldr = 0 sleep_ms(200) for i in range(3): bme.read() temperature = temperature + float(bme.temperature) pressure = pressure + bme.pressure humidity = humidity + bme.humidity ldrm = adc.read() ldr = ldr + ldrm sleep_ms(200) temperature = round(temperature / 3, 1) pressure = int(pressure / 3) / 100 humidity = round(humidity / 3, 1) ldr = round(ldr / 3, 1) messwerte.append(str(temperature)) messwerte.append(str(pressure)) messwerte.append(str(humidity)) messwerte.append(str(ldr)) return messwerte def time(): NTP_QUERY = bytearray(48) NTP_QUERY[0] = 0x1B addr = socket.getaddrinfo(host, 123)[0][-1] s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.settimeout(1) res = s.sendto(NTP_QUERY, addr) msg = s.recv(48) except: displayMeldung('Error: Time sync') else: displayMeldung('Time sync * Okay') finally: s.close() val = struct.unpack('!I', msg[40:44])[0] return val - NTP_DELTA def timeCheck(): ... # ähnlich time() # utime.localtime() liefert UTC-Zeit (as if it was .gmtime()) def settime(): t = time() tm = utime.localtime(t) machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0)) def datumString(s): mon = str(s[1]) if s[1] < 10: mon = '0' + str(s[1]) day = str(s[2]) if s[2] < 10: day = '0' + str(s[2]) return day + '.' + mon + '.' + str(s[0]) def zeitString(s): hou = str(s[3]) if s[3] < 10: hou = '0' + str(s[3]) minut = str(s[4]) if s[4] < 10: minut = '0' + str(s[4]) sek = str(s[5]) if s[5] < 10: sek = '0' + str(s[5]) return hou + ':' + minut + ':' + sek def zeitStringMEZ(s): ... # analog zeitString() def displayEinschaltenDatenAnzeigen(p25): messwerte = sensorenLesen() display.sleep(False) display.fill(0) display.text('Wetterdaten HOY', 0, 0, 1) display.hline(0, 12, 127, 1) display.text('Temp: ' + messwerte[0] + ' C', 0, 18, 1) display.text('Druc: ' + messwerte[1] + ' hPa', 0, 30, 1) display.text('AirH: ' + messwerte[2] + ' %', 0, 42, 1) display.text('Lumi: ' + messwerte[3], 0, 54, 1) display.show() timer1.init(period=25000, mode=machine.Timer.ONE_SHOT, callback=displayAbschalten) def displayEinschaltenZeitAnzeigen(p26): display.sleep(False) display.fill(0) display.text('Datum/Uhrzeit:', 0, 0, 1) display.hline(0, 12, 127, 1) display.text('NTP: ' + zeitString(rtc.datetime()), 0, 18, 1) display.text('UTC: ' + zeitString(utime.localtime()), 0, 30, 1) display.text('Dat: ' + datumString(utime.localtime()), 0, 42, 1) display.text('MEZ: ' + zeitStringMEZ(utime.localtime()), 0, 54, 1) display.show() timer1.init(period=25000, mode=machine.Timer.ONE_SHOT, callback=displayAbschalten) def displayEinschaltenStatusAnzeigen(p27): ... # diverse Tests der Hardware und Anzeige des aktuellen Status def displayAbschalten(t): display.sleep(True) def displayMeldung(text): display.sleep(False) display.fill(0) display.text('Meldung:', 0, 0, 1) display.hline(0, 12, 127, 1) display.text(text, 0, 30, 1) display.show() sleep(2) display.sleep(True) def uploadThingSpeak(t): ... # Upload zur Website ThingSpeak.com

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 # Hauptprogramm # ESP32: I2C, display-SH1106 und ADC aktivieren global display i2c = I2C(scl=Pin(22), sda=Pin(23), freq=400000) display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 60) displayMeldung('Display * Okay'# WLAN initialisieren ssid = 'xxxxx' password = 'yyyyy' api_key = 'zzzzz' station = network.WLAN(network.STA_IF) station.active(True) station.connect(ssid, password) while station.isconnected() == False: pass if station.isconnected(): displayMeldung('connected * Okay') else: displayMeldung('WLAN conn. fail') rtc = machine.RTC() # Zeitkorrektur (date(2000, 1, 1) - date(1900, 1, 1)).days * 24*60*60 # und NTP host NTP_DELTA = 3155673600 host = "pool.ntp.org" settime() # externe Interrupts an Pin 25, 26 und 27 mit Pull-Down Widerstand # für Einschalten des Display zur Anzeige der Messwerte p25 = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_DOWN) p25.irq(trigger=machine.Pin.IRQ_RISING, handler=displayEinschaltenDatenAnzeigen) # für Einschalten des Display für Anzeige Datum, Zeit p26 = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_DOWN) p26.irq(trigger=machine.Pin.IRQ_RISING, handler=displayEinschaltenZeitAnzeigen) # Taster Display für Statusanzeige/Selbsttest p27 = machine.Pin(27, machine.Pin.IN, machine.Pin.PULL_DOWN) p27.irq(trigger=machine.Pin.IRQ_RISING, handler=displayEinschaltenStatusAnzeigen) # Timer1 für Interrupt zum Abschalten des Display timer1 = machine.Timer(1) # Timer2 für Upload zu ThingSpeak alle 10 min timer2 = machine.Timer(2) timer2.init(period=600000, mode=machine.Timer.PERIODIC, callback=uploadThingSpeak) # 600 millisek = 10 min # Website einbinden s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', 80)) s.listen(5) displayAbschalten(1) sensorenLesen() uploadThingSpeak(1) while True: state = machine.disable_irq() machine.enable_irq(state) sleep_ms(500) conn, addr = s.accept() # auskommentiert: print('Got a connection from %s' % str(addr)) request = conn.recv(1024) # auskommentiert: print('Content = %s' % str(request)) response = web_page() conn.sendall(response) conn.close() sleep(2)

Diagramme von der Internetplattform ThingSpeak

Temperatur in °C

Temperatur in °C

Luftdruck in hPa

Luftdruck in hPa

Luftfeuchte in %

Luftfeuchte in %

Helligkeit einheitenlos

Je größer der Wert umso heller ist es. Der Wertebereich reicht von 0 bis 4095. Null heißt eigentlich dunkel. Leider ist die Auflösung des Sensors im schwachen Licht nicht besonders gut. Und Null wird bereits erreicht deutlich bevor es wirklich dunkel ist.