Files
grossanzeige/settings.py
T
admin 0a893d0684 Helligkeit: zwei Werte und eine Schwelle, zur Laufzeit aenderbar
Die vier abgestuften Helligkeiten des alten MSP430-Programms (5/15/50/100 %)
entfallen. Fuer die Sternwarte genuegt der Wechsel Tag/Nacht: unterhalb der
Schwelle laeuft die Anzeige mit dunkel_prozent, oberhalb mit hell_prozent.

Die Werte stehen nicht mehr im Code, sondern in settings.json auf dem Geraet --
Vorbereitung fuer die spaetere MQTT-Anbindung:

    settings.load()                        # mit Vorgaben aufgefuellt
    settings.update({"hell_prozent": 60})  # aendern und speichern

Vorgaben: dunkel 5 %, hell 50 %, Schwelle 1800, Hysterese 150. Die Schwelle liegt
mit Abstand zwischen den gemessenen Lagen (abgedeckt 0, Raumlicht 2453).

Beibehalten, weil die Messung vom 27.07. sie verlangt: der gleitende Mittelwert
und eine Hysterese um die Schwelle. Der ADC rauscht mit +/-32 (nach Mittelung,
1 Sigma) -- ohne Hysterese wuerde die Anzeige am Umschaltpunkt springen.

settings.py prueft jeden Wert beim Laden UND beim Schreiben auf Typ und Bereich.
Das ist der eigentliche Zweck: Mit MQTT kommen die Werte von aussen, und ein
Helligkeitswert von 500 % oder eine Schwelle von -1 wuerde die Anzeige sonst
unbrauchbar machen. Eine fehlende oder beschaedigte Datei faellt auf die Vorgaben
zurueck, ein einzelner kaputter Wert verwirft nur diesen -- ein Geraet in der
Sternwarte soll sich nicht an seiner Konfigurationsdatei aufhaengen.

settings.json wird von deploy.sh bewusst NICHT mitkopiert und steht in
.gitignore: Es ist Laufzeitzustand, kein Quellcode. Sonst waeren die
eingestellten Werte nach jedem Deployen weg.

Am Geraet bestaetigt: schreiben, wiederlesen, Controller uebernimmt die Werte,
beide Helligkeiten auf der Anzeige sichtbar. 119 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:34:47 +02:00

118 lines
4.3 KiB
Python

"""Einstellungen, die sich im Betrieb aendern lassen -- als JSON auf dem Geraet.
config.py ist Programmcode: Pinbelegung, Protokoll, Geometrie. Das aendert sich
nur beim Deployen. Werte, die im laufenden Betrieb verstellt werden sollen --
spaeter ueber MQTT -- gehoeren dagegen hierher. Sie liegen als JSON im
Dateisystem (settings.json) und lassen sich lesen und zurueckschreiben, ohne das
Programm neu zu spielen.
import settings
w = settings.load() # mit Vorgaben aufgefuellt
settings.update({"hell_prozent": 60}) # aendern und speichern
Fehlt die Datei oder ist sie beschaedigt, gelten die Vorgaben aus DEFAULTS --
die Anzeige laeuft dann weiter, statt beim Start auszufallen. Das ist Absicht:
ein Geraet in der Sternwarte soll sich nicht an einer kaputten Konfigurationsdatei
aufhaengen.
Alle Werte werden beim Laden UND beim Schreiben geprueft (siehe _pruefe). Das ist
wichtig, sobald MQTT dazukommt: Von dort kommen fremde Eingaben, und ein
Helligkeitswert von 500 % oder eine Schwelle von -1 wuerde die Anzeige sonst in
einen unbrauchbaren Zustand bringen.
"""
import json
PFAD = "settings.json"
# Vorgaben. Die Helligkeiten und die Schwelle stammen aus der LDR-Messung vom
# 2026-07-27 (siehe README, Abschnitt Helligkeitsregelung): abgedeckt ergab 0,
# Raumlicht 2453, angeleuchtet 3552 -- 1800 liegt mit Abstand zwischen dunkel
# und Raumlicht. Die Hysterese deckt das gemessene ADC-Rauschen ab (~32 nach
# der Mittelung, 1 Sigma).
DEFAULTS = {
"dunkel_prozent": 5, # Helligkeit unterhalb der Schwelle
"hell_prozent": 50, # Helligkeit oberhalb der Schwelle
"schwelle": 1800, # LDR-Wert (nach raw_to_ldr) fuer den Umschaltpunkt
"hysterese": 150, # Breite um die Schwelle, gegen Flattern
}
# Erlaubte Bereiche: (minimum, maximum). Die Prozente gehen an ESC H, das nur
# 0..100 kennt; die LDR-Werte kommen aus einem 12-Bit-ADC.
_GRENZEN = {
"dunkel_prozent": (0, 100),
"hell_prozent": (0, 100),
"schwelle": (0, 4095),
"hysterese": (0, 2000),
}
class SettingsError(ValueError):
"""Ein Wert liegt ausserhalb des erlaubten Bereichs oder ist unbekannt."""
def _pruefe(werte):
"""Werte auf Typ und Bereich pruefen. Liefert ein geprueftes neues dict."""
geprueft = {}
for name, wert in werte.items():
if name not in DEFAULTS:
raise SettingsError("unbekannte Einstellung %r" % (name,))
# bool ist in Python ein int -- hier aber sicher ein Irrtum.
if isinstance(wert, bool) or not isinstance(wert, int):
raise SettingsError("%s: %r ist keine ganze Zahl" % (name, wert))
low, high = _GRENZEN[name]
if not low <= wert <= high:
raise SettingsError("%s: %d liegt ausserhalb %d..%d"
% (name, wert, low, high))
geprueft[name] = wert
return geprueft
def load(pfad=None, log=None):
"""Einstellungen lesen, fehlende Werte aus DEFAULTS ergaenzen.
Wirft nicht: Fehlt die Datei, ist sie unlesbar oder steht Unsinn darin, gibt
es die Vorgaben. Ein einzelner unbrauchbarer Wert verwirft nur diesen, nicht
die ganze Datei.
"""
pfad = PFAD if pfad is None else pfad
werte = dict(DEFAULTS)
try:
with open(pfad) as f:
gelesen = json.load(f)
except (OSError, ValueError):
return werte # keine Datei oder kaputtes JSON
if not isinstance(gelesen, dict):
return werte
for name, wert in gelesen.items():
try:
werte.update(_pruefe({name: wert}))
except SettingsError as e:
if log:
log("settings: %s -- Vorgabe %r bleibt"
% (e, DEFAULTS.get(name)))
return werte
def save(werte, pfad=None):
"""Gepruefte Einstellungen schreiben. Liefert die gespeicherten Werte."""
pfad = PFAD if pfad is None else pfad
vollstaendig = dict(DEFAULTS)
vollstaendig.update(_pruefe(werte))
with open(pfad, "w") as f:
json.dump(vollstaendig, f)
return vollstaendig
def update(aenderungen, pfad=None, log=None):
"""Einzelne Werte aendern und speichern -- der Weg fuer MQTT.
Erst pruefen, dann mit dem Bestand mischen, dann schreiben. Ist etwas
ungueltig, bleibt die Datei unveraendert.
"""
geprueft = _pruefe(aenderungen)
werte = load(pfad, log=log)
werte.update(geprueft)
return save(werte, pfad)