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-12 23:35:07 +00:00
|
|
|
from august.activity import ACTIVITY_ACTION_STATES, ActivityType
|
2019-10-18 00:06:41 +00:00
|
|
|
from august.lock import LockDoorStatus
|
|
|
|
|
2019-03-21 05:56:46 +00:00
|
|
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
2020-02-12 01:43:56 +00:00
|
|
|
from homeassistant.util import dt
|
2019-03-21 05:56:46 +00:00
|
|
|
|
|
|
|
from . import DATA_AUGUST
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2020-02-18 20:11:05 +00:00
|
|
|
SCAN_INTERVAL = timedelta(seconds=5)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def _async_retrieve_door_state(data, lock):
|
2018-10-19 07:37:02 +00:00
|
|
|
"""Get the latest state of the DoorSense sensor."""
|
2020-02-16 05:08:52 +00:00
|
|
|
return await data.async_get_door_state(lock.device_id)
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def _async_retrieve_online_state(data, doorbell):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Get the latest state of the sensor."""
|
2020-02-16 05:08:52 +00:00
|
|
|
detail = await data.async_get_doorbell_detail(doorbell.device_id)
|
2018-10-23 12:09:08 +00:00
|
|
|
if detail is None:
|
|
|
|
return None
|
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
return detail.is_online
|
|
|
|
|
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def _async_retrieve_motion_state(data, doorbell):
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
return await _async_activity_time_based_state(
|
2019-07-31 19:25:30 +00:00
|
|
|
data, doorbell, [ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_DING]
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def _async_retrieve_ding_state(data, doorbell):
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
return await _async_activity_time_based_state(
|
|
|
|
data, doorbell, [ActivityType.DOORBELL_DING]
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def _async_activity_time_based_state(data, doorbell, activity_types):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Get the latest state of the sensor."""
|
2020-02-16 05:08:52 +00:00
|
|
|
latest = await data.async_get_latest_device_activity(
|
|
|
|
doorbell.device_id, *activity_types
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
if latest is not None:
|
|
|
|
start = latest.activity_start_time
|
2020-02-18 20:11:05 +00:00
|
|
|
end = latest.activity_end_time + timedelta(seconds=45)
|
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-17 18:30:14 +00:00
|
|
|
# sensor_type: [name, device_class, async_state_provider]
|
2020-02-16 05:08:52 +00:00
|
|
|
SENSOR_TYPES_DOOR = {"door_open": ["Open", "door", _async_retrieve_door_state]}
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
SENSOR_TYPES_DOORBELL = {
|
2020-02-16 05:08:52 +00:00
|
|
|
"doorbell_ding": ["Ding", "occupancy", _async_retrieve_ding_state],
|
|
|
|
"doorbell_motion": ["Motion", "motion", _async_retrieve_motion_state],
|
|
|
|
"doorbell_online": ["Online", "connectivity", _async_retrieve_online_state],
|
2018-02-18 08:24:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Set up the August binary sensors."""
|
|
|
|
data = hass.data[DATA_AUGUST]
|
|
|
|
devices = []
|
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
for door in data.locks:
|
|
|
|
for sensor_type in SENSOR_TYPES_DOOR:
|
2020-02-18 20:11:05 +00:00
|
|
|
if not data.lock_has_doorsense(door.device_id):
|
2018-10-19 07:37:02 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"Not adding sensor class %s for lock %s ",
|
2020-02-18 20:11:05 +00:00
|
|
|
SENSOR_TYPES_DOOR[sensor_type][SENSOR_DEVICE_CLASS],
|
2019-07-31 19:25:30 +00:00
|
|
|
door.device_name,
|
2018-10-19 07:37:02 +00:00
|
|
|
)
|
|
|
|
continue
|
|
|
|
|
|
|
|
_LOGGER.debug(
|
|
|
|
"Adding sensor class %s for %s",
|
2020-02-18 20:11:05 +00:00
|
|
|
SENSOR_TYPES_DOOR[sensor_type][SENSOR_DEVICE_CLASS],
|
2019-07-31 19:25:30 +00:00
|
|
|
door.device_name,
|
2018-10-19 07:37:02 +00:00
|
|
|
)
|
|
|
|
devices.append(AugustDoorBinarySensor(data, sensor_type, door))
|
|
|
|
|
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
|
|
|
|
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
class AugustDoorBinarySensor(BinarySensorDevice):
|
|
|
|
"""Representation of an August Door binary sensor."""
|
|
|
|
|
|
|
|
def __init__(self, data, sensor_type, door):
|
|
|
|
"""Initialize the sensor."""
|
|
|
|
self._data = data
|
|
|
|
self._sensor_type = sensor_type
|
|
|
|
self._door = door
|
|
|
|
self._state = None
|
|
|
|
self._available = False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return the availability of this sensor."""
|
|
|
|
return self._available
|
|
|
|
|
|
|
|
@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_DOOR[self._sensor_type][SENSOR_DEVICE_CLASS]
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the binary sensor."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "{} {}".format(
|
2020-02-18 20:11:05 +00:00
|
|
|
self._door.device_name, SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_NAME]
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-10-19 07:37:02 +00:00
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def async_update(self):
|
|
|
|
"""Get the latest state of the sensor and update activity."""
|
2020-02-18 20:11:05 +00:00
|
|
|
async_state_provider = SENSOR_TYPES_DOOR[self._sensor_type][
|
|
|
|
SENSOR_STATE_PROVIDER
|
|
|
|
]
|
|
|
|
lock_door_state = await async_state_provider(self._data, self._door)
|
|
|
|
self._available = (
|
|
|
|
lock_door_state is not None and lock_door_state != LockDoorStatus.UNKNOWN
|
|
|
|
)
|
|
|
|
self._state = lock_door_state == LockDoorStatus.OPEN
|
2018-10-19 07:37:02 +00:00
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
door_activity = await self._data.async_get_latest_device_activity(
|
2020-02-12 01:43:56 +00:00
|
|
|
self._door.device_id, ActivityType.DOOR_OPERATION
|
|
|
|
)
|
|
|
|
|
2020-02-12 23:35:07 +00:00
|
|
|
if door_activity is not None:
|
|
|
|
self._sync_door_activity(door_activity)
|
2020-02-12 01:43:56 +00:00
|
|
|
|
|
|
|
def _update_door_state(self, door_state, update_start_time):
|
|
|
|
new_state = door_state == LockDoorStatus.OPEN
|
|
|
|
if self._state != new_state:
|
|
|
|
self._state = new_state
|
|
|
|
self._data.update_door_state(
|
|
|
|
self._door.device_id, door_state, update_start_time
|
|
|
|
)
|
|
|
|
|
2020-02-12 23:35:07 +00:00
|
|
|
def _sync_door_activity(self, door_activity):
|
2020-02-12 01:43:56 +00:00
|
|
|
"""Check the activity for the latest door open/close activity (events).
|
|
|
|
|
|
|
|
We use this to determine the door state in between calls to the lock
|
|
|
|
api as we update it more frequently
|
|
|
|
"""
|
|
|
|
last_door_state_update_time_utc = self._data.get_last_door_state_update_time_utc(
|
|
|
|
self._door.device_id
|
|
|
|
)
|
2020-02-12 23:35:07 +00:00
|
|
|
activity_end_time_utc = dt.as_utc(door_activity.activity_end_time)
|
2020-02-12 01:43:56 +00:00
|
|
|
|
|
|
|
if activity_end_time_utc > last_door_state_update_time_utc:
|
|
|
|
_LOGGER.debug(
|
|
|
|
"The activity log has new events for %s: [action=%s] [activity_end_time_utc=%s] > [last_door_state_update_time_utc=%s]",
|
|
|
|
self.name,
|
2020-02-12 23:35:07 +00:00
|
|
|
door_activity.action,
|
2020-02-12 01:43:56 +00:00
|
|
|
activity_end_time_utc,
|
|
|
|
last_door_state_update_time_utc,
|
|
|
|
)
|
2020-02-12 23:35:07 +00:00
|
|
|
activity_start_time_utc = dt.as_utc(door_activity.activity_start_time)
|
|
|
|
if door_activity.action in ACTIVITY_ACTION_STATES:
|
|
|
|
self._update_door_state(
|
|
|
|
ACTIVITY_ACTION_STATES[door_activity.action],
|
|
|
|
activity_start_time_utc,
|
|
|
|
)
|
2020-02-12 01:43:56 +00:00
|
|
|
else:
|
|
|
|
_LOGGER.info(
|
|
|
|
"Unhandled door activity action %s for %s",
|
2020-02-12 23:35:07 +00:00
|
|
|
door_activity.action,
|
2020-02-12 01:43:56 +00:00
|
|
|
self.name,
|
|
|
|
)
|
|
|
|
|
2019-02-11 19:48:02 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Get the unique of the door open binary sensor."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "{:s}_{:s}".format(
|
2020-02-18 20:11:05 +00:00
|
|
|
self._door.device_id,
|
|
|
|
SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_NAME].lower(),
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-02-11 19:48:02 +00:00
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
|
|
|
|
class AugustDoorbellBinarySensor(BinarySensorDevice):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Representation of an August binary sensor."""
|
|
|
|
|
|
|
|
def __init__(self, data, sensor_type, doorbell):
|
|
|
|
"""Initialize the sensor."""
|
|
|
|
self._data = data
|
|
|
|
self._sensor_type = sensor_type
|
|
|
|
self._doorbell = doorbell
|
|
|
|
self._state = None
|
2018-10-23 12:09:08 +00:00
|
|
|
self._available = False
|
|
|
|
|
|
|
|
@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."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "{} {}".format(
|
2020-02-18 20:11:05 +00:00
|
|
|
self._doorbell.device_name,
|
|
|
|
SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME],
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2020-02-16 05:08:52 +00:00
|
|
|
async def async_update(self):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Get the latest state of the sensor."""
|
2020-02-18 20:11:05 +00:00
|
|
|
async_state_provider = SENSOR_TYPES_DOORBELL[self._sensor_type][
|
|
|
|
SENSOR_STATE_PROVIDER
|
|
|
|
]
|
2020-02-16 05:08:52 +00:00
|
|
|
self._state = await async_state_provider(self._data, self._doorbell)
|
2020-02-18 20:11:05 +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
|
|
|
|
self._available = self._doorbell.is_online or self._doorbell.status == "standby"
|
2019-02-11 19:48:02 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Get the unique id of the doorbell sensor."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "{:s}_{:s}".format(
|
|
|
|
self._doorbell.device_id,
|
2020-02-18 20:11:05 +00:00
|
|
|
SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME].lower(),
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|