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."""
import asyncio
import json
import logging
import os
@ -74,6 +75,8 @@ class HKDevice():
self.configurator = hass.components.configurator
self._connection_warning_logged = False
self.pairing_lock = asyncio.Lock(loop=hass.loop)
self.pairing = self.controller.pairings.get(hkid)
if self.pairing is not None:
@ -168,6 +171,32 @@ class HKDevice():
'name': 'HomeKit code',
'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):
"""Representation of a Home Assistant HomeKit device."""
@ -238,15 +267,15 @@ class HomeKitEntity(Entity):
# pylint: disable=not-callable
setup_fn(char)
def update(self):
async def async_update(self):
"""Obtain a HomeKit device's state."""
# pylint: disable=import-error
from homekit.exceptions import AccessoryDisconnectedError
pairing = self._accessory.pairing
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:
return
@ -280,22 +309,6 @@ class HomeKitEntity(Entity):
"""Define the homekit characteristics the entity cares about."""
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):
"""Set up for Homekit devices."""

View file

@ -74,28 +74,28 @@ class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
"""Return the state of the device."""
return self._state
def alarm_disarm(self, code=None):
async def async_alarm_disarm(self, code=None):
"""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."""
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."""
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."""
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."""
characteristics = [{'aid': self._aid,
'iid': self._chars['security-system-state.target'],
'value': TARGET_STATE_MAP[state]}]
self.put_characteristics(characteristics)
await self._accessory.put_characteristics(characteristics)
@property
def device_state_attributes(self):

View file

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

View file

@ -114,20 +114,20 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
"""Return if the cover is opening or not."""
return self._state == STATE_OPENING
def open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs):
"""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."""
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."""
characteristics = [{'aid': self._aid,
'iid': self._chars['door-state.target'],
'value': TARGET_GARAGE_STATE_MAP[state]}]
self.put_characteristics(characteristics)
await self._accessory.put_characteristics(characteristics)
@property
def device_state_attributes(self):
@ -232,41 +232,41 @@ class HomeKitWindowCover(HomeKitEntity, CoverDevice):
"""Return if the cover is opening or not."""
return self._state == STATE_OPENING
def open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs):
"""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."""
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."""
position = kwargs[ATTR_POSITION]
characteristics = [{'aid': self._aid,
'iid': self._chars['position.target'],
'value': position}]
self.put_characteristics(characteristics)
await self._accessory.put_characteristics(characteristics)
@property
def current_cover_tilt_position(self):
"""Return current position of cover tilt."""
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."""
tilt_position = kwargs[ATTR_TILT_POSITION]
if 'vertical-tilt.target' in self._chars:
characteristics = [{'aid': self._aid,
'iid': self._chars['vertical-tilt.target'],
'value': tilt_position}]
self.put_characteristics(characteristics)
await self._accessory.put_characteristics(characteristics)
elif 'horizontal-tilt.target' in self._chars:
characteristics = [{'aid': self._aid,
'iid':
self._chars['horizontal-tilt.target'],
'value': tilt_position}]
self.put_characteristics(characteristics)
await self._accessory.put_characteristics(characteristics)
@property
def device_state_attributes(self):

View file

@ -101,7 +101,7 @@ class HomeKitLight(HomeKitEntity, Light):
"""Flag supported features."""
return self._features
def turn_on(self, **kwargs):
async def async_turn_on(self, **kwargs):
"""Turn the specified light on."""
hs_color = kwargs.get(ATTR_HS_COLOR)
temperature = kwargs.get(ATTR_COLOR_TEMP)
@ -127,11 +127,11 @@ class HomeKitLight(HomeKitEntity, Light):
characteristics.append({'aid': self._aid,
'iid': self._chars['on'],
'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."""
characteristics = [{'aid': self._aid,
'iid': self._chars['on'],
'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 self._state is not None
def lock(self, **kwargs):
async def async_lock(self, **kwargs):
"""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."""
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."""
characteristics = [{'aid': self._aid,
'iid': self._chars['lock-mechanism.target-state'],
'value': TARGET_STATE_MAP[state]}]
self.put_characteristics(characteristics)
await self._accessory.put_characteristics(characteristics)
@property
def device_state_attributes(self):

View file

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