Blog | Home Assistant Developer Docs (2024)

Changes in setup entity platforms with group integration

·

2 min read

Changes in setup entity platforms with group integration

By default, the group integration allows entities to be grouped. If the default ON/OFF states for an entity default to on and off, then grouping is supported by default. The setup changes, though, for Entity platforms that can be grouped but have alternative states, e.g., cover (open/closed) or person (home/not_home), or platforms that are meant to be excluded, such as sensor. These entity platforms must implement async_describe_on_off_states in the group.py module.

In async_describe_on_off_states, the domain needs to be the first argument passed to the registry methods on_off_states and exclude_domain. When registering alternative ON/OFF states with registry.on_off_state, in addition to the ON states, the default ON state needs to be passed.

Example registering alternative states

New signature for registry.on_off_states:

 @callback
def on_off_states(
self, domain: str, on_states: set[str], default_on_state:str, off_state: str
) -> None:
"""Register on and off states for the current domain."""
...

Example group.py for the vacuum entity platform registering alternative ON/OFF states. Note the the first ON state now is considered to be the default ON state.

"""Describe group states."""

from typing import TYPE_CHECKING

from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, callback

if TYPE_CHECKING:
from homeassistant.components.group import GroupIntegrationRegistry

from .const import DOMAIN, STATE_CLEANING, STATE_ERROR, STATE_RETURNING


@callback
def async_describe_on_off_states(
hass: HomeAssistant, registry: "GroupIntegrationRegistry"
) -> None:
"""Describe group on off states."""
registry.on_off_states(
DOMAIN, # domain
# set with all group ON states
{
STATE_ON,
STATE_CLEANING,
STATE_RETURNING,
STATE_ERROR,
},
STATE_ON, # Default group ON state
STATE_OFF, # Group OFF state
)

Example excluding an entity platform from group entities

New signature for registry.exclude_domain:

 @callback
def exclude_domain(self, domain: str) -> None:
"""Exclude the current domain."""
...

Example group.py for the sensor entity platform to exclude sensor entities from group entities.

"""Describe group states."""

from typing import TYPE_CHECKING

from homeassistant.core import HomeAssistant, callback

if TYPE_CHECKING:
from homeassistant.components.group import GroupIntegrationRegistry

from .const import DOMAIN


@callback
def async_describe_on_off_states(
hass: HomeAssistant, registry: "GroupIntegrationRegistry"
) -> None:
"""Describe group on off states."""
registry.exclude_domain(DOMAIN)

LockEntity supports open/opening state

·

One min read

Recently we have added an open and opening state to LockEntity

This is useful if you have locks which can differentiate between unlocked (not locked but latched) state and open (unlocked and latch withdrawn) state.

LockEntity already supports the open method by implementing the feature flag LockEntityFeature.OPEN

Example (default implementation):

class MyLock(LockEntity):

@property
def is_opening(self) -> bool:
"""Return true if lock is open."""
return self._state == STATE_OPENING

@property
def is_open(self) -> bool:
"""Return true if lock is open."""
return self._state == STATE_OPEN

async def async_open(self, **kwargs: Any) -> None:
"""Open the door latch."""
self._state = STATE_OPEN
self.async_write_ha_state()

How we managed to speed up our CI to save 168+ days of execution time per month

We got a taste for speed after UV gave us back 215 compute hours a month.Our CI workflow gets triggered on each commit, and due to the high volume of contributions, it was triggered 6647 times in March 2024.The full runs, where the whole test suite is being executed, take a long time.

It turned out that the plugin, we used to split the tests into 10 groups, was inefficient. Each pytest job needed to discover all tests, even when the job intended to only execute a subset of them.

Now we have a separate job to discover all tests and split them into 10 groups. The 10 pytest jobs only need to execute a subset of all tests. Not doing full-discovery in each test runner saves us 3 hours on each full run!

A short analysis of the 6647 CI workflows in March 2024 revealed the following stats:

  • 2406 were canceled before termination
    • 1771 should be full runs
  • 1085 failed
    • 732 where failed full runs
  • 3007 terminated successfully
    • 1629 partial runs (only tests for a given integration where executed)
    • 1378 full runs

Considering the 1378 successful full runs, we would have saved around 4042 hours ~ 168 days of execution time in March 2024 with #114381. Even more, if I had also analyzed the failed/canceled ones.

