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
62 changes: 38 additions & 24 deletions homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,28 +200,8 @@ def async_set_context(self, context):
self._context = context
self._context_set = dt_util.utcnow()

async def async_update_ha_state(self, force_refresh=False):
"""Update Home Assistant with current state of entity.

If force_refresh == True will update entity before setting state.

This method must be run in the event loop.
"""
if self.hass is None:
balloob marked this conversation as resolved.
Show resolved Hide resolved
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))

# update entity data
if force_refresh:
try:
await self.async_device_update()
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Update for %s fails", self.entity_id)
return

def _get_state_and_attributes(self):
"""Get state and attributes."""
start = timer()

if not self.available:
Expand Down Expand Up @@ -281,6 +261,32 @@ async def async_update_ha_state(self, force_refresh=False):
"https://goo.gl/Nvioub", self.entity_id,
type(self), end - start)

return (state, attr)

async def async_update_ha_state(self, force_refresh=False, stateattr=None):
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want stateattr to be a part of the external interface? It seems like it would only be used internally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

"""Update Home Assistant with current state of entity.

If force_refresh == True will update entity before setting state.

This method must be run in the event loop.
"""
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))

# update entity data
if force_refresh:
try:
await self.async_device_update()
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Update for %s fails", self.entity_id)
return

state, attr = stateattr or self._get_state_and_attributes()
Copy link
Member

Choose a reason for hiding this comment

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

I know that stateattr is passed in as None when force refresh, but it is still confusing to read the code as such. Please put an explicit state, attr = self._get_state_and_attributes() inside the if force_refresh. You probably also want to safe guard and check if stateattr is None too. I know you do all that now, but it's hard to follow and this is important code for people to understand.


# Overwrite properties that have been set in the config file.
if DATA_CUSTOMIZE in self.hass.data:
attr.update(self.hass.data[DATA_CUSTOMIZE].get(self.entity_id))
Expand Down Expand Up @@ -313,12 +319,20 @@ def schedule_update_ha_state(self, force_refresh=False):

That avoid executor dead looks.
"""
self.hass.add_job(self.async_update_ha_state(force_refresh))
stateattr = None
if not force_refresh:
stateattr = self._get_state_and_attributes()
self.hass.add_job(
self.async_update_ha_state(force_refresh, stateattr))

@callback
def async_schedule_update_ha_state(self, force_refresh=False):
"""Schedule an update ha state change task."""
self.hass.async_create_task(self.async_update_ha_state(force_refresh))
stateattr = None
if not force_refresh:
stateattr = self._get_state_and_attributes()
self.hass.async_create_task(
self.async_update_ha_state(force_refresh, stateattr))

async def async_device_update(self, warning=True):
"""Process 'update' or 'async_update' from entity.
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