LDR-Schwellen am realen Aufbau gemessen statt vom MSP430 uebernommen
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>
This commit is contained in:
@@ -247,9 +247,10 @@ Mac:
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
- **LDR-Schwellen nachmessen.** Die Grenzen in `brightness.py` stammen vom alten
|
||||
MSP430-Aufbau. Sobald der LDR am ESP32 hängt, die Rohwerte bei Tag und Nacht
|
||||
ablesen und `HELL_LOW`/`HELL_HIGH`/`HELL_MAX` anpassen. Bis dahin läuft die
|
||||
- **LDR-Schwellen in der Sternwarte gegenprüfen.** Die Grenzen sind am realen
|
||||
Aufbau gemessen (2026-07-27, siehe *Helligkeitsregelung*), aber am Schreibtisch.
|
||||
Vor Ort bei Tag und Nacht die Rohwerte ablesen und `HELL_LOW`/`HELL_HIGH`/
|
||||
`HELL_MAX` bestätigen — besonders die Grenzlage bei 2400. Bis dahin läuft die
|
||||
Anzeige mit fester Helligkeit (`run_esp32.main()` ohne `with_ldr`).
|
||||
- **Netzwerk zur echten Montierung.** Die GM4000 steht in `192.168.1.115`, das
|
||||
Heimnetz des ESP32 ist `192.168.178.x` — der Mac erreicht sie per VPN, der
|
||||
@@ -342,23 +343,57 @@ zerlegt (Trennzeichen `:`, `*`, `0xDF`, `'` sind alle egal).
|
||||
|
||||
## Helligkeitsregelung
|
||||
|
||||
Portiert aus dem alten MSP430-Programm (`DoDimmen_V`, Werte aus `main.h`). Ein
|
||||
LDR-Rohwert (12-Bit-ADC, 0–4095: je heller die Umgebung, desto höher der Wert)
|
||||
wird über einen gleitenden Mittelwert (8 Werte) geglättet und auf eine
|
||||
Portiert aus dem alten MSP430-Programm (`DoDimmen_V`). Ein LDR-Rohwert
|
||||
(12-Bit-ADC, 0–4095) wird über `raw_to_ldr` in die Zählrichtung „groß = hell"
|
||||
gebracht, über einen gleitenden Mittelwert (8 Werte) geglättet und auf eine
|
||||
Helligkeitsstufe mit Hysterese abgebildet:
|
||||
|
||||
| Stufe | Helligkeit | erreicht ab (hoch / runter) |
|
||||
|------|-----------|------------------------------|
|
||||
| 0 | 5 % | < 270 |
|
||||
| 1 | 15 % | ≥ 330 / < 270 |
|
||||
| 2 | 50 % | ≥ 2650 / < 2350 |
|
||||
| 3 | 100 % | ≥ 3650 / < 3350 |
|
||||
| 0 | 5 % | < 1050 |
|
||||
| 1 | 15 % | ≥ 1350 / < 1050 |
|
||||
| 2 | 50 % | ≥ 2550 / < 2250 |
|
||||
| 3 | 100 % | ≥ 3350 / < 3050 |
|
||||
|
||||
Schwellen-Mitten `300 / 2500 / 3500`, Hysterese `±30 / ±150 / ±150`. **300 und 2500
|
||||
stammen aus dem alten Code**; die obere Grenze `HELL_MAX = 3500` für 100 % ist neu
|
||||
(der alte Code hatte keine 4. Stufe) und in `brightness.py` anpassbar, sobald der
|
||||
reale LDR-Wertebereich feststeht. Ein Helligkeitstelegramm wird nur bei
|
||||
**Stufenwechsel** gesendet (`ESC H 2 <ppp>`), wie im Original.
|
||||
Schwellen-Mitten `1200 / 2400 / 3200`, Hysterese einheitlich `±150`.
|
||||
|
||||
**Am realen Aufbau gemessen (2026-07-27)**, XIAO ESP32-C3 mit LDR an GPIO2,
|
||||
je 200 Messwerte, Median — Werte nach `raw_to_ldr`:
|
||||
|
||||
| Lichtsituation | roh | nach `raw_to_ldr` | Stufe |
|
||||
|---|---|---|---|
|
||||
| abgedeckt | 4095 (ADC-Anschlag) | 0 | 0 → 5 % |
|
||||
| Raumlicht | 1642 | 2453 | 1 → 15 % |
|
||||
| angeleuchtet (Taschenlampe) | 543 | 3552 | 3 → 100 % |
|
||||
|
||||
Die alten Werte `300 / 2500 / 3500` stammten vom MSP430-Aufbau und passten nicht:
|
||||
Stufe 3 begann bei 3650 und war **unerreichbar** — selbst eine Taschenlampe direkt
|
||||
auf dem Sensor kam nur auf 3552. Die Aufteilung ist jetzt so gewählt, dass der
|
||||
5-%-Bereich bis 1200 reicht („nachts länger dunkel", schont die Dunkeladaption).
|
||||
|
||||
Auch die **Hysterese war zu klein**: Der Rohwert streut bei Raumlicht um ±270,
|
||||
nach der Mittelung bleiben rund ±32 (1σ). Die früheren `±30` am untersten Übergang
|
||||
lagen darunter — die unterste Stufe hätte geflattert. Jetzt einheitlich ±150 (≈4,7σ)
|
||||
und damit weit unter dem Stufenabstand von 800.
|
||||
|
||||
Ein Helligkeitstelegramm wird nur bei **Stufenwechsel** gesendet (`ESC H 2 <ppp>`),
|
||||
wie im Original. Am Gerät bestätigt: Abdecken schaltet herunter, Freigeben wieder
|
||||
hoch, ohne Flattern.
|
||||
|
||||
> **Bekannte Grenzlage:** Der gemessene Raumlichtwert 2453 liegt nur 53 Zähler über
|
||||
> `HELL_HIGH` (2400) und damit innerhalb der Hysterese — Raumlicht landet deshalb auf
|
||||
> Stufe 1 (15 %), nicht auf 50 %. Ob das so gewollt ist, entscheidet sich erst an den
|
||||
> Lichtverhältnissen in der Sternwarte. `test_brightness.py` hält das Verhalten fest.
|
||||
|
||||
**Im Dunkeln steht der ADC am Anschlag** (4095). Der Teiler treibt A0 über den
|
||||
Messbereich, „Dämmerung" und „stockdunkel" sind daher nicht unterscheidbar. Für die
|
||||
Regelung unkritisch — nachts ist ohnehin die dunkelste Stufe gewollt. Eine feinere
|
||||
Nachtabstufung ginge nur über den Vorwiderstand: sättigungsfrei wäre
|
||||
`R2 ≥ R_LDR(dunkel) × (3,3 V − U_max)/U_max`, also grob `R2 ≥ 0,32 × R_LDR(dunkel)`.
|
||||
Bei einem typischen LDR (0,5–1 MΩ im Dunkeln) liefe das auf 160–330 kΩ hinaus — dann
|
||||
lägen Tag und Raumlicht aber bei ADC-Werten unter 15 und damit in der Totzone. Der
|
||||
LDR überstreicht rund Faktor 4500; ein einzelner Teiler bildet davon nur etwa zwei
|
||||
Dekaden ab. **10–22 kΩ** wäre der Kompromiss, wenn mehr Nachtauflösung gewünscht ist.
|
||||
|
||||
Auf dem Mac kommt der LDR-Wert per Kommandozeile (`run_display.py --ldr <wert>`),
|
||||
auf dem ESP32-C3 vom ADC an GPIO2 (`run_esp32.main(with_ldr=True)`).
|
||||
@@ -371,8 +406,9 @@ wird deshalb gespiegelt: `config.LDR_INVERTED = True`, angewendet in
|
||||
`brightness.raw_to_ldr`. Wer die Hardware lieber umbaut (LDR nach 3V3, R2 nach
|
||||
GND), setzt das Flag auf `False`.
|
||||
|
||||
Die Schwellen selbst sind noch die alten und **müssen am realen Aufbau
|
||||
nachgemessen werden**, siehe Offene Punkte.
|
||||
Die Schwellen sind am Aufbau gemessen, aber **am Schreibtisch, nicht in der
|
||||
Sternwarte**. Vor dem Echteinsatz dort bei Tag und Nacht gegenprüfen — deshalb
|
||||
bleibt `with_ldr` in `run_esp32.main` vorerst auf `False`.
|
||||
|
||||
## Antwortleitung (RX)
|
||||
|
||||
|
||||
+38
-14
@@ -1,11 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Helligkeitsregelung anhand eines LDR-Werts.
|
||||
|
||||
Portierung von DoDimmen_V aus dem alten MSP430-Programm (GrossAnzeige/main.c,
|
||||
Konstanten aus main.h): gleitender Mittelwert, 3 Helligkeitsstufen mit Hysterese,
|
||||
Prozentwerte aus hellTafel_ub. Der Fotowiderstand (LDR) liefert einen Rohwert
|
||||
(alte Hardware: 12-bit-ADC, 0..4095) -- je heller die Umgebung, desto hoeher der
|
||||
Wert und desto heller die Anzeige.
|
||||
Portierung von DoDimmen_V aus dem alten MSP430-Programm (GrossAnzeige/main.c):
|
||||
gleitender Mittelwert, Helligkeitsstufen mit Hysterese, Prozentwerte aus
|
||||
hellTafel_ub. Das alte Programm hatte 3 Stufen (bis 50 %), hier sind es 4 mit
|
||||
100 % als oberster.
|
||||
|
||||
Der Fotowiderstand (LDR) liefert einen Rohwert (12-bit-ADC, 0..4095). Die
|
||||
Schwellen rechnen in der Richtung "grosser Wert = helle Umgebung"; auf der
|
||||
ESP32-Platine ist die Kennlinie umgekehrt, deshalb laeuft der Rohwert zuerst
|
||||
durch raw_to_ldr(). Die Grenzen sind am realen Aufbau gemessen (siehe unten),
|
||||
nicht mehr vom MSP430 uebernommen.
|
||||
|
||||
Reines Python, laeuft auch unter MicroPython (dort spaeter vom ADC gespeist).
|
||||
|
||||
@@ -17,22 +22,41 @@ Reines Python, laeuft auch unter MicroPython (dort spaeter vom ADC gespeist).
|
||||
# CLI-Teil ganz unten gebraucht und darum erst dort importiert. So laesst sich
|
||||
# dieses Modul auf dem ESP32 einfach importieren.
|
||||
|
||||
# -- Werte aus dem alten Programm (main.h) --------------------------------
|
||||
HELL_LOW = 300 # Stufengrenze 0<->1 (aus dem alten Code)
|
||||
HELL_HIGH = 2500 # Stufengrenze 1<->2 (aus dem alten Code)
|
||||
HELL_MAX = 3500 # Stufengrenze 2<->3: NEU, nicht aus dem alten Code.
|
||||
# Anpassen, sobald der reale LDR-Wertebereich bekannt ist.
|
||||
HELL_HYST = 30 # Basis-Hysterese
|
||||
# -- Stufengrenzen, am realen Aufbau gemessen (2026-07-27) ----------------
|
||||
# Gemessen wurde am XIAO ESP32-C3 mit dem LDR an GPIO2 (200 Messwerte je Lage,
|
||||
# Median), Werte nach raw_to_ldr, also schon in Zaehlrichtung "gross = hell":
|
||||
#
|
||||
# abgedeckt roh 4095 (ADC-Anschlag) -> 0
|
||||
# Raumlicht roh 1642 -> 2453
|
||||
# angeleuchtet roh 543 -> 3552
|
||||
#
|
||||
# Die alten Grenzen (300/2500/3500) stammten vom MSP430-Aufbau und passten
|
||||
# nicht: Stufe 3 begann bei 3650 und war damit unerreichbar -- selbst eine
|
||||
# Taschenlampe direkt auf dem Sensor kam nur auf 3552.
|
||||
#
|
||||
# Aufteilung nach Nutzerwunsch "nachts laenger dunkel": der 5-%-Bereich reicht
|
||||
# bis 1200, damit schwaches Licht die Anzeige noch nicht aufhellt und die
|
||||
# Dunkeladaption der Beobachter geschont wird.
|
||||
HELL_LOW = 1200 # Stufengrenze 0<->1 (5 % bis hierher)
|
||||
HELL_HIGH = 2400 # Stufengrenze 1<->2
|
||||
HELL_MAX = 3200 # Stufengrenze 2<->3 (100 % ab hier, jetzt erreichbar)
|
||||
|
||||
# Hysterese: Der Rohwert streut bei Raumlicht um +/-270; nach dem gleitenden
|
||||
# Mittel ueber AVERAGE_WINDOW bleiben rund +/-32 (1 Sigma). 150 liegt sicher
|
||||
# darueber (~4,7 Sigma) und zugleich weit unter dem Stufenabstand von 800.
|
||||
# Frueher standen hier 30 fuer den untersten Uebergang (und 5*30 fuer die
|
||||
# oberen) -- 30 lag unter dem Rauschen, die unterste Stufe haette geflattert.
|
||||
HELL_HYST = 150
|
||||
AVERAGE_WINDOW = 8 # MAXHELLARRAY: gleitender Mittelwert ueber 8 Werte
|
||||
|
||||
# Helligkeits-Prozente je Stufe, aus hellTafel_ub (main.h): 5/15/50/100 %.
|
||||
PERCENTS = (5, 15, 50, 100)
|
||||
|
||||
# Mittelpunkte der Stufengrenzen und deren Hysterese (Stufe i <-> i+1).
|
||||
# Der alte Code nutzte am unteren Uebergang +/-HYST, am oberen +/-5*HYST; das
|
||||
# uebernehmen wir und ergaenzen die neue obere Grenze mit derselben Breite.
|
||||
# Jetzt einheitlich: die gemessene Streuung ist an allen Grenzen dieselbe, es
|
||||
# gibt keinen Grund mehr fuer die gestaffelten Breiten des alten Programms.
|
||||
_CENTERS = (HELL_LOW, HELL_HIGH, HELL_MAX)
|
||||
_HYSTS = (HELL_HYST, HELL_HYST * 5, HELL_HYST * 5)
|
||||
_HYSTS = (HELL_HYST, HELL_HYST, HELL_HYST)
|
||||
|
||||
|
||||
def raw_to_ldr(raw, inverted=None, full_scale=None):
|
||||
|
||||
+4
-3
@@ -101,9 +101,10 @@ def main(host=None, port=None, with_ldr=False, fake_display=False,
|
||||
|
||||
host/port Montierung; Vorgabe aus config.py. Zum Testen gegen den Mock
|
||||
auf dem Mac dessen LAN-Adresse angeben.
|
||||
with_ldr Helligkeitsregelung ueber den ADC dazuschalten. Vorgabe aus,
|
||||
solange die Schwellen am realen LDR nicht nachgemessen sind
|
||||
(siehe README, Abschnitt Helligkeitsregelung).
|
||||
with_ldr Helligkeitsregelung ueber den ADC dazuschalten. Die Schwellen
|
||||
sind am realen LDR gemessen (2026-07-27, siehe brightness.py),
|
||||
aber am Schreibtisch, nicht in der Sternwarte -- deshalb bleibt
|
||||
die Vorgabe aus, bis sie vor Ort bestaetigt sind.
|
||||
fake_display Anzeige nur als Hex in die REPL schreiben, statt ueber den
|
||||
UART zu senden -- fuer den Test ohne Pegelwandler und Anzeige.
|
||||
once nur ein Durchlauf, dann zurueck.
|
||||
|
||||
+66
-18
@@ -3,8 +3,13 @@
|
||||
|
||||
python3 -m unittest -v test_brightness
|
||||
|
||||
Grenzen: Mitten (300, 2500, 3500) mit Hysterese (30, 150, 150)
|
||||
-> hoch bei 330 / 2650 / 3650, runter bei 270 / 2350 / 3350.
|
||||
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
|
||||
@@ -19,32 +24,75 @@ class TestStufen(unittest.TestCase):
|
||||
self.c = BrightnessController()
|
||||
|
||||
def test_erststart_schaltet_von_0_hoch(self):
|
||||
self.assertEqual(self.c._next_level(329, None), 0)
|
||||
self.assertEqual(self.c._next_level(330, None), 1)
|
||||
self.assertEqual(self.c._next_level(2649, None), 1)
|
||||
self.assertEqual(self.c._next_level(2650, None), 2)
|
||||
self.assertEqual(self.c._next_level(3649, None), 2)
|
||||
self.assertEqual(self.c._next_level(3650, None), 3) # 100 % erreichbar
|
||||
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(270, 1), 1)
|
||||
self.assertEqual(self.c._next_level(269, 1), 0)
|
||||
self.assertEqual(self.c._next_level(2649, 1), 1)
|
||||
self.assertEqual(self.c._next_level(2650, 1), 2)
|
||||
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(2350, 2), 2)
|
||||
self.assertEqual(self.c._next_level(2349, 2), 1)
|
||||
self.assertEqual(self.c._next_level(3649, 2), 2)
|
||||
self.assertEqual(self.c._next_level(3650, 2), 3)
|
||||
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(3350, 3), 3)
|
||||
self.assertEqual(self.c._next_level(3349, 3), 2)
|
||||
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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user