The more than 168 monthly saved execution days can be used by other jobs and make the CI experience for all developers and our community better.We improved our sustainability by using fewer resources to run our test suite.

A big thank you to GitHub for providing Home Assistant with additional CI runners.

Second phase of notify entity platform implementation

·

One min read

Title option for send_message service notify entity platform

Recently we added the notify entity platform. The new notify platform method implements service send_message. This service now also accepts an optional title as an argument. This allows some new integrations that can be migrated now to use the new entity platform:

  • cisco_webex_teams
  • file
  • sendgrid
  • syslog
  • tibber

The architecture discussion is still ongoing.

When integrations are migrated, users will need to use the new notify.send_message service, so the migration changes will cause automations to break after the deprecation period is over.

Improved typing for hass.data

·

2 min read

In the past, one of the challenges with hass.data was to correctly assign type information. Since it was typed as dict[str, Any], the only options were annotation assignments or cast like:

data: MyData = hass.data[SOME_KEY]

This had several disadvantages. Not only was it necessary to annotate every assignment, but type checkers also basically pretended that the annotation would always be correct. Especially during refactoring, it could easily happen that one instance was missed, and while type-checking still succeeded, the actual code would be broken.

To fix that, it's now possible to use two new key types HassKey and HassEntryKey. With a little bit of magic, type checkers are now able to infer the type and make sure it's correct. Even when storing data.

An example could look like this:

# <integration>/__init__.py
from homeassistant.util.hass_dict import HassKey

MY_KEY: HassKey["MyData"] = HassKey(DOMAIN)

@dataclass
class MyData:
client: MyClient
other_data: dict[str, Any]

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
client = MyClient(...)

hass.data[MY_KEY] = MyData(client, {...})
hass.data[MY_KEY] = 1 # mypy error
# <integration>/switch.py
from . import MY_KEY

async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
data = hass.data[MY_KEY]
reveal_type(data) # MyData

async_add_entities([MySwitch(data.client)])

Storing data in a dict by entry.entry_id? It's often better to just store it inside the ConfigEntry directly. See the recent blog post about it. If that isn't an option, use HassEntryKey.

# <integration>/__init__.py
from homeassistant.util.hass_dict import HassEntryKey

MY_KEY: HassEntryKey["MyData"] = HassEntryKey(DOMAIN)

async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
) -> bool:
client = MyClient(...)

hass.data.setdefault(MY_KEY, {})[entry.entry_id] = MyData(client, {...})

Store runtime data inside the config entry

·

2 min read

Integrations often need to set up and track custom data, such as coordinators, API connections, or code objects. Previously, those were all stored inside hass.data, which made tracking them difficult.

With recent changes, it's now possible to use entry.runtime_data for that. The config entry is already available when setting up platforms and gets cleaned up automatically. No more deleting the key from hass.data after unloading.

It also better supports type-checking. ConfigEntry is generic now, so passing the data type along is possible. Use a typed data structure like dataclass for that. To simplify the annotation, it's recommended to define a type alias for it.

An example could look like this:

# <integration>/__init__.py

# The type alias needs to be suffixed with 'ConfigEntry'
MyConfigEntry = ConfigEntry["MyData"]

@dataclass
class MyData:
client: MyClient
other_data: dict[str, Any]

async def async_setup_entry(
hass: HomeAssistant,
entry: MyConfigEntry, # use type alias instead of ConfigEntry
) -> bool:
client = MyClient(...)

# Assign the runtime_data
entry.runtime_data = MyData(client, {...})
# <integration>/switch.py

from . import MyConfigEntry

async def async_setup_entry(
hass: HomeAssistant,
entry: MyConfigEntry, # use type alias instead of ConfigEntry
async_add_entities: AddEntitiesCallback,
) -> None:
# Access the runtime data form the config entry
data = entry.runtime_data

async_add_entities([MySwitch(data.client)])

Always reload after a successful re-auth flow

·

2 min read

Always reload after successful reauthentication

To update and reload the entry after a successful reauthentication flow, the helper async_update_reload_and_abort can be used. The default behavior of the helper has been changed. By default the entry will always reload if the helper is called. If an entry needs reauthentication, it is not always needed to update the entry if an account was temporary disabled or an API-key was temporary disallowed.

For cases where reloading is not wanted in case the entry is not changed, the reload_even_if_entry_is_unchanged=False parameter can be passed to the helper.

More about this helper can be found here here.

Example

