"""Die Poll-Schleife: Koordinaten holen, formatieren, anzeigen. Herausgeloest aus run_display.py, damit Mac und ESP32-C3 wirklich denselben Ablauf fahren. Auf dem Mac kommt drumherum die Kommandozeile (run_display.py), auf dem ESP32 das WLAN-Setup (run_esp32.py) -- die Schleife selbst ist gleich. Reines Python ohne Abhaengigkeiten ausser den Projektmodulen; argparse und sys.stderr bleiben bewusst draussen, beides gibt es unter MicroPython nicht. """ import time import config import coords import migra from mount_client import MountError class LinkError(Exception): """Die Netzverbindung fehlt (auf dem ESP32: kein WLAN).""" def format_coordinates(ra_raw, dec_raw): """Rohstrings der Montierung fuer die Anzeige aufbereiten. Bei **Niedrigpraezision** bekommen die Zeilen eine Beschriftung: RA:18h36.9 DE:+38°47' Das geht genau auf, weil die Montierung in diesem Modus ohnehin nur Zehntelminuten (7 Zeichen) und ganze Bogenminuten (7 Zeichen) liefert -- in zehn Zellen bleiben also drei fuer "RA:"/"DE:" uebrig. Die Zeilen sind linksbuendig gesetzt (config.CENTER_LINES), damit beide Beschriftungen buendig untereinander beginnen. Bei **Hochpraezision** entfaellt die Beschriftung: Dort braucht die Deklination mit +38°47'01" alle zehn Zeichen. """ if config.HIGH_PRECISION: return coords.format_ra(ra_raw), coords.format_dec(dec_raw) breite_ra = config.CHARS_PER_LINE - len(config.LABEL_RA) breite_dec = config.CHARS_PER_LINE - len(config.LABEL_DEC) return (config.LABEL_RA + coords.format_ra(ra_raw, width=breite_ra), config.LABEL_DEC + coords.format_dec(dec_raw, width=breite_dec)) def poll_loop(display, client, interval=None, once=False, read_ldr=None, dimmer=None, ensure_link=None, log=None, bridge=None, feed=None): """Zyklisch die Montierung abfragen und die Anzeige nachfuehren. display offenes Display-Objekt client MountClient (wird bei Bedarf selbst verbunden). Seine Adresse (client.host) darf sich zwischen zwei Durchlaeufen aendern -- die Schleife verbindet dann einfach neu. interval Sekunden zwischen zwei Abfragen (Vorgabe config.POLL_INTERVAL) once nur ein Durchlauf, dann zurueck read_ldr optional: Funktion ohne Argumente, liefert einen LDR-Rohwert in der Zaehlrichtung "hoch = hell" (siehe brightness.raw_to_ldr) dimmer optional: BrightnessController; noetig, wenn read_ldr gesetzt ist ensure_link optional: Funktion, die vor jeder Abfrage die Netzverbindung sicherstellt (auf dem ESP32 das WLAN) log optional: Ausgabefunktion, Vorgabe print; None-freies Logging erlaubt es, auf dem ESP32 still zu laufen (log=lambda *a: None) bridge optional: mqtt.MqttBridge -- empfaengt Einstellungen und veroeffentlicht den Zustand. Faellt der Broker aus, laeuft die Anzeige unveraendert weiter (mqtt.py faengt seine Fehler selbst). Setzt eine Nachricht die Adresse der Montierung um, trennt der Rueckruf die Verbindung (siehe run_esp32.main) -- noch in diesem Durchlauf wird dann die neue Adresse angefragt. feed optional: Funktion ohne Argumente, die den Watchdog fuettert. Wird zu Beginn **jedes** Durchlaufs gerufen -- auch nach einem Fehler, denn eine gemeldete Stoerung ist kein Stillstand: Die Schleife laeuft ja weiter und zeigt "KEIN LINK". Der Watchdog soll nur greifen, wenn gar nichts mehr geht. Laeuft bis KeyboardInterrupt oder -- bei once=True -- genau einmal. Fehler von Anzeige und Montierung werden abgefangen: die Schleife meldet sie und versucht es beim naechsten Durchlauf erneut, statt abzustuerzen. """ if interval is None: interval = config.POLL_INTERVAL if log is None: log = print last_shown = None while True: # Ganz am Anfang und ausserhalb des try: Der Watchdog misst, ob die # Schleife noch laeuft -- nicht, ob sie fehlerfrei laeuft. if feed is not None: feed() try: # Zuerst MQTT abholen: So wirkt eine gerade gesetzte Helligkeit noch # in diesem Durchlauf, statt eine Runde hinterherzuhinken. if bridge is not None: bridge.check() # Gleich nach dem Empfang: Ein "set/mount_host" wirkt schon in # diesem Durchlauf, und die Rueckmeldung zeigt, welche Adresse # angekommen ist -- auch dann, wenn die Montierung dort nicht # antwortet und der Rest der Runde in "KEIN LINK" endet. bridge.publish("mount_host", client.host) if ensure_link is not None: ensure_link() if not client.connected: client.connect() # Helligkeit nachfuehren -- nur senden, wenn sich die Stufe aendert. if read_ldr is not None and dimmer is not None: raw = read_ldr() level, percent, changed = dimmer.update(raw) if changed: display.set_brightness(percent) log("LDR %d -> %s = %d %%" % (raw, "HELL" if level else "DUNKEL", percent)) if bridge is not None: bridge.publish("ldr", raw) bridge.publish("helligkeit", percent) ra_raw, dec_raw = client.get_coordinates() ra_line, dec_line = format_coordinates(ra_raw, dec_raw) if (ra_line, dec_line) != last_shown: # Bei jeder Aenderung beide Zeilen neu zeichnen. Kein Vollbild- # Loeschen (kein Flimmern); die umgekehrte Zeilenreihenfolge in # show_lines schuetzt die Home-Zelle vor dem Gradzeichen- # Zeichensatzwechsel, auch bei staendiger Aktualisierung. first = last_shown is None or last_shown in (config.NOLINK_LINES, config.NOWIFI_LINES) display.show_lines(ra_line, dec_line, clear=first) last_shown = (ra_line, dec_line) log("RA %-11s DEC %-12s -> %s | %s" % (ra_raw, dec_raw, ra_line, dec_line)) if bridge is not None: bridge.publish("ra", ra_line) bridge.publish("dec", dec_line) if bridge is not None: bridge.publish("link", 1) except LinkError as e: # Kein Netz (WLAN): die Montierung gar nicht erst versuchen. log("Netz: %s" % e) client.close() last_shown = _show_notice(display, config.NOWIFI_LINES, last_shown, log) except migra.MigraError as e: # Anzeige-/Antwortfehler (z.B. gestoerte Antwort): nicht abstuerzen, # nur melden und beim naechsten Durchlauf neu zeichnen. log("Anzeige: %s" % e) last_shown = None except (MountError, ValueError) as e: log("Montierung: %s" % e) client.close() last_shown = _show_notice(display, config.NOLINK_LINES, last_shown, log) if bridge is not None: bridge.publish("link", 0) if once: return last_shown time.sleep(interval) def _show_notice(display, lines, last_shown, log): """Hinweiszeilen anzeigen, wenn sie nicht schon stehen.""" if lines == last_shown: return last_shown try: display.show_lines(lines[0], lines[1], clear=True) except migra.MigraError as e: log("Anzeige: %s" % e) return None return lines