Add battery sensor to Elgato (#87680)
This commit is contained in:
parent
e749521b29
commit
6582367917
9 changed files with 227 additions and 4 deletions
|
@ -6,7 +6,7 @@ from homeassistant.core import HomeAssistant
|
|||
from .const import DOMAIN
|
||||
from .coordinator import ElgatoDataUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.BUTTON, Platform.LIGHT]
|
||||
PLATFORMS = [Platform.BUTTON, Platform.LIGHT, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""DataUpdateCoordinator for Elgato."""
|
||||
from dataclasses import dataclass
|
||||
|
||||
from elgato import Elgato, ElgatoConnectionError, Info, Settings, State
|
||||
from elgato import BatteryInfo, Elgato, ElgatoConnectionError, Info, Settings, State
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
|
@ -16,6 +16,7 @@ from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
|||
class ElgatoData:
|
||||
"""Elgato data stored in the DataUpdateCoordinator."""
|
||||
|
||||
battery: BatteryInfo | None
|
||||
info: Info
|
||||
settings: Settings
|
||||
state: State
|
||||
|
@ -25,6 +26,7 @@ class ElgatoDataUpdateCoordinator(DataUpdateCoordinator[ElgatoData]):
|
|||
"""Class to manage fetching Elgato data."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
has_battery: bool | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
|
@ -44,7 +46,11 @@ class ElgatoDataUpdateCoordinator(DataUpdateCoordinator[ElgatoData]):
|
|||
async def _async_update_data(self) -> ElgatoData:
|
||||
"""Fetch data from the Elgato device."""
|
||||
try:
|
||||
if self.has_battery is None:
|
||||
self.has_battery = await self.client.has_battery()
|
||||
|
||||
return ElgatoData(
|
||||
battery=await self.client.battery() if self.has_battery else None,
|
||||
info=await self.client.info(),
|
||||
settings=await self.client.settings(),
|
||||
state=await self.client.state(),
|
||||
|
|
94
homeassistant/components/elgato/sensor.py
Normal file
94
homeassistant/components/elgato/sensor.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
"""Support for Elgato sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ElgatoData, ElgatoDataUpdateCoordinator
|
||||
from .entity import ElgatoEntity
|
||||
|
||||
|
||||
@dataclass
|
||||
class ElgatoEntityDescriptionMixin:
|
||||
"""Mixin values for Elgato entities."""
|
||||
|
||||
value_fn: Callable[[ElgatoData], float | int | None]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ElgatoSensorEntityDescription(
|
||||
SensorEntityDescription, ElgatoEntityDescriptionMixin
|
||||
):
|
||||
"""Class describing Elgato sensor entities."""
|
||||
|
||||
has_fn: Callable[[ElgatoData], bool] = lambda _: True
|
||||
|
||||
|
||||
SENSORS = [
|
||||
ElgatoSensorEntityDescription(
|
||||
key="battery",
|
||||
name="Battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
has_fn=lambda x: x.battery is not None,
|
||||
value_fn=lambda x: x.battery.level if x.battery else None,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Elgato sensor based on a config entry."""
|
||||
coordinator: ElgatoDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
ElgatoSensorEntity(
|
||||
coordinator=coordinator,
|
||||
description=description,
|
||||
)
|
||||
for description in SENSORS
|
||||
if description.has_fn(coordinator.data)
|
||||
)
|
||||
|
||||
|
||||
class ElgatoSensorEntity(ElgatoEntity, SensorEntity):
|
||||
"""Representation of a Elgato sensor."""
|
||||
|
||||
entity_description: ElgatoSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ElgatoDataUpdateCoordinator,
|
||||
description: ElgatoSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initiate Elgato sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.data.info.serial_number}_{description.key}"
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | int | None:
|
||||
"""Return the sensor value."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
|
@ -2,14 +2,14 @@
|
|||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from elgato import Info, Settings, State
|
||||
from elgato import BatteryInfo, ElgatoNoBatteryError, Info, Settings, State
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.elgato.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
from tests.common import MockConfigEntry, get_fixture_path, load_fixture
|
||||
from tests.components.light.conftest import mock_light_profiles # noqa: F401
|
||||
|
||||
|
||||
|
@ -90,6 +90,17 @@ def mock_elgato(
|
|||
elgato.settings.return_value = Settings.parse_raw(
|
||||
load_fixture(f"{device_fixtures}/settings.json", DOMAIN)
|
||||
)
|
||||
|
||||
# This may, or may not, be a battery-powered device
|
||||
if get_fixture_path(f"{device_fixtures}/battery.json", DOMAIN).exists():
|
||||
elgato.has_battery.return_value = True
|
||||
elgato.battery.return_value = BatteryInfo.parse_raw(
|
||||
load_fixture(f"{device_fixtures}/battery.json", DOMAIN)
|
||||
)
|
||||
else:
|
||||
elgato.has_battery.return_value = False
|
||||
elgato.battery.side_effect = ElgatoNoBatteryError
|
||||
|
||||
yield elgato
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"powerSource": 1,
|
||||
"level": 78.57,
|
||||
"status": 2,
|
||||
"currentBatteryVoltage": 3860,
|
||||
"inputChargeVoltage": 4208,
|
||||
"inputChargeCurrent": 3008
|
||||
}
|
16
tests/components/elgato/fixtures/key-light-mini/info.json
Normal file
16
tests/components/elgato/fixtures/key-light-mini/info.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"productName": "Elgato Key Light Mini",
|
||||
"hardwareBoardType": 202,
|
||||
"hardwareRevision": 0.1,
|
||||
"macAddress": "11:22:33:44:55:66",
|
||||
"firmwareBuildNumber": 229,
|
||||
"firmwareVersion": "1.0.4",
|
||||
"serialNumber": "GW24L1A02987",
|
||||
"displayName": "Frenck",
|
||||
"features": ["lights"],
|
||||
"wifi-info": {
|
||||
"ssid": "Frenck-IoT",
|
||||
"frequencyMHz": 2400,
|
||||
"rssi": -41
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"powerOnBehavior": 1,
|
||||
"powerOnBrightness": 20,
|
||||
"powerOnTemperature": 230,
|
||||
"switchOnDurationMs": 100,
|
||||
"switchOffDurationMs": 300,
|
||||
"colorChangeDurationMs": 100,
|
||||
"battery": {
|
||||
"energySaving": {
|
||||
"enable": 0,
|
||||
"minimumBatteryLevel": 15.0,
|
||||
"disableWifi": 0,
|
||||
"adjustBrightness": {
|
||||
"enable": 0,
|
||||
"brightness": 10.0
|
||||
}
|
||||
},
|
||||
"bypass": 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"on": 1,
|
||||
"brightness": 21,
|
||||
"temperature": 297
|
||||
}
|
63
tests/components/elgato/test_sensor.py
Normal file
63
tests/components/elgato/test_sensor.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
"""Tests for the Elgato sensor platform."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.elgato.const import DOMAIN
|
||||
from homeassistant.components.sensor import (
|
||||
ATTR_STATE_CLASS,
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
ATTR_ICON,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
PERCENTAGE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixtures", ["key-light-mini"])
|
||||
@pytest.mark.usefixtures(
|
||||
"device_fixtures",
|
||||
"entity_registry_enabled_by_default",
|
||||
"init_integration",
|
||||
"mock_elgato",
|
||||
)
|
||||
async def test_battery_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the Elgato battery sensor."""
|
||||
device_registry = dr.async_get(hass)
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
state = hass.states.get("sensor.frenck_battery")
|
||||
assert state
|
||||
assert state.state == "78.57"
|
||||
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.BATTERY
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck Battery"
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
|
||||
assert not state.attributes.get(ATTR_ICON)
|
||||
|
||||
entry = entity_registry.async_get("sensor.frenck_battery")
|
||||
assert entry
|
||||
assert entry.unique_id == "GW24L1A02987_battery"
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
assert entry.options == {"sensor": {"suggested_display_precision": 0}}
|
||||
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
assert device_entry.configuration_url is None
|
||||
assert device_entry.connections == {
|
||||
(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")
|
||||
}
|
||||
assert device_entry.entry_type is None
|
||||
assert device_entry.identifiers == {(DOMAIN, "GW24L1A02987")}
|
||||
assert device_entry.manufacturer == "Elgato"
|
||||
assert device_entry.model == "Elgato Key Light Mini"
|
||||
assert device_entry.name == "Frenck"
|
||||
assert device_entry.sw_version == "1.0.4 (229)"
|
||||
assert device_entry.hw_version == "202"
|
Loading…
Add table
Add a link
Reference in a new issue