Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Entity.async_write_ha_state() to not miss state transition #21590

Merged
merged 13 commits into from
Mar 9, 2019
6 changes: 3 additions & 3 deletions homeassistant/components/mqtt/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ async def discovery_update(self, discovery_payload):
await self.availability_discovery_update(config)
await self.device_info_discovery_update(config)
await self._subscribe_topics()
self.async_schedule_update_ha_state()
self.async_write_ha_state()

async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
Expand All @@ -130,7 +130,7 @@ def off_delay_listener(now):
"""Switch device off after a delay."""
self._delay_listener = None
self._state = False
self.async_schedule_update_ha_state()
self.async_write_ha_state()

@callback
def state_message_received(_topic, payload, _qos):
Expand Down Expand Up @@ -159,7 +159,7 @@ def state_message_received(_topic, payload, _qos):
self._delay_listener = evt.async_call_later(
self.hass, off_delay, off_delay_listener)

self.async_schedule_update_ha_state()
self.async_write_ha_state()

self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
Expand Down
35 changes: 33 additions & 2 deletions homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,23 @@ async def async_update_ha_state(self, force_refresh=False):
_LOGGER.exception("Update for %s fails", self.entity_id)
return

self._async_write_ha_state()

@callback
def async_write_ha_state(self):
"""Write the state to the state machine."""
if self.hass is None:
raise RuntimeError("Attribute hass is None for {}".format(self))

if self.entity_id is None:
raise NoEntitySpecifiedError(
"No entity id specified for entity {}".format(self.name))

self._async_write_ha_state()

@callback
def _async_write_ha_state(self):
"""Write the state to the state machine."""
start = timer()

if not self.available:
Expand Down Expand Up @@ -311,13 +328,27 @@ async def async_update_ha_state(self, force_refresh=False):
def schedule_update_ha_state(self, force_refresh=False):
"""Schedule an update ha state change task.

That avoid executor dead looks.
Scheduling the update avoids executor deadlocks.

Entity state and attributes are read when the update ha state change
task is executed.
If state is changed more than once before the ha state change task has
been executed, the intermediate state transitions will be missed.
"""
self.hass.add_job(self.async_update_ha_state(force_refresh))

@callback
def async_schedule_update_ha_state(self, force_refresh=False):
"""Schedule an update ha state change task."""
"""Schedule an update ha state change task.

This method must be run in the event loop.
Scheduling the update avoids executor deadlocks.

Entity state and attributes are read when the update ha state change
task is executed.
If state is changed more than once before the ha state change task has
been executed, the intermediate state transitions will be missed.
"""
self.hass.async_create_task(self.async_update_ha_state(force_refresh))

async def async_device_update(self, warning=True):
Expand Down
4 changes: 2 additions & 2 deletions tests/components/cast/test_media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,16 @@ async def test_entity_media_states(hass: HomeAssistantType):
state = hass.states.get('media_player.speaker')
assert state.state == 'playing'

entity.new_media_status(media_status)
media_status.player_is_playing = False
media_status.player_is_paused = True
entity.new_media_status(media_status)
await hass.async_block_till_done()
state = hass.states.get('media_player.speaker')
assert state.state == 'paused'

entity.new_media_status(media_status)
media_status.player_is_paused = False
media_status.player_is_idle = True
entity.new_media_status(media_status)
await hass.async_block_till_done()
state = hass.states.get('media_player.speaker')
assert state.state == 'idle'
Expand Down