Skip to main content

Use Kelvin as the preferred color temperature unit

· 2 min read

Summary of changes

In October 2022, Home Assistant migrated the preferred color temperature unit from mired to kelvin.

It is now time to add deprecation warnings for the corresponding attributes, constants and properties:

  • Deprecate state and capability attributes ATTR_COLOR_TEMP, ATTR_MIN_MIREDS and ATTR_MAX_MIREDS
  • Deprecate constants ATTR_KELVIN and ATTR_COLOR_TEMP from the light.turn_on service call
  • Deprecate properties LightEntity.color_temp, LightEntity.min_mireds and LightEntity.max_mireds
  • Deprecate corresponding attributes LightEntity._attr_color_temp, LightEntity._attr_min_mired and LightEntity._attr_max_mired

Examples

Custom minimum/maximum color temperature

class MyLight(LightEntity):
"""Representation of a light."""

# Old
# _attr_min_mireds = 200 # 5000K
# _attr_max_mireds = 400 # 2500K

# New
_attr_min_color_temp_kelvin = 2500 # 400 mireds
_attr_max_color_temp_kelvin = 5000 # 200 mireds

Default minimum/maximum color temperature

from homeassistant.components.light import DEFAULT_MAX_KELVIN, DEFAULT_MIN_KELVIN

class MyLight(LightEntity):
"""Representation of a light."""

# Old did not need to have _attr_min_mireds / _attr_max_mireds set
# New needs to set the default explicitly
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN

Dynamic minimum/maximum color temperature

from homeassistant.util import color as color_util

class MyLight(LightEntity):
"""Representation of a light."""

# Old
# def min_mireds(self) -> int:
# """Return the coldest color_temp that this light supports."""
# return device.coldest_temperature
#
# def max_mireds(self) -> int:
# """Return the warmest color_temp that this light supports."""
# return device.warmest_temperature

# New
def min_color_temp_kelvin(self) -> int:
"""Return the warmest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.warmest_temperature)

def max_color_temp_kelvin(self) -> int:
"""Return the coldest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.coldest_temperature)

Service call

from homeassistant.components.light import ATTR_COLOR_TEMP_KELVIN
from homeassistant.util import color as color_util

class MyLight(LightEntity):
"""Representation of a light."""
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
# Old
# if ATTR_COLOR_TEMP in kwargs:
# color_temp_mired = kwargs[ATTR_COLOR_TEMP]
# color_temp_kelvin = color_util.color_temperature_mired_to_kelvin(color_temp_mired)

# Old
# if ATTR_KELVIN in kwargs:
# color_temp_kelvin = kwargs[ATTR_KELVIN]
# color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)

# New
if ATTR_COLOR_TEMP_KELVIN in kwargs:
color_temp_kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)

Background information

Changed name of WaterHeaterEntityDescription

· One min read

A naming error of the entity description was found in the Water Heater integration, and we have now renamed WaterHeaterEntityEntityDescription to WaterHeaterEntityDescription. As there were no Core integrations and no custom integrations published on HACS using the entity description, we did not add a deprecation period. The changed entity description will be released in the 2025.1 release.

See details in the core PR: #132888.

New vacuum state property

· One min read

As of Home Assistant Core 2025.1, the constants used to return state in StateVacuumEntity are deprecated and replaced by the VacuumActivity enum.

Also with this change, integrations should set the activity property instead of directly setting the state property.

There is a one-year deprecation period, and the constants will stop working from 2026.1 to ensure all custom integration authors have time to adjust.

Example


from homeassistant.components.vacuum import VacuumActivity

class MyVacuumCleaner(StateVacuumEntity):
"""My vacuum cleaner."""

@property
def activity(self) -> VacuumActivity | None:
"""Return the state of the vacuum."""
if self.device.is_cleaning():
return VacuumActivity.CLEANING
return VacuumActivity.DOCKED

More details can be found in the vacuum documentation.

Climate entity now supports independent horizontal swing

· 2 min read

As of Home Assistant Core 2024.12, we have implemented an independent property and method for controlling horizontal swing in ClimateEntity.

