0a893d0684
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>
160 lines
5.7 KiB
Python
160 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests fuer die Helligkeitsumschaltung (zwei Stufen, ein Umschaltpunkt).
|
|
|
|
python3 -m unittest -v test_brightness
|
|
|
|
Die Tests geben ihre Werte selbst vor, statt settings.json zu lesen -- so
|
|
haengen sie nicht daran, was gerade auf einem Geraet konfiguriert ist.
|
|
Vorgabewerte: Schwelle 1800 +/- 150 -> hoch ab 1950, runter unter 1650.
|
|
"""
|
|
|
|
import unittest
|
|
|
|
import brightness
|
|
from brightness import BrightnessController, DUNKEL, HELL
|
|
|
|
WERTE = {"dunkel_prozent": 5, "hell_prozent": 50,
|
|
"schwelle": 1800, "hysterese": 150}
|
|
|
|
|
|
def controller(**abweichend):
|
|
werte = dict(WERTE)
|
|
werte.update(abweichend)
|
|
return BrightnessController(werte=werte)
|
|
|
|
|
|
class TestUmschalten(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.c = controller()
|
|
|
|
def test_erststart_entscheidet_an_der_blanken_schwelle(self):
|
|
"""Ohne bisherige Stufe zaehlt die Schwelle selbst, nicht ihre Kante.
|
|
|
|
Sonst startete die Anzeige im Bereich zwischen Schwelle und Kante
|
|
faelschlich dunkel und wachte erst spaeter auf.
|
|
"""
|
|
self.assertEqual(self.c._next_level(1799, None), DUNKEL)
|
|
self.assertEqual(self.c._next_level(1800, None), HELL)
|
|
|
|
def test_hochschalten_erst_ueber_der_oberen_kante(self):
|
|
self.assertEqual(self.c._next_level(1949, DUNKEL), DUNKEL)
|
|
self.assertEqual(self.c._next_level(1950, DUNKEL), HELL)
|
|
|
|
def test_herunterschalten_erst_unter_der_unteren_kante(self):
|
|
self.assertEqual(self.c._next_level(1650, HELL), HELL)
|
|
self.assertEqual(self.c._next_level(1649, HELL), DUNKEL)
|
|
|
|
def test_im_hysteresefenster_bleibt_die_stufe_stehen(self):
|
|
"""Der Kern der Sache: dazwischen aendert sich nichts, egal von wo."""
|
|
for wert in (1650, 1800, 1949):
|
|
self.assertEqual(self.c._next_level(wert, DUNKEL), DUNKEL)
|
|
self.assertEqual(self.c._next_level(wert, HELL), HELL)
|
|
|
|
def test_prozente_kommen_aus_den_einstellungen(self):
|
|
c = controller(dunkel_prozent=7, hell_prozent=80)
|
|
self.assertEqual(c.percent(DUNKEL), 7)
|
|
self.assertEqual(c.percent(HELL), 80)
|
|
|
|
def test_gemessene_lichtlagen(self):
|
|
"""Die am Geraet gemessenen Lagen (2026-07-27) in Stufen uebersetzt."""
|
|
self.assertEqual(self.c._next_level(0, None), DUNKEL) # abgedeckt
|
|
self.assertEqual(self.c._next_level(2453, None), HELL) # Raumlicht
|
|
self.assertEqual(self.c._next_level(3552, None), HELL) # angeleuchtet
|
|
|
|
|
|
class TestUpdate(unittest.TestCase):
|
|
|
|
def test_erste_meldung_gilt_als_aenderung(self):
|
|
c = controller()
|
|
level, prozent, changed = c.update(100)
|
|
self.assertEqual((level, prozent), (DUNKEL, 5))
|
|
self.assertTrue(changed)
|
|
|
|
def test_gleicher_wert_meldet_keine_aenderung(self):
|
|
c = controller()
|
|
c.update(100)
|
|
_, prozent, changed = c.update(100)
|
|
self.assertFalse(changed)
|
|
self.assertEqual(prozent, 5)
|
|
|
|
def test_dunkel_nach_hell_und_zurueck(self):
|
|
c = controller()
|
|
c.update(0)
|
|
self.assertEqual(c.level, DUNKEL)
|
|
for _ in range(brightness.AVERAGE_WINDOW):
|
|
c.update(3500)
|
|
self.assertEqual(c.level, HELL)
|
|
self.assertEqual(c.percent(c.level), 50)
|
|
for _ in range(brightness.AVERAGE_WINDOW):
|
|
c.update(0)
|
|
self.assertEqual(c.level, DUNKEL)
|
|
|
|
def test_rauschen_um_die_schwelle_schaltet_nicht(self):
|
|
"""Gemessen wurden rund +/-32 Streuung nach der Mittelung.
|
|
|
|
Genau dagegen ist die Hysterese da -- ohne sie wuerde die Anzeige an der
|
|
Schwelle zwischen den beiden Helligkeiten springen.
|
|
"""
|
|
c = controller()
|
|
for _ in range(brightness.AVERAGE_WINDOW):
|
|
c.update(1800)
|
|
stufe = c.level
|
|
for delta in (+60, -60, +55, -50, +45, -60, +60, -55):
|
|
neu, _, changed = c.update(1800 + delta)
|
|
self.assertFalse(changed, "Rauschen hat umgeschaltet")
|
|
self.assertEqual(neu, stufe)
|
|
|
|
def test_gleitender_mittelwert_begrenzt_das_fenster(self):
|
|
c = controller()
|
|
for _ in range(20):
|
|
c.update(1000)
|
|
self.assertLessEqual(len(c._buf), brightness.AVERAGE_WINDOW)
|
|
|
|
def test_mittelwert_daempft_einen_ausreisser(self):
|
|
"""Ein einzelner heller Messwert darf die Anzeige nicht aufblitzen lassen."""
|
|
c = controller()
|
|
for _ in range(brightness.AVERAGE_WINDOW):
|
|
c.update(0)
|
|
_, _, changed = c.update(4095)
|
|
self.assertFalse(changed)
|
|
self.assertEqual(c.level, DUNKEL)
|
|
|
|
|
|
class TestReload(unittest.TestCase):
|
|
"""Geaenderte Einstellungen uebernehmen -- der Weg, den MQTT spaeter geht."""
|
|
|
|
def test_reload_liest_die_datei_neu(self):
|
|
import os
|
|
import settings
|
|
pfad = "test_settings_tmp.json"
|
|
try:
|
|
settings.save({"schwelle": 1000, "hell_prozent": 90}, pfad)
|
|
c = BrightnessController(pfad=pfad)
|
|
self.assertEqual(c.settings["schwelle"], 1000)
|
|
self.assertEqual(c.percent(HELL), 90)
|
|
|
|
settings.save({"schwelle": 3000, "hell_prozent": 20}, pfad)
|
|
c.reload()
|
|
self.assertEqual(c.settings["schwelle"], 3000)
|
|
self.assertEqual(c.percent(HELL), 20)
|
|
finally:
|
|
try:
|
|
os.remove(pfad)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
class TestRawToLdr(unittest.TestCase):
|
|
|
|
def test_invertiert_spiegelt_den_rohwert(self):
|
|
self.assertEqual(brightness.raw_to_ldr(4095, inverted=True, full_scale=4095), 0)
|
|
self.assertEqual(brightness.raw_to_ldr(0, inverted=True, full_scale=4095), 4095)
|
|
|
|
def test_nicht_invertiert_laesst_den_rohwert_stehen(self):
|
|
self.assertEqual(brightness.raw_to_ldr(1234, inverted=False, full_scale=4095), 1234)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|