Move vera base entity to separate module (#126186)

This commit is contained in:
epenet 2024-09-18 09:50:07 +02:00 committed by GitHub
parent e7bb9a440a
commit da4f401d17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 130 additions and 111 deletions

View File

@ -5,7 +5,6 @@ from __future__ import annotations
import asyncio import asyncio
from collections import defaultdict from collections import defaultdict
import logging import logging
from typing import Any
import pyvera as veraApi import pyvera as veraApi
from requests.exceptions import RequestException from requests.exceptions import RequestException
@ -14,10 +13,6 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ARMED,
ATTR_BATTERY_LEVEL,
ATTR_LAST_TRIP_TIME,
ATTR_TRIPPED,
CONF_EXCLUDE, CONF_EXCLUDE,
CONF_LIGHTS, CONF_LIGHTS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
@ -26,10 +21,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from homeassistant.util.dt import utc_from_timestamp
from .common import ( from .common import (
ControllerData, ControllerData,
@ -39,7 +31,7 @@ from .common import (
set_controller_data, set_controller_data,
) )
from .config_flow import fix_device_id_list, new_options from .config_flow import fix_device_id_list, new_options
from .const import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN, VERA_ID_FORMAT from .const import CONF_CONTROLLER, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -204,83 +196,3 @@ def map_vera_device(
), ),
None, None,
) )
class VeraDevice[_DeviceTypeT: veraApi.VeraDevice](Entity):
"""Representation of a Vera device entity."""
def __init__(
self, vera_device: _DeviceTypeT, controller_data: ControllerData
) -> None:
"""Initialize the device."""
self.vera_device = vera_device
self.controller = controller_data.controller
self._name = self.vera_device.name
# Append device id to prevent name clashes in HA.
self.vera_id = VERA_ID_FORMAT.format(
slugify(vera_device.name), vera_device.vera_device_id
)
if controller_data.config_entry.data.get(CONF_LEGACY_UNIQUE_ID):
self._unique_id = str(self.vera_device.vera_device_id)
else:
self._unique_id = f"vera_{controller_data.config_entry.unique_id}_{self.vera_device.vera_device_id}"
async def async_added_to_hass(self) -> None:
"""Subscribe to updates."""
self.controller.register(self.vera_device, self._update_callback)
def _update_callback(self, _device: _DeviceTypeT) -> None:
"""Update the state."""
self.schedule_update_ha_state(True)
def update(self):
"""Force a refresh from the device if the device is unavailable."""
refresh_needed = self.vera_device.should_poll or not self.available
_LOGGER.debug("%s: update called (refresh=%s)", self._name, refresh_needed)
if refresh_needed:
self.vera_device.refresh()
@property
def name(self) -> str:
"""Return the name of the device."""
return self._name
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes of the device."""
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level
if self.vera_device.is_armable:
armed = self.vera_device.is_armed
attr[ATTR_ARMED] = "True" if armed else "False"
if self.vera_device.is_trippable:
if (last_tripped := self.vera_device.last_trip) is not None:
utc_time = utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped
attr[ATTR_TRIPPED] = "True" if tripped else "False"
attr["Vera Device Id"] = self.vera_device.vera_device_id
return attr
@property
def available(self):
"""If device communications have failed return false."""
return not self.vera_device.comm_failure
@property
def unique_id(self) -> str:
"""Return a unique ID.
The Vera assigns a unique and immutable ID number to each device.
"""
return self._unique_id

View File

@ -10,8 +10,8 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import VeraDevice
from .common import ControllerData, get_controller_data from .common import ControllerData, get_controller_data
from .entity import VeraEntity
async def async_setup_entry( async def async_setup_entry(
@ -30,7 +30,7 @@ async def async_setup_entry(
) )
class VeraBinarySensor(VeraDevice[veraApi.VeraBinarySensor], BinarySensorEntity): class VeraBinarySensor(VeraEntity[veraApi.VeraBinarySensor], BinarySensorEntity):
"""Representation of a Vera Binary Sensor.""" """Representation of a Vera Binary Sensor."""
_attr_is_on = False _attr_is_on = False
@ -39,7 +39,7 @@ class VeraBinarySensor(VeraDevice[veraApi.VeraBinarySensor], BinarySensorEntity)
self, vera_device: veraApi.VeraBinarySensor, controller_data: ControllerData self, vera_device: veraApi.VeraBinarySensor, controller_data: ControllerData
) -> None: ) -> None:
"""Initialize the binary_sensor.""" """Initialize the binary_sensor."""
VeraDevice.__init__(self, vera_device, controller_data) VeraEntity.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
def update(self) -> None: def update(self) -> None:

View File

@ -19,8 +19,8 @@ from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import VeraDevice
from .common import ControllerData, get_controller_data from .common import ControllerData, get_controller_data
from .entity import VeraEntity
FAN_OPERATION_LIST = [FAN_ON, FAN_AUTO] FAN_OPERATION_LIST = [FAN_ON, FAN_AUTO]
@ -43,7 +43,7 @@ async def async_setup_entry(
) )
class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): class VeraThermostat(VeraEntity[veraApi.VeraThermostat], ClimateEntity):
"""Representation of a Vera Thermostat.""" """Representation of a Vera Thermostat."""
_attr_hvac_modes = SUPPORT_HVAC _attr_hvac_modes = SUPPORT_HVAC
@ -60,7 +60,7 @@ class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity):
self, vera_device: veraApi.VeraThermostat, controller_data: ControllerData self, vera_device: veraApi.VeraThermostat, controller_data: ControllerData
) -> None: ) -> None:
"""Initialize the Vera device.""" """Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller_data) VeraEntity.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View File