Integrations that support completely independent control and state for vertical and horizontal swing can now use the previous swing_mode for vertical swing only and use the new swing_horizontal_mode for providing the horizontal swing state and control.

Integrations that don't have independent control should still keep using the current swing_mode for both vertical and horizontal support.

Example

Example requirements to implement swing and swing_horizontal in your climate entity.


class MyClimateEntity(ClimateEntity):
"""Implementation of my climate entity."""

@property
def supported_features(self) -> ClimateEntityFeature:
"""Return the list of supported features."""
return ClimateEntityFeature.SWING_MODE | ClimateEntityFeature.SWING_HORIZONTAL_MODE

@property
def swing_mode(self) -> str | None:
"""Return the swing setting.

Requires ClimateEntityFeature.SWING_MODE.
"""
return self._attr_swing_mode

@property
def swing_modes(self) -> list[str] | None:
"""Return the list of available swing modes.

Requires ClimateEntityFeature.SWING_MODE.
"""
return self._attr_swing_modes

@property
def swing_horizontal_mode(self) -> str | None:
"""Return the swing setting.

Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE.
"""
return self._attr_swing_horizontal_mode

@property
def swing_horizontal_modes(self) -> list[str] | None:
"""Return the list of available swing modes.

Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE.
"""
return self._attr_swing_horizontal_modes

async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
await self.api.set_swing_mode(swing_mode)

async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
"""Set new target horizontal swing operation."""
await self.api.set_swing_horizontal_mode(swing_horizontal_mode)

More details can be found in the climate documentation.

Utility function homeassistant.util.dt.utc_to_timestamp is deprecated

· One min read

The utility function homeassistant.util.dt.utc_to_timestamp is deprecated and will be removed in Home Assistant Core 2026.1, custom integrations which call it should instead call the stdlib method datetime.datetime.timestamp()

The reason for deprecation is that the stdlib method is just as fast as the utility function.

More details can be found in the core PR.

Camera API changes

· One min read

