e8a5b5d5e3
Gemessen am XIAO ESP32-C3 mit dem LDR an GPIO2, je 200 Werte, Median
(Angaben nach raw_to_ldr, also in Zaehlrichtung "gross = hell"):
abgedeckt roh 4095 (ADC-Anschlag) -> 0
Raumlicht roh 1642 -> 2453
angeleuchtet roh 543 -> 3552
Zwei Fehler in den alten Werten, die erst die Messung zeigt:
- Stufe 3 (100 %) war unerreichbar. Sie begann bei 3650, selbst eine Taschenlampe
direkt auf dem Sensor kam nur auf 3552. Die Anzeige haette nie voll aufgedreht.
- Die Hysterese von 30 am untersten Uebergang lag unter dem Rauschen. Der Rohwert
streut bei Raumlicht um +/-270, nach dem gleitenden Mittel bleiben rund +/-32
(1 Sigma) -- die unterste Stufe haette geflattert.
Neu: Mitten 1200/2400/3200, Hysterese einheitlich 150. Die Aufteilung haelt den
5-%-Bereich bis 1200 (Nutzerwunsch "nachts laenger dunkel", schont die
Dunkeladaption der Beobachter). Die gestaffelten Hysteresen des alten Programms
entfallen -- die gemessene Streuung ist an allen Grenzen dieselbe.
Bekannte Grenzlage, in test_brightness festgehalten: Raumlicht (2453) liegt nur
53 Zaehler ueber HELL_HIGH und damit innerhalb der Hysterese, faellt also auf
Stufe 1 statt 2. Ob das so gewollt ist, entscheidet sich in der Sternwarte.
Am Geraet bestaetigt: Regelung laeuft stabil ohne Flattern, Abdecken schaltet
herunter, Freigeben wieder hoch, Helligkeitstelegramm wird quittiert. Die
Schwellen sind aber am Schreibtisch gemessen -- with_ldr bleibt deshalb auf
False, bis sie vor Ort bestaetigt sind. 101 Tests gruen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
128 lines
5.0 KiB
Python
128 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests fuer die Helligkeitsregelung (verallgemeinert aus DoDimmen_V).
|
|
|
|
python3 -m unittest -v test_brightness
|
|
|
|
Grenzen am Geraet gemessen (2026-07-27): Mitten (1200, 2400, 3200) mit
|
|
einheitlicher Hysterese 150 -> hoch bei 1350 / 2550 / 3350,
|
|
runter bei 1050 / 2250 / 3050.
|
|
|
|
Die Zahlen stehen bewusst ausgeschrieben statt aus brightness.* gerechnet:
|
|
So faellt eine versehentliche Aenderung der Schwellen im Test auf, statt
|
|
stillschweigend mitzuwandern.
|
|
"""
|
|
|
|
import unittest
|
|
|
|
import brightness
|
|
from brightness import BrightnessController
|
|
|
|
|
|
class TestStufen(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.c = BrightnessController()
|
|
|
|
def test_erststart_schaltet_von_0_hoch(self):
|
|
self.assertEqual(self.c._next_level(1349, None), 0)
|
|
self.assertEqual(self.c._next_level(1350, None), 1)
|
|
self.assertEqual(self.c._next_level(2549, None), 1)
|
|
self.assertEqual(self.c._next_level(2550, None), 2)
|
|
self.assertEqual(self.c._next_level(3349, None), 2)
|
|
self.assertEqual(self.c._next_level(3350, None), 3) # 100 % erreichbar
|
|
|
|
def test_hysterese_von_stufe_1(self):
|
|
self.assertEqual(self.c._next_level(1050, 1), 1)
|
|
self.assertEqual(self.c._next_level(1049, 1), 0)
|
|
self.assertEqual(self.c._next_level(2549, 1), 1)
|
|
self.assertEqual(self.c._next_level(2550, 1), 2)
|
|
|
|
def test_hysterese_von_stufe_2(self):
|
|
self.assertEqual(self.c._next_level(2250, 2), 2)
|
|
self.assertEqual(self.c._next_level(2249, 2), 1)
|
|
self.assertEqual(self.c._next_level(3349, 2), 2)
|
|
self.assertEqual(self.c._next_level(3350, 2), 3)
|
|
|
|
def test_hysterese_von_stufe_3(self):
|
|
self.assertEqual(self.c._next_level(3050, 3), 3)
|
|
self.assertEqual(self.c._next_level(3049, 3), 2)
|
|
|
|
def test_prozente(self):
|
|
self.assertEqual(brightness.PERCENTS, (5, 15, 50, 100))
|
|
|
|
def test_gemessene_lagen_ergeben_die_gewollten_stufen(self):
|
|
"""Die am Geraet gemessenen Lichtlagen, in Stufen uebersetzt.
|
|
|
|
Das ist der eigentliche Zweck der Kalibrierung: abgedeckt muss auf die
|
|
dunkelste Stufe fallen, angeleuchtet auf die hellste. Beides ging mit
|
|
den alten MSP430-Schwellen daneben (100 % war unerreichbar).
|
|
"""
|
|
self.assertEqual(self.c._next_level(0, None), 0) # abgedeckt -> 5 %
|
|
self.assertEqual(self.c._next_level(3552, None), 3) # Lampe -> 100 %
|
|
|
|
def test_raumlicht_liegt_dicht_an_der_grenze(self):
|
|
"""Bekannte Grenzlage, absichtlich festgehalten.
|
|
|
|
Der gemessene Raumlichtwert 2453 liegt nur 53 Zaehler ueber HELL_HIGH
|
|
(2400) und damit unter der Hysterese: Hochgeschaltet wird erst ab 2550,
|
|
also bleibt Raumlicht auf Stufe 1 (15 %). Eine Stufengrenze so dicht an
|
|
einem realen Betriebspunkt ist keine gute Stelle -- welche Stufe dort
|
|
gewollt ist, entscheidet sich erst an den Lichtverhaeltnissen in der
|
|
Sternwarte. Bis dahin haelt dieser Test das Verhalten schriftlich fest,
|
|
damit es niemanden ueberrascht.
|
|
"""
|
|
self.assertEqual(self.c._next_level(2453, None), 1)
|
|
self.assertLess(abs(2453 - brightness.HELL_HIGH), brightness.HELL_HYST)
|
|
|
|
def test_hysterese_ueberdeckt_das_gemessene_rauschen(self):
|
|
"""Nach dem gleitenden Mittel bleiben rund 32 Zaehler Streuung (1 Sigma).
|
|
|
|
Die Hysterese muss deutlich darueber liegen, sonst springt die Stufe an
|
|
einer Grenze hin und her -- genau das waere mit den alten 30 passiert.
|
|
"""
|
|
self.assertGreaterEqual(brightness.HELL_HYST, 100)
|
|
|
|
def test_kein_stufensprung_bei_rauschen_um_eine_grenze(self):
|
|
"""Werte, die um eine Stufengrenze streuen, duerfen die Stufe halten."""
|
|
c = BrightnessController()
|
|
for _ in range(brightness.AVERAGE_WINDOW):
|
|
c.update(1200) # genau auf der Grenze einschwingen
|
|
stufe_vorher = c.level
|
|
for delta in (+60, -60, +55, -50, +45, -60, +60, -55):
|
|
stufe, _, changed = c.update(1200 + delta)
|
|
self.assertFalse(changed, "Rauschen um die Grenze hat umgeschaltet")
|
|
self.assertEqual(stufe, stufe_vorher)
|
|
|
|
|
|
class TestUpdate(unittest.TestCase):
|
|
|
|
def test_erste_aenderung_meldet_changed(self):
|
|
c = BrightnessController()
|
|
self.assertEqual(c.update(100), (0, 5, True))
|
|
|
|
def test_gleicher_wert_keine_aenderung(self):
|
|
c = BrightnessController()
|
|
c.update(100)
|
|
level, percent, changed = c.update(100)
|
|
self.assertFalse(changed)
|
|
self.assertEqual(percent, 5)
|
|
|
|
def test_dunkel_zu_hell_erreicht_100(self):
|
|
c = BrightnessController()
|
|
c.update(100)
|
|
level = percent = None
|
|
for _ in range(brightness.AVERAGE_WINDOW + 2):
|
|
level, percent, _ = c.update(4000)
|
|
self.assertEqual(level, 3)
|
|
self.assertEqual(percent, 100)
|
|
|
|
def test_gleitender_mittelwert_fenster(self):
|
|
c = BrightnessController()
|
|
for _ in range(20):
|
|
c.update(1000)
|
|
self.assertLessEqual(len(c._buf), brightness.AVERAGE_WINDOW)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|