Exemple #1
0
    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)
Exemple #2
0
    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())
Exemple #3
0
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()
Exemple #4
0
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
Exemple #5
0
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)
Exemple #6
0
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
Exemple #7
0
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()
Exemple #8
0
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
Exemple #9
0
    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())