2019-02-13 20:21:14 +00:00
|
|
|
"""Support for August binary sensors."""
|
2019-03-21 05:56:46 +00:00
|
|
|
from datetime import datetime, timedelta
|
2018-10-19 07:37:02 +00:00
|
|
|
import logging
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2020-02-21 05:06:24 +00:00
|
|
|
from august.activity import ActivityType
|
2019-10-18 00:06:41 +00:00
|
|
|
from august.lock import LockDoorStatus
|
2020-02-21 05:06:24 +00:00
|
|
|
from august.util import update_lock_detail_from_activity
|
2019-10-18 00:06:41 +00:00
|
|
|
|
2020-02-25 18:18:15 +00:00
|
|
|
from homeassistant.components.binary_sensor import (
|
|
|
|
DEVICE_CLASS_CONNECTIVITY,
|
2020-02-29 11:12:50 +00:00
|
|
|
DEVICE_CLASS_DOOR,
|
2020-02-25 18:18:15 +00:00
|
|
|
DEVICE_CLASS_MOTION,
|
|
|
|
DEVICE_CLASS_OCCUPANCY,
|
2020-04-23 19:57:07 +00:00
|
|
|
BinarySensorEntity,
|
2020-02-25 18:18:15 +00:00
|
|
|
)
|
2020-02-27 02:48:44 +00:00
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
|
|
|
from homeassistant.util.dt import utcnow
|
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
from .const import DATA_AUGUST, DOMAIN
|
|
|
|
from .entity import AugustEntityMixin
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2020-02-27 02:48:44 +00:00
|
|
|
TIME_TO_DECLARE_DETECTION = timedelta(seconds=60)
|
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
def _retrieve_online_state(data, detail):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Get the latest state of the sensor."""
|
2020-02-28 03:44:23 +00:00
|
|
|
# The doorbell will go into standby mode when there is no motion
|
|
|
|
# for a short while. It will wake by itself when needed so we need
|
|
|
|
# to consider is available or we will not report motion or dings
|
|
|
|
|
2020-02-26 07:43:41 +00:00
|
|
|
return detail.is_online or detail.is_standby
|
2018-10-23 12:09:08 +00:00
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
def _retrieve_motion_state(data, detail):
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
return _activity_time_based_state(
|
2020-02-25 18:18:15 +00:00
|
|
|
data,
|
|
|
|
detail.device_id,
|
|
|
|
[ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_DING],
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
def _retrieve_ding_state(data, detail):
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
return _activity_time_based_state(
|
2020-02-25 18:18:15 +00:00
|
|
|
data, detail.device_id, [ActivityType.DOORBELL_DING]
|
2020-02-16 05:08:52 +00:00
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
def _activity_time_based_state(data, device_id, activity_types):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Get the latest state of the sensor."""
|
2020-02-28 03:44:23 +00:00
|
|
|
latest = data.activity_stream.get_latest_device_activity(device_id, activity_types)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
if latest is not None:
|
|
|
|
start = latest.activity_start_time
|
2020-02-27 02:48:44 +00:00
|
|
|
end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION
|
2018-02-18 08:24:51 +00:00
|
|
|
return start <= datetime.now() <= end
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2020-02-18 20:11:05 +00:00
|
|
|
SENSOR_NAME = 0
|
|
|
|
SENSOR_DEVICE_CLASS = 1
|
|
|
|
SENSOR_STATE_PROVIDER = 2
|
2020-02-28 03:44:23 +00:00
|
|
|
SENSOR_STATE_IS_TIME_BASED = 3
|
2020-02-18 20:11:05 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
# sensor_type: [name, device_class, state_provider, is_time_based]
|
2018-10-19 07:37:02 +00:00
|
|
|
SENSOR_TYPES_DOORBELL = {
|
2020-02-28 03:44:23 +00:00
|
|
|
"doorbell_ding": ["Ding", DEVICE_CLASS_OCCUPANCY, _retrieve_ding_state, True],
|
|
|
|
"doorbell_motion": ["Motion", DEVICE_CLASS_MOTION, _retrieve_motion_state, True],
|
2020-02-25 18:18:15 +00:00
|
|
|
"doorbell_online": [
|
|
|
|
"Online",
|
|
|
|
DEVICE_CLASS_CONNECTIVITY,
|
2020-02-28 03:44:23 +00:00
|
|
|
_retrieve_online_state,
|
|
|
|
False,
|
2020-02-25 18:18:15 +00:00
|
|
|
],
|
2018-02-18 08:24:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-25 18:18:15 +00:00
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Set up the August binary sensors."""
|
2020-02-25 18:18:15 +00:00
|
|
|
data = hass.data[DOMAIN][config_entry.entry_id][DATA_AUGUST]
|
2018-02-18 08:24:51 +00:00
|
|
|
devices = []
|
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
for door in data.locks:
|
2020-02-28 03:44:23 +00:00
|
|
|
detail = data.get_device_detail(door.device_id)
|
|
|
|
if not detail.doorsense:
|
|
|
|
_LOGGER.debug(
|
2020-07-05 21:04:19 +00:00
|
|
|
"Not adding sensor class door for lock %s because it does not have doorsense",
|
2020-02-28 03:44:23 +00:00
|
|
|
door.device_name,
|
|
|
|
)
|
2020-02-21 05:06:24 +00:00
|
|
|
continue
|
|
|
|
|
2020-02-23 21:38:05 +00:00
|
|
|
_LOGGER.debug("Adding sensor class door for %s", door.device_name)
|
2020-02-21 05:06:24 +00:00
|
|
|
devices.append(AugustDoorBinarySensor(data, "door_open", door))
|
2018-10-19 07:37:02 +00:00
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
for doorbell in data.doorbells:
|
2018-10-19 07:37:02 +00:00
|
|
|
for sensor_type in SENSOR_TYPES_DOORBELL:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"Adding doorbell sensor class %s for %s",
|
2020-02-18 20:11:05 +00:00
|
|
|
SENSOR_TYPES_DOORBELL[sensor_type][SENSOR_DEVICE_CLASS],
|
2019-07-31 19:25:30 +00:00
|
|
|
doorbell.device_name,
|
2018-10-19 07:37:02 +00:00
|
|
|
)
|
2019-07-31 19:25:30 +00:00
|
|
|
devices.append(AugustDoorbellBinarySensor(data, sensor_type, doorbell))
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async_add_entities(devices, True)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
|
2020-04-23 19:57:07 +00:00
|
|
|
class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
2018-10-19 07:37:02 +00:00
|
|
|
"""Representation of an August Door binary sensor."""
|
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
def __init__(self, data, sensor_type, device):
|
2018-10-19 07:37:02 +00:00
|
|
|
"""Initialize the sensor."""
|
2020-02-28 03:44:23 +00:00
|
|
|
super().__init__(data, device)
|
2018-10-19 07:37:02 +00:00
|
|
|
self._data = data
|
|
|
|
self._sensor_type = sensor_type
|
2020-02-28 03:44:23 +00:00
|
|
|
self._device = device
|
|
|
|
self._update_from_data()
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return the availability of this sensor."""
|
2020-02-29 11:12:50 +00:00
|
|
|
return self._detail.bridge_is_online
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def is_on(self):
|
|
|
|
"""Return true if the binary sensor is on."""
|
2020-02-29 11:12:50 +00:00
|
|
|
return self._detail.door_state == LockDoorStatus.OPEN
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def device_class(self):
|
2020-02-21 05:06:24 +00:00
|
|
|
"""Return the class of this device."""
|
2020-02-29 11:12:50 +00:00
|
|
|
return DEVICE_CLASS_DOOR
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the binary sensor."""
|
2020-02-28 03:44:23 +00:00
|
|
|
return f"{self._device.device_name} Open"
|
2018-10-19 07:37:02 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
@callback
|
|
|
|
def _update_from_data(self):
|
2020-02-16 05:08:52 +00:00
|
|
|
"""Get the latest state of the sensor and update activity."""
|
2020-02-28 03:44:23 +00:00
|
|
|
door_activity = self._data.activity_stream.get_latest_device_activity(
|
|
|
|
self._device_id, [ActivityType.DOOR_OPERATION]
|
2020-02-12 01:43:56 +00:00
|
|
|
)
|
|
|
|
|
2020-02-12 23:35:07 +00:00
|
|
|
if door_activity is not None:
|
2020-02-29 11:12:50 +00:00
|
|
|
update_lock_detail_from_activity(self._detail, door_activity)
|
2020-02-12 01:43:56 +00:00
|
|
|
|
2019-02-11 19:48:02 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Get the unique of the door open binary sensor."""
|
2020-02-28 03:44:23 +00:00
|
|
|
return f"{self._device_id}_open"
|
2020-02-27 02:48:44 +00:00
|
|
|
|
|
|
|
|
2020-04-23 19:57:07 +00:00
|
|
|
class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Representation of an August binary sensor."""
|
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
def __init__(self, data, sensor_type, device):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Initialize the sensor."""
|
2020-02-28 03:44:23 +00:00
|
|
|
super().__init__(data, device)
|
2020-02-27 02:48:44 +00:00
|
|
|
self._check_for_off_update_listener = None
|
2018-02-18 08:24:51 +00:00
|
|
|
self._data = data
|
|
|
|
self._sensor_type = sensor_type
|
2020-02-28 03:44:23 +00:00
|
|
|
self._device = device
|
2018-02-18 08:24:51 +00:00
|
|
|
self._state = None
|
2018-10-23 12:09:08 +00:00
|
|
|
self._available = False
|
2020-02-28 03:44:23 +00:00
|
|
|
self._update_from_data()
|
2018-10-23 12:09:08 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return the availability of this sensor."""
|
|
|
|
return self._available
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def is_on(self):
|
|
|
|
"""Return true if the binary sensor is on."""
|
|
|
|
return self._state
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_class(self):
|
|
|
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
2020-02-18 20:11:05 +00:00
|
|
|
return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_DEVICE_CLASS]
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the binary sensor."""
|
2020-02-28 03:44:23 +00:00
|
|
|
return f"{self._device.device_name} {SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME]}"
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _state_provider(self):
|
|
|
|
"""Return the state provider for the binary sensor."""
|
|
|
|
return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_STATE_PROVIDER]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _is_time_based(self):
|
|
|
|
"""Return true of false if the sensor is time based."""
|
|
|
|
return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_STATE_IS_TIME_BASED]
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
@callback
|
|
|
|
def _update_from_data(self):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Get the latest state of the sensor."""
|
2020-02-27 02:48:44 +00:00
|
|
|
self._cancel_any_pending_updates()
|
2020-02-28 03:44:23 +00:00
|
|
|
self._state = self._state_provider(self._data, self._detail)
|
2020-02-25 18:18:15 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
if self._is_time_based:
|
|
|
|
self._available = _retrieve_online_state(self._data, self._detail)
|
|
|
|
self._schedule_update_to_recheck_turn_off_sensor()
|
|
|
|
else:
|
|
|
|
self._available = True
|
2020-02-27 02:48:44 +00:00
|
|
|
|
|
|
|
def _schedule_update_to_recheck_turn_off_sensor(self):
|
|
|
|
"""Schedule an update to recheck the sensor to see if it is ready to turn off."""
|
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
# If the sensor is already off there is nothing to do
|
|
|
|
if not self._state:
|
|
|
|
return
|
|
|
|
|
|
|
|
# self.hass is only available after setup is completed
|
|
|
|
# and we will recheck in async_added_to_hass
|
|
|
|
if not self.hass:
|
|
|
|
return
|
|
|
|
|
2020-02-27 02:48:44 +00:00
|
|
|
@callback
|
|
|
|
def _scheduled_update(now):
|
|
|
|
"""Timer callback for sensor update."""
|
|
|
|
self._check_for_off_update_listener = None
|
2020-02-28 03:44:23 +00:00
|
|
|
self._update_from_data()
|
2020-02-27 02:48:44 +00:00
|
|
|
|
|
|
|
self._check_for_off_update_listener = async_track_point_in_utc_time(
|
|
|
|
self.hass, _scheduled_update, utcnow() + TIME_TO_DECLARE_DETECTION
|
|
|
|
)
|
|
|
|
|
|
|
|
def _cancel_any_pending_updates(self):
|
|
|
|
"""Cancel any updates to recheck a sensor to see if it is ready to turn off."""
|
|
|
|
if self._check_for_off_update_listener:
|
2020-02-28 03:44:23 +00:00
|
|
|
_LOGGER.debug("%s: canceled pending update", self.entity_id)
|
2020-02-27 02:48:44 +00:00
|
|
|
self._check_for_off_update_listener()
|
|
|
|
self._check_for_off_update_listener = None
|
2019-02-11 19:48:02 +00:00
|
|
|
|
2020-02-28 03:44:23 +00:00
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Call the mixin to subscribe and setup an async_track_point_in_utc_time to turn off the sensor if needed."""
|
|
|
|
self._schedule_update_to_recheck_turn_off_sensor()
|
|
|
|
await super().async_added_to_hass()
|
|
|
|
|
2019-02-11 19:48:02 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Get the unique id of the doorbell sensor."""
|
2020-02-23 21:38:05 +00:00
|
|
|
return (
|
2020-02-28 03:44:23 +00:00
|
|
|
f"{self._device_id}_"
|
2020-02-23 21:38:05 +00:00
|
|
|
f"{SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME].lower()}"
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|