feat: nachgezogen, was ha-ninlight laengst hat

Michaels Befund: die Integration ist nicht dasselbe wie die von ninlight.
Verglichen und die Luecken geschlossen - alle Felder gibt es in der
Firmware schon, sie waren nur nicht bedienbar:

* number: fade_in, fade_out, transition_default in Millisekunden, mit den
  Grenzen der Firmware (10 s, 10 s, 60 s).
* select: Einschaltverhalten (last, on, off).
* switch: bei Aenderung einschalten.
* light: Uebergangszeit beim Ein- und Ausschalten. HA rechnet in Sekunden,
  das Geraet in Millisekunden; laengeres als 60 s wird gekappt, sonst
  wiese das Geraet die ganze Anfrage mit 400 ab. Ohne Angabe wird kein
  Feld geschickt, sonst ueberschriebe jeder Schaltbefehl die Vorgabe.
* sensor: letzter Update-Fehler und Startzeit (Zeitstempel statt einer
  Laufzeit, die jede Abfrage weiterlaeuft), beide ab Werk aus.
* diagnostics.py: Diagnosedaten zum Herunterladen, ohne Token, Adressen,
  MAC und SSID.
* config_flow: Neu-Konfigurieren von Adresse und Token, mit Gegenprobe der
  MAC - ein Tippfehler in der Adresse soll nicht still den falschen
  Eintrag umbiegen.
* Hinweis in den Reparaturen, wenn ein Geraet noch auf dem oeffentlichen
  Werks-Token steht.

Nicht uebernommen: die Auswahl sacn_layout. ninstrip ist immer rgb, ein
Strip hat keinen Masterkanal - eine Auswahl mit einer Option waere nur
Zierde.

241 Tests, 97 %. Die Kappung der Uebergangszeit per Mutation gegengeprueft.
This commit is contained in:
Michael Sauer
2026-09-23 00:35:12 +02:00
parent c40c68b537
commit 9eb7adcdd7
18 changed files with 737 additions and 20 deletions
+44
View File
@@ -0,0 +1,44 @@
"""Diagnosedaten - und was darin nicht stehen darf."""
from __future__ import annotations
from pathlib import Path
from homeassistant.core import HomeAssistant
from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.ninstrip.diagnostics import async_get_config_entry_diagnostics
from custom_components.ninstrip.shared import DATA_SHARED, async_scan_catalog
from tests_unit.images import build_image
from tests_unit.samples import HOST_K1Y, MAC_K1Y, TOKEN
def ablegen(verzeichnis: Path, name: str, device_type: str, version: str) -> None:
"""Blockierendes Schreiben ausserhalb der Ereignisschleife - sonst meldet
ruff ASYNC240."""
verzeichnis.mkdir(parents=True, exist_ok=True)
(verzeichnis / name).write_bytes(build_image(device_type, version))
async def test_geheimnisse_stehen_nicht_drin(
hass: HomeAssistant, init_integration: MockConfigEntry
) -> None:
daten = await async_get_config_entry_diagnostics(hass, init_integration)
text = repr(daten)
for geheim in (TOKEN, HOST_K1Y, MAC_K1Y, "ninIOT"):
assert geheim not in text, geheim
assert daten["state"]["brightness"] == 100
assert daten["info"]["fw_version"] == "0.1.0-12-g71a57"
assert daten["segments"]["segments"][0]["mode"] == "white"
async def test_nur_abbilder_der_eigenen_platine(
hass: HomeAssistant, init_integration: MockConfigEntry, firmware_dir: Path
) -> None:
await hass.async_add_executor_job(
ablegen, firmware_dir, "meins.img", "ninstrip-k1y", "0.1.1-rc1"
)
await hass.async_add_executor_job(ablegen, firmware_dir, "fremd.img", "ninstrip-rgbc3", "0.2.0")
await async_scan_catalog(hass, hass.data[DATA_SHARED])
daten = await async_get_config_entry_diagnostics(hass, init_integration)
assert [i["version"] for i in daten["firmware_images"]] == ["0.1.1-rc1"]