async def async_setup(self): """Async setup of august device data and activities.""" token = self._august_gateway.access_token user_data, locks, doorbells = await asyncio.gather( self._api.async_get_user(token), self._api.async_get_operable_locks(token), self._api.async_get_doorbells(token), ) if not doorbells: doorbells = [] if not locks: locks = [] self._doorbells_by_id = { device.device_id: device for device in doorbells } self._locks_by_id = {device.device_id: device for device in locks} self._house_ids = { device.house_id for device in chain(locks, doorbells) } await self._async_refresh_device_detail_by_ids( [device.device_id for device in chain(locks, doorbells)]) # We remove all devices that we are missing # detail as we cannot determine if they are usable. # This also allows us to avoid checking for # detail being None all over the place self._remove_inoperative_locks() self._remove_inoperative_doorbells() pubnub = AugustPubNub() for device in self._device_detail_by_id.values(): pubnub.register_device(device) self.activity_stream = ActivityStream(self._hass, self._api, self._august_gateway, self._house_ids, pubnub) await self.activity_stream.async_setup() pubnub.subscribe(self.async_pubnub_message) self._pubnub_unsub = async_create_pubnub(user_data["UserID"], pubnub) if self._locks_by_id: tasks = [] for lock_id in self._locks_by_id: detail = self._device_detail_by_id[lock_id] tasks.append( self.async_status_async( lock_id, bool(detail.bridge and detail.bridge.hyper_bridge))) await asyncio.gather(*tasks)
async def async_setup(self): """Async setup of august device data and activities.""" token = self._august_gateway.access_token user_data, locks, doorbells = await asyncio.gather( self._api.async_get_user(token), self._api.async_get_operable_locks(token), self._api.async_get_doorbells(token), ) if not doorbells: doorbells = [] if not locks: locks = [] self._doorbells_by_id = { device.device_id: device for device in doorbells } self._locks_by_id = {device.device_id: device for device in locks} self._house_ids = { device.house_id for device in chain(locks, doorbells) } await self._async_refresh_device_detail_by_ids( [device.device_id for device in chain(locks, doorbells)]) # We remove all devices that we are missing # detail as we cannot determine if they are usable. # This also allows us to avoid checking for # detail being None all over the place self._remove_inoperative_locks() self._remove_inoperative_doorbells() pubnub = AugustPubNub() for device in self._device_detail_by_id.values(): pubnub.register_device(device) self.activity_stream = ActivityStream(self._hass, self._api, self._august_gateway, self._house_ids, pubnub) await self.activity_stream.async_setup() pubnub.subscribe(self.async_pubnub_message) self._pubnub_unsub = async_create_pubnub(user_data["UserID"], pubnub) if self._locks_by_id: # Do not prevent setup as the sync can timeout # but it is not a fatal error as the lock # will recover automatically when it comes back online. asyncio.create_task(self._async_initial_sync())
async def test_door_sense_update_via_pubnub(hass): """Test creation of a lock with doorsense and bridge.""" lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) assert lock_one.pubsub_channel == "pubsub" pubnub = AugustPubNub() activities = await _mock_activities_from_fixture(hass, "get_activity.lock.json") config_entry = await _create_august_with_devices( hass, [lock_one], activities=activities, pubnub=pubnub ) binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_ON pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={"status": "kAugLockState_Unlocking", "doorState": "closed"}, ), ) await hass.async_block_till_done() binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_OFF pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={"status": "kAugLockState_Locking", "doorState": "open"}, ), ) await hass.async_block_till_done() binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_ON async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30)) await hass.async_block_till_done() binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_ON pubnub.connected = True async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30)) await hass.async_block_till_done() binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_ON # Ensure pubnub status is always preserved async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=2)) await hass.async_block_till_done() binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_ON pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={"status": "kAugLockState_Unlocking", "doorState": "open"}, ), ) await hass.async_block_till_done() binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_ON async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=4)) await hass.async_block_till_done() binary_sensor_online_with_doorsense_name = hass.states.get( "binary_sensor.online_with_doorsense_name_open" ) assert binary_sensor_online_with_doorsense_name.state == STATE_ON await hass.config_entries.async_unload(config_entry.entry_id) await hass.async_block_till_done()
async def test_doorbell_update_via_pubnub(hass): """Test creation of a doorbell that can be updated via pubnub.""" doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") pubnub = AugustPubNub() await _create_august_with_devices(hass, [doorbell_one], pubnub=pubnub) assert doorbell_one.pubsub_channel == "7c7a6672-59c8-3333-ffff-dcd98705cccc" binary_sensor_k98gidt45gul_name_motion = hass.states.get( "binary_sensor.k98gidt45gul_name_motion" ) assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding" ) assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF pubnub.message( pubnub, Mock( channel=doorbell_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={ "status": "imagecapture", "data": { "result": { "created_at": "2021-03-16T01:07:08.817Z", "secure_url": "https://dyu7azbnaoi74.cloudfront.net/zip/images/zip.jpeg", }, }, }, ), ) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_motion = hass.states.get( "binary_sensor.k98gidt45gul_name_motion" ) assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding" ) assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) with patch( "homeassistant.components.august.binary_sensor._native_datetime", return_value=native_time, ): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_motion = hass.states.get( "binary_sensor.k98gidt45gul_name_motion" ) assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF pubnub.message( pubnub, Mock( channel=doorbell_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={ "status": "buttonpush", }, ), ) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding" ) assert binary_sensor_k98gidt45gul_name_ding.state == STATE_ON new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) with patch( "homeassistant.components.august.binary_sensor._native_datetime", return_value=native_time, ): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding" ) assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
async def test_one_lock_operation_pubnub_connected(hass): """Test lock and unlock operations are async when pubnub is connected.""" lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) assert lock_one.pubsub_channel == "pubsub" pubnub = AugustPubNub() await _create_august_with_devices(hass, [lock_one], pubnub=pubnub) pubnub.connected = True lock_online_with_doorsense_name = hass.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_LOCKED assert lock_online_with_doorsense_name.attributes.get( "battery_level") == 92 assert (lock_online_with_doorsense_name.attributes.get("friendly_name") == "online_with_doorsense Name") data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"} assert await hass.services.async_call(LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True) await hass.async_block_till_done() pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=(dt_util.utcnow().timestamp() + 1) * 10000000, message={ "status": "kAugLockState_Unlocked", }, ), ) await hass.async_block_till_done() await hass.async_block_till_done() lock_online_with_doorsense_name = hass.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_UNLOCKED assert lock_online_with_doorsense_name.attributes.get( "battery_level") == 92 assert (lock_online_with_doorsense_name.attributes.get("friendly_name") == "online_with_doorsense Name") assert await hass.services.async_call(LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True) await hass.async_block_till_done() pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000, message={ "status": "kAugLockState_Locked", }, ), ) await hass.async_block_till_done() await hass.async_block_till_done() lock_online_with_doorsense_name = hass.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_LOCKED # No activity means it will be unavailable until the activity feed has data entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator") assert lock_operator_sensor assert (hass.states.get("sensor.online_with_doorsense_name_operator").state == STATE_UNKNOWN)
async def _create_august_with_devices( # noqa: C901 hass, devices, api_call_side_effects=None, activities=None, pubnub=None): if api_call_side_effects is None: api_call_side_effects = {} if pubnub is None: pubnub = AugustPubNub() device_data = {"doorbells": [], "locks": []} for device in devices: if isinstance(device, LockDetail): device_data["locks"].append({ "base": _mock_august_lock(device.device_id), "detail": device }) elif isinstance(device, DoorbellDetail): device_data["doorbells"].append({ "base": _mock_august_doorbell(device.device_id), "detail": device }) else: raise ValueError def _get_device_detail(device_type, device_id): for device in device_data[device_type]: if device["detail"].device_id == device_id: return device["detail"] raise ValueError def _get_base_devices(device_type): base_devices = [] for device in device_data[device_type]: base_devices.append(device["base"]) return base_devices def get_lock_detail_side_effect(access_token, device_id): return _get_device_detail("locks", device_id) def get_doorbell_detail_side_effect(access_token, device_id): return _get_device_detail("doorbells", device_id) def get_operable_locks_side_effect(access_token): return _get_base_devices("locks") def get_doorbells_side_effect(access_token): return _get_base_devices("doorbells") def get_house_activities_side_effect(access_token, house_id, limit=10): if activities is not None: return activities return [] def lock_return_activities_side_effect(access_token, device_id): lock = _get_device_detail("locks", device_id) return [ # There is a check to prevent out of order events # so we set the doorclosed & lock event in the future # to prevent a race condition where we reject the event # because it happened before the dooropen & unlock event. _mock_lock_operation_activity(lock, "lock", 2000), _mock_door_operation_activity(lock, "doorclosed", 2000), ] def unlock_return_activities_side_effect(access_token, device_id): lock = _get_device_detail("locks", device_id) return [ _mock_lock_operation_activity(lock, "unlock", 0), _mock_door_operation_activity(lock, "dooropen", 0), ] if "get_lock_detail" not in api_call_side_effects: api_call_side_effects["get_lock_detail"] = get_lock_detail_side_effect if "get_doorbell_detail" not in api_call_side_effects: api_call_side_effects[ "get_doorbell_detail"] = get_doorbell_detail_side_effect if "get_operable_locks" not in api_call_side_effects: api_call_side_effects[ "get_operable_locks"] = get_operable_locks_side_effect if "get_doorbells" not in api_call_side_effects: api_call_side_effects["get_doorbells"] = get_doorbells_side_effect if "get_house_activities" not in api_call_side_effects: api_call_side_effects[ "get_house_activities"] = get_house_activities_side_effect if "lock_return_activities" not in api_call_side_effects: api_call_side_effects[ "lock_return_activities"] = lock_return_activities_side_effect if "unlock_return_activities" not in api_call_side_effects: api_call_side_effects[ "unlock_return_activities"] = unlock_return_activities_side_effect api_instance, entry = await _mock_setup_august_with_api_side_effects( hass, api_call_side_effects, pubnub) if device_data["locks"]: # Ensure we sync status when the integration is loaded if there # are any locks assert api_instance.async_status_async.mock_calls return entry
async def test_lock_update_via_pubnub(opp): """Test creation of a lock with doorsense and bridge.""" lock_one = await _mock_doorsense_enabled_august_lock_detail(opp) assert lock_one.pubsub_channel == "pubsub" pubnub = AugustPubNub() activities = await _mock_activities_from_fixture(opp, "get_activity.lock.json") config_entry = await _create_august_with_devices(opp, [lock_one], activities=activities, pubnub=pubnub) lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_LOCKED pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={ "status": "kAugLockState_Unlocking", }, ), ) await opp.async_block_till_done() lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_UNLOCKED pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={ "status": "kAugLockState_Locking", }, ), ) await opp.async_block_till_done() lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_LOCKED async_fire_time_changed(opp, dt_util.utcnow() + datetime.timedelta(seconds=30)) await opp.async_block_till_done() lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_LOCKED pubnub.connected = True async_fire_time_changed(opp, dt_util.utcnow() + datetime.timedelta(seconds=30)) await opp.async_block_till_done() lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_LOCKED # Ensure pubnub status is always preserved async_fire_time_changed(opp, dt_util.utcnow() + datetime.timedelta(hours=2)) await opp.async_block_till_done() lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_LOCKED pubnub.message( pubnub, Mock( channel=lock_one.pubsub_channel, timetoken=dt_util.utcnow().timestamp() * 10000000, message={ "status": "kAugLockState_Unlocking", }, ), ) await opp.async_block_till_done() lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_UNLOCKED async_fire_time_changed(opp, dt_util.utcnow() + datetime.timedelta(hours=4)) await opp.async_block_till_done() lock_online_with_doorsense_name = opp.states.get( "lock.online_with_doorsense_name") assert lock_online_with_doorsense_name.state == STATE_UNLOCKED await opp.config_entries.async_unload(config_entry.entry_id) await opp.async_block_till_done()
async def test_doorbell_update_via_pubnub(hass): """Test creation of a doorbell that can be updated via pubnub.""" doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") pubnub = AugustPubNub() await _create_august_with_devices(hass, [doorbell_one], pubnub=pubnub) assert doorbell_one.pubsub_channel == "7c7a6672-59c8-3333-ffff-dcd98705cccc" binary_sensor_k98gidt45gul_name_motion = hass.states.get( "binary_sensor.k98gidt45gul_name_motion") assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding") assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF pubnub.message( pubnub, Mock( channel=doorbell_one.pubsub_channel, timetoken=_timetoken(), message={ "status": "imagecapture", "data": { "result": { "created_at": "2021-03-16T01:07:08.817Z", "secure_url": "https://dyu7azbnaoi74.cloudfront.net/zip/images/zip.jpeg", }, }, }, ), ) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_image_capture = hass.states.get( "binary_sensor.k98gidt45gul_name_image_capture") assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_ON pubnub.message( pubnub, Mock( channel=doorbell_one.pubsub_channel, timetoken=_timetoken(), message={ "status": "doorbell_motion_detected", "data": { "event": "doorbell_motion_detected", "image": { "height": 640, "width": 480, "format": "jpg", "created_at": "2021-03-16T02:36:26.886Z", "bytes": 14061, "secure_url": "https://dyu7azbnaoi74.cloudfront.net/images/1f8.jpeg", "url": "https://dyu7azbnaoi74.cloudfront.net/images/1f8.jpeg", "etag": "09e839331c4ea59eef28081f2caa0e90", }, "doorbellName": "Front Door", "callID": None, "origin": "mars-api", "mutableContent": True, }, }, ), ) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_motion = hass.states.get( "binary_sensor.k98gidt45gul_name_motion") assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding") assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) with patch( "homeassistant.components.august.binary_sensor._native_datetime", return_value=native_time, ): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_image_capture = hass.states.get( "binary_sensor.k98gidt45gul_name_image_capture") assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_OFF pubnub.message( pubnub, Mock( channel=doorbell_one.pubsub_channel, timetoken=_timetoken(), message={ "status": "buttonpush", }, ), ) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding") assert binary_sensor_k98gidt45gul_name_ding.state == STATE_ON new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) with patch( "homeassistant.components.august.binary_sensor._native_datetime", return_value=native_time, ): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() binary_sensor_k98gidt45gul_name_ding = hass.states.get( "binary_sensor.k98gidt45gul_name_ding") assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
async def async_setup(self): """Async setup of august device data and activities.""" token = self._august_gateway.access_token # This used to be a gather but it was less reliable with august's recent api changes. user_data = await self._api.async_get_user(token) locks = await self._api.async_get_operable_locks(token) doorbells = await self._api.async_get_doorbells(token) if not doorbells: doorbells = [] if not locks: locks = [] self._doorbells_by_id = { device.device_id: device for device in doorbells } self._locks_by_id = {device.device_id: device for device in locks} self._house_ids = { device.house_id for device in chain(locks, doorbells) } await self._async_refresh_device_detail_by_ids( [device.device_id for device in chain(locks, doorbells)]) # We remove all devices that we are missing # detail as we cannot determine if they are usable. # This also allows us to avoid checking for # detail being None all over the place # Currently we know how to feed data to yalexe_ble # but we do not know how to send it to homekit_controller # yet _async_trigger_ble_lock_discovery( self._hass, [ lock_detail for lock_detail in self._device_detail_by_id.values() if isinstance(lock_detail, LockDetail) and lock_detail.offline_key ], ) self._remove_inoperative_locks() self._remove_inoperative_doorbells() pubnub = AugustPubNub() for device in self._device_detail_by_id.values(): pubnub.register_device(device) self.activity_stream = ActivityStream(self._hass, self._api, self._august_gateway, self._house_ids, pubnub) await self.activity_stream.async_setup() pubnub.subscribe(self.async_pubnub_message) self._pubnub_unsub = async_create_pubnub(user_data["UserID"], pubnub) if self._locks_by_id: # Do not prevent setup as the sync can timeout # but it is not a fatal error as the lock # will recover automatically when it comes back online. asyncio.create_task(self._async_initial_sync())