mirror of
https://github.com/home-assistant/core
synced 2024-08-02 23:40:32 +02:00
Move lock and devicelock attributes into sensors for all AVM Fritz!Smarthome entities (#60426)
This commit is contained in:
parent
b3f3e7259e
commit
9deebaa65f
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@ -17,17 +18,8 @@ from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
ATTR_STATE_DEVICE_LOCKED,
|
||||
ATTR_STATE_LOCKED,
|
||||
CONF_CONNECTIONS,
|
||||
CONF_COORDINATOR,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .const import CONF_CONNECTIONS, CONF_COORDINATOR, DOMAIN, LOGGER, PLATFORMS
|
||||
from .coordinator import FritzboxDataUpdateCoordinator
|
||||
from .model import FritzExtraAttributes
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
@ -65,6 +57,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"Migrating unique_id [%s] to [%s]", entry.unique_id, new_unique_id
|
||||
)
|
||||
return {"new_unique_id": new_unique_id}
|
||||
|
||||
if entry.domain == BINARY_SENSOR_DOMAIN and "_" not in entry.unique_id:
|
||||
new_unique_id = f"{entry.unique_id}_alarm"
|
||||
LOGGER.info(
|
||||
"Migrating unique_id [%s] to [%s]", entry.unique_id, new_unique_id
|
||||
)
|
||||
return {"new_unique_id": new_unique_id}
|
||||
return None
|
||||
|
||||
await async_migrate_entries(hass, entry.entry_id, _update_unique_id)
|
||||
@ -138,11 +137,3 @@ class FritzBoxEntity(CoordinatorEntity):
|
||||
sw_version=self.device.fw_version,
|
||||
configuration_url=self.coordinator.configuration_url,
|
||||
)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> FritzExtraAttributes:
|
||||
"""Return the state attributes of the device."""
|
||||
return {
|
||||
ATTR_STATE_DEVICE_LOCKED: self.device.device_lock,
|
||||
ATTR_STATE_LOCKED: self.device.lock,
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ENTITY_CATEGORY_CONFIG
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
@ -44,6 +45,22 @@ BINARY_SENSOR_TYPES: Final[tuple[FritzBinarySensorEntityDescription, ...]] = (
|
||||
suitable=lambda device: device.has_alarm, # type: ignore[no-any-return]
|
||||
is_on=lambda device: device.alert_state, # type: ignore[no-any-return]
|
||||
),
|
||||
FritzBinarySensorEntityDescription(
|
||||
key="lock",
|
||||
name="Button Lock on Device",
|
||||
device_class=BinarySensorDeviceClass.LOCK,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
suitable=lambda device: device.lock is not None,
|
||||
is_on=lambda device: not device.lock,
|
||||
),
|
||||
FritzBinarySensorEntityDescription(
|
||||
key="device_lock",
|
||||
name="Button Lock via UI",
|
||||
device_class=BinarySensorDeviceClass.LOCK,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
suitable=lambda device: device.device_lock is not None,
|
||||
is_on=lambda device: not device.device_lock,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@ -76,8 +93,8 @@ class FritzboxBinarySensor(FritzBoxEntity, BinarySensorEntity):
|
||||
) -> None:
|
||||
"""Initialize the FritzBox entity."""
|
||||
super().__init__(coordinator, ain, entity_description)
|
||||
self._attr_name = self.device.name
|
||||
self._attr_unique_id = ain
|
||||
self._attr_name = f"{self.device.name} {entity_description.name}"
|
||||
self._attr_unique_id = f"{ain}_{entity_description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
|
@ -26,9 +26,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from . import FritzBoxEntity
|
||||
from .const import (
|
||||
ATTR_STATE_BATTERY_LOW,
|
||||
ATTR_STATE_DEVICE_LOCKED,
|
||||
ATTR_STATE_HOLIDAY_MODE,
|
||||
ATTR_STATE_LOCKED,
|
||||
ATTR_STATE_SUMMER_MODE,
|
||||
ATTR_STATE_WINDOW_OPEN,
|
||||
CONF_COORDINATOR,
|
||||
@ -176,8 +174,6 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity):
|
||||
"""Return the device specific state attributes."""
|
||||
attrs: ClimateExtraAttributes = {
|
||||
ATTR_STATE_BATTERY_LOW: self.device.battery_low,
|
||||
ATTR_STATE_DEVICE_LOCKED: self.device.device_lock,
|
||||
ATTR_STATE_LOCKED: self.device.lock,
|
||||
}
|
||||
|
||||
# the following attributes are available since fritzos 7
|
||||
|
@ -7,9 +7,7 @@ from typing import Final
|
||||
from homeassistant.const import Platform
|
||||
|
||||
ATTR_STATE_BATTERY_LOW: Final = "battery_low"
|
||||
ATTR_STATE_DEVICE_LOCKED: Final = "device_locked"
|
||||
ATTR_STATE_HOLIDAY_MODE: Final = "holiday_mode"
|
||||
ATTR_STATE_LOCKED: Final = "locked"
|
||||
ATTR_STATE_SUMMER_MODE: Final = "summer_mode"
|
||||
ATTR_STATE_WINDOW_OPEN: Final = "window_open"
|
||||
|
||||
|
@ -8,14 +8,8 @@ from typing import TypedDict
|
||||
from pyfritzhome import FritzhomeDevice
|
||||
|
||||
|
||||
class FritzExtraAttributes(TypedDict):
|
||||
"""TypedDict for sensors extra attributes."""
|
||||
|
||||
device_locked: bool
|
||||
locked: bool
|
||||
|
||||
|
||||
class ClimateExtraAttributes(FritzExtraAttributes, total=False):
|
||||
@dataclass
|
||||
class ClimateExtraAttributes(TypedDict, total=False):
|
||||
"""TypedDict for climates extra attributes."""
|
||||
|
||||
battery_level: int
|
||||
|
@ -15,6 +15,6 @@ MOCK_CONFIG = {
|
||||
}
|
||||
|
||||
CONF_FAKE_NAME = "fake_name"
|
||||
CONF_FAKE_AIN = "fake_ain"
|
||||
CONF_FAKE_AIN = "12345 1234567"
|
||||
CONF_FAKE_MANUFACTURER = "fake_manufacturer"
|
||||
CONF_FAKE_PRODUCTNAME = "fake_productname"
|
||||
|
@ -14,6 +14,7 @@ from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
CONF_DEVICES,
|
||||
PERCENTAGE,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
@ -35,13 +36,32 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
state = hass.states.get(f"{ENTITY_ID}_alarm")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Alarm"
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.WINDOW
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get(f"{ENTITY_ID}_button_lock_on_device")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert (
|
||||
state.attributes[ATTR_FRIENDLY_NAME]
|
||||
== f"{CONF_FAKE_NAME} Button Lock on Device"
|
||||
)
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.LOCK
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get(f"{ENTITY_ID}_button_lock_via_ui")
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert (
|
||||
state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Button Lock via UI"
|
||||
)
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.LOCK
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_battery")
|
||||
assert state
|
||||
assert state.state == "23"
|
||||
@ -58,7 +78,15 @@ async def test_is_off(hass: HomeAssistant, fritz: Mock):
|
||||
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
state = hass.states.get(f"{ENTITY_ID}_alarm")
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
state = hass.states.get(f"{ENTITY_ID}_button_lock_on_device")
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
state = hass.states.get(f"{ENTITY_ID}_button_lock_via_ui")
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
@ -23,9 +23,7 @@ from homeassistant.components.climate.const import (
|
||||
)
|
||||
from homeassistant.components.fritzbox.const import (
|
||||
ATTR_STATE_BATTERY_LOW,
|
||||
ATTR_STATE_DEVICE_LOCKED,
|
||||
ATTR_STATE_HOLIDAY_MODE,
|
||||
ATTR_STATE_LOCKED,
|
||||
ATTR_STATE_SUMMER_MODE,
|
||||
ATTR_STATE_WINDOW_OPEN,
|
||||
DOMAIN as FB_DOMAIN,
|
||||
@ -70,9 +68,7 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||
assert state.attributes[ATTR_PRESET_MODE] is None
|
||||
assert state.attributes[ATTR_PRESET_MODES] == [PRESET_ECO, PRESET_COMFORT]
|
||||
assert state.attributes[ATTR_STATE_BATTERY_LOW] is True
|
||||
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
||||
assert state.attributes[ATTR_STATE_HOLIDAY_MODE] == "fake_holiday"
|
||||
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
||||
assert state.attributes[ATTR_STATE_SUMMER_MODE] == "fake_summer"
|
||||
assert state.attributes[ATTR_STATE_WINDOW_OPEN] == "fake_window"
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 19.5
|
||||
|
@ -4,8 +4,10 @@ from __future__ import annotations
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
from pyfritzhome import LoginError
|
||||
import pytest
|
||||
from requests.exceptions import ConnectionError, HTTPError
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
@ -42,7 +44,37 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||
]
|
||||
|
||||
|
||||
async def test_update_unique_id(hass: HomeAssistant, fritz: Mock):
|
||||
@pytest.mark.parametrize(
|
||||
"entitydata,old_unique_id,new_unique_id",
|
||||
[
|
||||
(
|
||||
{
|
||||
"domain": SENSOR_DOMAIN,
|
||||
"platform": FB_DOMAIN,
|
||||
"unique_id": CONF_FAKE_AIN,
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
},
|
||||
CONF_FAKE_AIN,
|
||||
f"{CONF_FAKE_AIN}_temperature",
|
||||
),
|
||||
(
|
||||
{
|
||||
"domain": BINARY_SENSOR_DOMAIN,
|
||||
"platform": FB_DOMAIN,
|
||||
"unique_id": CONF_FAKE_AIN,
|
||||
},
|
||||
CONF_FAKE_AIN,
|
||||
f"{CONF_FAKE_AIN}_alarm",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_update_unique_id(
|
||||
hass: HomeAssistant,
|
||||
fritz: Mock,
|
||||
entitydata: dict,
|
||||
old_unique_id: str,
|
||||
new_unique_id: str,
|
||||
):
|
||||
"""Test unique_id update of integration."""
|
||||
entry = MockConfigEntry(
|
||||
domain=FB_DOMAIN,
|
||||
@ -52,23 +84,55 @@ async def test_update_unique_id(hass: HomeAssistant, fritz: Mock):
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
entity = entity_registry.async_get_or_create(
|
||||
SENSOR_DOMAIN,
|
||||
FB_DOMAIN,
|
||||
CONF_FAKE_AIN,
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
entity: er.RegistryEntry = entity_registry.async_get_or_create(
|
||||
**entitydata,
|
||||
config_entry=entry,
|
||||
)
|
||||
assert entity.unique_id == CONF_FAKE_AIN
|
||||
assert entity.unique_id == old_unique_id
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||
assert entity_migrated
|
||||
assert entity_migrated.unique_id == f"{CONF_FAKE_AIN}_temperature"
|
||||
assert entity_migrated.unique_id == new_unique_id
|
||||
|
||||
|
||||
async def test_update_unique_id_no_change(hass: HomeAssistant, fritz: Mock):
|
||||
@pytest.mark.parametrize(
|
||||
"entitydata,unique_id",
|
||||
[
|
||||
(
|
||||
{
|
||||
"domain": SENSOR_DOMAIN,
|
||||
"platform": FB_DOMAIN,
|
||||
"unique_id": f"{CONF_FAKE_AIN}_temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
},
|
||||
f"{CONF_FAKE_AIN}_temperature",
|
||||
),
|
||||
(
|
||||
{
|
||||
"domain": BINARY_SENSOR_DOMAIN,
|
||||
"platform": FB_DOMAIN,
|
||||
"unique_id": f"{CONF_FAKE_AIN}_alarm",
|
||||
},
|
||||
f"{CONF_FAKE_AIN}_alarm",
|
||||
),
|
||||
(
|
||||
{
|
||||
"domain": BINARY_SENSOR_DOMAIN,
|
||||
"platform": FB_DOMAIN,
|
||||
"unique_id": f"{CONF_FAKE_AIN}_other",
|
||||
},
|
||||
f"{CONF_FAKE_AIN}_other",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_update_unique_id_no_change(
|
||||
hass: HomeAssistant,
|
||||
fritz: Mock,
|
||||
entitydata: dict,
|
||||
unique_id: str,
|
||||
):
|
||||
"""Test unique_id is not updated of integration."""
|
||||
entry = MockConfigEntry(
|
||||
domain=FB_DOMAIN,
|
||||
@ -79,19 +143,16 @@ async def test_update_unique_id_no_change(hass: HomeAssistant, fritz: Mock):
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
entity = entity_registry.async_get_or_create(
|
||||
SENSOR_DOMAIN,
|
||||
FB_DOMAIN,
|
||||
f"{CONF_FAKE_AIN}_temperature",
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
**entitydata,
|
||||
config_entry=entry,
|
||||
)
|
||||
assert entity.unique_id == f"{CONF_FAKE_AIN}_temperature"
|
||||
assert entity.unique_id == unique_id
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||
assert entity_migrated
|
||||
assert entity_migrated.unique_id == f"{CONF_FAKE_AIN}_temperature"
|
||||
assert entity_migrated.unique_id == unique_id
|
||||
|
||||
|
||||
async def test_coordinator_update_after_reboot(hass: HomeAssistant, fritz: Mock):
|
||||
|
@ -4,11 +4,7 @@ from unittest.mock import Mock
|
||||
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from homeassistant.components.fritzbox.const import (
|
||||
ATTR_STATE_DEVICE_LOCKED,
|
||||
ATTR_STATE_LOCKED,
|
||||
DOMAIN as FB_DOMAIN,
|
||||
)
|
||||
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, DOMAIN, SensorStateClass
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME,
|
||||
@ -40,8 +36,6 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||
assert state
|
||||
assert state.state == "1.23"
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Temperature"
|
||||
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
||||
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS
|
||||
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
|
||||
|
||||
|
@ -4,11 +4,7 @@ from unittest.mock import Mock
|
||||
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from homeassistant.components.fritzbox.const import (
|
||||
ATTR_STATE_DEVICE_LOCKED,
|
||||
ATTR_STATE_LOCKED,
|
||||
DOMAIN as FB_DOMAIN,
|
||||
)
|
||||
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
|
||||
from homeassistant.components.sensor import (
|
||||
ATTR_STATE_CLASS,
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
@ -50,16 +46,12 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME
|
||||
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
||||
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
|
||||
state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_temperature")
|
||||
assert state
|
||||
assert state.state == "1.23"
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Temperature"
|
||||
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
||||
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS
|
||||
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user