Add an asyncio Lock around pairing, which cant be used concurrently (#21933)

This commit is contained in:
Jc2k 2019-03-11 18:59:41 +00:00 committed by Paulus Schoutsen
parent 4f5446ff02
commit 5e2302e469
7 changed files with 76 additions and 63 deletions

View file

@ -1,4 +1,5 @@
"""Support for Homekit device discovery.""" """Support for Homekit device discovery."""
import asyncio
import json import json
import logging import logging
import os import os
@ -74,6 +75,8 @@ class HKDevice():
self.configurator = hass.components.configurator self.configurator = hass.components.configurator
self._connection_warning_logged = False self._connection_warning_logged = False
self.pairing_lock = asyncio.Lock(loop=hass.loop)
self.pairing = self.controller.pairings.get(hkid) self.pairing = self.controller.pairings.get(hkid)
if self.pairing is not None: if self.pairing is not None:
@ -168,6 +171,32 @@ class HKDevice():
'name': 'HomeKit code', 'name': 'HomeKit code',
'type': 'string'}]) 'type': 'string'}])
async def get_characteristics(self, *args, **kwargs):
"""Read latest state from homekit accessory."""
async with self.pairing_lock:
chars = await self.hass.async_add_executor_job(
self.pairing.get_characteristics,
*args,
**kwargs,
)
return chars
async def put_characteristics(self, characteristics):
"""Control a HomeKit device state from Home Assistant."""
chars = []
for row in characteristics:
chars.append((
row['aid'],
row['iid'],
row['value'],
))
async with self.pairing_lock:
await self.hass.async_add_executor_job(
self.pairing.put_characteristics,
chars
)
class HomeKitEntity(Entity): class HomeKitEntity(Entity):
"""Representation of a Home Assistant HomeKit device.""" """Representation of a Home Assistant HomeKit device."""
@ -238,15 +267,15 @@ class HomeKitEntity(Entity):
# pylint: disable=not-callable # pylint: disable=not-callable
setup_fn(char) setup_fn(char)
def 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
pairing = self._accessory.pairing
try: try:
new_values_dict = pairing.get_characteristics(self._chars_to_poll) new_values_dict = await self._accessory.get_characteristics(
self._chars_to_poll
)
except AccessoryDisconnectedError: except AccessoryDisconnectedError:
return return
@ -280,22 +309,6 @@ class HomeKitEntity(Entity):
"""Define the homekit characteristics the entity cares about.""" """Define the homekit characteristics the entity cares about."""
raise NotImplementedError raise NotImplementedError
def update_characteristics(self, characteristics):
"""Synchronise a HomeKit device state with Home Assistant."""
pass
def put_characteristics(self, characteristics):
"""Control a HomeKit device state from Home Assistant."""
chars = []
for row in characteristics:
chars.append((
row['aid'],
row['iid'],
row['value'],
))
self._accessory.pairing.put_characteristics(chars)
def setup(hass, config): def setup(hass, config):
"""Set up for Homekit devices.""" """Set up for Homekit devices."""

View file

@ -74,28 +74,28 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
"""Return the state of the device.""" """Return the state of the device."""
return self._state return self._state
def alarm_disarm(self, code=None): async def async_alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
self.set_alarm_state(STATE_ALARM_DISARMED, code) await self.set_alarm_state(STATE_ALARM_DISARMED, code)
def alarm_arm_away(self, code=None): async def async_alarm_arm_away(self, code=None):
"""Send arm command.""" """Send arm command."""
self.set_alarm_state(STATE_ALARM_ARMED_AWAY, code) await self.set_alarm_state(STATE_ALARM_ARMED_AWAY, code)
def alarm_arm_home(self, code=None): async def async_alarm_arm_home(self, code=None):
"""Send stay command.""" """Send stay command."""
self.set_alarm_state(STATE_ALARM_ARMED_HOME, code) await self.set_alarm_state(STATE_ALARM_ARMED_HOME, code)
def alarm_arm_night(self, code=None): async def async_alarm_arm_night(self, code=None):
"""Send night command.""" """Send night command."""
self.set_alarm_state(STATE_ALARM_ARMED_NIGHT, code) await self.set_alarm_state(STATE_ALARM_ARMED_NIGHT, code)
def set_alarm_state(self, state, code=None): async def set_alarm_state(self, state, code=None):
"""Send state command.""" """Send state command."""
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['security-system-state.target'], 'iid': self._chars['security-system-state.target'],
'value': TARGET_STATE_MAP[state]}] 'value': TARGET_STATE_MAP[state]}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View file

@ -80,21 +80,21 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
def _update_temperature_target(self, value): def _update_temperature_target(self, value):
self._target_temp = value self._target_temp = value
def set_temperature(self, **kwargs): async def async_set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE) temp = kwargs.get(ATTR_TEMPERATURE)
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['temperature.target'], 'iid': self._chars['temperature.target'],
'value': temp}] 'value': temp}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
def set_operation_mode(self, operation_mode): async def async_set_operation_mode(self, operation_mode):
"""Set new target operation mode.""" """Set new target operation mode."""
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['heating-cooling.target'], 'iid': self._chars['heating-cooling.target'],
'value': MODE_HASS_TO_HOMEKIT[operation_mode]}] 'value': MODE_HASS_TO_HOMEKIT[operation_mode]}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property @property
def state(self): def state(self):

