mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 01:37:08 +00:00
Add enum sensor for Sonos Power Source (#147449)
* feat: add power source sensor * fix: translations * fix:cleanup * fix: simpify * fix: improve coverage * fix: improve coverage * fix: add missing test * fix: call it charging_base * fix: disable entity by default * update snapshots * Update homeassistant/components/sonos/strings.json Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * fix: update test --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
1286b5d9d8
commit
345ec97dd5
@ -24,6 +24,20 @@ from .speaker import SonosSpeaker
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SONOS_POWER_SOURCE_BATTERY = "BATTERY"
|
||||
SONOS_POWER_SOURCE_CHARGING_RING = "SONOS_CHARGING_RING"
|
||||
SONOS_POWER_SOURCE_USB = "USB_POWER"
|
||||
|
||||
HA_POWER_SOURCE_BATTERY = "battery"
|
||||
HA_POWER_SOURCE_CHARGING_BASE = "charging_base"
|
||||
HA_POWER_SOURCE_USB = "usb"
|
||||
|
||||
power_source_map = {
|
||||
SONOS_POWER_SOURCE_BATTERY: HA_POWER_SOURCE_BATTERY,
|
||||
SONOS_POWER_SOURCE_CHARGING_RING: HA_POWER_SOURCE_CHARGING_BASE,
|
||||
SONOS_POWER_SOURCE_USB: HA_POWER_SOURCE_USB,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -42,9 +56,15 @@ async def async_setup_entry(
|
||||
|
||||
@callback
|
||||
def _async_create_battery_sensor(speaker: SonosSpeaker) -> None:
|
||||
_LOGGER.debug("Creating battery level sensor on %s", speaker.zone_name)
|
||||
entity = SonosBatteryEntity(speaker, config_entry)
|
||||
async_add_entities([entity])
|
||||
_LOGGER.debug(
|
||||
"Creating battery level and power source sensor on %s", speaker.zone_name
|
||||
)
|
||||
async_add_entities(
|
||||
[
|
||||
SonosBatteryEntity(speaker, config_entry),
|
||||
SonosPowerSourceEntity(speaker, config_entry),
|
||||
]
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_create_favorites_sensor(favorites: SonosFavorites) -> None:
|
||||
@ -101,6 +121,48 @@ class SonosBatteryEntity(SonosEntity, SensorEntity):
|
||||
return self.speaker.available and self.speaker.power_source is not None
|
||||
|
||||
|
||||
class SonosPowerSourceEntity(SonosEntity, SensorEntity):
|
||||
"""Representation of a Sonos Power Source entity."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.ENUM
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_entity_registry_enabled_default = False
|
||||
_attr_options = [
|
||||
HA_POWER_SOURCE_BATTERY,
|
||||
HA_POWER_SOURCE_CHARGING_BASE,
|
||||
HA_POWER_SOURCE_USB,
|
||||
]
|
||||
_attr_translation_key = "power_source"
|
||||
|
||||
def __init__(self, speaker: SonosSpeaker, config_entry: SonosConfigEntry) -> None:
|
||||
"""Initialize the power source sensor."""
|
||||
super().__init__(speaker, config_entry)
|
||||
self._attr_unique_id = f"{self.soco.uid}-power_source"
|
||||
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Poll the device for the current state."""
|
||||
await self.speaker.async_poll_battery()
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
"""Return the state of the sensor."""
|
||||
if not (power_source := self.speaker.power_source):
|
||||
return None
|
||||
if not (value := power_source_map.get(power_source)):
|
||||
_LOGGER.warning(
|
||||
"Unknown power source '%s' for speaker %s",
|
||||
power_source,
|
||||
self.speaker.zone_name,
|
||||
)
|
||||
return None
|
||||
return value
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return whether this entity is available."""
|
||||
return self.speaker.available and self.speaker.power_source is not None
|
||||
|
||||
|
||||
class SonosAudioInputFormatSensorEntity(SonosPollingEntity, SensorEntity):
|
||||
"""Representation of a Sonos audio import format sensor entity."""
|
||||
|
||||
|
@ -53,6 +53,14 @@
|
||||
"sensor": {
|
||||
"audio_input_format": {
|
||||
"name": "Audio input format"
|
||||
},
|
||||
"power_source": {
|
||||
"name": "Power source",
|
||||
"state": {
|
||||
"battery": "Battery",
|
||||
"charging_base": "Charging base",
|
||||
"usb": "USB"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
@ -1,20 +1,35 @@
|
||||
"""Tests for the Sonos battery sensor platform."""
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
from unittest.mock import PropertyMock, patch
|
||||
|
||||
import pytest
|
||||
from soco.exceptions import NotSupportedException
|
||||
|
||||
from homeassistant.components.sensor import SCAN_INTERVAL
|
||||
from homeassistant.components.sonos import DOMAIN
|
||||
from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE
|
||||
from homeassistant.components.sonos.sensor import (
|
||||
HA_POWER_SOURCE_BATTERY,
|
||||
HA_POWER_SOURCE_CHARGING_BASE,
|
||||
HA_POWER_SOURCE_USB,
|
||||
SensorDeviceClass,
|
||||
)
|
||||
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.const import (
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import entity_registry as er, translation
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .conftest import SonosMockEvent
|
||||
from .conftest import MockSoCo, SonosMockEvent
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
@ -42,8 +57,10 @@ async def test_entity_registry_supported(
|
||||
assert "media_player.zone_a" in entity_registry.entities
|
||||
assert "sensor.zone_a_battery" in entity_registry.entities
|
||||
assert "binary_sensor.zone_a_charging" in entity_registry.entities
|
||||
assert "sensor.zone_a_power_source" in entity_registry.entities
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_battery_attributes(
|
||||
hass: HomeAssistant, async_autosetup_sonos, soco, entity_registry: er.EntityRegistry
|
||||
) -> None:
|
||||
@ -60,6 +77,71 @@ async def test_battery_attributes(
|
||||
power_state.attributes.get(ATTR_BATTERY_POWER_SOURCE) == "SONOS_CHARGING_RING"
|
||||
)
|
||||
|
||||
power_source = entity_registry.entities["sensor.zone_a_power_source"]
|
||||
power_source_state = hass.states.get(power_source.entity_id)
|
||||
assert power_source_state.state == HA_POWER_SOURCE_CHARGING_BASE
|
||||
assert power_source_state.attributes.get("device_class") == SensorDeviceClass.ENUM
|
||||
assert power_source_state.attributes.get("options") == [
|
||||
HA_POWER_SOURCE_BATTERY,
|
||||
HA_POWER_SOURCE_CHARGING_BASE,
|
||||
HA_POWER_SOURCE_USB,
|
||||
]
|
||||
result = translation.async_translate_state(
|
||||
hass,
|
||||
power_source_state.state,
|
||||
Platform.SENSOR,
|
||||
DOMAIN,
|
||||
power_source.translation_key,
|
||||
None,
|
||||
)
|
||||
assert result == "Charging base"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_power_source_unknown_state(
|
||||
hass: HomeAssistant,
|
||||
async_setup_sonos: Callable[[], Coroutine[Any, Any, None]],
|
||||
soco: MockSoCo,
|
||||
entity_registry: er.EntityRegistry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test bad value for power source."""
|
||||
soco.get_battery_info.return_value = {
|
||||
"Level": 100,
|
||||
"PowerSource": "BAD_POWER_SOURCE",
|
||||
}
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
await async_setup_sonos()
|
||||
assert "Unknown power source" in caplog.text
|
||||
assert "BAD_POWER_SOURCE" in caplog.text
|
||||
assert "Zone A" in caplog.text
|
||||
|
||||
power_source = entity_registry.entities["sensor.zone_a_power_source"]
|
||||
power_source_state = hass.states.get(power_source.entity_id)
|
||||
assert power_source_state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_power_source_none(
|
||||
hass: HomeAssistant,
|
||||
async_setup_sonos: Callable[[], Coroutine[Any, Any, None]],
|
||||
soco: MockSoCo,
|
||||
entity_registry: er.EntityRegistry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test none value for power source."""
|
||||
soco.get_battery_info.return_value = {
|
||||
"Level": 100,
|
||||
"PowerSource": None,
|
||||
}
|
||||
|
||||
await async_setup_sonos()
|
||||
|
||||
power_source = entity_registry.entities["sensor.zone_a_power_source"]
|
||||
power_source_state = hass.states.get(power_source.entity_id)
|
||||
assert power_source_state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_battery_on_s1(
|
||||
hass: HomeAssistant,
|
||||
|
Loading…
x
Reference in New Issue
Block a user