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

* Copy state in schedule_update_ha_state

* Lint

* Fix broken test

* Review comment, improve docstring

* Preserve order of state updates

* Rewrite

* Break up async_update_ha_state

* Update binary_sensor.py

* Review comments

* Update docstring

* hass -> ha

* Update entity.py

* Update entity.py
This commit is contained in:
emontnemery 2019-03-09 18:52:22 +01:00 committed by Paulus Schoutsen
parent 458548daec
commit fc81826763
3 changed files with 38 additions and 7 deletions

View File

@ -117,7 +117,7 @@ class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
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."""
@ -130,7 +130,7 @@ class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""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):
@ -159,7 +159,7 @@ class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
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,

View File

@ -222,6 +222,23 @@ class Entity:
_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:
@ -311,13 +328,27 @@ class Entity:
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):

View File

@ -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'