@ -12,8 +12,8 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import VeraDevice
from .common import ControllerData, get_controller_data from .common import ControllerData, get_controller_data
from .entity import VeraEntity
async def async_setup_entry( async def async_setup_entry(
@ -32,14 +32,14 @@ async def async_setup_entry(
) )
class VeraCover(VeraDevice[veraApi.VeraCurtain], CoverEntity): class VeraCover(VeraEntity[veraApi.VeraCurtain], CoverEntity):
"""Representation a Vera Cover.""" """Representation a Vera Cover."""
def __init__( def __init__(
self, vera_device: veraApi.VeraCurtain, controller_data: ControllerData self, vera_device: veraApi.VeraCurtain, controller_data: ControllerData
) -> None: ) -> None:
"""Initialize the Vera device.""" """Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller_data) VeraEntity.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View File

@ -0,0 +1,103 @@
"""Support for Vera devices."""
from __future__ import annotations
import logging
from typing import Any
import pyvera as veraApi
from homeassistant.const import (
ATTR_ARMED,
ATTR_BATTERY_LEVEL,
ATTR_LAST_TRIP_TIME,
ATTR_TRIPPED,
)
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
from homeassistant.util.dt import utc_from_timestamp
from .common import ControllerData
from .const import CONF_LEGACY_UNIQUE_ID, VERA_ID_FORMAT
_LOGGER = logging.getLogger(__name__)
class VeraEntity[_DeviceTypeT: veraApi.VeraDevice](Entity):
"""Representation of a Vera device entity."""
def __init__(
self, vera_device: _DeviceTypeT, controller_data: ControllerData
) -> None:
"""Initialize the device."""
self.vera_device = vera_device
self.controller = controller_data.controller
self._name = self.vera_device.name
# Append device id to prevent name clashes in HA.
self.vera_id = VERA_ID_FORMAT.format(
slugify(vera_device.name), vera_device.vera_device_id
)
if controller_data.config_entry.data.get(CONF_LEGACY_UNIQUE_ID):
self._unique_id = str(self.vera_device.vera_device_id)
else:
self._unique_id = f"vera_{controller_data.config_entry.unique_id}_{self.vera_device.vera_device_id}"
async def async_added_to_hass(self) -> None:
"""Subscribe to updates."""
self.controller.register(self.vera_device, self._update_callback)
def _update_callback(self, _device: _DeviceTypeT) -> None:
"""Update the state."""
self.schedule_update_ha_state(True)
def update(self):
"""Force a refresh from the device if the device is unavailable."""
refresh_needed = self.vera_device.should_poll or not self.available
_LOGGER.debug("%s: update called (refresh=%s)", self._name, refresh_needed)
if refresh_needed:
self.vera_device.refresh()
@property
def name(self) -> str:
"""Return the name of the device."""
return self._name
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes of the device."""
attr = {}
if self.vera_device.has_battery:
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level
if self.vera_device.is_armable:
armed = self.vera_device.is_armed
attr[ATTR_ARMED] = "True" if armed else "False"
if self.vera_device.is_trippable:
if (last_tripped := self.vera_device.last_trip) is not None:
utc_time = utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = utc_time.isoformat()
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.is_tripped
attr[ATTR_TRIPPED] = "True" if tripped else "False"
attr["Vera Device Id"] = self.vera_device.vera_device_id
return attr
@property
def available(self):
"""If device communications have failed return false."""
return not self.vera_device.comm_failure
@property
def unique_id(self) -> str:
"""Return a unique ID.
The Vera assigns a unique and immutable ID number to each device.
"""
return self._unique_id

View File

@ -19,8 +19,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from . import VeraDevice
from .common import ControllerData, get_controller_data from .common import ControllerData, get_controller_data
from .entity import VeraEntity
async def async_setup_entry( async def async_setup_entry(
@ -39,7 +39,7 @@ async def async_setup_entry(
) )
class VeraLight(VeraDevice[veraApi.VeraDimmer], LightEntity): class VeraLight(VeraEntity[veraApi.VeraDimmer], LightEntity):
"""Representation of a Vera Light, including dimmable.""" """Representation of a Vera Light, including dimmable."""
_attr_is_on = False _attr_is_on = False
@ -50,7 +50,7 @@ class VeraLight(VeraDevice[veraApi.VeraDimmer], LightEntity):
self, vera_device: veraApi.VeraDimmer, controller_data: ControllerData self, vera_device: veraApi.VeraDimmer, controller_data: ControllerData
) -> None: ) -> None:
"""Initialize the light.""" """Initialize the light."""
VeraDevice.__init__(self, vera_device, controller_data) VeraEntity.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View File

