Clean up SimpliSafe entity inheritance structure (#58063)

* Migrate SimpliSafe to new web-based authentication

* Ensure we're storing data correctly

* Re-organize SimpliSafe device structure

* Constants

* More work

* Code review
This commit is contained in:
Aaron Bach 2021-10-21 04:54:50 -06:00 committed by GitHub
parent c7ff6eb5ee
commit 2ff356393c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 72 deletions

View file

@ -7,8 +7,7 @@ from datetime import timedelta
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING, cast
from simplipy import API from simplipy import API
from simplipy.device.sensor.v2 import SensorV2 from simplipy.device import Device
from simplipy.device.sensor.v3 import SensorV3
from simplipy.errors import ( from simplipy.errors import (
EndpointUnavailableError, EndpointUnavailableError,
InvalidCredentialsError, InvalidCredentialsError,
@ -62,6 +61,8 @@ from .const import (
EVENT_SIMPLISAFE_NOTIFICATION = "SIMPLISAFE_NOTIFICATION" EVENT_SIMPLISAFE_NOTIFICATION = "SIMPLISAFE_NOTIFICATION"
DEFAULT_ENTITY_MODEL = "alarm_control_panel"
DEFAULT_ENTITY_NAME = "Alarm Control Panel"
DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
DEFAULT_SOCKET_MIN_RETRY = 15 DEFAULT_SOCKET_MIN_RETRY = 15
@ -159,7 +160,7 @@ async def async_register_base_station(
device_registry = await dr.async_get_registry(hass) device_registry = await dr.async_get_registry(hass)
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=entry.entry_id, config_entry_id=entry.entry_id,
identifiers={(DOMAIN, system.serial)}, identifiers={(DOMAIN, system.system_id)},
manufacturer="SimpliSafe", manufacturer="SimpliSafe",
model=system.version, model=system.version,
name=system.address, name=system.address,
@ -424,29 +425,34 @@ class SimpliSafeEntity(CoordinatorEntity):
self, self,
simplisafe: SimpliSafe, simplisafe: SimpliSafe,
system: SystemV2 | SystemV3, system: SystemV2 | SystemV3,
name: str,
*, *,
serial: str | None = None, device: Device | None = None,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
assert simplisafe.coordinator assert simplisafe.coordinator
super().__init__(simplisafe.coordinator) super().__init__(simplisafe.coordinator)
if serial: if device:
self._serial = serial model = device.type.name
device_name = device.name
serial = device.serial
else: else:
self._serial = system.serial model = DEFAULT_ENTITY_MODEL
device_name = DEFAULT_ENTITY_NAME
serial = system.serial
self._attr_extra_state_attributes = {ATTR_SYSTEM_ID: system.system_id} self._attr_extra_state_attributes = {ATTR_SYSTEM_ID: system.system_id}
self._attr_device_info = { self._attr_device_info = {
"identifiers": {(DOMAIN, system.system_id)}, "identifiers": {(DOMAIN, serial)},
"manufacturer": "SimpliSafe", "manufacturer": "SimpliSafe",
"model": str(system.version), "model": model,
"name": name, "name": device_name,
"via_device": (DOMAIN, system.serial), "via_device": (DOMAIN, system.system_id),
} }
self._attr_name = f"{system.address} {name}"
self._attr_unique_id = self._serial self._attr_name = f"{system.address} {device_name} {' '.join([w.title() for w in model.split('_')])}"
self._attr_unique_id = serial
self._device = device
self._online = True self._online = True
self._simplisafe = simplisafe self._simplisafe = simplisafe
self._system = system self._system = system
@ -481,29 +487,3 @@ class SimpliSafeEntity(CoordinatorEntity):
def async_update_from_rest_api(self) -> None: def async_update_from_rest_api(self) -> None:
"""Update the entity with the provided REST API data.""" """Update the entity with the provided REST API data."""
raise NotImplementedError() raise NotImplementedError()
class SimpliSafeBaseSensor(SimpliSafeEntity):
"""Define a SimpliSafe base (binary) sensor."""
def __init__(
self,
simplisafe: SimpliSafe,
system: SystemV2 | SystemV3,
sensor: SensorV2 | SensorV3,
) -> None:
"""Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
self._attr_device_info = {
"identifiers": {(DOMAIN, sensor.serial)},
"manufacturer": "SimpliSafe",
"model": sensor.type.name,
"name": sensor.name,
"via_device": (DOMAIN, system.serial),
}
human_friendly_name = " ".join([w.title() for w in sensor.type.name.split("_")])
self._attr_name = f"{super().name} {human_friendly_name}"
self._sensor = sensor

View file

@ -80,7 +80,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity):
def __init__(self, simplisafe: SimpliSafe, system: SystemV2 | SystemV3) -> None: def __init__(self, simplisafe: SimpliSafe, system: SystemV2 | SystemV3) -> None:
"""Initialize the SimpliSafe alarm.""" """Initialize the SimpliSafe alarm."""
super().__init__(simplisafe, system, "Alarm Control Panel") super().__init__(simplisafe, system)
if code := self._simplisafe.entry.options.get(CONF_CODE): if code := self._simplisafe.entry.options.get(CONF_CODE):
if code.isdigit(): if code.isdigit():

View file

@ -2,9 +2,7 @@
from __future__ import annotations from __future__ import annotations
from simplipy.device import DeviceTypes from simplipy.device import DeviceTypes
from simplipy.device.sensor.v2 import SensorV2
from simplipy.device.sensor.v3 import SensorV3 from simplipy.device.sensor.v3 import SensorV3
from simplipy.system.v2 import SystemV2
from simplipy.system.v3 import SystemV3 from simplipy.system.v3 import SystemV3
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
@ -22,7 +20,7 @@ from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import SimpliSafe, SimpliSafeBaseSensor from . import SimpliSafe, SimpliSafeEntity
from .const import DATA_CLIENT, DOMAIN, LOGGER from .const import DATA_CLIENT, DOMAIN, LOGGER
SUPPORTED_BATTERY_SENSOR_TYPES = [ SUPPORTED_BATTERY_SENSOR_TYPES = [
@ -77,45 +75,45 @@ async def async_setup_entry(
async_add_entities(sensors) async_add_entities(sensors)
class TriggeredBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity): class TriggeredBinarySensor(SimpliSafeEntity, BinarySensorEntity):
"""Define a binary sensor related to whether an entity has been triggered.""" """Define a binary sensor related to whether an entity has been triggered."""
def __init__( def __init__(
self, self,
simplisafe: SimpliSafe, simplisafe: SimpliSafe,
system: SystemV2 | SystemV3, system: SystemV3,
sensor: SensorV2 | SensorV3, sensor: SensorV3,
device_class: str, device_class: str,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(simplisafe, system, sensor) super().__init__(simplisafe, system, device=sensor)
self._attr_device_class = device_class self._attr_device_class = device_class
self._device: SensorV3
@callback @callback
def async_update_from_rest_api(self) -> None: def async_update_from_rest_api(self) -> None:
"""Update the entity with the provided REST API data.""" """Update the entity with the provided REST API data."""
self._attr_is_on = self._sensor.triggered self._attr_is_on = self._device.triggered
class BatteryBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity): class BatteryBinarySensor(SimpliSafeEntity, BinarySensorEntity):
"""Define a SimpliSafe battery binary sensor entity.""" """Define a SimpliSafe battery binary sensor entity."""
_attr_device_class = DEVICE_CLASS_BATTERY _attr_device_class = DEVICE_CLASS_BATTERY
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC _attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__( def __init__(
self, self, simplisafe: SimpliSafe, system: SystemV3, sensor: SensorV3
simplisafe: SimpliSafe,
system: SystemV2 | SystemV3,
sensor: SensorV2 | SensorV3,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(simplisafe, system, sensor) super().__init__(simplisafe, system, device=sensor)
self._attr_name = f"{super().name} Battery"
self._attr_unique_id = f"{super().unique_id}-battery" self._attr_unique_id = f"{super().unique_id}-battery"
self._device: SensorV3
@callback @callback
def async_update_from_rest_api(self) -> None: def async_update_from_rest_api(self) -> None:
"""Update the entity with the provided REST API data.""" """Update the entity with the provided REST API data."""
self._attr_is_on = self._sensor.low_battery self._attr_is_on = self._device.low_battery

View file

@ -42,16 +42,16 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity):
def __init__(self, simplisafe: SimpliSafe, system: SystemV3, lock: Lock) -> None: def __init__(self, simplisafe: SimpliSafe, system: SystemV3, lock: Lock) -> None:
"""Initialize.""" """Initialize."""
super().__init__(simplisafe, system, lock.name, serial=lock.serial) super().__init__(simplisafe, system, device=lock)
self._lock = lock self._device: Lock
async def async_lock(self, **kwargs: Any) -> None: async def async_lock(self, **kwargs: Any) -> None:
"""Lock the lock.""" """Lock the lock."""
try: try:
await self._lock.async_lock() await self._device.async_lock()
except SimplipyError as err: except SimplipyError as err:
LOGGER.error('Error while locking "%s": %s', self._lock.name, err) LOGGER.error('Error while locking "%s": %s', self._device.name, err)
return return
self._attr_is_locked = True self._attr_is_locked = True
@ -60,9 +60,9 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity):
async def async_unlock(self, **kwargs: Any) -> None: async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the lock.""" """Unlock the lock."""
try: try:
await self._lock.async_unlock() await self._device.async_unlock()
except SimplipyError as err: except SimplipyError as err:
LOGGER.error('Error while unlocking "%s": %s', self._lock.name, err) LOGGER.error('Error while unlocking "%s": %s', self._device.name, err)
return return
self._attr_is_locked = False self._attr_is_locked = False
@ -73,10 +73,10 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity):
"""Update the entity with the provided REST API data.""" """Update the entity with the provided REST API data."""
self._attr_extra_state_attributes.update( self._attr_extra_state_attributes.update(
{ {
ATTR_LOCK_LOW_BATTERY: self._lock.lock_low_battery, ATTR_LOCK_LOW_BATTERY: self._device.lock_low_battery,
ATTR_PIN_PAD_LOW_BATTERY: self._lock.pin_pad_low_battery, ATTR_PIN_PAD_LOW_BATTERY: self._device.pin_pad_low_battery,
} }
) )
self._attr_is_jammed = self._lock.state == LockStates.jammed self._attr_is_jammed = self._device.state == LockStates.jammed
self._attr_is_locked = self._lock.state == LockStates.locked self._attr_is_locked = self._device.state == LockStates.locked

View file

@ -1,8 +1,9 @@
"""Support for SimpliSafe freeze sensor.""" """Support for SimpliSafe freeze sensor."""
from typing import TYPE_CHECKING from __future__ import annotations
from simplipy.device import DeviceTypes from simplipy.device import DeviceTypes
from simplipy.device.sensor.v3 import SensorV3 from simplipy.device.sensor.v3 import SensorV3
from simplipy.system.v3 import SystemV3
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -10,7 +11,7 @@ from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import SimpliSafeBaseSensor from . import SimpliSafe, SimpliSafeEntity
from .const import DATA_CLIENT, DOMAIN, LOGGER from .const import DATA_CLIENT, DOMAIN, LOGGER
@ -33,16 +34,22 @@ async def async_setup_entry(
async_add_entities(sensors) async_add_entities(sensors)
class SimplisafeFreezeSensor(SimpliSafeBaseSensor, SensorEntity): class SimplisafeFreezeSensor(SimpliSafeEntity, SensorEntity):
"""Define a SimpliSafe freeze sensor entity.""" """Define a SimpliSafe freeze sensor entity."""
_attr_device_class = DEVICE_CLASS_TEMPERATURE _attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_native_unit_of_measurement = TEMP_FAHRENHEIT _attr_native_unit_of_measurement = TEMP_FAHRENHEIT
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(
self, simplisafe: SimpliSafe, system: SystemV3, sensor: SensorV3
) -> None:
"""Initialize."""
super().__init__(simplisafe, system, device=sensor)
self._device: SensorV3
@callback @callback
def async_update_from_rest_api(self) -> None: def async_update_from_rest_api(self) -> None:
"""Update the entity with the provided REST API data.""" """Update the entity with the provided REST API data."""
if TYPE_CHECKING: self._attr_native_value = self._device.temperature
assert isinstance(self._sensor, SensorV3)
self._attr_native_value = self._sensor.temperature