Einfache HTML-Seite mit Werten von Sensor in Python erstellen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Hallo,

ich möchte mit einem Raspberry Pi Pico W eine Art Webserver erstellen, auf dem ne primitive HTML-Seite angezeigt wird. Diese Seite aktualisiert nach einer gewissen Zeit immer mal wieder die Werte (Temperatur / Luftfeuchtigkeit)

Ich habe mir da einige Codeschnipsel von Google geklaut und versucht diese für mein Vorhaben zu "optimieren".

Die HTML-Seite im Code wird angezeigt, wenn ich diese öffne.

Lasse ich das Programm laufen, wird eine Verbindung 192.168.2.XXX hergestellt, sobald ich die IP in die Adresszeile des Browsers eingebe, laufen mehrere gleiche IP Adressen mit verschiedenen Anschlüssen durch und letzendlich wird disconnected, ohne das ein Fehler angezeigt wird.

Die HTML-Seite wird auch nicht angezeigt. KAnn sich jemand den kopierten Code mal anschauen und mir nen Tip geben, waran das liegt.

Code: Alles auswählen

# Bibliotheken laden
import rp2
import network
import socket
from machine import Pin
from utime import sleep
from dht import DHT22

# Initialisierung GPIO und DHT22
dht22_sensor = DHT22(Pin(15, Pin.IN, Pin.PULL_UP))
rp2.country('DE')

ssid = 'xxxxx'
password = 'xxxxxxxx'


wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><head><title>Pi Pico W - BME280 Sensordaten</title><style>h1{text-align:center;font-size:28px;}fieldset{width:340px;margin:0 auto;}label {width:190px;display:inline-block;}input[type=text]{width:120px;}input[type=text], label{font-size:20px;}legend{font-size:26px;}</style></head><body><h1>Raspberry Pi Pico W - Sensordaten vom BME280</h1><fieldset><legend>Sensordaten</legend><label for="temperaturText">Temperatur:&nbsp;</label><input type="text" value="%s °C" id="temperaturText" disabled=disabled/><br/><label for="luftfeuchtigkeitText">rel. Luftfeuchtigkeit:&nbsp;</label><input type="text" value="%s %" id="luftfeuchtigkeitText" disabled=disabled/></body></html>'


print('waiting for connection...')
max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('.', end='')
    sleep(1)
print('')
if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print('ip = ' + status[0])
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)
print('listening on', addr)


while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        
        dht22_sensor.measure()
        temperatur = dht22_sensor.temperature()
        luftfeuchtigkeit = dht22_sensor.humidity()
         
         
        website = html % (temperatur, luftfeuchtigkeit )
       
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(website)
        cl.close()
    except OSError as e:
        cl.close()
        print('connection closed')

imonbln
User
Beiträge: 191
Registriert: Freitag 3. Dezember 2021, 17:07

Kannst du diesen Teil bitte etwas genauer Formulieren?
Googelist hat geschrieben: Mittwoch 15. Februar 2023, 17:24
Die HTML-Seite im Code wird angezeigt, wenn ich diese öffne.
Klingt für mich so als würde alles wie erwartet funktionieren.
Googelist hat geschrieben: Mittwoch 15. Februar 2023, 17:24 Lasse ich das Programm laufen, wird eine Verbindung 192.168.2.XXX hergestellt, sobald ich die IP in die Adresszeile des Browsers eingebe, laufen mehrere gleiche IP Adressen mit verschiedenen Anschlüssen durch und letzendlich wird disconnected, ohne das ein Fehler angezeigt wird.
Auf die Beschreibung "laufen mehrere gleiche IP Adressen mit verschiedenen Anschlüssen" kann ich mir keinen Reim machen, warum sollten da unterschiedliche IP-Adressen involviert sein und was sind Anschlüsse? Meinst du vielleicht URLs oder Ports oder was ganz anderes?
Wo siehst du die Verbindungen, in der Debug Console des Browsers, Wireshark oder irgendwelchen Logfiles? Ich finde diese Beschreibung wenig hilfreich, um zu verstehen, was du beobachtest und was du erwartest. Dass die Verbindung geschlossen (disconnected) wird, ist wiederum logisch, den dein Server schließt nach dem Senden der Webseite die Verbindung aktiv.
Googelist hat geschrieben: Mittwoch 15. Februar 2023, 17:24 Die HTML-Seite wird auch nicht angezeigt.
Hast du nicht damit angefangen, dass die HTML-Seite angezeigt wird?
Was ist jetzt anders als dein Statement am Anfang, meinst du einmal im Browser und einmal woanders?
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Also, wenn ich den Text hinter "html =" kopiere und mit einem Em Editor unter xxx.html speichere, wird die Seite im Browser angezeigt wenn ich auf due Datei draufklicke (noch ohne Werte).

