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>
This commit is contained in:
+117
@@ -0,0 +1,117 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user