Willkommen zu meinem heutigen Beitrag auf meinem Technikblog! Vor kurzem habe ich einen interessanten Kommentar von einem aufmerksamen Leser erhalten. Er stellte die Frage, wie man mehrere Sensordaten, insbesondere Temperaturwerte, auf einer Webseite darstellen kann. Eine faszinierende Idee, die mich dazu inspiriert hat, diesen Beitrag zu verfassen. In diesem Artikel werde ich dir zeigen, wie du genau das mit dem DS18B20-Sensor und dem Raspberry Pi Pico W erreichen kannst. Zusammen werden wir lernen, wie man die Sensoren anschließt, die Daten ausliest und sie auf einer Webseite in ansprechender Weise präsentiert.
Im Beitrag Raspberry Pi Pico W – anzeigen von Sensordaten auf einer Webseite habe ich dir bereits erläutert, wie man die Sensordaten eines BME280 Sensors auf einer Webseite visualisiert. Hier möchte ich teile des Quellcodes wiederverwenden und dir aufzeigen, wie man die Sensordaten von mehreren DS18B20 Sensoren am Pi Pico auf einer Webseite anzeigen kannst.
Inhaltsverzeichnis
- Aufbau der Schaltung am Raspberry Pi Pico W
- Auslesen der Sensoren in Micropython
- Anzeigen der Sensorwerte von mehreren DS18B20 am Pi Pico auf einer Webseite
- Mögliche Erweiterung – minimale, maximale, sowie die Durchschnittstemperatur anzeigen
- Download des Quellcodes für das Anzeigen der Sensorwerte von mehreren DS18B20 am Pi Pico auf einer Webseite
Aufbau der Schaltung am Raspberry Pi Pico W
Der Raspberry Pi Pico / Pico W hat 26 GPIOs (davon 6 analoge Pins) somit können wir eine Menge an Sensoren anschließen und auf der Seite anzeigen. In meinem Fall möchte ich 6 Temperatursensoren anschließen und benötige für die Schaltung:
- einen Raspberry Pi Pico W*,
- ein Micro-USB Datenkabel*,
- sechs Temperatursensoren DS18B20*,
- einen 4,7 kOhm Widerstand*,
- ein paar Breadboardkabel*, männlich-männlich, 10 cm,
- ein 830 Pin Breadboard*
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Die Abbildung verdeutlicht, dass die digitalen Pins der Temperatursensoren in Serie geschaltet sind und nur einen GPIO-Anschluss des Pi Pico verwenden (während die Spannungsversorgung parallel erfolgt).
Jeder Temperatursensor vom Typ DS18B20 hat eine eigene einzigartige ID über welche dieser referenziert werden kann und somit können wir quasi fast beliebig viele Sensoren hintereinander schalten. Wir müssen lediglich darauf achten, die maximale Stromaufnahme pro GPIO (16 mA) nicht zu überschreiten.
Auslesen der Sensoren in Micropython
Zunächst schreiben wir ein kleines Programm in Micropython um die Sensoren auszulesen.
#Import der benötigten Module #zum Zugriff auf den Sensor DS18B20 #und der Pins des Pi Pico import machine, onewire, ds18x20, time #der Sensor ist am GPIO16 angeschlossen sensorPin = machine.Pin(16) #initialisieren eines Objektes vom Typ DS18X20 ds18b20Sensor = ds18x20.DS18X20(onewire.OneWire(sensorPin)) #anlegen eines Feldes für die IDs der Sensoren roms = None def setup(): #Zugriff auf die Globale Variable roms global roms #auslesen der IDs der Sensoren roms = ds18b20Sensor.scan() #ausgeben der Anzahl der Sensoren auf #der seriellen Schnittstelle print('Anzahl gefundener Sensoren: ', len(roms)) def main(): #Zugriff auf die Globale Variable roms global roms #starten einer Endlosschleife, ... while True: #auslesen der Sensorwerte ds18b20Sensor.convert_temp() #nach dem aufruf der Funktion "convert_temp()" #soll man gem. Dokumentation 750ms warten time.sleep_ms(750) #erzeugen eines Indexes idx = 0 #Schleife über die gefundenen IDs / Sensoren for rom in roms: #Index um eins erhöhen idx = idx + 1 #Ausgeben der Temperatur, zusätzlich wird noch der Temperaturwert #auf 2 Stellen nach dem Komma gekürzt print("#", str(idx), " Temperatur:", round(ds18b20Sensor.read_temp(rom),2), "°C") #Pause von 5 Sekunden time.sleep(5) setup() main()
Wenn wir diesen Code auf dem Pi Pico / Pico W ausführen, dann sollten wir in der Konsole nun die Werte der Sensoren ablesen können.
Anzeigen der Sensorwerte von mehreren DS18B20 am Pi Pico auf einer Webseite
Im nächsten Schritt wollen wir diese Sensorwerte jetzt auf einer kleinen Webseite anzeigen lassen. Zusätzlich möchte ich einen Schritt weitergehen und ein Diagramm anzeigen lassen, in welchem die Sensordaten visualisiert werden.
Schritt 1 – Aufbau des Codes
Zunächst erzeugen wir uns zwei Funktionen
setup: Diese Funktion wird einmalig aufgerufen.
main: Diese Funktion beinhaltet den Ablauf des Programmes.
Wenn du bereits Erfahrung mit dem Arduino gesammelt hast, dann wird dir dieses nicht fremd sein.
Der Vorteil ist, dass man hier besser nachvollziehen kann, wann was gestartet wird.
def setup(): pass def main(): pass setup() main()
Schritt 2 – Aufbau der WiFi-Verbindung
Im ersten Schritt bauen wir die WiFi-Verbindung auf. Dazu benötigen wir die SSID sowie das Passwort für das lokale WiFi-Netzwerk. Diese beiden Werte legen wir uns in Felder im Code ab.
#Import der benötigten Module #für den Aufbau der WiFi-Verbindung import network import socket ssid = 'FRITZBox7590GI24' password = '22894580214767401850' #anlegen eines Feldes für die WiFi-Verbindung wlan = None #anlegen eines Feldes für die Socketverbindung s = None def doConnect(): global wlan, s wlan=network.WLAN(network.STA_IF) wlan.active(True) #Wenn die Verbindung nicht erstellt wurde, dann... if not wlan.isconnected(): #Aufbau der WiFi-Verbindung mit den Daten wlan.connect(ssid, password) #solange die Verbindung noch nicht hergestellt wurde #dann soll diese Schleife laufen und einen Punkt ausgeben while not wlan.isconnected(): #einen Punkt ausgeben ohne Zeilenumbruch! print(".", end="") #kleine Pause von 250 Millisekunden time.sleep(0.250) print("") #Wenn die WiFi-Verbindung erfolgreich aufgebaut wurde, #dann soll auf der seriellen Schnittstelle der Text ausgegeben werden print('Verbindung erfolgreich zu', ssid, 'aufgebaut!') status = wlan.ifconfig() print('IP-Adresse: ' + status[0]) addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.bind(addr) s.listen(1) def setup(): doConnect() def main(): pass setup() main()
Wenn wir den Code ausführen, dann wird beim Aufbau einer WiFi-Verbindung die IP-Adresse ausgegeben, welche wir im späteren Verlauf im Browser eingeben können, um die Webseite aufzurufen.
Schritt 3 – Auslesen der Sensordaten in einem Intervall
Wir müssen eine Endlosschleife starten, in welcher wir “lauschen”, ob sich Clients mit dem Pi Pico verbinden wollen. Zeitgleich müssen wir aber auch die Sensordaten in einem Intervall auslesen.
Wir können hier aber nicht die Funktion sleep aus dem Modul time verwenden, denn dann legt der Mikrocontroller eine Pause ein und in dieser Zeit kann sich kein neuer Client verbinden.
Meine Lösung ist, die Sekunden seit dem 01.0.1.1970 auszulesen und zu prüfen ob seit dem letzen Lesevorgang eine Zeit X zbsp. 30 Sekunden vergangen ist.
#Import der benötigten Module #zum Zugriff auf den Sensor DS18B20 #und der Pins des Pi Pico import machine, onewire, ds18x20, time #Import der benötigten Module #für den Aufbau der WiFi-Verbindung import network import socket ssid = 'xxx' password = 'yyy' #der Sensor ist am GPIO16 angeschlossen sensorPin = machine.Pin(16) #initialisieren eines Objektes vom Typ DS18X20 ds18b20Sensor = ds18x20.DS18X20(onewire.OneWire(sensorPin)) #anlegen eines Feldes für die IDs der Sensoren roms = None #anlegen eines Feldes für die WiFi-Verbindung wlan = None s = None data = [] actualTime = 0 lastReadSensordata = 0 readSensordataIntervall = 30 def doConnect(): global wlan, s wlan=network.WLAN(network.STA_IF) wlan.active(True) #Wenn die Verbindung nicht erstellt wurde, dann... if not wlan.isconnected(): #Aufbau der WiFi-Verbindung mit den Daten wlan.connect(ssid, password) #solange die Verbindung noch nicht hergestellt wurde #dann soll diese Schleife laufen und einen Punkt ausgeben while not wlan.isconnected(): #einen Punkt ausgeben ohne Zeilenumbruch! print(".", end="") #kleine Pause von 250 Millisekunden time.sleep(0.250) print("") #Wenn die WiFi-Verbindung erfolgreich aufgebaut wurde, #dann soll auf der seriellen Schnittstelle der Text ausgegeben werden print('Verbindung erfolgreich aufgebaut!') status = wlan.ifconfig() print('IP-Adresse: ' + status[0]) addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.bind(addr) s.listen(1) def readSensordata(): result = [] #auslesen der Sensorwerte ds18b20Sensor.convert_temp() #nach dem aufruf der Funktion "convert_temp()" #soll man gem. Dokumentation 750ms warten time.sleep_ms(750) #erzeugen eines Indexes #Schleife über die gefundenen IDs / Sensoren for rom in roms: result.append(round(ds18b20Sensor.read_temp(rom),2)) return result def setup(): #Zugriff auf die globalen Variablen roms, wifi global roms #auslesen der IDs der Sensoren roms = ds18b20Sensor.scan() #ausgeben der Anzahl der Sensoren auf #der seriellen Schnittstelle print('Anzahl gefundener Sensoren: ', len(roms)) doConnect() def main(): #Zugriff auf die Globale Variable roms global roms, data, lastReadSensordata #starten einer Endlosschleife, ... while True: #nur alle 30 Sekunden neue Sensordaten lesen actualTime = time.time() if (lastReadSensordata+readSensordataIntervall)< actualTime: lastReadSensordata = actualTime data = readSensordata() #ausgeben der Daten auf der seriellen Schnittstelle print(data) setup() main()
Wenn wir nun auf die Konsole schauen, dann sehen wir, dass im angegebenen Intervall die Sensordaten gelesen und ausgegeben werden.
Schritt 4 – Anzeigen der Webseite mit den Sensordaten
Nachdem wir nun die Sensordaten in eine Liste ausgelesen haben wollen wir diese auf eine Webseite anzeigen. Dazu schreiben wir eine kleine Webseite mit ebenso ein wenig CSS für das Styling.
<!DOCTYPE html> <html lang="de"> <head> <meta http-equiv="refresh" content="35"> </head> <body> <div style="margin:0px auto; border: 1px solid gray; box-shadow:5px 5px 5px gray;width:60%;padding:25px;text-align:center;"> <h1>Sensordaten</h1> <center> {dataTable} </center> </div> </body> </html>
In dem HTML Code setzen wir einen Platzhalter {dataTable} an der Stelle ein, wo wir die Tabelle einfügen wollen. Diese Datei speichern wir nun auf dem Pi Pico als Datei “website.html”.
Diese Datei lesen wir nun in eine Variable ein und ersetzen mit der Funktion format den Platzhalter mit den Daten unserer Tabelle, welche wir zuvor in eine HTML-Tabelle umgewandelt haben.
#Import der benötigten Module #zum Zugriff auf den Sensor DS18B20 #und der Pins des Pi Pico import machine, onewire, ds18x20, time #Import der benötigten Module #für den Aufbau der WiFi-Verbindung import network import socket ssid = 'xxx' password = 'yyyy' #der Sensor ist am GPIO16 angeschlossen sensorPin = machine.Pin(16) #initialisieren eines Objektes vom Typ DS18X20 ds18b20Sensor = ds18x20.DS18X20(onewire.OneWire(sensorPin)) #anlegen eines Feldes für die IDs der Sensoren roms = None #anlegen eines Feldes für die WiFi-Verbindung wlan = None s = None data = [] actualTime = 0 lastReadSensordata = 0 readSensordataIntervall = 30 def doConnect(): global wlan, s wlan=network.WLAN(network.STA_IF) wlan.active(True) #Wenn die Verbindung nicht erstellt wurde, dann... if not wlan.isconnected(): #Aufbau der WiFi-Verbindung mit den Daten wlan.connect(ssid, password) #solange die Verbindung noch nicht hergestellt wurde #dann soll diese Schleife laufen und einen Punkt ausgeben while not wlan.isconnected(): #einen Punkt ausgeben ohne Zeilenumbruch! print(".", end="") #kleine Pause von 250 Millisekunden time.sleep(0.250) print("") #Wenn die WiFi-Verbindung erfolgreich aufgebaut wurde, #dann soll auf der seriellen Schnittstelle der Text ausgegeben werden print('Verbindung erfolgreich aufgebaut!') status = wlan.ifconfig() print('IP-Adresse: ' + status[0]) addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.bind(addr) s.listen(1) def readSensordata(): result = [] #auslesen der Sensorwerte ds18b20Sensor.convert_temp() #nach dem aufruf der Funktion "convert_temp()" #soll man gem. Dokumentation 750ms warten time.sleep_ms(750) #erzeugen eines Indexes #Schleife über die gefundenen IDs / Sensoren for rom in roms: result.append(round(ds18b20Sensor.read_temp(rom),2)) return result def createHtmlTable(data): htmlTable = """ <table border='1' style='border-collapse: collapse;'> <thead> <tr> <th>Sensor</th> <th>Wert</th> </tr> </thead> <tbody> {bodyRows} </tbody> </table> """ idx = 0 rows = "" for value in data: idx = idx + 1 rows += "<tr><td>{sensor}</td><td>{wert}</td></tr>".format(sensor=str(idx), wert=str(value)+"°C") return htmlTable.format(bodyRows=rows) def getWebsite(dataTbl): with open('website.html', "r") as file: website = file.read() file.close() return website.format(dataTable=dataTbl) def deliverWebsite(website): global s cl, addr = s.accept() cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') cl.send(website) cl.close() def setup(): #Zugriff auf die globalen Variablen roms, wifi global roms #auslesen der IDs der Sensoren roms = ds18b20Sensor.scan() #ausgeben der Anzahl der Sensoren auf #der seriellen Schnittstelle print('Anzahl gefundener Sensoren: ', len(roms)) doConnect() def main(): #Zugriff auf die Globale Variable roms global roms, data, lastReadSensordata #starten einer Endlosschleife, ... while True: #nur alle 30 Sekunden neue Sensordaten lesen actualTime = time.time() if (lastReadSensordata+readSensordataIntervall)< actualTime: lastReadSensordata = actualTime data = readSensordata() htmlTable = createHtmlTable(data) website = getWebsite(htmlTable) deliverWebsite(website) setup() main()
Damit haben wir nun die Hauptaufgabe erledigt, es werden die Sensordaten der angeschlossenen DS18B20 auf der Webseite in tabellarischer Form angezeigt.
Mögliche Erweiterung – minimale, maximale, sowie die Durchschnittstemperatur anzeigen
Die eigentliche Aufgabe war es, die aktuellen Sensordaten auf einer Webseite anzuzeigen. Wir können aber noch ein Feature dazu programmieren. Wenn wir schon die Temperatur messen, können wir zusätzlich noch die drei Werte berechnen und in der Tabelle speichern.
Jedoch müssen wir dazu die Sensordaten speichern. Ich nutze hier das Datenformat JSON, zum einen, weil dieses sehr einfach zum Lesen und die Implementierung in Python sehr gut gelungen ist.
{ "sensors" :[ {"idx":1, "values":[], "max":0, "min":0,"average":0}, {"idx":2, "values":[], "max":0, "min":0,"average":0}, {"idx":3, "values":[], "max":0, "min":0,"average":0}, {"idx":4, "values":[], "max":0, "min":0,"average":0}, {"idx":5, "values":[], "max":0, "min":0,"average":0}, {"idx":6, "values":[], "max":0, "min":0,"average":0} ] }
Diese Datenstruktur legen wir in der Datei data.json auf dem Pi Pico ab, welche wir später im Code lesen und beschreiben werden.
Zusätzlich habe ich noch etwas CSS implementiert und in der Datei styles.css abgelegt. Dieses dient lediglich für das Styling der Tabelle.
Die JSON-Datei müssen wir zunächst in unser Dictionary einlesen, um die Werte daraus zu lesen und zu ändern.
#Feld für die gespeicherte Daten vom Pi Pico storedData = {} def readJsonToData(): global storedData file = open('data.json') storedData = json.load(file) print(storedData) file.close()
Natürlich müssen wir auch die Daten wieder in die JSON-Datei schreiben.
def storeDataToJson(): global storedData with open("data.json", "w") as outfile: json.dump(storedData, outfile)
Das lesen der Daten erfolgt initial in der Funktion setup und das speichern jeweils nach einem erfolgreichen laden und verarbeiten der Daten.
Den neuen Temperaturwert fügen wir der Liste hinzu, jedoch speichern wir nur die letzten 10 Werte in der Liste. (Damit wird vorgebeugt, dass der Speicher des Pi Pico irgendwann voll läuft.)
def appendDataToJson(tempValue, idx): tempList = storedData["sensors"][idx]["values"] if len(tempList)>=10: tempList.insert(0, tempValue) else: tempList.append(tempValue) maxTemp = storedData["sensors"][idx]["max"] maxTemp = max(maxTemp, tempValue) storedData["sensors"][idx]["max"] = maxTemp minTemp = storedData["sensors"][idx]["min"] minTemp = min(minTemp, tempValue) storedData["sensors"][idx]["min"] = minTemp averageTemp = storedData["sensors"][idx]["average"] averageTemp = round(sum(tempList) / len(tempList),2) storedData["sensors"][idx]["average"] = averageTemp
Zusätzlich müssen wir noch die Funktion createHtmlTable erweitern, damit diese noch zusätzlich die drei neuen Werte aufnimmt.
def createHtmlTable(data): htmlTable = """ <table border='1' style='border-collapse: collapse;'> <thead> <tr> <th>Sensor</th> <th>Wert</th> <th>min</th> <th>max</th> <th>durchschnitt</th> </tr> </thead> <tbody> {bodyRows} </tbody> </table> """ idx = 0 rows = "" for value in data: idx = idx + 1 minValue = storedData["sensors"][idx-1]["min"] maxValue = storedData["sensors"][idx-1]["max"] average = storedData["sensors"][idx-1]["average"] rows += "<tr><td>{sensor}</td><td>{wert}</td><td>{minValue}</td><td>{maxValue}</td><td>{average}</td></tr>".format( sensor=str(idx), wert=str(value)+" °C", minValue=str(minValue)+" °C", maxValue=str(maxValue)+" °C", average=str(average)+" °C") return htmlTable.format(bodyRows=rows)
Download des Quellcodes für das Anzeigen der Sensorwerte von mehreren DS18B20 am Pi Pico auf einer Webseite
Hier nun der Download des gesamten Quellcodes inkl. der Erweiterung um die Min, Max und durchschnittstemperatur.
Hallo Stefan
Die “Hauptaufgabe” klappt auch hier bei mir. Dank an dich.
Allerdings musste ich mich an das richtige “Pico-Handling” erinnern, dass die “website.html” nicht einfach per “drag and drop” auf den Pico kopiert werden kann, sondern sie muss aus der IDE (hier “Thonny”) “ordentlich” auf den Pico geschrieben werden.
Probleme habe ich noch mit der json-erweiterung. Wie und wo müssen diese Module im Hauptprogramm platziert werden , einfach hineinkopieren?
Der Download des Gesmtprogramms klappt bei mir nicht, es kommt nur “RaspberryPiPicoW_multiple_DS18B20.zip” mit einem leeren Verzeichnis an. Was mache ich falsch?
Hallo,
der Beitrag ist ja super aufgebaut, gut dokumentiert und es klappt alles wie beschrieben.
Einfach top!
Vielen Dank für diese tolle Anleitung.
Mit bestem Gruß aus der Lüneburger-Heide
Robert Waldow