nach abgebrochener WLAN-Verbindung erfolgt kein Wiederaufbau

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo liebe Forummitglieder,

    ich habe mit meinem RaspberryPi Pico W eine Garagentorsteuerung aufgebaut. Mit Hilfe des RaspberryPi Pico W kann ich weltweit (dyndns) mein Garagentor über eine kleine html-Oberfläche Bedienen und die Endlage des Tors abfragen.

    Leider habe ich das Problem, dass das Programm sporadisch nach ca. 1 bis 3 Tagen nicht mehr funktioniert. Ich habe herausgefunden, dass wenn der Pico einen WLAN-Abbruch hatte, keinen neuen Verbindungsversuch aufbaut und dauerhaft offline bleibt.

    Folgend der Teil des Programmcodes für den Verbindungsaufbau:

    Wenn die WLAN-Verbindung unterbrochen ist, hilft nur ein Neustart. Ich habe einen recht destruktiven Workaround implementiert, der den Raspberry Pi Pico über einen Watchdog zyklich neu startet (Programm läuft über eine Schleife auf dem zweiten Kern). Der Ansatz ist sicherlich alles andere als elegant, aber nun läuft das Programm stabiler, wenn auch nicht komplett ohne Aussetzer:


    Code
    def blink_forever1():
        max_wait = 30000
        while max_wait > 0:
            max_wait -= 1
            print(max_wait)
            wdt = WDT(timeout=8300)
            wdt.feed()
            time.sleep(1)
       
    _thread.start_new_thread(blink_forever1, ())

    Ich habe auch schon versucht den Watchdog etwas konstruktiver zu gestalten, in dem ich den WLAN-Status abfrage und bei einem Status ungleich 3 oder kleiner gleich O die Schleife unterbreche und somit den Watchdog und dadurch den Neustart auslöse:

    Problem ist nur, dass bei einer Verbindungsunterbrechung mit dem WLAN der Status auf 3 bleibt (IP vergeben und bleibt erhalten).

    Ich weiß nicht, ob das Problem der Verbindungsabbrüche und des fehlenden Wiederaufbaus häufig vorkommt, bzw., ob es eine einfache Lösung gibt.

    Über Hinweise wäre ich sehr dankbar.


    VG

    Erik

  • nach abgebrochener WLAN-Verbindung erfolgt kein Wiederaufbau? Schau mal ob du hier fündig wirst!

  • Folgend der Teil des Programmcodes für den Verbindungsaufbau:

    Code
    ssid = 'xxxx'
    password = 'xxxxx'
    
    wlan = network.WLAN(network.STA_IF)

    Hast Du auch schon mit dem wpa_supplicant oder mit iwd versucht, die Wlan-Verbindung herzustellen bzw. zu halten?

  • Hallo und vielen Dank für den Hinweis.


    Leider bin ich nicht so vertraut mit dem wpa_supplicant oder iwd. Ist dies auch für den Pico W anwendbar? Ich war der Meinung, dass dies nur auf einem „gewöhnlichen“ raspberry pi nutzbar ist.


    VG

    Erik

  • ... wpa_supplicant oder iwd. Ist dies auch für den Pico W anwendbar?

    Welches OS/Version hast Du auf dem Pico W?

    EDIT:

    Code
    cat /etc/os-release
    apt policy wpasupplicant iwd
    ps aux | grep -i [w]pa

    ?

  • ... ein Microcontroller. Da läuft kein OS drauf.

    OK, danke für den Hinweis.

  • erik.22 Warum ist die Funktion denn async? Also das ist sie ja eigentlich gar nicht, also macht das keinen Sinn.

    Beim Thread stellt sich die Frage ob die verwendeten Funktionen überhaupt thread-sicher sind.

    Die while-Schleifen mit den Zählern sind ein bisschen komisch. Für so etwas verwendet man for-Schleifen. blink_forever() blinkt gar nicht ewig.

    “On the eighth day, God telephoned his lawyers and began asking all sorts of questions about product liability.” — Tom Holt, Overtime

  • Hallo,

    macht `async` hier wirklich Sinn? Solange keine W-lan-Verbindung steht, soll ja auch sonst nichts gemacht werden.

    Kannst du mal den gesamten Code zeigen? Damit man die Zusammenhänge sieht. Du stellst die Verbindung nicht in der `boot.py` - Datei her, sondern hast alles in der `main.py` ?


    Grüße

    Dennis

    🎧 The music, if you can call it that, physically assaults anyone dumb enough to listen 🎧

  • Hallo in die Runde,

    vielen Dank für die Anmerkungen. Folgend sende ich mal den ganzen Code

    Variante async:

    Es ist so, dass ich mit dem Pico W die Schalterstellung abfrage (der Ausgang an der Torsteuerung nennt sich OTW und ZRM). Über lediglich einen Eingang (Steuersignal) wird das Tor geöffnet, gestoppt und geschlossen. Wenn das Tor geschlossen ist, wird über einen kurzen Steuerimpuls das Tor geöffnet, ist das Tor geschlossen, wird bei einem kurzen Steuerimpuls das Tor geöffnet. Ist das Tor weder geöffnet noch geschlossen (Mittellage), wird das Tor über einen Steuerimpuls gestoppt. Es gibt also physikalisch keinen getrennten Steuereingang für Öffnen, Stoppen oder Schließen.

    Ich hatte im Übrigen das Konzept mit der async-Variante erst verfolgt, als es mit meinem ersten Code genau die gleichen Probleme mit dem Verbindungsabbruch gab. Bei der ersten Variante, dessen Code ich folgend auch teile, gab es noch häufiger Verbindungsprobleme

    Variante alt:

    Und ja, die Funktion blink_forever hat einen historischen Hintergrund und ist nicht wörtlich zu nehmen.

    Das Programm habe ich unter main.py auf dem Pico gespeichert.

    VG

    Erik

    Edited once, last by erik.22 (May 19, 2024 at 6:29 PM).

  • Hallo,


    schreibe mal den Teil, der die W-Lan-Verbindung aufbaut in eine Datei Namens `boot.py`.

    Also zum Beispiel sowas:

    Diese Datei wird als erstes aufgerufen, falls sie existiert:

    micropython/ports/rp2/main.c at master · micropython/micropython
    MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems - micropython/micropython
    github.com


    Mit einem ESP32 habe ich das auch so und hatte selten Probleme. Der Empfang ist ausreichend?


    Grüße

    Dennis

    🎧 The music, if you can call it that, physically assaults anyone dumb enough to listen 🎧

  • Also in der ESP32 Szene wird da eine Schleife gemacht, die abfragt, ob die Verbindung noch steht.

    Sollte die Verbindung abgebrochen sein, wird ein erneuter Verbindungsaufbau initiiert, bis die Verbindung wieder steht.

    Ich denke, solche Routinen gibt es für den Pico auch.

    Da ich aber inzwischen wieder auf den ESP32 zurück bin (die Dinger sind einfach deutlich leistungsfähiger fürs gleiche Geld), muss ich mit einer fertigen Routine passen.

    ;) Gruß Outi :D
    Pis: 2x Pi B, 1x Pi B+, 1x Pi 2 B in Rente / 2x Pi 3 B (RaspberryMatic / Repetier Server) / 2x Pi Zero 1.2 / 2x Pi Zero 1.3 / 2x Pi Zero W 1.1 / 1x Pi Zero 2 (BW+CUPS/SANE) / 1x Pi 3 B+ (Tests) / 1x Pi 4 B 4GB (Tests) / Pi 400 (BW) / 1x Pi 5 8GB (BW) / 2x Pi Pico / 2x Pi Pico W
    HATs: Sense HAT / HM-MOD-RPI-PCB / RPI-RF-MOD / PiFi DAC+ V2.0 / TV HAT / Pi 5 Kühler HAT / Pimoroni NVMe BASE
    Cams: orig. Raspberry Pi Camera Module V1 & V3 / PS3 Eye

  • Hallo Dennis,


    danke für den Hinweis, ich werde den Ansatz in den nächsten Tagen mal testen. Bzgl. des Empfangs ist es so, dass dieser ausreichend ist, da sich der Pico aber im Pfosten meines Garagenschiebetors befindet und sich der WLAN-Rout ca. 8m entfernt im Haus befindet, ist der Empfang sicherlich besser sein könnte. Prinzipiell ist dieser aber ausreichend. Problematisch ist es bloß, wenn der Empfang abreißt. Dann ist das Programm nicht in der Lage eigenständig erneut eine Verbindung aufzubauen. Es läuft dann dahin hinaus, dass ich im Zählerschrank den Leitungsschutzschalter für das Schiebetor betätige und dadurch den Pico kurz spannungslos schalte und dieser daraufhin neu startet. Das führt dazu, dass die Verbindung wieder initiiert wird und das führ zum Erfolg.


    Das wäre genau der Mechanismus, den ich mir automatisiert erhoffe. Das ist das was Outlaw anspricht.

    Eigentlich ist die Aufgabenstellung wie folgt zu formulieren: Wie kann ein Programmcode aussehen, damit der Pico W bei einer getrennten Internetverbindung einen erneuten Verbindungsaufbau initiiert oder prinzipiell neu startet (z.B. über einen Watchdog).


    Also im Sinne mache irgendwas, if wlan.status() != 3. Ich habe aber die Vermutung, dass wenn einmal die WLAN-Verbindung hergestellt ist, bei einem Abbruch der Verbindung der Status==3 gesetzt bleibt. Somit würde diese Abfrage keinen Sinn machen.

    VG

    Erik

  • Also, ich habe mich nun ein wenig in den Foundation Infos umgesehen:

    Connecting to the Internet with Raspberry Pi Pico W <= (PDF Direktlink)

    Darin sollte das drin sein, was Du suchst:

    Quote

    3.8.3. Ensuring robust connections
    This partial example illustrates a more robust approach to connecting to a network using urequests.

    Code
    Hier würde ich normal den Code zitieren, jedoch gehen bei dem PDF alle Formatierungen verloren und diese dämlichen Zeilennummern aus dem PDF (nicht die hier zu sehenden) werden leider mitkopiert.
    Daher lade Dir die Doku runter und scrolle auf Seite 19 und 20.
    Quote

    Here we handle the possibility that we lose connection to our wireless network and then will seek to reconnect.

    Teste das mal und gib bitte Bescheid, ob das geholfen hat.

    ;) Gruß Outi :D
    Pis: 2x Pi B, 1x Pi B+, 1x Pi 2 B in Rente / 2x Pi 3 B (RaspberryMatic / Repetier Server) / 2x Pi Zero 1.2 / 2x Pi Zero 1.3 / 2x Pi Zero W 1.1 / 1x Pi Zero 2 (BW+CUPS/SANE) / 1x Pi 3 B+ (Tests) / 1x Pi 4 B 4GB (Tests) / Pi 400 (BW) / 1x Pi 5 8GB (BW) / 2x Pi Pico / 2x Pi Pico W
    HATs: Sense HAT / HM-MOD-RPI-PCB / RPI-RF-MOD / PiFi DAC+ V2.0 / TV HAT / Pi 5 Kühler HAT / Pimoroni NVMe BASE
    Cams: orig. Raspberry Pi Camera Module V1 & V3 / PS3 Eye

    Edited 4 times, last by Outlaw (May 26, 2024 at 3:46 PM).

  • Wie kann ein Programmcode aussehen, damit der Pico W bei einer getrennten Internetverbindung einen erneuten Verbindungsaufbau initiiert oder prinzipiell neu startet

    Ungetestet:


    Der Programmablauf wird allerdings solange blockiert, bis eine W-lan-Verbindung steht. Aber das sollte ja schon in Ordnung sein, weil ohne die Verbindung das Programm eh nichts machen kann?


    Grüße

    Dennis

    🎧 The music, if you can call it that, physically assaults anyone dumb enough to listen 🎧

  • Hallo Dennis,

    ja ohne Verbindung ist das Programm nutzlos, daher ist es absolut vertretbar, dass die while-Schleife durchlaufen wird, bis die Verbindung steht.

    Ich bin leider immer etwas mit dem Befehl if __name__ == "__main__": überfordert. Wie würde sich mein Programmcode in deinen Programmcode einbetten. Speichere ich den Code auf den Pico W einfach als boot.py ab und das Hauptprogramm ohne den Teil des W-LAN-Verbindungsaufbaus als main.py ab? Oder kann ich das Hauptprogramm von mir einfach unter dein Programmcode von dir (nach if __name__ == "__main__": main()) einfügen?

    Vielen lieben Dank für den Hinweis und die Unterstützung.


    Outlaw Vielen Dank für deinen Hinweis. Den Abschnitt 3.8.3 aus dem von Dir beschriebenen Dokument (Connecting to the Internet with Raspberry Pi Pico W) finde ich sehr treffen, es geht in eine ähnliche Richtung wie der von Dennis vorgeschlagene Ansatz (Schleife für Wiederaufbau der Verbindung). Hier hätte ich die gleiche Frage, wie könnte beispielsweise der Teil der WLAN-Verbindung aus dem Beispiel "3.9.2. Controlling an LED via a web server" mit dem Code aus 3.8.3 "angereichert" werden?

    Vielen Dank im Voraus.

    VG Erik

  • Hallo,


    das hätte ich vielleicht dazu sagen sollen. Das was ich schrieb, kannst du als Art Grundgerüst für deine `main.py` nehmen und darin deine Logik integrieren.

    Ich bin leider immer etwas mit dem Befehl if __name__ == "__main__": überfordert.

    Diese Abfrage macht nichts anderes, als die `main` -Funktion aufzurufen, wenn die Datei direkt ausgeführt wird. Wird die Datei ausgeführt, dann bekommt `__name__` den Wert `__main__` zugewiesen. Wenn man das Programm zum Beispiel in ein anderes importieren will, dann darf durch den reinen Import kein Code ausgeführt werden und das verhindert diese `if` - Abfrage. Vorausgesetzt der Rest ist auch ordentlich programmiert.

    Das erzähle ich dir dann gleich anhand deines Codes. Auf Modulebene (der Code ohne Einrückungen) werden nur Klassen, Funktionen und Konstanten definiert. Hier darf kein ausführbarer Code stehen. Konstanten bei dir wären `ssid`, `password` und `html`. Namen von Konstanten schreibt man GANZ_GROSS, Klassen in PascalCase-Schreibweise und den Rest klein_mit_unterstrich.

    Da die oben erwähnte `if`-Abfrage die `main`-Funktion startet liegt es Nahe, das ein Programmfluss in dieser Funktion gesteuert wird. Aus dieser Funktion werden weitere Funktionen aufgerufen, Argumente über geben und Rückgaben entgegen genommen.

    Es macht Sinn Pin-Nummern als Konstanten zu definieren. Falls man die mal änder will/muss dann muss man die nicht im Programm suchen und evtl. auch mehrfach ändern. Man sollte auch gleich darauf achten sprechende Namen zu wählen. `SchalterstellungZRM` könnte vielleicht eher `endlage_zu` heißen.

    `ledState` wird gar nicht verwendet. `addr`, `s` und `cl` sollten auch gleich ordentlich benannt werden.

    Mit `find` findest du kein Kommando, sondern den Index bei dem der angegebene Wert startet. Man könnte das und die folgenden `if`-Abfragen mit `in` kombinieren. Wenn `on` oder `off` nicht gefunden wird, braucht man die letzte Abfrage nicht und kann eigentlich ein einfaches `else` verwenden. Je nachdem wie der Inhalt von `request` aussieht, kann man das noch vereinfachen, aber ich denke so ist der Unterschied zu deinem Code offensichtlicher.

    Man muss nicht gegen `1` abfragen, da 1 als `True` und 0 als `False` interpretiert wird, kann man anstatt `if state == 1:` einfach `if state:` schreiben.

    Doppelter Code ist fehleranfällig. Wenn in einer Funktion drei mal der gleiche Code steht, der immer eine Led an und aus macht, dann sollte man den in eine Funktion auslagern.

    Die 4 `if`-Abfragen die `buttonState` beschreiben können ja nicht alle vier auf einmal/hinter einander zutreffen. Daher verwendet man `elif` , wenn ein Zweig dann ausgeführt wird, wird der Rest nicht mehr abgefragt. Wobei das jetzt die gleichen Abfragen wie in `control_door` sind, nur ohne die `led`. Auch hier, das würde man in eine Funktion schreiben.

    Wieso bindest du `buttonState` an `stateis` ?

    Wenn man jetzt einmal durch ist, merkt man, dass `led_onboard` gar nicht genutzt wird.

    Der erste Zwischenstand könnte dann ,ungetestet, so aussehen:

    Ich habe deinen Code an sich nur geringfügig geändert. Zum einen weil ich mir mit dem Ablauf nicht ganz sicher bin, zum anderen will ich dass du deine Logik noch erkennst. Von diesem Code könnte man jetzt weiter aufbauen. Anfangen würde ich mit der Fehlerbehandlung von `socket`. Oder allgemein mit der Verbindung. Entweder man öffnet die einmal oder man schließt sie nach dem Gebrauch und öffnet sie dann wieder.


    Grüße

    Dennis

    🎧 The music, if you can call it that, physically assaults anyone dumb enough to listen 🎧

  • Das mit dem nicht so leicht kopieren können der Quelltexte aus dem PDF hat mich gereizt. Nicht das jetzt der Aufwand irgendwie gerechtfertigt wäre, aber hier ist ein Python-Programm das die Quelltexte da raus holt. 🤓

    Es hat ein Problem mit einem Python-Quelltext das auf Seite 34 anfängt, wo eine lange Quelltextzeile im PDF auf zwei Zeilen aufgeteilt wird. Da das Programm alle Zeilen die nicht mit einer Zahl (Zeilennummer) anfangen, heraus filtert, ist die zweite Hälfte dieser langen Zeile nicht im Ergebnis. Das ist ein Listing das es auch auf Github gibt, darum habe ich auf dieses Problem keine Zeit aufwenden wollen. 🙂

    “On the eighth day, God telephoned his lawyers and began asking all sorts of questions about product liability.” — Tom Holt, Overtime

  • Hallo Dennis,

    vielen Dank für deine ausführliche Erläuterung, man lernt nicht aus, der if __name__ == "__main__ Befehl ietet tatsächlich viele Vorteile.

    Ich gebe zu, dass der Programmierstil recht schlampig ist. Ich habe den Code für den Verbindungsaufbau und den Webserver (Controlling an LED via a web server) aus dem offiziellen Dokument des RP2040 entnommen. Dann habe ich noch einige Anpassungen vorgenommen, welches dann teilweise unschön und nicht effizient ist, die stärkere Nutzung würden das ganze übersichtlicher machen. Dein Code sieht eleganter aus, auch der Programmteil in der main-Funktion ist schön gelöst.

    `buttonState` an `stateis` zu binden macht natürlich kein Sinn, ich kann Dir nicht sagen wieso ich das so umgesetzt habe. Es ist überflüssig.

    Ansonsten habe ich meinen Code erst einmal nur geringfügig angepasst und vor allem den Verbindungsaufbau wie von Dir beschrieben in eine Funktion gepackt und diese wird dann bei fehlender Verbindung aufgerufen. Ich habe den Code auf meinen Pico (main.py) aufgespielt und bisher läuft es stabil, mal schauen, bisher war es so, dass der alte Code teilweise auch 3 Tage stabil lief, bis er dann einfach hängen geblieben ist. Hier wäre es sicherlich interessant mal eine debugging-Session durchzuführen, ist es möglich eine Art Logfile vom Programmverhalten zu generieren, wenn das Programm stand-alone auf dem PicoW durchgeführt wird?

    Wenn ich deinen Code 1:1 übernehme kommt es zu folgenden Fehlercode:

    Traceback (most recent call last):
    File "<stdin>", line 156, in <module>
    File "<stdin>", line 129, in main
    OSError: [Errno 98] EADDRINUSE

    Dieser Fehlercode tritt in meinem Programm auch auf, aber nur sporadisch, bzw. sehr selten.

    Ich habe bei dem neuen Programmcode mal künstlich die W-LAN-Verbindung unterbrochen und wieder gestartet, und es hat funktioniert. Das hatte mit dem alten Programmcode aber auch schon funktioniert, meine Vermutung ist die, dass das Programm sich immer aufgehängt hat, wenn nach einem Verbindungsabbruch sich der erneute Verbindungsaufbau nicht innerhalb von ca. 10 Sekunden hergestellt hat, die Verbindung nie wieder aufgebaut werden konnte. Hier hat nur ein Neustart des Pico geholfen.

    Ich beobachte mal die nächsten Tage das Verhalten nach der Programmanpassung.

    Vielen lieben Dank in die Runde.

    VG

    Erik

  • Hallo,

    der Fehler hängt mit der Art und Weise zusammen, wie du mit `socket` umgehst.

    In jedem Schleifendurchgang wird eine neue `socket`-Verbindung aufgebaut, die "alte" wird aber nicht geschlossen.

    Das ist der Teil, den ich angesprochen habe, der meiner Meinung nach als erstes überarbeitet werden sollte. Ich kann da mangels Erfahrung nur etwas "probieren", es gibt hier aber User, die können das und wissen was sie tun.

    Display Spoiler

    (wenn sie nicht gerade damit beschäftigt sind, Code aus PDF-Dateien auszulesen) 8o

    Vorschlagen würde ich mal sowas in der Art:

    Wenn die W-lan Verbindung abbricht, macht es sicherlich Sinn, die `socket`-Verbindung neu aufzubauen und wenn die Ausnahme ausgelöst wird eventuell auch? Laut Doku hat MicroPython keine eigene Exception dafür und es wird auf `OSError` verwiesen.


    Grüße

    Dennis

    🎧 The music, if you can call it that, physically assaults anyone dumb enough to listen 🎧

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!