View file

@ -114,20 +114,20 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
return self._state == STATE_OPENING return self._state == STATE_OPENING
def open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Send open command.""" """Send open command."""
self.set_door_state(STATE_OPEN) await self.set_door_state(STATE_OPEN)
def close_cover(self, **kwargs): async def async_close_cover(self, **kwargs):
"""Send close command.""" """Send close command."""
self.set_door_state(STATE_CLOSED) await self.set_door_state(STATE_CLOSED)
def set_door_state(self, state): async def set_door_state(self, state):
"""Send state command.""" """Send state command."""
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['door-state.target'], 'iid': self._chars['door-state.target'],
'value': TARGET_GARAGE_STATE_MAP[state]}] 'value': TARGET_GARAGE_STATE_MAP[state]}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@ -232,41 +232,41 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
return self._state == STATE_OPENING return self._state == STATE_OPENING
def open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Send open command.""" """Send open command."""
self.set_cover_position(position=100) await self.async_set_cover_position(position=100)
def close_cover(self, **kwargs): async def close_cover(self, **kwargs):
"""Send close command.""" """Send close command."""
self.set_cover_position(position=0) await self.async_set_cover_position(position=0)
def set_cover_position(self, **kwargs): async def async_set_cover_position(self, **kwargs):
"""Send position command.""" """Send position command."""
position = kwargs[ATTR_POSITION] position = kwargs[ATTR_POSITION]
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['position.target'], 'iid': self._chars['position.target'],
'value': position}] 'value': position}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property @property
def current_cover_tilt_position(self): def current_cover_tilt_position(self):
"""Return current position of cover tilt.""" """Return current position of cover tilt."""
return self._tilt_position return self._tilt_position
def set_cover_tilt_position(self, **kwargs): async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position.""" """Move the cover tilt to a specific position."""
tilt_position = kwargs[ATTR_TILT_POSITION] tilt_position = kwargs[ATTR_TILT_POSITION]
if 'vertical-tilt.target' in self._chars: if 'vertical-tilt.target' in self._chars:
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['vertical-tilt.target'], 'iid': self._chars['vertical-tilt.target'],
'value': tilt_position}] 'value': tilt_position}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
elif 'horizontal-tilt.target' in self._chars: elif 'horizontal-tilt.target' in self._chars:
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': 'iid':
self._chars['horizontal-tilt.target'], self._chars['horizontal-tilt.target'],
'value': tilt_position}] 'value': tilt_position}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View file

@ -101,7 +101,7 @@ class HomeKitLight(HomeKitEntity, Light):
"""Flag supported features.""" """Flag supported features."""
return self._features return self._features
def turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn the specified light on.""" """Turn the specified light on."""
hs_color = kwargs.get(ATTR_HS_COLOR) hs_color = kwargs.get(ATTR_HS_COLOR)
temperature = kwargs.get(ATTR_COLOR_TEMP) temperature = kwargs.get(ATTR_COLOR_TEMP)
@ -127,11 +127,11 @@ class HomeKitLight(HomeKitEntity, Light):
characteristics.append({'aid': self._aid, characteristics.append({'aid': self._aid,
'iid': self._chars['on'], 'iid': self._chars['on'],
'value': True}) 'value': True})
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
def turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn the specified light off.""" """Turn the specified light off."""
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['on'], 'iid': self._chars['on'],
'value': False}] 'value': False}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)

View file

@ -75,20 +75,20 @@ class HomeKitLock(HomeKitEntity, LockDevice):
"""Return True if entity is available.""" """Return True if entity is available."""
return self._state is not None return self._state is not None
def lock(self, **kwargs): async def async_lock(self, **kwargs):
"""Lock the device.""" """Lock the device."""
self._set_lock_state(STATE_LOCKED) await self._set_lock_state(STATE_LOCKED)
def unlock(self, **kwargs): async def async_unlock(self, **kwargs):
"""Unlock the device.""" """Unlock the device."""
self._set_lock_state(STATE_UNLOCKED) await self._set_lock_state(STATE_UNLOCKED)
def _set_lock_state(self, state): async def _set_lock_state(self, state):
"""Send state command.""" """Send state command."""
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['lock-mechanism.target-state'], 'iid': self._chars['lock-mechanism.target-state'],
'value': TARGET_STATE_MAP[state]}] 'value': TARGET_STATE_MAP[state]}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View file

@ -48,20 +48,20 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice):
"""Return true if device is on.""" """Return true if device is on."""
return self._on return self._on
def turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn the specified switch on.""" """Turn the specified switch on."""
self._on = True self._on = True
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['on'], 'iid': self._chars['on'],
'value': True}] 'value': True}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
def turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn the specified switch off.""" """Turn the specified switch off."""
characteristics = [{'aid': self._aid, characteristics = [{'aid': self._aid,
'iid': self._chars['on'], 'iid': self._chars['on'],
'value': False}] 'value': False}]
self.put_characteristics(characteristics) await self._accessory.put_characteristics(characteristics)
@property @property
def device_state_attributes(self): def device_state_attributes(self):