Significantly reduce code in august integration (#32030)

* Significantly reduce code in august integration

* Activity updates can now be processed by py-august
  this allows us to eliminate the activity sync
  code for the door sensors and locks

* Lock and door state can now be consumed from
  the lock detail api which allows us to
  remove the status call apis and reduce
  the number of API calls to august

* Refactor the testing method for locks (part #1)

* Update homeassistant/components/august/binary_sensor.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Switch to asynctest instead of unittest for mock.patch

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
J. Nick Koston 2020-02-20 23:06:24 -06:00 committed by GitHub
parent a12c4da0ca
commit d4075fb262
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 579 additions and 429 deletions

View file

@ -2,11 +2,11 @@
from datetime import datetime, timedelta
import logging
from august.activity import ACTIVITY_ACTION_STATES, ActivityType
from august.activity import ActivityType
from august.lock import LockDoorStatus
from august.util import update_lock_detail_from_activity
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.util import dt
from . import DATA_AUGUST
@ -15,11 +15,6 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
async def _async_retrieve_door_state(data, lock):
"""Get the latest state of the DoorSense sensor."""
return await data.async_get_door_state(lock.device_id)
async def _async_retrieve_online_state(data, doorbell):
"""Get the latest state of the sensor."""
detail = await data.async_get_doorbell_detail(doorbell.device_id)
@ -61,8 +56,6 @@ SENSOR_DEVICE_CLASS = 1
SENSOR_STATE_PROVIDER = 2
# sensor_type: [name, device_class, async_state_provider]
SENSOR_TYPES_DOOR = {"door_open": ["Open", "door", _async_retrieve_door_state]}
SENSOR_TYPES_DOORBELL = {
"doorbell_ding": ["Ding", "occupancy", _async_retrieve_ding_state],
"doorbell_motion": ["Motion", "motion", _async_retrieve_motion_state],
@ -76,21 +69,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
devices = []
for door in data.locks:
for sensor_type in SENSOR_TYPES_DOOR:
if not data.lock_has_doorsense(door.device_id):
_LOGGER.debug(
"Not adding sensor class %s for lock %s ",
SENSOR_TYPES_DOOR[sensor_type][SENSOR_DEVICE_CLASS],
door.device_name,
)
continue
if not data.lock_has_doorsense(door.device_id):
_LOGGER.debug(
"Adding sensor class %s for %s",
SENSOR_TYPES_DOOR[sensor_type][SENSOR_DEVICE_CLASS],
door.device_name,
"Not adding sensor class door for lock %s ", door.device_name,
)
devices.append(AugustDoorBinarySensor(data, sensor_type, door))
continue
_LOGGER.debug(
"Adding sensor class door for %s", door.device_name,
)
devices.append(AugustDoorBinarySensor(data, "door_open", door))
for doorbell in data.doorbells:
for sensor_type in SENSOR_TYPES_DOORBELL:
@ -127,81 +115,35 @@ class AugustDoorBinarySensor(BinarySensorDevice):
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_DEVICE_CLASS]
"""Return the class of this device."""
return "door"
@property
def name(self):
"""Return the name of the binary sensor."""
return "{} {}".format(
self._door.device_name, SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_NAME]
)
return "{} Open".format(self._door.device_name)
async def async_update(self):
"""Get the latest state of the sensor and update activity."""
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
door_activity = await self._data.async_get_latest_device_activity(
self._door.device_id, ActivityType.DOOR_OPERATION
)
detail = await self._data.async_get_lock_detail(self._door.device_id)
if door_activity is not None:
self._sync_door_activity(door_activity)
update_lock_detail_from_activity(detail, door_activity)
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
)
lock_door_state = None
if detail is not None:
lock_door_state = detail.door_state
def _sync_door_activity(self, door_activity):
"""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
)
activity_end_time_utc = dt.as_utc(door_activity.activity_end_time)
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,
door_activity.action,
activity_end_time_utc,
last_door_state_update_time_utc,
)
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,
)
else:
_LOGGER.info(
"Unhandled door activity action %s for %s",
door_activity.action,
self.name,
)
self._available = lock_door_state != LockDoorStatus.UNKNOWN
self._state = lock_door_state == LockDoorStatus.OPEN
@property
def unique_id(self) -> str:
"""Get the unique of the door open binary sensor."""
return "{:s}_{:s}".format(
self._door.device_id,
SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_NAME].lower(),
)
return f"{self._door.device_id}_open"
class AugustDoorbellBinarySensor(BinarySensorDevice):