Set homekit controller entity as unavailable if new connections fail (#21901)
* Set entity as unavailable if new connections fail * Fix docstringpull/22004/head
parent
d66cc9befa
commit
c0b859d8da
|
@ -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."""
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue