Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0e1d12b
Fix Z-Wave RGB light turn on causing rare `ZeroDivisionError` (#153422)
TheJulianJES Oct 2, 2025
7ab99c0
Add new test fixture for Tuya wk category (#153457)
epenet Oct 2, 2025
ee4a1de
Add translation for turbo fan mode in SmartThings (#153445)
joostlek Oct 2, 2025
a172f67
Fix Nord Pool 15 minute interval (#153350)
gjohansson-ST Oct 2, 2025
229ebe1
Disable baudrate bootloader reset for ZBT-2 (#153443)
puddly Oct 2, 2025
d206315
Bump aioshelly 13.11.0 (#153458)
thecode Oct 2, 2025
cd69b82
Add light, security and climate panel (#153261)
piitaya Oct 2, 2025
f9f61b8
Portainer add configuration URL's (#153466)
erwindouna Oct 2, 2025
a2a067a
Add serial number to the list of discovered devices (#153448)
LaStrada Oct 2, 2025
3f7a288
Add data_description field for Airthings BLE (#153442)
LaStrada Oct 2, 2025
6487589
Fix sentence-casing in user-facing strings of `slack` (#153427)
NoRi2909 Oct 2, 2025
d92004a
Add missing translation for media browser default title (#153430)
timmo001 Oct 2, 2025
22f2f86
Improve recorder migration tests dropping indices (#153456)
emontnemery Oct 2, 2025
a7f4836
Add PARALLEL_UPDATES to Squeezebox switch platform (#153477)
peteS-UK Oct 2, 2025
571b2e3
Fix Airthings config flow description (#153452)
LaStrada Oct 2, 2025
d2aa057
Add relative humidity to matter climate entities (#152554)
MrEbbinghaus Oct 2, 2025
4011d62
Improve enable_migrate_event_ids recorder test fixture (#153470)
emontnemery Oct 2, 2025
aed2d38
Update OVOEnergy to 3.0.1 (#153476)
timmo001 Oct 2, 2025
95198ae
Bump pyTibber to 0.32.2 (#153484)
Danielhiversen Oct 2, 2025
275e948
Fix missing powerconsumptionreport in Smartthings (#153438)
joostlek Oct 2, 2025
2169ce1
Remove state attributes from Firefly 3 (#153285)
joostlek Oct 2, 2025
3bf995e
Fix next event in workday calendar (#153465)
gjohansson-ST Oct 2, 2025
3491bb1
Fix missing parameter pass in onedrive (#153478)
zweckj Oct 2, 2025
d66da0c
Respect filtering of WS subscribe_entities when there are unserializa…
emontnemery Oct 2, 2025
01ff3cf
Start recorder data migration after schema migration (#153471)
emontnemery Oct 2, 2025
7b3c96e
Remove deprication code for reolink Hub switches (#153483)
dollaransh17 Oct 2, 2025
e19bfd6
Bump recorder live schema migration to schema version 48 (#153404)
emontnemery Oct 2, 2025
b87910e
Bump reolink-aio to 0.16.1 (#153489)
starkillerOG Oct 2, 2025
71b3ebd
Cleanup reolink update entity migration (#153492)
starkillerOG Oct 2, 2025
7055276
Allign naming of Reolink host switch entities (#153494)
starkillerOG Oct 2, 2025
6764463
Use new Reolink rec_enable flag (#153496)
starkillerOG Oct 2, 2025
12085e6
Improve Reolink docstrings (#153498)
starkillerOG Oct 2, 2025
78e1649
Remove runtime support for recorder DB without States.last_reported_t…
emontnemery Oct 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions homeassistant/components/airthings/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
}
)

URL_API_INTEGRATION = {
"url": "https://dashboard.airthings.com/integrations/api-integration"
}


class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Airthings."""
Expand All @@ -37,11 +41,7 @@ async def async_step_user(
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
description_placeholders={
"url": (
"https://dashboard.airthings.com/integrations/api-integration"
),
},
description_placeholders=URL_API_INTEGRATION,
)

errors = {}
Expand All @@ -65,5 +65,8 @@ async def async_step_user(
return self.async_create_entry(title="Airthings", data=user_input)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors,
description_placeholders=URL_API_INTEGRATION,
)
6 changes: 3 additions & 3 deletions homeassistant/components/airthings/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"user": {
"data": {
"id": "ID",
"secret": "Secret",
"description": "Login at {url} to find your credentials"
}
"secret": "Secret"
},
"description": "Login at {url} to find your credentials"
}
},
"error": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/airthings_ble/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ async def async_step_user(
return self.async_abort(reason="no_devices_found")

titles = {
address: discovery.device.name
address: get_name(discovery.device)
for (address, discovery) in self._discovered_devices.items()
}
return self.async_show_form(
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/airthings_ble/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"description": "[%key:component::bluetooth::config::step::user::description%]",
"data": {
"address": "[%key:common::config_flow::data::device%]"
},
"data_description": {
"address": "The Airthings devices discovered via Bluetooth."
}
},
"bluetooth_confirm": {
Expand Down
9 changes: 0 additions & 9 deletions homeassistant/components/firefly_iii/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,6 @@ def native_value(self) -> str | None:
"""Return the state of the sensor."""
return self._account.attributes.current_balance

@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return extra state attributes for the account entity."""
return {
"account_role": self._account.attributes.account_role or "",
"account_type": self._account.attributes.type or "",
"current_balance": str(self._account.attributes.current_balance or ""),
}


class FireflyCategoryEntity(FireflyBaseEntity, SensorEntity):
"""Entity for Firefly III category."""
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:

hass.http.app.router.register_resource(IndexView(repo_path, hass))

async_register_built_in_panel(hass, "light")
async_register_built_in_panel(hass, "security")
async_register_built_in_panel(hass, "climate")

async_register_built_in_panel(hass, "profile")

async_register_built_in_panel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@ class ZBT2FirmwareMixin(ConfigEntryBaseFlow, FirmwareInstallFlowProtocol):
"""Mixin for Home Assistant Connect ZBT-2 firmware methods."""

context: ConfigFlowContext

# `rts_dtr` targets older adapters, `baudrate` works for newer ones. The reason we
# try them in this order is that on older adapters `baudrate` entered the ESP32-S3
# bootloader instead of the MG24 bootloader.
BOOTLOADER_RESET_METHODS = [ResetTarget.RTS_DTR, ResetTarget.BAUDRATE]
BOOTLOADER_RESET_METHODS = [ResetTarget.RTS_DTR]

async def async_step_install_zigbee_firmware(
self, user_input: dict[str, Any] | None = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ async def async_setup_entry(
class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
"""Connect ZBT-2 firmware update entity."""

bootloader_reset_methods = [ResetTarget.RTS_DTR, ResetTarget.BAUDRATE]
bootloader_reset_methods = [ResetTarget.RTS_DTR]

def __init__(
self,
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/matter/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .helpers import get_matter
from .models import MatterDiscoverySchema

HUMIDITY_SCALING_FACTOR = 100
TEMPERATURE_SCALING_FACTOR = 100
HVAC_SYSTEM_MODE_MAP = {
HVACMode.OFF: 0,
Expand Down Expand Up @@ -261,6 +262,18 @@ def _update_from_device(self) -> None:
self._attr_current_temperature = self._get_temperature_in_degrees(
clusters.Thermostat.Attributes.LocalTemperature
)

self._attr_current_humidity = (
int(raw_measured_humidity) / HUMIDITY_SCALING_FACTOR
if (
raw_measured_humidity := self.get_matter_attribute_value(
clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue
)
)
is not None
else None
)

if self.get_matter_attribute_value(clusters.OnOff.Attributes.OnOff) is False:
# special case: the appliance has a dedicated Power switch on the OnOff cluster
# if the mains power is off - treat it as if the HVAC mode is off
Expand Down Expand Up @@ -428,6 +441,7 @@ def _get_temperature_in_degrees(
clusters.Thermostat.Attributes.TemperatureSetpointHold,
clusters.Thermostat.Attributes.UnoccupiedCoolingSetpoint,
clusters.Thermostat.Attributes.UnoccupiedHeatingSetpoint,
clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue,
clusters.OnOff.Attributes.OnOff,
),
device_type=(device_types.Thermostat, device_types.RoomAirConditioner),
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/matter/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ def _update_from_device(self) -> None:
required_attributes=(
clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue,
),
allow_multi=True, # also used for climate entity
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
Expand Down
6 changes: 5 additions & 1 deletion homeassistant/components/media_source/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.translation import async_get_cached_translations

from .const import MEDIA_SOURCE_DATA, URI_SCHEME, URI_SCHEME_REGEX

Expand Down Expand Up @@ -62,12 +63,15 @@ def media_source_id(self) -> str:
async def async_browse(self) -> BrowseMediaSource:
"""Browse this item."""
if self.domain is None:
title = async_get_cached_translations(
self.hass, self.hass.config.language, "common", "media_source"
).get("component.media_source.common.sources_default", "Media Sources")
base = BrowseMediaSource(
domain=None,
identifier=None,
media_class=MediaClass.APP,
media_content_type=MediaType.APPS,
title="Media Sources",
title=title,
can_play=False,
can_expand=True,
children_media_class=MediaClass.APP,
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/media_source/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
"unknown_media_source": {
"message": "Unknown media source: {domain}"
}
},
"common": {
"sources_default": "Media sources"
}
}
1 change: 1 addition & 0 deletions homeassistant/components/nordpool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async def async_setup_entry(

coordinator = NordPoolDataUpdateCoordinator(hass, config_entry)
await coordinator.fetch_data(dt_util.utcnow(), True)
await coordinator.update_listeners(dt_util.utcnow())
if not coordinator.last_update_success:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
Expand Down
41 changes: 32 additions & 9 deletions homeassistant/components/nordpool/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ def __init__(self, hass: HomeAssistant, config_entry: NordPoolConfigEntry) -> No
name=DOMAIN,
)
self.client = NordPoolClient(session=async_get_clientsession(hass))
self.unsub: Callable[[], None] | None = None
self.data_unsub: Callable[[], None] | None = None
self.listener_unsub: Callable[[], None] | None = None

def get_next_interval(self, now: datetime) -> datetime:
def get_next_data_interval(self, now: datetime) -> datetime:
"""Compute next time an update should occur."""
next_hour = dt_util.utcnow() + timedelta(hours=1)
next_run = datetime(
Expand All @@ -56,23 +57,45 @@ def get_next_interval(self, now: datetime) -> datetime:
next_hour.hour,
tzinfo=dt_util.UTC,
)
LOGGER.debug("Next update at %s", next_run)
LOGGER.debug("Next data update at %s", next_run)
return next_run

def get_next_15_interval(self, now: datetime) -> datetime:
"""Compute next time we need to notify listeners."""
next_run = dt_util.utcnow() + timedelta(minutes=15)
next_minute = next_run.minute // 15 * 15
next_run = next_run.replace(
minute=next_minute, second=0, microsecond=0, tzinfo=dt_util.UTC
)

LOGGER.debug("Next listener update at %s", next_run)
return next_run

async def async_shutdown(self) -> None:
"""Cancel any scheduled call, and ignore new runs."""
await super().async_shutdown()
if self.unsub:
self.unsub()
self.unsub = None
if self.data_unsub:
self.data_unsub()
self.data_unsub = None
if self.listener_unsub:
self.listener_unsub()
self.listener_unsub = None

async def update_listeners(self, now: datetime) -> None:
"""Update entity listeners."""
self.listener_unsub = async_track_point_in_utc_time(
self.hass,
self.update_listeners,
self.get_next_15_interval(dt_util.utcnow()),
)
self.async_update_listeners()

async def fetch_data(self, now: datetime, initial: bool = False) -> None:
"""Fetch data from Nord Pool."""
self.unsub = async_track_point_in_utc_time(
self.hass, self.fetch_data, self.get_next_interval(dt_util.utcnow())
self.data_unsub = async_track_point_in_utc_time(
self.hass, self.fetch_data, self.get_next_data_interval(dt_util.utcnow())
)
if self.config_entry.pref_disable_polling and not initial:
self.async_update_listeners()
return
try:
data = await self.handle_data(initial)
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/onedrive/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ async def async_upload_backup(
)
try:
backup_file = await LargeFileUploadClient.upload(
self._token_function, file, session=async_get_clientsession(self._hass)
self._token_function,
file,
upload_chunk_size=UPLOAD_CHUNK_SIZE,
session=async_get_clientsession(self._hass),
)
except HashMismatchError as err:
raise BackupAgentError(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ovo_energy/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["ovoenergy"],
"requirements": ["ovoenergy==2.0.1"]
"requirements": ["ovoenergy==3.0.1"]
}
8 changes: 8 additions & 0 deletions homeassistant/components/portainer/entity.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Base class for Portainer entities."""

from pyportainer.models.docker import DockerContainer
from yarl import URL

from homeassistant.const import CONF_URL
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

Expand Down Expand Up @@ -31,6 +33,9 @@ def __init__(
identifiers={
(DOMAIN, f"{coordinator.config_entry.entry_id}_{self.device_id}")
},
configuration_url=URL(
f"{coordinator.config_entry.data[CONF_URL]}#!/{self.device_id}/docker/dashboard"
),
manufacturer=DEFAULT_NAME,
model="Endpoint",
name=device_info.endpoint.name,
Expand Down Expand Up @@ -63,6 +68,9 @@ def __init__(
(DOMAIN, f"{self.coordinator.config_entry.entry_id}_{device_name}")
},
manufacturer=DEFAULT_NAME,
configuration_url=URL(
f"{coordinator.config_entry.data[CONF_URL]}#!/{self.endpoint_id}/docker/containers/{self.device_id}"
),
model="Container",
name=device_name,
via_device=(
Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/recorder/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
CONTEXT_ID_AS_BINARY_SCHEMA_VERSION = 36
EVENT_TYPE_IDS_SCHEMA_VERSION = 37
STATES_META_SCHEMA_VERSION = 38
LAST_REPORTED_SCHEMA_VERSION = 43
CIRCULAR_MEAN_SCHEMA_VERSION = 49

LEGACY_STATES_EVENT_ID_INDEX_SCHEMA_VERSION = 28
Expand Down
9 changes: 5 additions & 4 deletions homeassistant/components/recorder/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
DEFAULT_MAX_BIND_VARS,
DOMAIN,
KEEPALIVE_TIME,
LAST_REPORTED_SCHEMA_VERSION,
MARIADB_PYMYSQL_URL_PREFIX,
MARIADB_URL_PREFIX,
MAX_QUEUE_BACKLOG_MIN_VALUE,
Expand Down Expand Up @@ -806,6 +805,10 @@ def _run(self) -> None:

# Catch up with missed statistics
self._schedule_compile_missing_statistics()

# Kick off live migrations
migration.migrate_data_live(self, self.get_session, schema_status)

_LOGGER.debug("Recorder processing the queue")
self._adjust_lru_size()
self.hass.add_job(self._async_set_recorder_ready_migration_done)
Expand All @@ -822,8 +825,6 @@ def _activate_and_set_db_ready(
# there are a lot of statistics graphs on the frontend.
self.statistics_meta_manager.load(session)

migration.migrate_data_live(self, self.get_session, schema_status)

# We must only set the db ready after we have set the table managers
# to active if there is no data to migrate.
#
Expand Down Expand Up @@ -1224,7 +1225,7 @@ def _commit_event_session(self) -> None:
if (
pending_last_reported
:= self.states_manager.get_pending_last_reported_timestamp()
) and self.schema_version >= LAST_REPORTED_SCHEMA_VERSION:
):
with session.no_autoflush:
session.execute(
update(States),
Expand Down
Loading
Loading