From c0b859d8da76f5d992860eb0edb04800ec28ccf3 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Wed, 13 Mar 2019 01:45:34 +0000 Subject: [PATCH] Set homekit controller entity as unavailable if new connections fail (#21901) * Set entity as unavailable if new connections fail * Fix docstring --- .../components/homekit_controller/__init__.py | 17 +++++++++--- tests/components/homekit_controller/common.py | 6 ++++- .../homekit_controller/test_light.py | 26 +++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 4b73aa26455f..ec38cf881d63 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -203,6 +203,7 @@ class HomeKitEntity(Entity): def __init__(self, accessory, devinfo): """Initialise a generic HomeKit device.""" + self._available = True self._name = accessory.model self._accessory = accessory self._aid = devinfo['aid'] @@ -270,14 +271,24 @@ class HomeKitEntity(Entity): async def async_update(self): """Obtain a HomeKit device's state.""" # pylint: disable=import-error - from homekit.exceptions import AccessoryDisconnectedError + from homekit.exceptions import ( + AccessoryDisconnectedError, AccessoryNotFoundError) try: new_values_dict = await self._accessory.get_characteristics( self._chars_to_poll ) - except AccessoryDisconnectedError: + except AccessoryNotFoundError: + # Not only did the connection fail, but also the accessory is not + # visible on the network. + self._available = False return + except AccessoryDisconnectedError: + # Temporary connection failure. Device is still available but our + # connection was dropped. + return + + self._available = True for (_, iid), result in new_values_dict.items(): if 'value' not in result: @@ -303,7 +314,7 @@ class HomeKitEntity(Entity): @property def available(self) -> bool: """Return True if entity is available.""" - return self._accessory.pairing is not None + return self._available def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 29e7f4e986e0..0447de979296 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -6,7 +6,7 @@ from homekit.model.services import AbstractService, ServicesTypes from homekit.model.characteristics import ( AbstractCharacteristic, CharacteristicPermissions, CharacteristicsTypes) from homekit.model import Accessory, get_id - +from homekit.exceptions import AccessoryNotFoundError from homeassistant.components.homekit_controller import ( DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, SERVICE_HOMEKIT) from homeassistant.setup import async_setup_component @@ -26,6 +26,7 @@ class FakePairing: """Create a fake pairing from an accessory model.""" self.accessories = accessories self.pairing_data = {} + self.available = True def list_accessories_and_characteristics(self): """Fake implementation of list_accessories_and_characteristics.""" @@ -38,6 +39,9 @@ class FakePairing: def get_characteristics(self, characteristics): """Fake implementation of get_characteristics.""" + if not self.available: + raise AccessoryNotFoundError('Accessory not found') + results = {} for aid, cid in characteristics: for accessory in self.accessories: diff --git a/tests/components/homekit_controller/test_light.py b/tests/components/homekit_controller/test_light.py index 0509d70c0b93..59363f721468 100644 --- a/tests/components/homekit_controller/test_light.py +++ b/tests/components/homekit_controller/test_light.py @@ -126,3 +126,29 @@ async def test_switch_read_light_state_color_temp(hass, utcnow): assert state.state == 'on' assert state.attributes['brightness'] == 255 assert state.attributes['color_temp'] == 400 + + +async def test_light_becomes_unavailable_but_recovers(hass, utcnow): + """Test transition to and from unavailable state.""" + bulb = create_lightbulb_service_with_color_temp() + helper = await setup_test_component(hass, [bulb]) + + # Initial state is that the light is off + state = await helper.poll_and_get_state() + assert state.state == 'off' + + # Test device goes offline + helper.pairing.available = False + state = await helper.poll_and_get_state() + assert state.state == 'unavailable' + + # Simulate that someone switched on the device in the real world not via HA + helper.characteristics[LIGHT_ON].set_value(True) + helper.characteristics[LIGHT_BRIGHTNESS].value = 100 + helper.characteristics[LIGHT_COLOR_TEMP].value = 400 + helper.pairing.available = True + + state = await helper.poll_and_get_state() + assert state.state == 'on' + assert state.attributes['brightness'] == 255 + assert state.attributes['color_temp'] == 400