mirror of
https://github.com/home-assistant/core.git
synced 2026-05-19 15:00:27 +01:00
216 lines
7.6 KiB
Python
216 lines
7.6 KiB
Python
"""Test the OpenDisplay button event platform."""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import entity_registry as er
|
|
|
|
from . import (
|
|
TEST_ADDRESS,
|
|
make_binary_inputs,
|
|
make_button_device_config,
|
|
make_v1_service_info,
|
|
)
|
|
|
|
from tests.common import MockConfigEntry
|
|
from tests.components.bluetooth import inject_bluetooth_service_info
|
|
|
|
|
|
async def test_no_entities_without_binary_inputs(
|
|
hass: HomeAssistant,
|
|
mock_config_entry: MockConfigEntry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""No event entities are created when the device has no binary inputs configured."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
entries = er.async_entries_for_config_entry(
|
|
entity_registry, mock_config_entry.entry_id
|
|
)
|
|
assert not any(e.domain == "event" for e in entries)
|
|
|
|
|
|
async def test_entities_created_per_active_button(
|
|
hass: HomeAssistant,
|
|
mock_three_button_config_entry: MockConfigEntry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""One event entity is created per active button bit in binary_inputs."""
|
|
mock_three_button_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(
|
|
mock_three_button_config_entry.entry_id
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
entries = er.async_entries_for_config_entry(
|
|
entity_registry, mock_three_button_config_entry.entry_id
|
|
)
|
|
event_entries = [e for e in entries if e.domain == "event"]
|
|
assert len(event_entries) == 3
|
|
assert {e.unique_id for e in event_entries} == {
|
|
f"{TEST_ADDRESS}-button_0_0",
|
|
f"{TEST_ADDRESS}-button_0_1",
|
|
f"{TEST_ADDRESS}-button_0_2",
|
|
}
|
|
|
|
|
|
async def test_multiple_binary_input_instances(
|
|
hass: HomeAssistant,
|
|
mock_multi_instance_config_entry: MockConfigEntry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""Entities get globally sequential button numbers."""
|
|
mock_multi_instance_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(
|
|
mock_multi_instance_config_entry.entry_id
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
entries = er.async_entries_for_config_entry(
|
|
entity_registry, mock_multi_instance_config_entry.entry_id
|
|
)
|
|
event_entries = [e for e in entries if e.domain == "event"]
|
|
assert len(event_entries) == 3
|
|
assert {e.unique_id for e in event_entries} == {
|
|
f"{TEST_ADDRESS}-button_0_0",
|
|
f"{TEST_ADDRESS}-button_0_1",
|
|
f"{TEST_ADDRESS}-button_1_0",
|
|
}
|
|
# Button numbers must be globally sequential, not reset per instance
|
|
names = {
|
|
e.unique_id: hass.states.get(e.entity_id).attributes.get("friendly_name", "")
|
|
for e in event_entries
|
|
}
|
|
assert names[f"{TEST_ADDRESS}-button_0_0"].endswith("Button 1")
|
|
assert names[f"{TEST_ADDRESS}-button_0_1"].endswith("Button 2")
|
|
assert names[f"{TEST_ADDRESS}-button_1_0"].endswith("Button 3")
|
|
|
|
|
|
async def test_stale_entities_removed_on_config_change(
|
|
hass: HomeAssistant,
|
|
mock_two_button_config_entry: MockConfigEntry,
|
|
mock_opendisplay_device: MagicMock,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""Entities for buttons no longer in device config are removed on reload."""
|
|
mock_two_button_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(mock_two_button_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert (
|
|
len(
|
|
[
|
|
e
|
|
for e in er.async_entries_for_config_entry(
|
|
entity_registry, mock_two_button_config_entry.entry_id
|
|
)
|
|
if e.domain == "event"
|
|
]
|
|
)
|
|
== 2
|
|
)
|
|
|
|
# Device reconfigured: now only 1 active button
|
|
mock_opendisplay_device.config = make_button_device_config(
|
|
[make_binary_inputs(input_flags=0x01)]
|
|
)
|
|
assert await hass.config_entries.async_unload(mock_two_button_config_entry.entry_id)
|
|
assert await hass.config_entries.async_setup(mock_two_button_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
event_entries = [
|
|
e
|
|
for e in er.async_entries_for_config_entry(
|
|
entity_registry, mock_two_button_config_entry.entry_id
|
|
)
|
|
if e.domain == "event"
|
|
]
|
|
assert len(event_entries) == 1
|
|
assert event_entries[0].unique_id == f"{TEST_ADDRESS}-button_0_0"
|
|
|
|
|
|
async def test_button_down_event_fired(
|
|
hass: HomeAssistant,
|
|
mock_button_config_entry: MockConfigEntry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""A 'button_down' event fires when the button transitions to the down state."""
|
|
mock_button_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(mock_button_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
# First advertisement — seeds tracker state (no events emitted)
|
|
inject_bluetooth_service_info(hass, make_v1_service_info())
|
|
await hass.async_block_till_done()
|
|
|
|
entity_id = entity_registry.async_get_entity_id(
|
|
"event", "opendisplay", f"{TEST_ADDRESS}-button_0_0"
|
|
)
|
|
assert entity_id is not None
|
|
state_before = hass.states.get(entity_id)
|
|
|
|
# Second advertisement — byte 0: pressed=True, button_id=0 → raw=0x80
|
|
inject_bluetooth_service_info(hass, make_v1_service_info(b"\x80" + b"\x00" * 10))
|
|
await hass.async_block_till_done()
|
|
|
|
state_after = hass.states.get(entity_id)
|
|
assert state_after is not None
|
|
assert state_after.attributes.get("event_type") == "button_down"
|
|
assert state_before != state_after
|
|
|
|
|
|
async def test_button_up_event_fired(
|
|
hass: HomeAssistant,
|
|
mock_button_config_entry: MockConfigEntry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""A 'button_up' event fires when the button transitions from down to up."""
|
|
mock_button_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(mock_button_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
entity_id = entity_registry.async_get_entity_id(
|
|
"event", "opendisplay", f"{TEST_ADDRESS}-button_0_0"
|
|
)
|
|
assert entity_id is not None
|
|
|
|
# Seed with pressed state (pressed=True, button_id=0 → raw=0x80)
|
|
inject_bluetooth_service_info(hass, make_v1_service_info(b"\x80" + b"\x00" * 10))
|
|
await hass.async_block_till_done()
|
|
|
|
# Transition to released (pressed=False, button_id=0 → raw=0x00)
|
|
inject_bluetooth_service_info(hass, make_v1_service_info())
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(entity_id)
|
|
assert state is not None
|
|
assert state.attributes.get("event_type") == "button_up"
|
|
|
|
|
|
async def test_no_event_for_wrong_button_id(
|
|
hass: HomeAssistant,
|
|
mock_two_button_config_entry: MockConfigEntry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""Entity watching button_id=1 does not fire when button_id=0 changes."""
|
|
mock_two_button_config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(mock_two_button_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
inject_bluetooth_service_info(hass, make_v1_service_info())
|
|
await hass.async_block_till_done()
|
|
|
|
entity_id = entity_registry.async_get_entity_id(
|
|
"event", "opendisplay", f"{TEST_ADDRESS}-button_0_1"
|
|
)
|
|
assert entity_id is not None
|
|
state_before = hass.states.get(entity_id)
|
|
|
|
# Press button_id=0 only (raw=0x80: pressed=True, button_id=0)
|
|
inject_bluetooth_service_info(hass, make_v1_service_info(b"\x80" + b"\x00" * 10))
|
|
await hass.async_block_till_done()
|
|
|
|
assert hass.states.get(entity_id) == state_before
|