From 8a45bc2d1313c6f862aaf721ab52cd9e864ad39e Mon Sep 17 00:00:00 2001 From: Niccolo Zapponi Date: Mon, 12 Oct 2020 18:31:55 +0100 Subject: [PATCH] Add support for SimpliSafe sensors (#41080) * Add support for SimpliSafe sensors * Turn sensor refresh rate to a configurable setting * Set minimum to scan interval * Removed dynamic sensor refresh rate * Refactoring * Refactoring * Move battery entities to binary_sensor platform * Bug fix * Clean up * Simplified device info override * Ignore sensor cache --- .coveragerc | 2 + .../components/simplisafe/__init__.py | 37 +++-- .../components/simplisafe/binary_sensor.py | 142 ++++++++++++++++++ homeassistant/components/simplisafe/sensor.py | 67 +++++++++ 4 files changed, 234 insertions(+), 14 deletions(-) create mode 100644 homeassistant/components/simplisafe/binary_sensor.py create mode 100644 homeassistant/components/simplisafe/sensor.py diff --git a/.coveragerc b/.coveragerc index f84c0753dfd..ad966e48d0e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -782,7 +782,9 @@ omit = homeassistant/components/simplepush/notify.py homeassistant/components/simplisafe/__init__.py homeassistant/components/simplisafe/alarm_control_panel.py + homeassistant/components/simplisafe/binary_sensor.py homeassistant/components/simplisafe/lock.py + homeassistant/components/simplisafe/sensor.py homeassistant/components/simulated/sensor.py homeassistant/components/sisyphus/* homeassistant/components/sky_hub/* diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 0a42d42be3a..563d8048e53 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -70,6 +70,13 @@ EVENT_SIMPLISAFE_NOTIFICATION = "SIMPLISAFE_NOTIFICATION" DEFAULT_SOCKET_MIN_RETRY = 15 +SUPPORTED_PLATFORMS = ( + "alarm_control_panel", + "binary_sensor", + "lock", + "sensor", +) + WEBSOCKET_EVENTS_REQUIRING_SERIAL = [EVENT_LOCK_LOCKED, EVENT_LOCK_UNLOCKED] WEBSOCKET_EVENTS_TO_TRIGGER_HASS_EVENT = [ EVENT_CAMERA_MOTION_DETECTED, @@ -246,9 +253,9 @@ async def async_setup_entry(hass, config_entry): await simplisafe.async_init() hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = simplisafe - for component in ("alarm_control_panel", "lock"): + for platform in SUPPORTED_PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) @callback @@ -349,18 +356,20 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, entry): """Unload a SimpliSafe config entry.""" - tasks = [ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in ("alarm_control_panel", "lock") - ] + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in SUPPORTED_PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN][DATA_CLIENT].pop(entry.entry_id) + remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(entry.entry_id) + remove_listener() - await asyncio.gather(*tasks) - - hass.data[DOMAIN][DATA_CLIENT].pop(entry.entry_id) - remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(entry.entry_id) - remove_listener() - - return True + return unload_ok async def async_update_options(hass, config_entry): @@ -517,7 +526,7 @@ class SimpliSafe: async def update_system(system): """Update a system.""" - await system.update() + await system.update(cached=False) self._async_process_new_notifications(system) LOGGER.debug('Updated REST API data for "%s"', system.address) async_dispatcher_send( diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py new file mode 100644 index 00000000000..ab30deaf70b --- /dev/null +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -0,0 +1,142 @@ +"""Support for SimpliSafe binary sensors.""" +from simplipy.entity import EntityTypes + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_DOOR, + DEVICE_CLASS_GAS, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_SMOKE, + BinarySensorEntity, +) +from homeassistant.core import callback + +from . import SimpliSafeEntity +from .const import DATA_CLIENT, DOMAIN + +SUPPORTED_BATTERY_SENSOR_TYPES = [ + EntityTypes.entry, + EntityTypes.carbon_monoxide, + EntityTypes.smoke, + EntityTypes.leak, + EntityTypes.temperature, +] + +SUPPORTED_SENSOR_TYPES = [ + EntityTypes.entry, + EntityTypes.carbon_monoxide, + EntityTypes.smoke, + EntityTypes.leak, +] + +HA_SENSOR_TYPES = { + EntityTypes.entry: DEVICE_CLASS_DOOR, + EntityTypes.carbon_monoxide: DEVICE_CLASS_GAS, + EntityTypes.smoke: DEVICE_CLASS_SMOKE, + EntityTypes.leak: DEVICE_CLASS_MOISTURE, +} + +SENSOR_MODELS = { + EntityTypes.entry: "Entry Sensor", + EntityTypes.carbon_monoxide: "Carbon Monoxide Detector", + EntityTypes.smoke: "Smoke Detector", + EntityTypes.leak: "Water Sensor", +} + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up SimpliSafe binary sensors based on a config entry.""" + simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + + # Add sensor + sensors = [ + SimpliSafeBinarySensor(simplisafe, system, sensor) + for system in simplisafe.systems.values() + for sensor in system.sensors.values() + if sensor.type in SUPPORTED_SENSOR_TYPES + ] + + # Add low battery status entity for every sensor + battery_sensors = [ + SimpliSafeSensorBattery(simplisafe, system, sensor) + for system in simplisafe.systems.values() + for sensor in system.sensors.values() + if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES + ] + + async_add_entities(sensors + battery_sensors) + + +class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity): + """Define a SimpliSafe binary sensor entity.""" + + def __init__(self, simplisafe, system, sensor): + """Initialize.""" + super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) + self._system = system + self._sensor = sensor + self._is_on = False + + @property + def device_class(self): + """Return type of sensor.""" + return HA_SENSOR_TYPES[self._sensor.type] + + @property + def device_info(self): + """Return device registry information for this entity.""" + info = super().device_info + info["identifiers"] = {(DOMAIN, self._sensor.serial)} + info["model"] = SENSOR_MODELS[self._sensor.type] + info["name"] = self._sensor.name + return info + + @property + def is_on(self): + """Return true if the sensor is on.""" + return self._is_on + + @callback + def async_update_from_rest_api(self): + """Update the entity with the provided REST API data.""" + self._is_on = self._sensor.triggered + + +class SimpliSafeSensorBattery(SimpliSafeEntity, BinarySensorEntity): + """Define a SimpliSafe battery binary sensor entity.""" + + def __init__(self, simplisafe, system, sensor): + """Initialize.""" + super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) + self._system = system + self._sensor = sensor + self._is_low = False + + @property + def device_class(self): + """Return type of sensor.""" + return DEVICE_CLASS_BATTERY + + @property + def unique_id(self): + """Return unique ID of sensor.""" + return f"{self._sensor.serial}-battery" + + @property + def device_info(self): + """Return device registry information for this entity.""" + info = super().device_info + info["identifiers"] = {(DOMAIN, self._sensor.serial)} + info["model"] = SENSOR_MODELS[self._sensor.type] + info["name"] = self._sensor.name + return info + + @property + def is_on(self): + """Return true if the battery is low.""" + return self._is_low + + @callback + def async_update_from_rest_api(self): + """Update the entity with the provided REST API data.""" + self._is_low = self._sensor.low_battery diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py new file mode 100644 index 00000000000..fcea8b4dab3 --- /dev/null +++ b/homeassistant/components/simplisafe/sensor.py @@ -0,0 +1,67 @@ +"""Support for SimpliSafe freeze sensor.""" +from simplipy.entity import EntityTypes + +from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT +from homeassistant.core import callback + +from . import SimpliSafeEntity +from .const import DATA_CLIENT, DOMAIN + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up SimpliSafe freeze sensors based on a config entry.""" + simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + + async_add_entities( + [ + SimplisafeFreezeSensor(simplisafe, system, sensor) + for system in simplisafe.systems.values() + for sensor in system.sensors.values() + if sensor.type == EntityTypes.temperature + ] + ) + + +class SimplisafeFreezeSensor(SimpliSafeEntity): + """Define a SimpliSafe freeze sensor entity.""" + + def __init__(self, simplisafe, system, sensor): + """Initialize.""" + super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) + self._system = system + self._sensor = sensor + self._state = None + + @property + def device_class(self): + """Return type of sensor.""" + return DEVICE_CLASS_TEMPERATURE + + @property + def unique_id(self): + """Return unique ID of sensor.""" + return self._sensor.serial + + @property + def device_info(self): + """Return device registry information for this entity.""" + info = super().device_info + info["identifiers"] = {(DOMAIN, self._sensor.serial)} + info["model"] = "Freeze Sensor" + info["name"] = self._sensor.name + return info + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return TEMP_FAHRENHEIT + + @property + def state(self): + """Return the sensor state.""" + return self._state + + @callback + def async_update_from_rest_api(self): + """Update the entity with the provided REST API data.""" + self._state = self._sensor.temperature