Die neun Effekte der Firmware waren in Home Assistant nicht erreichbar -
das Auswahlfeld, das es schon gab, sind die Szenen. Jetzt bietet die
Lichtentitaet sie an:
* Gewaehlt wird ueber den NAMEN aus der Firmware ("Regenbogen"),
geschickt wird die Kennung ("rainbow"). Eine Liste englischer
Kennungen waere in einer deutschen Oberflaeche unnoetig sperrig.
* Ein Effekt laeuft wie eine Farbe in ALLEN Segmenten; angezeigt wird er
nur, wenn alle denselben fahren.
* Eine Kennung, die nicht in der Liste steht (neuere Firmware, aeltere
Liste), wird roh angezeigt statt verschluckt.
* Ein unbekannter Name wird abgelehnt, statt stillschweigend nichts zu
tun.
Die Liste steht fest in der Firmware und wird deshalb einmal geholt,
nicht je Abfragezyklus - erneut erst, wenn sich die Version aendert. Eine
sechste Anfrage je Zyklus fuer etwas, das sich nie aendert, waere
Verschwendung.
Effekt-Parameter (Geschwindigkeit, Intensitaet, Palette) fehlen noch;
dafuer braucht es Entitaeten je Segment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
150 lines
5.7 KiB
Python
150 lines
5.7 KiB
Python
"""Die Felder aus device/state, die ninlight schon lange hat: Zeiten,
|
|
Einschaltverhalten, Aufwecken - und die Uebergangszeit beim Schalten."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock
|
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
import pytest
|
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
|
|
|
from tests.helpers import abfragen, entity_id_for
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("key", "wert", "grenze"),
|
|
[("fade_in", 500, 10_000), ("fade_out", 500, 10_000), ("transition_default", 300, 60_000)],
|
|
)
|
|
async def test_zeiten_zeigen_und_setzen(
|
|
hass: HomeAssistant,
|
|
init_integration: MockConfigEntry,
|
|
mock_client: MagicMock,
|
|
key: str,
|
|
wert: int,
|
|
grenze: int,
|
|
) -> None:
|
|
entity_id = entity_id_for(hass, "number", key)
|
|
state = hass.states.get(entity_id)
|
|
assert state.state == f"{float(wert)}"
|
|
# Die Obergrenzen sind die der Firmware (nl_api_device.c); alles darueber
|
|
# wiese sie mit 400 ab.
|
|
assert state.attributes["max"] == grenze
|
|
assert state.attributes["unit_of_measurement"] == "ms"
|
|
await hass.services.async_call(
|
|
"number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 1200}, blocking=True
|
|
)
|
|
mock_client.set_state.assert_awaited_once_with(**{key: 1200})
|
|
|
|
|
|
@pytest.mark.parametrize("key", ["fade_in", "fade_out", "transition_default"])
|
|
async def test_zeiten_ueber_der_grenze_werden_abgelehnt(
|
|
hass: HomeAssistant, init_integration: MockConfigEntry, mock_client: MagicMock, key: str
|
|
) -> None:
|
|
with pytest.raises(ServiceValidationError):
|
|
await hass.services.async_call(
|
|
"number",
|
|
"set_value",
|
|
{ATTR_ENTITY_ID: entity_id_for(hass, "number", key), "value": 60_001},
|
|
blocking=True,
|
|
)
|
|
mock_client.set_state.assert_not_awaited()
|
|
|
|
|
|
async def test_einschaltverhalten(
|
|
hass: HomeAssistant, init_integration: MockConfigEntry, mock_client: MagicMock
|
|
) -> None:
|
|
entity_id = entity_id_for(hass, "select", "power_on_behaviour")
|
|
state = hass.states.get(entity_id)
|
|
assert state.state == "last"
|
|
assert state.attributes["options"] == ["last", "on", "off"]
|
|
await hass.services.async_call(
|
|
"select", "select_option", {ATTR_ENTITY_ID: entity_id, "option": "off"}, blocking=True
|
|
)
|
|
mock_client.set_state.assert_awaited_once_with(power_on_behaviour="off")
|
|
assert hass.states.get(entity_id).state == "off"
|
|
|
|
|
|
async def test_unbekanntes_einschaltverhalten_zeigt_nichts(
|
|
hass: HomeAssistant,
|
|
init_integration: MockConfigEntry,
|
|
strip_state: dict[str, Any],
|
|
freezer: FrozenDateTimeFactory,
|
|
) -> None:
|
|
# Gemeint ist das Verhalten, nicht die Zeile Code: Home Assistant selbst
|
|
# zeigt "unknown", sobald der Wert nicht in den Optionen steht. Die Pruefung
|
|
# in der Entitaet ist derselbe Riemen doppelt - wie in ha-ninlight.
|
|
strip_state["power_on_behaviour"] = "irgendwas"
|
|
await abfragen(hass, freezer)
|
|
assert hass.states.get(entity_id_for(hass, "select", "power_on_behaviour")).state == "unknown"
|
|
|
|
|
|
async def test_aufwecken_bei_aenderung(
|
|
hass: HomeAssistant, init_integration: MockConfigEntry, mock_client: MagicMock
|
|
) -> None:
|
|
entity_id = entity_id_for(hass, "switch", "wake_on_change")
|
|
assert hass.states.get(entity_id).state == STATE_OFF
|
|
await hass.services.async_call("switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True)
|
|
mock_client.set_state.assert_awaited_once_with(wake_on_change=True)
|
|
assert hass.states.get(entity_id).state == STATE_ON
|
|
await hass.services.async_call("switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True)
|
|
mock_client.set_state.assert_awaited_with(wake_on_change=False)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("sekunden", "millisekunden"), [(0, 0), (2.5, 2500), (0.04, 40), (120, 60_000)]
|
|
)
|
|
async def test_uebergangszeit_beim_einschalten(
|
|
hass: HomeAssistant,
|
|
init_integration: MockConfigEntry,
|
|
mock_client: MagicMock,
|
|
sekunden: float,
|
|
millisekunden: int,
|
|
) -> None:
|
|
"""HA rechnet in Sekunden, das Geraet in Millisekunden - und laengeres als
|
|
60 s weist es mit 400 ab, deshalb wird gekappt statt durchgereicht."""
|
|
await hass.services.async_call(
|
|
"light",
|
|
"turn_on",
|
|
{ATTR_ENTITY_ID: entity_id_for(hass, "light", "light"), "transition": sekunden},
|
|
blocking=True,
|
|
)
|
|
mock_client.set_state.assert_awaited_once_with(on=True, transition=millisekunden)
|
|
|
|
|
|
async def test_uebergangszeit_beim_ausschalten(
|
|
hass: HomeAssistant, init_integration: MockConfigEntry, mock_client: MagicMock
|
|
) -> None:
|
|
await hass.services.async_call(
|
|
"light",
|
|
"turn_off",
|
|
{ATTR_ENTITY_ID: entity_id_for(hass, "light", "light"), "transition": 1.5},
|
|
blocking=True,
|
|
)
|
|
mock_client.set_state.assert_awaited_once_with(on=False, transition=1500)
|
|
|
|
|
|
async def test_ohne_uebergang_wird_kein_feld_geschickt(
|
|
hass: HomeAssistant, init_integration: MockConfigEntry, mock_client: MagicMock
|
|
) -> None:
|
|
"""Sonst ueberschriebe jeder Schaltbefehl die Vorgabe des Geraets."""
|
|
await hass.services.async_call(
|
|
"light", "turn_off", {ATTR_ENTITY_ID: entity_id_for(hass, "light", "light")}, blocking=True
|
|
)
|
|
mock_client.set_state.assert_awaited_once_with(on=False)
|
|
|
|
|
|
async def test_die_lichtentitaet_meldet_uebergaenge_und_effekte(
|
|
hass: HomeAssistant, init_integration: MockConfigEntry
|
|
) -> None:
|
|
from homeassistant.components.light import LightEntityFeature
|
|
|
|
merkmale = hass.states.get(entity_id_for(hass, "light", "light")).attributes[
|
|
"supported_features"
|
|
]
|
|
assert merkmale == LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT
|