mirror of
https://github.com/home-assistant/core.git
synced 2026-05-08 17:49:37 +01:00
Add Squeezebox binary sensors for player alarm status (#154491)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
@@ -10,17 +10,26 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import SqueezeboxConfigEntry
|
||||
from .const import STATUS_SENSOR_NEEDSRESTART, STATUS_SENSOR_RESCAN
|
||||
from .entity import LMSStatusEntity
|
||||
from . import SqueezeboxConfigEntry, SqueezeBoxPlayerUpdateCoordinator
|
||||
from .const import (
|
||||
PLAYER_SENSOR_ALARM_ACTIVE,
|
||||
PLAYER_SENSOR_ALARM_SNOOZE,
|
||||
PLAYER_SENSOR_ALARM_UPCOMING,
|
||||
SIGNAL_PLAYER_DISCOVERED,
|
||||
STATUS_SENSOR_NEEDSRESTART,
|
||||
STATUS_SENSOR_RESCAN,
|
||||
)
|
||||
from .entity import LMSStatusEntity, SqueezeboxEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
SENSORS: tuple[BinarySensorEntityDescription, ...] = (
|
||||
SERVER_SENSORS: tuple[BinarySensorEntityDescription, ...] = (
|
||||
BinarySensorEntityDescription(
|
||||
key=STATUS_SENSOR_RESCAN,
|
||||
device_class=BinarySensorDeviceClass.RUNNING,
|
||||
@@ -32,6 +41,23 @@ SENSORS: tuple[BinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
)
|
||||
|
||||
PLAYER_SENSORS: tuple[BinarySensorEntityDescription, ...] = (
|
||||
BinarySensorEntityDescription(
|
||||
key=PLAYER_SENSOR_ALARM_UPCOMING,
|
||||
translation_key=PLAYER_SENSOR_ALARM_UPCOMING,
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key=PLAYER_SENSOR_ALARM_ACTIVE,
|
||||
translation_key=PLAYER_SENSOR_ALARM_ACTIVE,
|
||||
device_class=BinarySensorDeviceClass.RUNNING,
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key=PLAYER_SENSOR_ALARM_SNOOZE,
|
||||
translation_key=PLAYER_SENSOR_ALARM_SNOOZE,
|
||||
device_class=BinarySensorDeviceClass.RUNNING,
|
||||
),
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -42,9 +68,29 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Platform setup using common elements."""
|
||||
|
||||
@callback
|
||||
def _player_discovered(
|
||||
player_coordinator: SqueezeBoxPlayerUpdateCoordinator,
|
||||
) -> None:
|
||||
_LOGGER.debug(
|
||||
"Setting up binary sensor entities for player %s, model %s",
|
||||
player_coordinator.player.name,
|
||||
player_coordinator.player.model,
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
SqueezeboxBinarySensorEntity(player_coordinator, description)
|
||||
for description in PLAYER_SENSORS
|
||||
)
|
||||
|
||||
entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"{SIGNAL_PLAYER_DISCOVERED}{entry.entry_id}", _player_discovered
|
||||
)
|
||||
)
|
||||
async_add_entities(
|
||||
ServerStatusBinarySensor(entry.runtime_data.coordinator, description)
|
||||
for description in SENSORS
|
||||
for description in SERVER_SENSORS
|
||||
)
|
||||
|
||||
|
||||
@@ -55,3 +101,24 @@ class ServerStatusBinarySensor(LMSStatusEntity, BinarySensorEntity):
|
||||
def is_on(self) -> bool:
|
||||
"""LMS Status directly from coordinator data."""
|
||||
return bool(self.coordinator.data[self.entity_description.key])
|
||||
|
||||
|
||||
class SqueezeboxBinarySensorEntity(SqueezeboxEntity, BinarySensorEntity):
|
||||
"""Representation of player based binary sensors."""
|
||||
|
||||
description: BinarySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: SqueezeBoxPlayerUpdateCoordinator,
|
||||
description: BinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the SqueezeBox sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{format_mac(self._player.player_id)}_{description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return the state of the binary sensor."""
|
||||
return getattr(self.coordinator.player, self.entity_description.key, None)
|
||||
|
||||
@@ -19,6 +19,9 @@ STATUS_SENSOR_INFO_TOTAL_GENRES = "info total genres"
|
||||
STATUS_SENSOR_INFO_TOTAL_SONGS = "info total songs"
|
||||
STATUS_SENSOR_PLAYER_COUNT = "player count"
|
||||
STATUS_SENSOR_OTHER_PLAYER_COUNT = "other player count"
|
||||
PLAYER_SENSOR_ALARM_UPCOMING = "alarm_upcoming"
|
||||
PLAYER_SENSOR_ALARM_SNOOZE = "alarm_snooze"
|
||||
PLAYER_SENSOR_ALARM_ACTIVE = "alarm_active"
|
||||
STATUS_QUERY_LIBRARYNAME = "libraryname"
|
||||
STATUS_QUERY_MAC = "mac"
|
||||
STATUS_QUERY_UUID = "uuid"
|
||||
|
||||
@@ -41,6 +41,15 @@
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"alarm_active": {
|
||||
"name": "Alarm active"
|
||||
},
|
||||
"alarm_snooze": {
|
||||
"name": "Alarm snoozed"
|
||||
},
|
||||
"alarm_upcoming": {
|
||||
"name": "Alarm upcoming"
|
||||
},
|
||||
"needsrestart": {
|
||||
"name": "Needs restart"
|
||||
},
|
||||
|
||||
@@ -297,6 +297,9 @@ def mock_pysqueezebox_player(uuid: str) -> MagicMock:
|
||||
mock_player.model_type = None
|
||||
mock_player.firmware = None
|
||||
mock_player.alarms_enabled = True
|
||||
mock_player.alarm_upcoming = True
|
||||
mock_player.alarm_snooze = False
|
||||
mock_player.alarm_active = False
|
||||
|
||||
return mock_player
|
||||
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
"""Test squeezebox binary sensors."""
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.squeezebox.const import PLAYER_UPDATE_INTERVAL
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import FAKE_QUERY_RESPONSE
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_binary_sensor(
|
||||
@pytest.fixture(autouse=True)
|
||||
def squeezebox_binary_sensor_platform():
|
||||
"""Only set up the binary_sensor platform for squeezebox tests."""
|
||||
with patch(
|
||||
"homeassistant.components.squeezebox.PLATFORMS", [Platform.BINARY_SENSOR]
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
async def test_binary_server_sensor(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test binary sensor states and attributes."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.squeezebox.PLATFORMS",
|
||||
[Platform.BINARY_SENSOR],
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.squeezebox.Server.async_query",
|
||||
return_value=deepcopy(FAKE_QUERY_RESPONSE),
|
||||
),
|
||||
with patch(
|
||||
"homeassistant.components.squeezebox.Server.async_query",
|
||||
return_value=deepcopy(FAKE_QUERY_RESPONSE),
|
||||
):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
@@ -32,4 +41,92 @@ async def test_binary_sensor(
|
||||
state = hass.states.get("binary_sensor.fakelib_needs_restart")
|
||||
|
||||
assert state is not None
|
||||
assert state.state == "off"
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_player(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
lms: MagicMock,
|
||||
) -> MagicMock:
|
||||
"""Set up the squeezebox integration and return the mocked player."""
|
||||
|
||||
# Mock server status data for coordinator update
|
||||
# called on update, return something != None to not raise
|
||||
lms.async_prepared_status.return_value = {
|
||||
"dummy": False,
|
||||
}
|
||||
with patch("homeassistant.components.squeezebox.Server", return_value=lms):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
# Return the player mock
|
||||
return (await lms.async_get_players())[0]
|
||||
|
||||
|
||||
async def test_player_alarm_sensors_device_class(
|
||||
hass: HomeAssistant,
|
||||
mock_player: MagicMock,
|
||||
) -> None:
|
||||
"""Test player alarm binary sensors have correct device class."""
|
||||
|
||||
# Test alarm upcoming sensor device class
|
||||
upcoming_state = hass.states.get("binary_sensor.none_alarm_upcoming")
|
||||
assert upcoming_state is not None
|
||||
assert upcoming_state.attributes.get("device_class") is None
|
||||
|
||||
# Test alarm active sensor device class
|
||||
active_state = hass.states.get("binary_sensor.none_alarm_active")
|
||||
assert active_state is not None
|
||||
assert (
|
||||
active_state.attributes.get("device_class") == BinarySensorDeviceClass.RUNNING
|
||||
)
|
||||
|
||||
# Test alarm snooze sensor device class
|
||||
snooze_state = hass.states.get("binary_sensor.none_alarm_snoozed")
|
||||
assert snooze_state is not None
|
||||
assert (
|
||||
snooze_state.attributes.get("device_class") == BinarySensorDeviceClass.RUNNING
|
||||
)
|
||||
|
||||
|
||||
async def test_player_alarm_sensors_state(
|
||||
hass: HomeAssistant,
|
||||
mock_player: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test player alarm binary sensors with default states."""
|
||||
|
||||
player = mock_player
|
||||
|
||||
# Test alarm upcoming sensor
|
||||
upcoming_state = hass.states.get("binary_sensor.none_alarm_upcoming")
|
||||
assert upcoming_state is not None
|
||||
assert upcoming_state.state == STATE_ON
|
||||
|
||||
# Test alarm active sensor
|
||||
active_state = hass.states.get("binary_sensor.none_alarm_active")
|
||||
assert active_state is not None
|
||||
assert active_state.state == STATE_OFF
|
||||
|
||||
# Test alarm snooze sensor
|
||||
snooze_state = hass.states.get("binary_sensor.none_alarm_snoozed")
|
||||
assert snooze_state is not None
|
||||
assert snooze_state.state == STATE_OFF
|
||||
|
||||
# Toggle alarm states and verify sensors update
|
||||
player.alarm_upcoming = False
|
||||
player.alarm_active = True
|
||||
player.alarm_snooze = True
|
||||
freezer.tick(timedelta(seconds=PLAYER_UPDATE_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
upcoming_state = hass.states.get("binary_sensor.none_alarm_upcoming")
|
||||
assert upcoming_state is not None
|
||||
assert upcoming_state.state == STATE_OFF
|
||||
|
||||
active_state = hass.states.get("binary_sensor.none_alarm_active")
|
||||
assert active_state is not None
|
||||
assert active_state.state == STATE_ON
|
||||
|
||||
@@ -25,7 +25,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_plat
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def squeezebox_alarm_platform():
|
||||
"""Only set up the media_player platform for squeezebox tests."""
|
||||
"""Only set up the switch platform for squeezebox tests."""
|
||||
with patch("homeassistant.components.squeezebox.PLATFORMS", [Platform.SWITCH]):
|
||||
yield
|
||||
|
||||
|
||||
Reference in New Issue
Block a user