mirror of
https://github.com/esphome/esphome.git
synced 2026-11-09 14:45:57 +00:00
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
"""Integration test verifying light effects can dim to 0% brightness while staying on.
|
|
|
|
Regression test for https://github.com/esphome/esphome/issues/17639, where PR #17103's
|
|
"make turn-on visible" logic in LightCall::validate_() also clobbered brightness set by a
|
|
running effect (e.g. pulse, strobe), forcing it back to 100% and breaking the dark phase
|
|
of those effects.
|
|
|
|
Effect ticks are published with `publish: false` (so Home Assistant isn't spammed with
|
|
every frame), so the effect's actual output can't be observed via API state broadcasts.
|
|
Instead, this test reads the output component's log lines, which are written on every
|
|
update regardless of the publish flag.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import re
|
|
from typing import Any
|
|
|
|
from aioesphomeapi import LightState
|
|
import pytest
|
|
|
|
from .state_utils import require_entity, subscribe_after_initial_states
|
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_light_effect_zero_brightness(
|
|
yaml_config: str,
|
|
run_compiled: RunCompiledFunction,
|
|
api_client_connected: APIClientConnectedFactory,
|
|
) -> None:
|
|
"""Pulse and strobe effects must be able to reach 0% brightness while the light stays on."""
|
|
output_pattern = re.compile(r"PULSE_OUTPUT:([\d.]+)")
|
|
observed: list[float] = []
|
|
|
|
def on_log_line(line: str) -> None:
|
|
match = output_pattern.search(line)
|
|
if match:
|
|
observed.append(float(match.group(1)))
|
|
|
|
async with (
|
|
run_compiled(yaml_config, line_callback=on_log_line),
|
|
api_client_connected() as client,
|
|
):
|
|
entities, _ = await client.list_entities_services()
|
|
light = require_entity(entities, "test_pulse_light", exact=True)
|
|
|
|
# ESPHome sends the current state of every entity right after connecting; drain
|
|
# that initial burst so it can't be mistaken for the response to a command below.
|
|
sub = await subscribe_after_initial_states(client, entities)
|
|
|
|
async def send_and_wait(**kwargs: Any) -> LightState:
|
|
"""Send a light command and return the light's next state."""
|
|
next_light = sub.waiter.next_state(light.key, LightState)
|
|
client.light_command(key=light.key, **kwargs)
|
|
return await next_light
|
|
|
|
# Turn the light on first so the effect starts from a known, visible state.
|
|
state = await send_and_wait(state=True, brightness=1.0)
|
|
assert state.state is True
|
|
assert state.brightness == pytest.approx(1.0)
|
|
|
|
for effect_name in ("Fast Pulse", "Fast Strobe"):
|
|
observed.clear()
|
|
state = await send_and_wait(effect=effect_name)
|
|
assert state.effect == effect_name
|
|
# Let several effect cycles run (update_interval/duration is 50ms in the fixture).
|
|
await asyncio.sleep(1.0)
|
|
|
|
assert observed, f"No output observed while running effect {effect_name!r}"
|
|
assert min(observed) == pytest.approx(0.0, abs=0.01), (
|
|
f"Effect {effect_name!r} never dimmed to 0% brightness while the light "
|
|
f"stayed on -- got min={min(observed):.4f} (values: {observed})"
|
|
)
|
|
assert max(observed) > 0.5, (
|
|
f"Effect {effect_name!r} never reached full brightness -- "
|
|
f"got max={max(observed):.4f}"
|
|
)
|
|
|
|
client.light_command(key=light.key, effect="None")
|