class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
"""Config flow to handle OAuth2 authentication."""

reauth_entry: ConfigEntry | None = None

async def async_step_reauth(self, user_input=None):
"""Perform reauth upon an API authentication error."""
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(self, user_input=None):
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user()

async def async_oauth_create_entry(self, data: dict) -> dict:
"""Create an oauth config entry or update existing entry for reauth."""
if self.reauth_entry:
# Only reload if the entry was updated
return self.async_update_reload_and_abort(
self.reauth_entry,
data=data,
reload_even_if_entry_is_unchanged=False,
)
return await super().async_oauth_create_entry(data)

Replacing `async_track_state_change` with `async_track_state_change_event`

·

One min read

async_track_state_change is deprecated and will be removed in Home Assistant 2025.5. async_track_state_change_event should be used instead.

async_track_state_change always creates a top-level listener for EVENT_STATE_CHANGED, which would have to reject all state changes that did not match the desired entities. This design presented a performance problem when there were many integrations using async_track_state_change. async_track_state_change has been phased out in core since the introduction of async_track_state_change_event, with the last instance being removed in 2024.5.

Example with async_track_state_change:

from homeassistant.core import State, callback
from homeassistant.helper.event import async_track_state_change

@callback
def _async_on_change(entity_id: str, old_state: State | None, new_state: State | None) -> None:
...

unsub = async_track_state_change(hass, "sensor.one", _async_on_change)
unsub()

Example replacement with async_track_state_change_event:

from homeassistant.core import Event, EventStateChangedData, callback
from homeassistant.helper.event import async_track_state_change_event

@callback
def _async_on_change(event: Event[EventStateChangedData]) -> None:
entity_id = event.data["entity_id"]
old_state = event.data["old_state"]
new_state = event.data["new_state"]
...

unsub = async_track_state_change_event(hass, "sensor.one", _async_on_change)
unsub()

New notify entity platform

·

One min read

New notify entity platform

The notify platform is now available as an entity platform. The MVP for the new notify platform implements the method and service send_message. It accepts message as a required attribute.Unlike the legacy notify.notify service we have no targets as argument, as it is an entity we can target multiple notify entities when calling send_message.

The architecture discussion is ongoing, and is about the device classes to implement and the implementation of recipient support in the form of contacts via a contact registry.

Existing integrations that implement the legacy notify services will be migrated in phases. The first step is to migrate the integrations than only use message as a parameter to notify.

The integrations identified for migration are:

  • circuit
  • clickatell
  • clicksend
  • command_line
  • demo
  • ecobee
  • flock
  • free_mobile
  • knx
  • mastodon

As soon as we have title and/or recipient support we can migrate more integrations to use the new platform.

When integrations are migrated, users will need to use the new notify.send_message service, so the migration changes will cause automations to break after the deprecation period is over.

Deprecate old backports and typing alias

·

One min read

In the past, we've backported features from upstream CPython to use them early and improve user and developers' experience. Home Assistant only supports Python 3.12, so these can be used directly from Python. These backports are now deprecated and will be removed in the future.

DeprecatedReplacementPython version
homeassistant.backports.enum.StrEnumenum.StrEnum>= 3.11
homeassistant.backports.functools.cached_propertyfunctools.cached_property>= 3.8, >= 3.12 (performance improvement)

In addition, some typing aliases are also deprecated now.

DeprecatedReplacement
homeassistant.helpers.typing.ContextTypehomeassistant.core.Context
homeassistant.helpers.typing.EventTypehomeassistant.core.Event
homeassistant.helpers.typing.HomeAssistantTypehomeassistant.core.HomeAssistant
homeassistant.helpers.typing.ServiceCallTypehomeassistant.core.ServiceCall
Blog | Home Assistant Developer Docs (2024)
Top Articles
Latest Posts
Article information

Author: Stevie Stamm

Last Updated:

Views: 6231

Rating: 5 / 5 (60 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Stevie Stamm

Birthday: 1996-06-22

Address: Apt. 419 4200 Sipes Estate, East Delmerview, WY 05617

Phone: +342332224300

Job: Future Advertising Analyst

Hobby: Leather crafting, Puzzles, Leather crafting, scrapbook, Urban exploration, Cabaret, Skateboarding

Introduction: My name is Stevie Stamm, I am a colorful, sparkling, splendid, vast, open, hilarious, tender person who loves writing and wants to share my knowledge and understanding with you.