With Home Assistant 2024.11 we added WebRTC for most cameras. To add support for it we needed to refactor and improve the camera entity. Today we would like to announce that with the upcoming Home Assistant release 2024.12 the following methods are deprecated and will be removed with Home Assistant version 2025.6:

  • The property frontend_stream_type will be removed. As of 2024.11 Home assistant will identify the frontend stream type by checking if the camera entity implements the native WebRTC methods (#130932). Card developers can use the new websocket command camera/capabilities to get the frontend stream types.

  • The method async_handle_web_rtc_offer will be removed. Please use async_handle_async_webrtc_offer and the async WebRTC signaling approach (#131285).

  • The method async_register_rtsp_to_web_rtc_provider has been deprecated. Please use async_register_webrtc_provider, which offers more flexibility and supports the async WebRTC signaling approach (#131462).

More information about the replacements can be found in the camera entity documentation.

Translating units of measurement

· One min read

Home Assistant 2024.12 will support the translation of custom units of measurement. Previously, those would have been hardcoded in their integrations.

Just like for entity names, integrations just need to set the translation_key on the entity definition and provide the unit designation in the strings.json file (with the new unit_of_measurement key):

{
"entity": {
"sensor": {
"subscribers_count": {
"unit_of_measurement": "subscribers"
},
}
}
}

Check our backend localization documentation for details.

Integration quality scale

· 3 min read

Since the original quality scale was introduced in 2020, Home Assistant changed quite a lot. Both in how integrations are developed and what features they can provide. The quality scale, however, has not been updated to reflect these changes.

For the last couple of months, we have been working with the community on a new integration quality scale that fits the current state of Home Assistant. To give an overview of the changes:

  • The quality scale now has four scaled tiers: Bronze, Silver, Gold, and Platinum. The newly added Bronze tier is the new minimum requirement for new core integrations going forward. This does lower the bar for new integrations for a bit, but it does make it clear upfront what is expected from a core integration in this day and age.
  • A new non-scaled tier, legacy, has been added for integrations that are not configurable via the UI yet. This tier would indicate to the user that the integration might not meet their expectation compared to integrations configurable via the UI.
  • To allow the scale to grow with Home Assistant and to avoid another major rework in the future, we have designed a rulebook for the scale so it's known what needs to happen when a rule is added.
  • To show that the project has a commitment on keeping the scale up to date, we have created an architecture decision record (ADR) that describes the goal and the process of keeping the scale up to date.
  • The developer documentation now has a brand new section dedicated to the Integration Quality Scale. It includes the rules and the reasoning for every rule, and it provides examples that can be used to give an idea of how to implement it. There is also a checklist that can be used for changing the scale of an integration.
  • Documentation now also plays a role in the quality of the integrations, since the user experience of an integration goes beyond code.
  • To track the progress of integration, we have added a quality_scale.yaml file to the integration folder. This will contain a list of all the rules the integration is adhering to and notes of possible exemptions and the reasons behind them.

With this in place, you might wonder what the next steps are. Because this is a major rework of the scale, we have decided to drop the scale of the current integrations. This means that those integrations have to create a PR to get their scale back to the level they think it should be.

I would also like to motivate the community to help out with getting as many integrations to Bronze (or above) as possible. I also want to thank the community for their input in the quality scale and their effort in maintaining the integrations. Home Assistant would not be where it is today without the community ❤️.

New options flow properties

· 2 min read

Summary of changes

New helper properties have been added to the OptionsFlow:

  • self._config_entry_id provides the config entry ID
  • self.config_entry returns the config entry

Backwards compatibility

Until Home Assistant Core 2025.12, it is possible to set self.config_entry manually, but doing so will log a warning asking users to open an issue on the custom integration's bug tracker.

New code:

@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Create the options flow."""
return OptionsFlowHandler()

class OptionsFlowHandler(OptionsFlow):
"""Options flow handler."""

def __init__(self) -> None:
"""Initialize options flow."""
self._conf_app_id: str | None = None

Old code with OptionsFlow properties:

@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)

class OptionsFlowHandler(OptionsFlow):
"""Options flow handler."""

def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
self._conf_app_id: str | None = None

Special handling of OptionsFlowWithConfigEntry

The main purpose of the OptionsFlowWithConfigEntry class was to provide the self.config_entry property, which is now already provided by the parent class. There are currently no plans to remove the OptionsFlowWithConfigEntry class, but it is kept for backward compatibility only and it should be avoided in new code.

Custom integrations that wish to drop references to OptionsFlowWithConfigEntry will need to consider how they are referencing self.options:

  • if self.options is not referenced, then the migration to OptionsFlow is straightforward (see PR #129651)
  • if you are only reading the options values, then it is recommended that you adjust the reads to self.config_entry.options (see PR #129895)
  • if you are updating/mutating the options values inside a single step, then it may be necessary to first copy the options (options = deepcopy(dict(self.config_entry.options)) (see PR #129928)
  • if you are updating/mutating the options values through multiple step, then it may be necessary to copy the options inside the class initialisation (self.options = deepcopy(dict(config_entry.options)) (see PR #129890)

More details can be found in the options flow documentation.

Reauth and reconfigure flows need to be linked to a config entry

· One min read

Starting a reauth or a reconfigure flow without a link to the config entry has been deprecated, and will start failing in 2025.12.

Custom integrations should be updated to trigger the reauth flow using the entry.async_start_reauth(hass) helper.

    async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.device.press_button()
except DevicePasswordProtected as ex:
self.entry.async_start_reauth(self.hass)

Old incorrect code:

    async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.device.press_button()
except DevicePasswordProtected as ex:
# old incorrect code:
self.hass.async_create_task(
hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_REAUTH}
)
)

Custom integrations can also raise a ConfigEntryAuthFailed exception during the initialization phase, or within the update method of a data update coordinator.

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up integration from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]

if not _credentials_valid(username, password):
raise ConfigEntryAuthFailed()

Starting a reconfigure flow is only done by the frontend and custom integrations should not need to change anything for these flows.

More details can be found in the reconfigure and reauthentication documentation.