Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 73 additions & 6 deletions homeassistant/components/squeezebox/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@
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,
Expand All @@ -32,6 +41,23 @@
),
)

PLAYER_SENSORS: tuple[BinarySensorEntityDescription, ...] = (
BinarySensorEntityDescription(
key=PLAYER_SENSOR_ALARM_UPCOMING,
Comment thread
MartinHjelmare marked this conversation as resolved.
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__)


Expand All @@ -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(
Comment thread
wollew marked this conversation as resolved.
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(
Comment thread
wollew marked this conversation as resolved.
ServerStatusBinarySensor(entry.runtime_data.coordinator, description)
for description in SENSORS
for description in SERVER_SENSORS
)


Expand All @@ -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)
3 changes: 3 additions & 0 deletions homeassistant/components/squeezebox/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
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"
Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/squeezebox/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
3 changes: 3 additions & 0 deletions tests/components/squeezebox/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
125 changes: 111 additions & 14 deletions tests/components/squeezebox/test_binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,132 @@
"""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


@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_sensor(
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(
Comment thread
wollew marked this conversation as resolved.
"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)

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(
Comment thread
wollew marked this conversation as resolved.
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
2 changes: 1 addition & 1 deletion tests/components/squeezebox/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

@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."""
Comment thread
wollew marked this conversation as resolved.
with patch("homeassistant.components.squeezebox.PLATFORMS", [Platform.SWITCH]):
yield

Expand Down
Loading