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.