@ -12,8 +12,8 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import VeraDevice
from .common import ControllerData, get_controller_data from .common import ControllerData, get_controller_data
from .entity import VeraEntity
ATTR_LAST_USER_NAME = "changed_by_name" ATTR_LAST_USER_NAME = "changed_by_name"
ATTR_LOW_BATTERY = "low_battery" ATTR_LOW_BATTERY = "low_battery"
@ -35,14 +35,14 @@ async def async_setup_entry(
) )
class VeraLock(VeraDevice[veraApi.VeraLock], LockEntity): class VeraLock(VeraEntity[veraApi.VeraLock], LockEntity):
"""Representation of a Vera lock.""" """Representation of a Vera lock."""
def __init__( def __init__(
self, vera_device: veraApi.VeraLock, controller_data: ControllerData self, vera_device: veraApi.VeraLock, controller_data: ControllerData
) -> None: ) -> None:
"""Initialize the Vera device.""" """Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller_data) VeraEntity.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
def lock(self, **kwargs: Any) -> None: def lock(self, **kwargs: Any) -> None:

View File

@ -23,8 +23,8 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import VeraDevice
from .common import ControllerData, get_controller_data from .common import ControllerData, get_controller_data
from .entity import VeraEntity
SCAN_INTERVAL = timedelta(seconds=5) SCAN_INTERVAL = timedelta(seconds=5)
@ -45,7 +45,7 @@ async def async_setup_entry(
) )
class VeraSensor(VeraDevice[veraApi.VeraSensor], SensorEntity): class VeraSensor(VeraEntity[veraApi.VeraSensor], SensorEntity):
"""Representation of a Vera Sensor.""" """Representation of a Vera Sensor."""
def __init__( def __init__(
@ -54,7 +54,7 @@ class VeraSensor(VeraDevice[veraApi.VeraSensor], SensorEntity):
"""Initialize the sensor.""" """Initialize the sensor."""
self._temperature_units: str | None = None self._temperature_units: str | None = None
self.last_changed_time = None self.last_changed_time = None
VeraDevice.__init__(self, vera_device, controller_data) VeraEntity.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR:
self._attr_device_class = SensorDeviceClass.TEMPERATURE self._attr_device_class = SensorDeviceClass.TEMPERATURE

View File

@ -12,8 +12,8 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import VeraDevice
from .common import ControllerData, get_controller_data from .common import ControllerData, get_controller_data
from .entity import VeraEntity
async def async_setup_entry( async def async_setup_entry(
@ -32,7 +32,7 @@ async def async_setup_entry(
) )
class VeraSwitch(VeraDevice[veraApi.VeraSwitch], SwitchEntity): class VeraSwitch(VeraEntity[veraApi.VeraSwitch], SwitchEntity):
"""Representation of a Vera Switch.""" """Representation of a Vera Switch."""
_attr_is_on = False _attr_is_on = False
@ -41,7 +41,7 @@ class VeraSwitch(VeraDevice[veraApi.VeraSwitch], SwitchEntity):
self, vera_device: veraApi.VeraSwitch, controller_data: ControllerData self, vera_device: veraApi.VeraSwitch, controller_data: ControllerData
) -> None: ) -> None:
"""Initialize the Vera device.""" """Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller_data) VeraEntity.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
def turn_on(self, **kwargs: Any) -> None: def turn_on(self, **kwargs: Any) -> None:

View File

@ -5,7 +5,11 @@ from unittest.mock import MagicMock, patch
from requests.exceptions import RequestException from requests.exceptions import RequestException
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.vera import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN from homeassistant.components.vera.const import (
CONF_CONTROLLER,
CONF_LEGACY_UNIQUE_ID,
DOMAIN,
)
from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType