Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions homeassistant/components/homekit/accessories.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,24 @@ def run(self):
async_track_state_change(
self.hass, self.entity_id, self.update_state_callback)

@ha_callback
def update_state_callback(self, entity_id=None, old_state=None,
new_state=None):
"""Callback from state change listener."""
"""Callback from state change listener.

Must be run from in the event loop.
"""
_LOGGER.debug('New_state: %s', new_state)
if new_state is None:
return
self.update_state(new_state)
self.async_update_state(new_state)

def update_state(self, new_state):
def async_update_state(self, new_state):
"""Method called on state change to update HomeKit value.

Overridden by accessory types.
Method is run in the event loop.
"""
pass
raise NotImplementedError()


class HomeBridge(Bridge):
Expand Down
80 changes: 56 additions & 24 deletions homeassistant/components/homekit/type_covers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,36 @@ def __init__(self, *args):

def set_state(self, value):
"""Change garage state if call came from HomeKit."""
self.hass.add_job(self.async_set_state, value)

async def async_set_state(self, value):
"""Change garage state if call came from HomeKit. Coroutine."""
_LOGGER.debug('%s: Set state to %d', self.entity_id, value)
self.flag_target_state = True

params = {ATTR_ENTITY_ID: self.entity_id}
if value == 0:
self.char_current_state.set_value(3)
self.hass.components.cover.open_cover(self.entity_id)
self.hass.async_add_job(self.char_current_state.set_value, 3)
await self.hass.services.async_call(
DOMAIN, SERVICE_OPEN_COVER, params)
elif value == 1:
self.char_current_state.set_value(2)
self.hass.components.cover.close_cover(self.entity_id)
self.hass.async_add_job(self.char_current_state.set_value, 2)
await self.hass.services.async_call(
DOMAIN, SERVICE_CLOSE_COVER, params)

def async_update_state(self, new_state):
"""Update cover state after state changed.

def update_state(self, new_state):
"""Update cover state after state changed."""
Method is run in the event loop.
"""
hass_state = new_state.state
if hass_state in (STATE_OPEN, STATE_CLOSED):
current_state = 0 if hass_state == STATE_OPEN else 1
self.char_current_state.set_value(current_state)
self.hass.async_add_job(
self.char_current_state.set_value, current_state)
if not self.flag_target_state:
self.char_target_state.set_value(current_state)
self.hass.async_add_job(
self.char_target_state.set_value, current_state)
self.flag_target_state = False


Expand All @@ -83,20 +95,31 @@ def __init__(self, *args):
@debounce
def move_cover(self, value):
"""Move cover to value if call came from HomeKit."""
self.hass.add_job(self.async_move_cover, value)

async def async_move_cover(self, value):
"""Move cover to value if call came from HomeKit. Coroutine."""
_LOGGER.debug('%s: Set position to %d', self.entity_id, value)
self.homekit_target = value

params = {ATTR_ENTITY_ID: self.entity_id, ATTR_POSITION: value}
self.hass.services.call(DOMAIN, SERVICE_SET_COVER_POSITION, params)
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_POSITION: value}
await self.hass.services.async_call(
DOMAIN, SERVICE_SET_COVER_POSITION, params)

def update_state(self, new_state):
"""Update cover position after state changed."""
def async_update_state(self, new_state):
"""Update cover position after state changed.

Method is run in the event loop.
"""
current_position = new_state.attributes.get(ATTR_CURRENT_POSITION)
if isinstance(current_position, int):
self.char_current_position.set_value(current_position)
self.hass.async_add_job(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to await these calls if you want to mimic the old behavior (and thus method needs to be async). However, If you're going to call to the thread pool twice, you might aswell run the whole method in a thread right away, probably more efficient.

self.char_current_position.set_value, current_position)
if self.homekit_target is None or \
abs(current_position - self.homekit_target) < 6:
self.char_target_position.set_value(current_position)
self.hass.async_add_job(
self.char_target_position.set_value, current_position)
self.homekit_target = None


Expand Down Expand Up @@ -126,6 +149,10 @@ def __init__(self, *args):
@debounce
def move_cover(self, value):
"""Move cover to value if call came from HomeKit."""
self.hass.add_job(self.async_move_cover, value)

async def async_move_cover(self, value):
"""Move cover to value if call came from HomeKit. Coroutine."""
_LOGGER.debug('%s: Set position to %d', self.entity_id, value)

if self.supports_stop:
Expand All @@ -141,19 +168,24 @@ def move_cover(self, value):
else:
service, position = (SERVICE_CLOSE_COVER, 0)

self.hass.services.call(DOMAIN, service,
{ATTR_ENTITY_ID: self.entity_id})
params = {ATTR_ENTITY_ID: self.entity_id}
await self.hass.services.async_call(DOMAIN, service, params)

# Snap the current/target position to the expected final position.
self.char_current_position.set_value(position)
self.char_target_position.set_value(position)
self.char_position_state.set_value(2)
self.hass.async_add_job(self.char_current_position.set_value, position)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very inefficient calling 3 times async add job. They are all queued up but will also be picked up by 3 threads at the same time.

self.hass.async_add_job(self.char_target_position.set_value, position)
self.hass.async_add_job(self.char_position_state.set_value, 2)

def async_update_state(self, new_state):
"""Update cover position after state changed.

def update_state(self, new_state):
"""Update cover position after state changed."""
Method is run in the event loop.
"""
position_mapping = {STATE_OPEN: 100, STATE_CLOSED: 0}
hk_position = position_mapping.get(new_state.state)
if hk_position is not None:
self.char_current_position.set_value(hk_position)
self.char_target_position.set_value(hk_position)
self.char_position_state.set_value(2)
self.hass.async_add_job(
self.char_current_position.set_value, hk_position)
self.hass.async_add_job(
self.char_target_position.set_value, hk_position)
self.hass.async_add_job(self.char_position_state.set_value, 2)
35 changes: 27 additions & 8 deletions homeassistant/components/homekit/type_fans.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,42 +57,59 @@ def __init__(self, *args):

def set_state(self, value):
"""Set state if call came from HomeKit."""
self.hass.add_job(self.async_set_state, value)

async def async_set_state(self, value):
"""Set state if call came from HomeKit. Coroutine."""
if self._state == value:
return

_LOGGER.debug('%s: Set state to %d', self.entity_id, value)
self._flag[CHAR_ACTIVE] = True
service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call(DOMAIN, service, params)
await self.hass.services.async_call(DOMAIN, service, params)

def set_direction(self, value):
"""Set state if call came from HomeKit."""
self.hass.add_job(self.async_set_direction, value)

async def async_set_direction(self, value):
"""Set state if call came from HomeKit. Coroutine."""
_LOGGER.debug('%s: Set direction to %d', self.entity_id, value)
self._flag[CHAR_ROTATION_DIRECTION] = True
direction = DIRECTION_REVERSE if value == 1 else DIRECTION_FORWARD
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_DIRECTION: direction}
self.hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, params)
await self.hass.services.async_call(
DOMAIN, SERVICE_SET_DIRECTION, params)

def set_oscillating(self, value):
"""Set state if call came from HomeKit."""
self.hass.add_job(self.async_set_oscillating, value)

async def async_set_oscillating(self, value):
"""Set state if call came from HomeKit. Coroutine."""
_LOGGER.debug('%s: Set oscillating to %d', self.entity_id, value)
self._flag[CHAR_SWING_MODE] = True
oscillating = True if value == 1 else False
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_OSCILLATING: oscillating}
self.hass.services.call(DOMAIN, SERVICE_OSCILLATE, params)
await self.hass.services.async_call(DOMAIN, SERVICE_OSCILLATE, params)

def async_update_state(self, new_state):
"""Update fan after state change.

def update_state(self, new_state):
"""Update fan after state change."""
Method is run in the event loop.
"""
# Handle State
state = new_state.state
if state in (STATE_ON, STATE_OFF):
self._state = 1 if state == STATE_ON else 0
if not self._flag[CHAR_ACTIVE] and \
self.char_active.value != self._state:
self.char_active.set_value(self._state)
self.hass.async_add_job(
self.char_active.set_value, self._state)
self._flag[CHAR_ACTIVE] = False

# Handle Direction
Expand All @@ -102,7 +119,8 @@ def update_state(self, new_state):
direction in (DIRECTION_FORWARD, DIRECTION_REVERSE):
hk_direction = 1 if direction == DIRECTION_REVERSE else 0
if self.char_direction.value != hk_direction:
self.char_direction.set_value(hk_direction)
self.hass.async_add_job(
self.char_direction.set_value, hk_direction)
self._flag[CHAR_ROTATION_DIRECTION] = False

# Handle Oscillating
Expand All @@ -112,5 +130,6 @@ def update_state(self, new_state):
oscillating in (True, False):
hk_oscillating = 1 if oscillating else 0
if self.char_swing.value != hk_oscillating:
self.char_swing.set_value(hk_oscillating)
self.hass.async_add_job(
self.char_swing.set_value, hk_oscillating)
self._flag[CHAR_SWING_MODE] = False
Loading