Wenn ich jedoch das ganze Programm laufen lasse, erhalte ich zunächst die Meldung Verbunden mit 192.168.2.111.

Gebe ich nun die Adressr im Browser ein, laufen in der Konsole mehrere Meldungen wie folgt durch. listen on 192.168.2.101 453322, listen on 192.168.2.101 56533.......
das ganze wird ca. 10 mal durchgeführt und die Verbindung beendet. Im Browser wird jedoch nur angezeigt "Seite kann nicht angezeigt werden."

Ich verstehe nicht ganz warum mir die IP 192.168.2.101 in der Konsole angezeigt wird, wenn ich doch als erste Meldung erhalte "Verbunden mit 192.168.2.111".
Die anderen Nummern sind dann wohl Ports die geöffnet werden.

Ich hoffe ich konnte mein Problem nun etwas besser beschreiben und bedanke mich schonmal für Euere Geduld.
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Also, wenn ich den Text hinter "html =" kopiere und mit einem Em Editor unter xxx.html speichere, wird die Seite im Browser angezeigt wenn ich auf due Datei draufklicke (noch ohne Werte).

Wenn ich jedoch das ganze Programm laufen lasse, erhalte ich zunächst die Meldung Verbunden mit 192.168.2.111.

Gebe ich nun die Adressr im Browser ein, laufen in der Konsole mehrere Meldungen wie folgt durch. listen on 192.168.2.101 453322, listen on 192.168.2.101 56533.......
das ganze wird ca. 10 mal durchgeführt und die Verbindung beendet. Im Browser wird jedoch nur angezeigt "Seite kann nicht angezeigt werden."

Ich verstehe nicht ganz warum mir die IP 192.168.2.101 in der Konsole angezeigt wird, wenn ich doch als erste Meldung erhalte "Verbunden mit 192.168.2.111".
Die anderen Nummern sind dann wohl Ports die geöffnet werden.

Ich hoffe ich konnte mein Problem nun etwas besser beschreiben und bedanke mich schonmal für Euere Geduld.
Benutzeravatar
snafu
User
Beiträge: 6872
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Googelist
Bitte nicht falsch auffassen, aber dein ganzer Ansatz ist Murks. Du brauchst keine Socket-Programmierung, wenn du bloß eine Webseite dynamisch anzeigen willst. Dafür gibt es entsprechende Frameworks, die sich um die technischen Details kümmern. Viele nehmen dafür Flask, was sich auch sehr gut für kleinere Projekte eignet, da man mit relativ kurzer Einarbeitung meist zum Ziel kommt. Schau dir einfach mal den Quickstart an: https://flask.palletsprojects.com/en/2.2.x/quickstart/

Da kommen unter anderem Templates vor. Die brauchst du als Platzhalter für deine Sensordaten. Den DHT22 musst du natürlich weiterhin einbinden und abfragen. Mit Flask stellst du die Daten dann entsprechend aufbereitet als HTML-Seite für den Browser zur Verfügung.

Und dann lässt du das Programm wie in der Doku beschrieben via ``flask --app deine_app run`` auf dem RPi laufen. Der wird ja wohl mittels Mac-Adresse von deinem Router erkannt werden. Ich wüsste nicht, was man da umständlich mit der Suche nach irgendwelchen Ports bei jedem Programmstart machen müsste. Der RPi ist halt der Server mit laufender Flask-Dauerschleife und kann dann über dein Netzwerk im Browser abgefragt werden.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@snafu: das ist ein PicoW. Da läuft kein flask drauf.
imonbln
User
Beiträge: 191
Registriert: Freitag 3. Dezember 2021, 17:07

Hallo @snafu, was @Googelist nur sehr indirekt gesagt hat, er benutzt kein cPython, sondern Mirco Python. Der Raspberry Pi Pico ist eigentlich kein richtiger Raspberry Pi, sondern hat ein RP2040-Mikrocontroller-Chip, das ist eher mit einem ESP32 zu vergleichen.  Ich fürchte, dass das Flask Framework nicht auf Mirco Python läuft, da es selbst nur ein Subset von Python implementiert.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

@Googelist: im ganzen Skript wird "listen on" nur einmal aufgerufen, und zwar mit der Addresse ("0.0.0.0", 80), also keiner konkreten IP-Addresse und keiner Port-Nummer, die weit über irgendeiner gültigen Portnummer liegt.
Der Text "Verbunden mit" kommt in Deinem Programm nirgends vor, kann also nicht ausgegeben werden.
Wie lautet also die exakte Ausgabe komplett, ohne irgendeiner Interpretation Deinerseits, wie lauten die IP-Adressen des Raspi und Deines Rechners?

