Set homekit controller entity as unavailable if new connections fail (#21901)

* Set entity as unavailable if new connections fail

* Fix docstring
pull/22004/head
Jc2k 2019-03-13 01:45:34 +00:00 committed by Robbie Trencheny
parent d66cc9befa
commit c0b859d8da
3 changed files with 45 additions and 4 deletions

View File

@ -203,6 +203,7 @@ class HomeKitEntity(Entity):
def __init__(self, accessory, devinfo): def __init__(self, accessory, devinfo):
"""Initialise a generic HomeKit device.""" """Initialise a generic HomeKit device."""
self._available = True
self._name = accessory.model self._name = accessory.model
self._accessory = accessory self._accessory = accessory
self._aid = devinfo['aid'] self._aid = devinfo['aid']
@ -270,14 +271,24 @@ class HomeKitEntity(Entity):
async def async_update(self): async def async_update(self):
"""Obtain a HomeKit device's state.""" """Obtain a HomeKit device's state."""
# pylint: disable=import-error # pylint: disable=import-error
from homekit.exceptions import AccessoryDisconnectedError from homekit.exceptions import (
AccessoryDisconnectedError, AccessoryNotFoundError)
try: try:
new_values_dict = await self._accessory.get_characteristics( new_values_dict = await self._accessory.get_characteristics(
self._chars_to_poll 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 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(): for (_, iid), result in new_values_dict.items():
if 'value' not in result: if 'value' not in result:
@ -303,7 +314,7 @@ class HomeKitEntity(Entity):
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
return self._accessory.pairing is not None return self._available
def get_characteristic_types(self): def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""

View File

@ -6,7 +6,7 @@ from homekit.model.services import AbstractService, ServicesTypes
from homekit.model.characteristics import ( from homekit.model.characteristics import (
AbstractCharacteristic, CharacteristicPermissions, CharacteristicsTypes) AbstractCharacteristic, CharacteristicPermissions, CharacteristicsTypes)
from homekit.model import Accessory, get_id from homekit.model import Accessory, get_id
from homekit.exceptions import AccessoryNotFoundError
from homeassistant.components.homekit_controller import ( from homeassistant.components.homekit_controller import (
DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, SERVICE_HOMEKIT) DOMAIN, HOMEKIT_ACCESSORY_DISPATCH, SERVICE_HOMEKIT)
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -26,6 +26,7 @@ class FakePairing:
"""Create a fake pairing from an accessory model.""" """Create a fake pairing from an accessory model."""
self.accessories = accessories self.accessories = accessories
self.pairing_data = {} self.pairing_data = {}
self.available = True
def list_accessories_and_characteristics(self): def list_accessories_and_characteristics(self):
"""Fake implementation of list_accessories_and_characteristics.""" """Fake implementation of list_accessories_and_characteristics."""
@ -38,6 +39,9 @@ class FakePairing:
def get_characteristics(self, characteristics): def get_characteristics(self, characteristics):
"""Fake implementation of get_characteristics.""" """Fake implementation of get_characteristics."""
if not self.available:
raise AccessoryNotFoundError('Accessory not found')
results = {} results = {}
for aid, cid in characteristics: for aid, cid in characteristics:
for accessory in self.accessories: for accessory in self.accessories:

View File

@ -126,3 +126,29 @@ async def test_switch_read_light_state_color_temp(hass, utcnow):
assert state.state == 'on' assert state.state == 'on'
assert state.attributes['brightness'] == 255 assert state.attributes['brightness'] == 255
assert state.attributes['color_temp'] == 400 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