mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Add an RSSI sensor to the LIFX integration (#80993)
This commit is contained in:
parent
0d4b1866a7
commit
dde763418a
@ -57,7 +57,13 @@ CONFIG_SCHEMA = vol.All(
|
||||
)
|
||||
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LIGHT, Platform.SELECT]
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.LIGHT,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
]
|
||||
DISCOVERY_INTERVAL = timedelta(minutes=15)
|
||||
MIGRATION_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
@ -199,6 +205,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
coordinator.async_setup()
|
||||
try:
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
await coordinator.sensor_coordinator.async_config_entry_first_refresh()
|
||||
except ConfigEntryNotReady:
|
||||
connection.async_stop()
|
||||
raise
|
||||
|
@ -12,8 +12,8 @@ from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, HEV_CYCLE_STATE
|
||||
from .coordinator import LIFXUpdateCoordinator
|
||||
from .entity import LIFXEntity
|
||||
from .coordinator import LIFXSensorUpdateCoordinator, LIFXUpdateCoordinator
|
||||
from .entity import LIFXSensorEntity
|
||||
from .util import lifx_features
|
||||
|
||||
HEV_CYCLE_STATE_SENSOR = BinarySensorEntityDescription(
|
||||
@ -34,28 +34,28 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
LIFXHevCycleBinarySensorEntity(
|
||||
coordinator=coordinator, description=HEV_CYCLE_STATE_SENSOR
|
||||
coordinator=coordinator.sensor_coordinator,
|
||||
description=HEV_CYCLE_STATE_SENSOR,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class LIFXHevCycleBinarySensorEntity(LIFXEntity, BinarySensorEntity):
|
||||
class LIFXHevCycleBinarySensorEntity(LIFXSensorEntity, BinarySensorEntity):
|
||||
"""LIFX HEV cycle state binary sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: LIFXUpdateCoordinator,
|
||||
coordinator: LIFXSensorUpdateCoordinator,
|
||||
description: BinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise the sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
self._attr_name = description.name
|
||||
self._attr_unique_id = f"{coordinator.serial_number}_{description.key}"
|
||||
self._attr_unique_id = f"{coordinator.parent.serial_number}_{description.key}"
|
||||
self._async_update_attrs()
|
||||
|
||||
@callback
|
||||
|
@ -12,8 +12,8 @@ from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, IDENTIFY, RESTART
|
||||
from .coordinator import LIFXUpdateCoordinator
|
||||
from .entity import LIFXEntity
|
||||
from .coordinator import LIFXSensorUpdateCoordinator, LIFXUpdateCoordinator
|
||||
from .entity import LIFXSensorEntity
|
||||
|
||||
RESTART_BUTTON_DESCRIPTION = ButtonEntityDescription(
|
||||
key=RESTART,
|
||||
@ -38,20 +38,22 @@ async def async_setup_entry(
|
||||
domain_data = hass.data[DOMAIN]
|
||||
coordinator: LIFXUpdateCoordinator = domain_data[entry.entry_id]
|
||||
async_add_entities(
|
||||
cls(coordinator) for cls in (LIFXRestartButton, LIFXIdentifyButton)
|
||||
cls(coordinator.sensor_coordinator)
|
||||
for cls in (LIFXRestartButton, LIFXIdentifyButton)
|
||||
)
|
||||
|
||||
|
||||
class LIFXButton(LIFXEntity, ButtonEntity):
|
||||
class LIFXButton(LIFXSensorEntity, ButtonEntity):
|
||||
"""Base LIFX button."""
|
||||
|
||||
_attr_has_entity_name: bool = True
|
||||
_attr_should_poll: bool = False
|
||||
|
||||
def __init__(self, coordinator: LIFXUpdateCoordinator) -> None:
|
||||
def __init__(self, coordinator: LIFXSensorUpdateCoordinator) -> None:
|
||||
"""Initialise a LIFX button."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.serial_number}_{self.entity_description.key}"
|
||||
f"{coordinator.parent.serial_number}_{self.entity_description.key}"
|
||||
)
|
||||
|
||||
|
||||
|
@ -35,6 +35,7 @@ ATTR_INDICATION = "indication"
|
||||
ATTR_INFRARED = "infrared"
|
||||
ATTR_POWER = "power"
|
||||
ATTR_REMAINING = "remaining"
|
||||
ATTR_RSSI = "rssi"
|
||||
ATTR_ZONES = "zones"
|
||||
|
||||
ATTR_THEME = "theme"
|
||||
|
@ -2,9 +2,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from datetime import timedelta
|
||||
from enum import IntEnum
|
||||
from functools import partial
|
||||
from math import floor, log10
|
||||
from typing import Any, cast
|
||||
|
||||
from aiolifx.aiolifx import (
|
||||
@ -15,8 +17,13 @@ from aiolifx.aiolifx import (
|
||||
)
|
||||
from aiolifx.connection import LIFXConnection
|
||||
from aiolifx_themes.themes import ThemeLibrary, ThemePainter
|
||||
from awesomeversion import AwesomeVersion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import (
|
||||
SIGNAL_STRENGTH_DECIBELS,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@ -41,8 +48,11 @@ from .util import (
|
||||
lifx_features,
|
||||
)
|
||||
|
||||
LIGHT_UPDATE_INTERVAL = 10
|
||||
SENSOR_UPDATE_INTERVAL = 30
|
||||
REQUEST_REFRESH_DELAY = 0.35
|
||||
LIFX_IDENTIFY_DELAY = 3.0
|
||||
RSSI_DBM_FW = AwesomeVersion("2.77")
|
||||
|
||||
|
||||
class FirmwareEffect(IntEnum):
|
||||
@ -69,14 +79,13 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator):
|
||||
self.device: Light = connection.device
|
||||
self.lock = asyncio.Lock()
|
||||
self.active_effect = FirmwareEffect.OFF
|
||||
update_interval = timedelta(seconds=10)
|
||||
self.last_used_theme: str = ""
|
||||
self.sensor_coordinator = LIFXSensorUpdateCoordinator(hass, self, title)
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{title} ({self.device.ip_addr})",
|
||||
update_interval=update_interval,
|
||||
update_interval=timedelta(seconds=LIGHT_UPDATE_INTERVAL),
|
||||
# We don't want an immediate refresh since the device
|
||||
# takes a moment to reflect the state change
|
||||
request_refresh_debouncer=Debouncer(
|
||||
@ -112,11 +121,6 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Return the label of the bulb."""
|
||||
return cast(str, self.device.label)
|
||||
|
||||
@property
|
||||
def current_infrared_brightness(self) -> str | None:
|
||||
"""Return the current infrared brightness as a string."""
|
||||
return infrared_brightness_value_to_option(self.device.infrared_brightness)
|
||||
|
||||
async def diagnostics(self) -> dict[str, Any]:
|
||||
"""Return diagnostic information about the device."""
|
||||
features = lifx_features(self.device)
|
||||
@ -162,19 +166,6 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator):
|
||||
platform, DOMAIN, f"{self.serial_number}_{key}"
|
||||
)
|
||||
|
||||
async def async_identify_bulb(self) -> None:
|
||||
"""Identify the device by flashing it three times."""
|
||||
bulb: Light = self.device
|
||||
if bulb.power_level:
|
||||
# just flash the bulb for three seconds
|
||||
await self.async_set_waveform_optional(value=IDENTIFY_WAVEFORM)
|
||||
return
|
||||
# Turn the bulb on first, flash for 3 seconds, then turn off
|
||||
await self.async_set_power(state=True, duration=1)
|
||||
await self.async_set_waveform_optional(value=IDENTIFY_WAVEFORM)
|
||||
await asyncio.sleep(LIFX_IDENTIFY_DELAY)
|
||||
await self.async_set_power(state=False, duration=1)
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch all device data from the api."""
|
||||
async with self.lock:
|
||||
@ -203,12 +194,6 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator):
|
||||
await self.async_get_color_zones()
|
||||
await self.async_get_multizone_effect()
|
||||
|
||||
if lifx_features(self.device)["hev"]:
|
||||
await self.async_get_hev_cycle()
|
||||
|
||||
if lifx_features(self.device)["infrared"]:
|
||||
response = await async_execute_lifx(self.device.get_infrared)
|
||||
|
||||
async def async_get_color_zones(self) -> None:
|
||||
"""Get updated color information for each zone."""
|
||||
zone = 0
|
||||
@ -234,17 +219,6 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator):
|
||||
f"Timeout getting color zones from {self.name}"
|
||||
) from ex
|
||||
|
||||
def async_get_hev_cycle_state(self) -> bool | None:
|
||||
"""Return the current HEV cycle state."""
|
||||
if self.device.hev_cycle is None:
|
||||
return None
|
||||
return bool(self.device.hev_cycle.get(ATTR_REMAINING, 0) > 0)
|
||||
|
||||
async def async_get_hev_cycle(self) -> None:
|
||||
"""Update the HEV cycle status from a LIFX Clean bulb."""
|
||||
if lifx_features(self.device)["hev"]:
|
||||
await async_execute_lifx(self.device.get_hev_cycle)
|
||||
|
||||
async def async_set_waveform_optional(
|
||||
self, value: dict[str, Any], rapid: bool = False
|
||||
) -> None:
|
||||
@ -381,6 +355,109 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Return the enum value of the currently active firmware effect."""
|
||||
return self.active_effect.value
|
||||
|
||||
|
||||
class LIFXSensorUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""DataUpdateCoordinator to gather data for a specific lifx device."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
parent: LIFXUpdateCoordinator,
|
||||
title: str,
|
||||
) -> None:
|
||||
"""Initialize DataUpdateCoordinator."""
|
||||
self.parent: LIFXUpdateCoordinator = parent
|
||||
self.device: Light = parent.device
|
||||
self._update_rssi: bool = False
|
||||
self._rssi: int = 0
|
||||
self.last_used_theme: str = ""
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{title} Sensors ({self.device.ip_addr})",
|
||||
update_interval=timedelta(seconds=SENSOR_UPDATE_INTERVAL),
|
||||
# Refresh immediately because the changes are not visible
|
||||
request_refresh_debouncer=Debouncer(
|
||||
hass, _LOGGER, cooldown=0, immediate=True
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def rssi(self) -> int:
|
||||
"""Return stored RSSI value."""
|
||||
return self._rssi
|
||||
|
||||
@property
|
||||
def rssi_uom(self) -> str:
|
||||
"""Return the RSSI unit of measurement."""
|
||||
if AwesomeVersion(self.device.host_firmware_version) <= RSSI_DBM_FW:
|
||||
return SIGNAL_STRENGTH_DECIBELS
|
||||
|
||||
return SIGNAL_STRENGTH_DECIBELS_MILLIWATT
|
||||
|
||||
@property
|
||||
def current_infrared_brightness(self) -> str | None:
|
||||
"""Return the current infrared brightness as a string."""
|
||||
return infrared_brightness_value_to_option(self.device.infrared_brightness)
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch all device data from the api."""
|
||||
|
||||
if self._update_rssi is True:
|
||||
await self.async_update_rssi()
|
||||
|
||||
if lifx_features(self.device)["hev"]:
|
||||
await self.async_get_hev_cycle()
|
||||
|
||||
if lifx_features(self.device)["infrared"]:
|
||||
await async_execute_lifx(self.device.get_infrared)
|
||||
|
||||
async def async_set_infrared_brightness(self, option: str) -> None:
|
||||
"""Set infrared brightness."""
|
||||
infrared_brightness = infrared_brightness_option_to_value(option)
|
||||
await async_execute_lifx(partial(self.device.set_infrared, infrared_brightness))
|
||||
|
||||
async def async_identify_bulb(self) -> None:
|
||||
"""Identify the device by flashing it three times."""
|
||||
bulb: Light = self.device
|
||||
if bulb.power_level:
|
||||
# just flash the bulb for three seconds
|
||||
await self.parent.async_set_waveform_optional(value=IDENTIFY_WAVEFORM)
|
||||
return
|
||||
# Turn the bulb on first, flash for 3 seconds, then turn off
|
||||
await self.parent.async_set_power(state=True, duration=1)
|
||||
await self.parent.async_set_waveform_optional(value=IDENTIFY_WAVEFORM)
|
||||
await asyncio.sleep(LIFX_IDENTIFY_DELAY)
|
||||
await self.parent.async_set_power(state=False, duration=1)
|
||||
|
||||
def async_enable_rssi_updates(self) -> Callable[[], None]:
|
||||
"""Enable RSSI signal strength updates."""
|
||||
|
||||
@callback
|
||||
def _async_disable_rssi_updates() -> None:
|
||||
"""Disable RSSI updates when sensor removed."""
|
||||
self._update_rssi = False
|
||||
|
||||
self._update_rssi = True
|
||||
return _async_disable_rssi_updates
|
||||
|
||||
async def async_update_rssi(self) -> None:
|
||||
"""Update RSSI value."""
|
||||
resp = await async_execute_lifx(self.device.get_wifiinfo)
|
||||
self._rssi = int(floor(10 * log10(resp.signal) + 0.5))
|
||||
|
||||
def async_get_hev_cycle_state(self) -> bool | None:
|
||||
"""Return the current HEV cycle state."""
|
||||
if self.device.hev_cycle is None:
|
||||
return None
|
||||
return bool(self.device.hev_cycle.get(ATTR_REMAINING, 0) > 0)
|
||||
|
||||
async def async_get_hev_cycle(self) -> None:
|
||||
"""Update the HEV cycle status from a LIFX Clean bulb."""
|
||||
if lifx_features(self.device)["hev"]:
|
||||
await async_execute_lifx(self.device.get_hev_cycle)
|
||||
|
||||
async def async_set_hev_cycle_state(self, enable: bool, duration: int = 0) -> None:
|
||||
"""Start or stop an HEV cycle on a LIFX Clean bulb."""
|
||||
if lifx_features(self.device)["hev"]:
|
||||
@ -388,13 +465,8 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator):
|
||||
partial(self.device.set_hev_cycle, enable=enable, duration=duration)
|
||||
)
|
||||
|
||||
async def async_set_infrared_brightness(self, option: str) -> None:
|
||||
"""Set infrared brightness."""
|
||||
infrared_brightness = infrared_brightness_option_to_value(option)
|
||||
await async_execute_lifx(partial(self.device.set_infrared, infrared_brightness))
|
||||
|
||||
async def async_apply_theme(self, theme_name: str) -> None:
|
||||
"""Apply the selected theme to the device."""
|
||||
self.last_used_theme = theme_name
|
||||
theme = ThemeLibrary().get_theme(theme_name)
|
||||
await ThemePainter(self.hass.loop).paint(theme, [self.device])
|
||||
await ThemePainter(self.hass.loop).paint(theme, [self.parent.device])
|
||||
|
@ -8,7 +8,7 @@ from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LIFXUpdateCoordinator
|
||||
from .coordinator import LIFXSensorUpdateCoordinator, LIFXUpdateCoordinator
|
||||
|
||||
|
||||
class LIFXEntity(CoordinatorEntity[LIFXUpdateCoordinator]):
|
||||
@ -26,3 +26,20 @@ class LIFXEntity(CoordinatorEntity[LIFXUpdateCoordinator]):
|
||||
model=products.product_map.get(self.bulb.product, "LIFX Bulb"),
|
||||
sw_version=self.bulb.host_firmware_version,
|
||||
)
|
||||
|
||||
|
||||
class LIFXSensorEntity(CoordinatorEntity[LIFXSensorUpdateCoordinator]):
|
||||
"""Representation of a LIFX sensor entity with a sensor coordinator."""
|
||||
|
||||
def __init__(self, coordinator: LIFXSensorUpdateCoordinator) -> None:
|
||||
"""Initialise the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.bulb = coordinator.parent.device
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.parent.serial_number)},
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, coordinator.parent.mac_address)},
|
||||
manufacturer="LIFX",
|
||||
name=coordinator.parent.label,
|
||||
model=products.product_map.get(self.bulb.product, "LIFX Bulb"),
|
||||
sw_version=self.bulb.host_firmware_version,
|
||||
)
|
||||
|
@ -271,7 +271,9 @@ class LIFXLight(LIFXEntity, LightEntity):
|
||||
"This device does not support setting HEV cycle state"
|
||||
)
|
||||
|
||||
await self.coordinator.async_set_hev_cycle_state(power, duration or 0)
|
||||
await self.coordinator.sensor_coordinator.async_set_hev_cycle_state(
|
||||
power, duration or 0
|
||||
)
|
||||
await self.update_during_transition(duration or 0)
|
||||
|
||||
async def set_power(
|
||||
|
@ -15,8 +15,8 @@ from .const import (
|
||||
INFRARED_BRIGHTNESS,
|
||||
INFRARED_BRIGHTNESS_VALUES_MAP,
|
||||
)
|
||||
from .coordinator import LIFXUpdateCoordinator
|
||||
from .entity import LIFXEntity
|
||||
from .coordinator import LIFXSensorUpdateCoordinator, LIFXUpdateCoordinator
|
||||
from .entity import LIFXSensorEntity
|
||||
from .util import lifx_features
|
||||
|
||||
THEME_NAMES = [theme_name.lower() for theme_name in ThemeLibrary().themes]
|
||||
@ -41,36 +41,41 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up LIFX from a config entry."""
|
||||
coordinator: LIFXUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
entities: list[LIFXEntity] = []
|
||||
|
||||
entities: list[LIFXSensorEntity] = []
|
||||
|
||||
if lifx_features(coordinator.device)["infrared"]:
|
||||
entities.append(
|
||||
LIFXInfraredBrightnessSelectEntity(
|
||||
coordinator=coordinator, description=INFRARED_BRIGHTNESS_ENTITY
|
||||
coordinator.sensor_coordinator, description=INFRARED_BRIGHTNESS_ENTITY
|
||||
)
|
||||
)
|
||||
|
||||
if lifx_features(coordinator.device)["multizone"] is True:
|
||||
entities.append(
|
||||
LIFXThemeSelectEntity(coordinator=coordinator, description=THEME_ENTITY)
|
||||
LIFXThemeSelectEntity(
|
||||
coordinator.sensor_coordinator, description=THEME_ENTITY
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class LIFXInfraredBrightnessSelectEntity(LIFXEntity, SelectEntity):
|
||||
class LIFXInfraredBrightnessSelectEntity(LIFXSensorEntity, SelectEntity):
|
||||
"""LIFX Nightvision infrared brightness configuration entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, coordinator: LIFXUpdateCoordinator, description: SelectEntityDescription
|
||||
self,
|
||||
coordinator: LIFXSensorUpdateCoordinator,
|
||||
description: SelectEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise the IR brightness config entity."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_name = description.name
|
||||
self._attr_unique_id = f"{coordinator.serial_number}_{description.key}"
|
||||
self._attr_unique_id = f"{coordinator.parent.serial_number}_{description.key}"
|
||||
self._attr_current_option = coordinator.current_infrared_brightness
|
||||
|
||||
@callback
|
||||
@ -89,21 +94,22 @@ class LIFXInfraredBrightnessSelectEntity(LIFXEntity, SelectEntity):
|
||||
await self.coordinator.async_set_infrared_brightness(option)
|
||||
|
||||
|
||||
class LIFXThemeSelectEntity(LIFXEntity, SelectEntity):
|
||||
class LIFXThemeSelectEntity(LIFXSensorEntity, SelectEntity):
|
||||
"""Theme entity for LIFX multizone devices."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(
|
||||
self, coordinator: LIFXUpdateCoordinator, description: SelectEntityDescription
|
||||
self,
|
||||
coordinator: LIFXSensorUpdateCoordinator,
|
||||
description: SelectEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise the theme selection entity."""
|
||||
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_name = description.name
|
||||
self._attr_unique_id = f"{coordinator.serial_number}_{description.key}"
|
||||
self._attr_unique_id = f"{coordinator.parent.serial_number}_{description.key}"
|
||||
self._attr_current_option = None
|
||||
|
||||
@callback
|
||||
|
74
homeassistant/components/lifx/sensor.py
Normal file
74
homeassistant/components/lifx/sensor.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""Sensors for LIFX lights."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ATTR_RSSI, DOMAIN
|
||||
from .coordinator import LIFXSensorUpdateCoordinator, LIFXUpdateCoordinator
|
||||
from .entity import LIFXSensorEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
RSSI_SENSOR = SensorEntityDescription(
|
||||
key=ATTR_RSSI,
|
||||
name="RSSI",
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up LIFX sensor from config entry."""
|
||||
coordinator: LIFXUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities([LIFXRssiSensor(coordinator.sensor_coordinator, RSSI_SENSOR)])
|
||||
|
||||
|
||||
class LIFXRssiSensor(LIFXSensorEntity, SensorEntity):
|
||||
"""LIFX RSSI sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: LIFXSensorUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise the RSSI sensor."""
|
||||
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_name = description.name
|
||||
self._attr_unique_id = f"{coordinator.parent.serial_number}_{description.key}"
|
||||
self._attr_native_unit_of_measurement = coordinator.rssi_uom
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._async_update_attrs()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Handle coordinator updates."""
|
||||
self._attr_native_value = self.coordinator.rssi
|
||||
|
||||
@callback
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Enable RSSI updates."""
|
||||
self.async_on_remove(self.coordinator.async_enable_rssi_updates())
|
||||
return await super().async_added_to_hass()
|
@ -91,6 +91,7 @@ def _mocked_bulb() -> Light:
|
||||
bulb.set_power = MockLifxCommand(bulb)
|
||||
bulb.set_color = MockLifxCommand(bulb)
|
||||
bulb.get_hostfirmware = MockLifxCommand(bulb)
|
||||
bulb.get_wifiinfo = MockLifxCommand(bulb, signal=100)
|
||||
bulb.get_version = MockLifxCommand(bulb)
|
||||
bulb.set_waveform_optional = MockLifxCommand(bulb)
|
||||
bulb.product = 1 # LIFX Original 1000
|
||||
@ -168,6 +169,12 @@ def _mocked_tile() -> Light:
|
||||
return bulb
|
||||
|
||||
|
||||
def _mocked_bulb_old_firmware() -> Light:
|
||||
bulb = _mocked_bulb()
|
||||
bulb.host_firmware_version = "2.77"
|
||||
return bulb
|
||||
|
||||
|
||||
def _mocked_bulb_new_firmware() -> Light:
|
||||
bulb = _mocked_bulb()
|
||||
bulb.host_firmware_version = "3.90"
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Test the lifx binary sensor platwform."""
|
||||
"""Test the lifx binary sensor platform."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
131
tests/components/lifx/test_sensor.py
Normal file
131
tests/components/lifx/test_sensor.py
Normal file
@ -0,0 +1,131 @@
|
||||
"""Test the LIFX sensor platform."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components import lifx
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
CONF_HOST,
|
||||
SIGNAL_STRENGTH_DECIBELS,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import (
|
||||
DEFAULT_ENTRY_TITLE,
|
||||
IP_ADDRESS,
|
||||
MAC_ADDRESS,
|
||||
_mocked_bulb,
|
||||
_mocked_bulb_old_firmware,
|
||||
_patch_config_flow_try_connect,
|
||||
_patch_device,
|
||||
_patch_discovery,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_rssi_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test LIFX RSSI sensor entity."""
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=lifx.DOMAIN,
|
||||
title=DEFAULT_ENTRY_TITLE,
|
||||
data={CONF_HOST: IP_ADDRESS},
|
||||
unique_id=MAC_ADDRESS,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
bulb = _mocked_bulb()
|
||||
with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
|
||||
device=bulb
|
||||
), _patch_device(device=bulb):
|
||||
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "sensor.my_bulb_rssi"
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
entry = entity_registry.entities.get(entity_id)
|
||||
assert entry
|
||||
assert entry.disabled
|
||||
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
# Test enabling entity
|
||||
updated_entry = entity_registry.async_update_entity(
|
||||
entry.entity_id, **{"disabled_by": None}
|
||||
)
|
||||
|
||||
with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
|
||||
device=bulb
|
||||
), _patch_device(device=bulb):
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert updated_entry != entry
|
||||
assert updated_entry.disabled is False
|
||||
assert updated_entry.unit_of_measurement == SIGNAL_STRENGTH_DECIBELS_MILLIWATT
|
||||
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
rssi = hass.states.get(entity_id)
|
||||
assert (
|
||||
rssi.attributes[ATTR_UNIT_OF_MEASUREMENT] == SIGNAL_STRENGTH_DECIBELS_MILLIWATT
|
||||
)
|
||||
assert rssi.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.SIGNAL_STRENGTH
|
||||
assert rssi.attributes["state_class"] == SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
async def test_rssi_sensor_old_firmware(hass: HomeAssistant) -> None:
|
||||
"""Test LIFX RSSI sensor entity."""
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=lifx.DOMAIN,
|
||||
title=DEFAULT_ENTRY_TITLE,
|
||||
data={CONF_HOST: IP_ADDRESS},
|
||||
unique_id=MAC_ADDRESS,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
bulb = _mocked_bulb_old_firmware()
|
||||
with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
|
||||
device=bulb
|
||||
), _patch_device(device=bulb):
|
||||
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "sensor.my_bulb_rssi"
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
entry = entity_registry.entities.get(entity_id)
|
||||
assert entry
|
||||
assert entry.disabled
|
||||
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
# Test enabling entity
|
||||
updated_entry = entity_registry.async_update_entity(
|
||||
entry.entity_id, **{"disabled_by": None}
|
||||
)
|
||||
|
||||
with _patch_discovery(device=bulb), _patch_config_flow_try_connect(
|
||||
device=bulb
|
||||
), _patch_device(device=bulb):
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert updated_entry != entry
|
||||
assert updated_entry.disabled is False
|
||||
assert updated_entry.unit_of_measurement == SIGNAL_STRENGTH_DECIBELS
|
||||
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
rssi = hass.states.get(entity_id)
|
||||
assert rssi.attributes[ATTR_UNIT_OF_MEASUREMENT] == SIGNAL_STRENGTH_DECIBELS
|
||||
assert rssi.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.SIGNAL_STRENGTH
|
||||
assert rssi.attributes["state_class"] == SensorStateClass.MEASUREMENT
|
Loading…
x
Reference in New Issue
Block a user