Und wie immer benutzt Du sockets falsch, was bei so micro-Rechnern auch tatsächlich zu Problemen führen kann: `send` sendet nicht alles, sondern nur ein Paket. Wenn Du Dich also nicht um solche Nebensächlichkeiten kümmern mußt, dann benutze `sendall`. Ich weiß nicht, wie der TCP-Stack vom Pico zurecht kommt, wenn man die ankommenden Bytes nicht liest. Jedenfalls machst Du das nicht, solltest Du aber.
Benutzeravatar
__blackjack__
User
Beiträge: 14065
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Googelist: Auch auf dem Pico würde ich das Hauptprogramm in eine Funktion stecken und den üblichen Namenskonventionen für Konstanten folgen.

`html` wäre dann `HTML` und man müsste sich mit `website` nicht einen anderen Namen, der auch inhaltlich nicht so wirklich stimmt, überlegen. Wobei ich das `HTML` wohl auch noch zu `HTML_TEMPLATE` präzisieren würde.

Die ``while``-Schleife die auf den WLAN-Verbindungsaufbau wartet, ist eigentlich eine ``for``-Schleife.

Die Abbruchbedingung lässt sich mit verketteten Vergleichsoperatoren, statt mit ``or`` ausdrücken.

Beim `print()` mit den Punkten fehlt das `flush`-Argument.

Wenn man nur eine neue Zeile mit einem `print()` anfangen will, braucht es kein Argument, auch keine leere Zeichenkette.

Der Name `status` ist verwirrend wenn der nicht das Ergebnis vom `wlan.status()`- sondern vom `wlan.ifconfig()`-Aufruf ist.

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur aus solchen bestehen. Insbesondere ein- oder zweibuchstabige Kürzel sagen in der Regel so gar nichts aus.

Der `getaddrinfo()`-Aufruf ist komisch — warum machst Du den? Und wo wird garantiert, dass an Index 0 das passende steht, und nicht bei 1 oder 2? Das sieht sehr fragil aus.

Das mit `cl.close()` zweimal im Quelltext ist unschön, so etwas regelt man darüber das `socket`-Objekte Kontextmanager sind, mit der ``with``-Anweisung.

Und dann kann die kaputte Ausnahmebehandlung auch ganz weg, denn im Moment wird da ja nur "connection closed" ausgegeben, man erfährt aber gar nicht *warum* genau. Das macht die Fehlersuche schwer bis unmöglich.

Zwischenstand (ungetestet):

Code: Alles auswählen

import socket

import network
import rp2
from dht import DHT22
from machine import Pin
from utime import sleep

SSID = "xxxxx"
PASSWORD = "xxxxxxxx"

DHT_SENSOR_PIN = 15

HTML_TEMPLATE = (
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"'
    ' "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><head>'
    '<title>Pi Pico W - BME280 Sensordaten</title><style>'
    'h1{text-align:center;font-size:28px;}fieldset{width:340px;margin:0 auto;}'
    'label {width:190px;display:inline-block;}input[type=text]{width:120px;}'
    'input[type=text], label{font-size:20px;}legend{font-size:26px;}'
    '</style></head><body><h1>Raspberry Pi Pico W - Sensordaten vom'
    ' BME280</h1><fieldset><legend>Sensordaten</legend>'
    '<label for="temperaturText">Temperatur:&nbsp;</label>'
    '<input type="text" value="%s °C" id="temperaturText" disabled=disabled/>'
    '<br/><label for="luftfeuchtigkeitText">rel. Luftfeuchtigkeit:&nbsp;'
    '</label><input type="text" value="%s %" id="luftfeuchtigkeitText"'
    ' disabled=disabled/></body></html>'
)


def main():
    dht22_sensor = DHT22(Pin(DHT_SENSOR_PIN, Pin.IN, Pin.PULL_UP))

    rp2.country("DE")
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)

    print("waiting for connection...")
    for _ in range(10):
        if not 0 <= wlan.status() < 3:
            break
        print(".", end="", flush=True)
        sleep(1)
    print()
    if wlan.status() != 3:
        raise RuntimeError("network connection failed")

    print("connected")
    print("ip =", wlan.ifconfig()[0])

    address = ("0.0.0.0", 80)
    with socket.socket() as server_socket:
        server_socket.bind(address)
        server_socket.listen(1)
        print("listening on", address)
        while True:
            client_socket, address = server_socket.accept()
            with client_socket:
                print("client connected from", address)

                dht22_sensor.measure()
                temperatur = dht22_sensor.temperature()
                luftfeuchtigkeit = dht22_sensor.humidity()

                client_socket.sendall(
                    "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n"
                )
                client_socket.sendall(
                    HTML_TEMPLATE % (temperatur, luftfeuchtigkeit)
                )


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Ich möchte mich für die zahlreichen Antworten bedanken und diese auch kurz kommentieren.

