From 987897b0fa2a08cf5d41ab40dae23a8d9b44e5bc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Aug 2023 11:28:01 -1000 Subject: [PATCH] Add support for Yale Doorman to august (#97997) --- .../components/august/binary_sensor.py | 47 +++++--- .../fixtures/lock_with_doorbell.online.json | 100 ++++++++++++++++++ tests/components/august/test_binary_sensor.py | 11 ++ 3 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 tests/components/august/fixtures/lock_with_doorbell.online.json diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index c6f406a5094..2cbeeeee5aa 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -13,7 +13,7 @@ from yalexs.activity import ( ActivityType, ) from yalexs.doorbell import Doorbell, DoorbellDetail -from yalexs.lock import Lock, LockDoorStatus +from yalexs.lock import Lock, LockDetail, LockDoorStatus from yalexs.util import update_lock_detail_from_activity from homeassistant.components.binary_sensor import ( @@ -39,13 +39,16 @@ TIME_TO_RECHECK_DETECTION = timedelta( ) -def _retrieve_online_state(data: AugustData, detail: DoorbellDetail) -> bool: +def _retrieve_online_state( + data: AugustData, detail: DoorbellDetail | LockDetail +) -> bool: """Get the latest state of the sensor.""" # 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 - - return detail.is_online or detail.is_standby + if isinstance(detail, DoorbellDetail): + return detail.is_online or detail.is_standby + return detail.bridge_is_online def _retrieve_motion_state(data: AugustData, detail: DoorbellDetail) -> bool: @@ -72,7 +75,7 @@ def _retrieve_image_capture_state(data: AugustData, detail: DoorbellDetail) -> b return _activity_time_based_state(latest) -def _retrieve_ding_state(data: AugustData, detail: DoorbellDetail) -> bool: +def _retrieve_ding_state(data: AugustData, detail: DoorbellDetail | LockDetail) -> bool: assert data.activity_stream is not None latest = data.activity_stream.get_latest_device_activity( detail.device_id, {ActivityType.DOORBELL_DING} @@ -135,15 +138,7 @@ SENSOR_TYPE_DOOR = AugustBinarySensorEntityDescription( name="Open", ) - -SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] = ( - AugustDoorbellBinarySensorEntityDescription( - key="doorbell_ding", - name="Ding", - device_class=BinarySensorDeviceClass.OCCUPANCY, - value_fn=_retrieve_ding_state, - is_time_based=True, - ), +SENSOR_TYPES_VIDEO_DOORBELL = ( AugustDoorbellBinarySensorEntityDescription( key="doorbell_motion", name="Motion", @@ -169,6 +164,17 @@ SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] = ) +SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] = ( + AugustDoorbellBinarySensorEntityDescription( + key="doorbell_ding", + name="Ding", + device_class=BinarySensorDeviceClass.OCCUPANCY, + value_fn=_retrieve_ding_state, + is_time_based=True, + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -193,8 +199,17 @@ async def async_setup_entry( _LOGGER.debug("Adding sensor class door for %s", door.device_name) entities.append(AugustDoorBinarySensor(data, door, SENSOR_TYPE_DOOR)) + if detail.doorbell: + for description in SENSOR_TYPES_DOORBELL: + _LOGGER.debug( + "Adding doorbell sensor class %s for %s", + description.device_class, + door.device_name, + ) + entities.append(AugustDoorbellBinarySensor(data, door, description)) + for doorbell in data.doorbells: - for description in SENSOR_TYPES_DOORBELL: + for description in SENSOR_TYPES_DOORBELL + SENSOR_TYPES_VIDEO_DOORBELL: _LOGGER.debug( "Adding doorbell sensor class %s for %s", description.device_class, @@ -261,7 +276,7 @@ class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity): def __init__( self, data: AugustData, - device: Doorbell, + device: Doorbell | Lock, description: AugustDoorbellBinarySensorEntityDescription, ) -> None: """Initialize the sensor.""" diff --git a/tests/components/august/fixtures/lock_with_doorbell.online.json b/tests/components/august/fixtures/lock_with_doorbell.online.json new file mode 100644 index 00000000000..bb2367d1111 --- /dev/null +++ b/tests/components/august/fixtures/lock_with_doorbell.online.json @@ -0,0 +1,100 @@ +{ + "LockName": "Front Door Lock", + "Type": 7, + "Created": "2017-12-10T03:12:09.210Z", + "Updated": "2017-12-10T03:12:09.210Z", + "LockID": "A6697750D607098BAE8D6BAA11EF8063", + "HouseID": "000000000000", + "HouseName": "My House", + "Calibrated": false, + "skuNumber": "AUG-SL02-M02-S02", + "timeZone": "America/Vancouver", + "battery": 0.88, + "SerialNumber": "X2FSW05DGA", + "LockStatus": { + "status": "locked", + "doorState": "closed", + "dateTime": "2017-12-10T04:48:30.272Z", + "isLockStatusChanged": true, + "valid": true + }, + "currentFirmwareVersion": "109717e9-3.0.44-3.0.30", + "homeKitEnabled": false, + "zWaveEnabled": false, + "isGalileo": false, + "Bridge": { + "_id": "aaacab87f7efxa0015884999", + "mfgBridgeID": "AAGPP102XX", + "deviceModel": "august-doorbell", + "firmwareVersion": "2.3.0-RC153+201711151527", + "operative": true + }, + "keypad": { + "_id": "5bc65c24e6ef2a263e1450a8", + "serialNumber": "K1GXB0054Z", + "lockID": "92412D1B44004595B5DEB134E151A8D3", + "currentFirmwareVersion": "2.27.0", + "battery": {}, + "batteryLevel": "Medium", + "batteryRaw": 170 + }, + "OfflineKeys": { + "created": [], + "loaded": [ + { + "UserID": "cccca94e-373e-aaaa-bbbb-333396827777", + "slot": 1, + "key": "kkk01d4300c1dcxxx1c330f794941111", + "created": "2017-12-10T03:12:09.215Z", + "loaded": "2017-12-10T03:12:54.391Z" + } + ], + "deleted": [], + "loadedhk": [ + { + "key": "kkk01d4300c1dcxxx1c330f794941222", + "slot": 256, + "UserID": "cccca94e-373e-aaaa-bbbb-333396827777", + "created": "2017-12-10T03:12:09.218Z", + "loaded": "2017-12-10T03:12:55.563Z" + } + ] + }, + "parametersToSet": {}, + "users": { + "cccca94e-373e-aaaa-bbbb-333396827777": { + "UserType": "superuser", + "FirstName": "Foo", + "LastName": "Bar", + "identifiers": ["email:foo@bar.com", "phone:+177777777777"], + "imageInfo": { + "original": { + "width": 948, + "height": 949, + "format": "jpg", + "url": "http://www.image.com/foo.jpeg", + "secure_url": "https://www.image.com/foo.jpeg" + }, + "thumbnail": { + "width": 128, + "height": 128, + "format": "jpg", + "url": "http://www.image.com/foo.jpeg", + "secure_url": "https://www.image.com/foo.jpeg" + } + } + } + }, + "pubsubChannel": "3333a674-ffff-aaaa-b351-b3a4473f3333", + "ruleHash": {}, + "cameras": [], + "geofenceLimits": { + "ios": { + "debounceInterval": 90, + "gpsAccuracyMultiplier": 2.5, + "maximumGeofence": 5000, + "minimumGeofence": 100, + "minGPSAccuracyRequired": 80 + } + } +} diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index f66ba73cebc..2787cdbe23d 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -396,3 +396,14 @@ async def test_door_sense_update_via_pubnub(hass: HomeAssistant) -> None: await hass.config_entries.async_unload(config_entry.entry_id) await hass.async_block_till_done() + + +async def test_create_lock_with_doorbell(hass: HomeAssistant) -> None: + """Test creation of a lock with a doorbell.""" + lock_one = await _mock_lock_from_fixture(hass, "lock_with_doorbell.online.json") + await _create_august_with_devices(hass, [lock_one]) + + ding_sensor = hass.states.get( + "binary_sensor.a6697750d607098bae8d6baa11ef8063_name_ding" + ) + assert ding_sensor.state == STATE_OFF