#!/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)