Allgemein sein gesagt, ich muss mich dafür entschuldigen, das ich nicht dazu geschrieben habe, daß ich das Programm auf einem Pico W laufen habe bzw. laufen lassen will und somit auf Micro Python angewiesen bin.

@Sirius ich habe das Programm nochmals laufen lassen und es ist tatsächlich so, daß connected ip = 192.168.2.111 listening on "('0.0.0.0' ,80) ausgegeben wird. Sobald ich nun die Adresse 192.168.2.111 im Browser eingebe, erscheint in der Kommandozeile "client connected from ('192.168.2.101' , 43730). Eine "Webseite" baut sich im Browser jedoch nicht auf.

Mein Raspi hat im Netzwerk die Adresse192.168.2.111 an meinen Rechner ist die Adresse 192.168.2.103 vergeben.



@blackjack
Ich mache gerade die ersten Schritte in Sachen Programmierung und habe mir den Code nur kopiert und versucht mit dem Bisschen was ich bisher so verstanden habe daran herumzubasteln.

Insofern habe ich noch jede Menge nachholbedarf um zu verstehen was deine Kommentare im einzelnen bedeuten bzw. wie man sie benutzt.

Dennoch habe ich dein Programm auf dem Raspi laufen lassen und bin leider nicht weiter gekommen.

Das Programm läuft bis "waiting for connection...." und bleibt ohne Fehlermeldung stehen.

Da ich wie bereits erwähnt keine Erfahrungen mit Programmierung habe, wäre ich auch bereit, einen kleinen Unkostenbeitrag zu leisten, sollte mir jemand das Programm zum Laufen bringen.
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Edit: Ich erhalte die Fehlermeldung Type Error: extra keyword arguments given (in line 43)
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist eine micropython unbekanntes Argument. Nimm das print doch einfach raus, oder entferne das Schluesselwort, dass in deinen anderen print-Anweisungen *nicht* vorkommt.
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Also, die o.g. Fehlermeldung ist verschwunden, nachdem ich die Einrückung geändert habe. Warum auch immer. Das nächste Problem scheint aber zu sein, daß die Version 2.7 von Python die auf dem Pico läuft nicht mit der Socketprogrammierung zurecht kommt und folgenden Fehler ausgibt 'socket' object has no attribute '__exit__'

Nun müsste ich nach meiner Recherche nach zu urteilen den Pico auf Python 3.2 updaten oder versuchen einen anderen Weg um die With Anweisung zu finden. Ersteres dürfte vermutlich etwas schwieriger sein, da Version 3.2 vermutlich nicht auf den Pico passt bzw. Pico mit den meisten Modulen in Python 3.2 nichts anfangen kann.


Obwohl ich hier nicht ständig mit meinem Unwissen nerven will, wäre ich doch um eine Einschätzung zum Update auf den Pico dankbar.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie hast Du denn ein Python2.7 auf den PICO bekommen? Aktuell ist Micropython 3.10. Da kann es gut sein, dass die meisten Module mit Python 3.2 nichts mehr anfangen können, spielt ja aber auch keine Rolle. Woher hast du Deine Informationen?
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Ich muß mich korrigieren.

In dem Beitrag den ich gelesen habe (https://stackoverflow.com/questions/494 ... error-exit) ging es um die Micropython Version 2.7 weshalb ich offensichtlich auch davon ausgegangen bin, das meine Version auch die 2.7 ist.

Tatsächlich habe ich aber Micropython 3.10 mit der Firmware für den Pico-W heruntergeladen. Diese Version scheint aber, so wie ich den von mir genannten Beitrag lese, das Modul auch noch nicht zu unterstützen.

Ein Micropython Version 3.2 konnte ich für den Pico-W bisher noch nirgends finden, sodass ich wohl eher dem Beispiel aus dem Beitrag folgen muß.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du wollstest auf Python 3.2 updaten. Warum auch immer. Niemand hier hat das vorgeschlagen.

Der Fehler sollte wieder eine der Unterschiede zwischen micropython und macropython sein. __blackjack__ hat with socket benutzt, weil das den automatisch schliesst, wenn der nicht mehr gebraucht wird, oder etwas schief geht. Das kann man aber auch mit try/finally erreichen. Musst du einfach umschreiben.
Googelist
User
Beiträge: 8
Registriert: Mittwoch 15. Februar 2023, 17:11

Die Absicht mein Python auf Version 3.2 upzudaten ist auf meinem eigenen Mist gewachsen, da ich dachte es wäre die Lösung des Problems.

Ich werde mich dann wohl eher, wie von Dir/Euch vorgeschlagen, mit try und finally auseinander setzen.

Vielen Dank für Euere Unterstützung.
Antworten