Skip to content
Merged

2026.5.1 #170146

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2d05931
Added wfsens as a occupancy source in wiz (#166799)
th3spis May 7, 2026
76b878b
Fix WiZ Light config flow timeout by properly closing UDP connections…
robwasripped May 8, 2026
aac49a5
Fix IntelliFire setup recovery (#169739)
jeeftor May 7, 2026
a4227ef
Fix hassio auth IndexError on Supervisor Unix socket requests (#169911)
agners May 6, 2026
348f614
Update gardena ble to 2.8.1 (#169914)
elupus May 6, 2026
6f87d02
Bump serialx to 1.7.1 (#169928)
puddly May 6, 2026
4e61581
Bump holidays to 0.96 (#169939)
gjohansson-ST May 6, 2026
f644448
Add support for options to todo triggers (#169947)
emontnemery May 6, 2026
6fabbb3
Bump pyTibber to 0.37.5 (#169981)
Danielhiversen May 7, 2026
03aa979
Bump python-duco-client to 0.4.0 (#169776)
ronaldvdmeer May 5, 2026
5dd0436
Bump python-duco-client to 0.4.1 (#169991)
ronaldvdmeer May 7, 2026
44b1fea
Proper handling of malformed data during FRITZ!Box Tools setup (#170030)
mib1185 May 7, 2026
e1ad765
Fix websocket certificate verification Bump axis to v70 (#170038)
Kane610 May 8, 2026
cc140be
Fix `is_closed` state for DynamicGarageDoor in Overkiz (#170052)
iMicknl May 8, 2026
7b749b9
Fix tilt controls for TiltOnlyVenetianBlind in Overkiz (#170055)
iMicknl May 8, 2026
89649df
Fix cover controls for UpDownBioclimaticPergola in Overkiz (#170058)
iMicknl May 8, 2026
85c1167
Bump pyOverkiz to 1.20.3 (#170060)
iMicknl May 7, 2026
3a902e1
Bump deebot-client to 18.3.0 (#170066)
edenhaus May 7, 2026
1677577
Set `is_closed` state to `None` when a cover state returns "unknown" …
iMicknl May 8, 2026
106f815
Fix sensors getting wrong unit from MeasuredValueType attribute in Ov…
iMicknl May 8, 2026
fb7504e
Fix Z-Wave discovery crash with unknown node firmware version (#170090)
TheJulianJES May 8, 2026
ba18cde
Bump ZHA to 1.3.1 (#170095)
TheJulianJES May 8, 2026
5f98d5a
Bump python-bsblan to 5.2.1 (#170100)
liudger May 8, 2026
4940a0a
Bump blebox_uniapi to v2.5.3 (#170115)
bkobus-bbx May 8, 2026
a23131e
Fix is_closed state for DynamicGate covers in Overkiz (#170130)
iMicknl May 8, 2026
18ea40c
Fix tilt support for UpDownVenetianBlind (rts:VenetianBlindRTSCompone…
iMicknl May 8, 2026
dd0cdc4
Bump version to 2026.5.1
frenck May 8, 2026
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
2 changes: 1 addition & 1 deletion homeassistant/components/acer_projector/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["serialx==1.7.0"]
"requirements": ["serialx==1.7.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/axis/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["axis"],
"requirements": ["axis==69"],
"requirements": ["axis==70"],
"ssdp": [
{
"manufacturer": "AXIS"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/blebox/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["blebox_uniapi"],
"requirements": ["blebox-uniapi==2.5.2"],
"requirements": ["blebox-uniapi==2.5.3"],
"zeroconf": ["_bbxsrv._tcp.local."]
}
2 changes: 1 addition & 1 deletion homeassistant/components/bsblan/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["bsblan"],
"quality_scale": "silver",
"requirements": ["python-bsblan==5.2.0"],
"requirements": ["python-bsblan==5.2.1"],
"zeroconf": [
{
"name": "bsb-lan*",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/duco/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"iot_class": "local_polling",
"loggers": ["duco"],
"quality_scale": "platinum",
"requirements": ["python-duco-client==0.3.10"],
"requirements": ["python-duco-client==0.4.1"],
"zeroconf": [
{
"name": "duco [[][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][]].*",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ecovacs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.11", "deebot-client==18.2.0"]
"requirements": ["py-sucks==0.9.11", "deebot-client==18.3.0"]
}
11 changes: 9 additions & 2 deletions homeassistant/components/fritz/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import logging
import re
from typing import Any, TypedDict, cast
from xml.etree.ElementTree import ParseError

from fritzconnection import FritzConnection
from fritzconnection.core.exceptions import FritzActionError
Expand All @@ -26,7 +27,7 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send
Expand Down Expand Up @@ -228,7 +229,13 @@ def setup(self) -> None:
self.fritz_guest_wifi = FritzGuestWLAN(fc=self.connection)
self.fritz_status = FritzStatus(fc=self.connection)
self.fritz_call = FritzCall(fc=self.connection)
info = self.fritz_status.get_device_info()
try:
info = self.fritz_status.get_device_info()
except ParseError as ex:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="error_parse_device_info",
) from ex

_LOGGER.debug(
"gathered device info of %s %s",
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/fritz/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@
"config_entry_not_found": {
"message": "Failed to perform action \"{service}\". Config entry for target not found"
},
"error_parse_device_info": {
"message": "Error parsing device info. Please check the system event log of your FRITZ!Box for malformed data and clear the event list."
},
"error_refresh_hosts_info": {
"message": "Error refreshing hosts info"
},
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/gardena_bluetooth/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["bleak", "bleak_esphome", "gardena_bluetooth"],
"requirements": ["gardena-bluetooth==2.4.0"]
"requirements": ["gardena-bluetooth==2.8.1"]
}
21 changes: 13 additions & 8 deletions homeassistant/components/hassio/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from homeassistant.auth.models import User
from homeassistant.auth.providers import homeassistant as auth_ha
from homeassistant.components.http import KEY_HASS, KEY_HASS_USER, HomeAssistantView
from homeassistant.components.http.const import is_supervisor_unix_socket_request
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
Expand Down Expand Up @@ -41,14 +42,18 @@ def __init__(self, hass: HomeAssistant, user: User) -> None:

def _check_access(self, request: web.Request) -> None:
"""Check if this call is from Supervisor."""
# Check caller IP
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
assert request.transport
if ip_address(request.transport.get_extra_info("peername")[0]) != ip_address(
hassio_ip
):
_LOGGER.error("Invalid auth request from %s", request.remote)
raise HTTPUnauthorized
# Requests over the Supervisor Unix socket are authenticated by the
# http auth middleware as the Supervisor user, so the caller-IP check
# below does not apply (and would crash, since `peername` is empty for
# Unix sockets). The user-ID check still runs to ensure only the
# Supervisor user can reach this endpoint.
if not is_supervisor_unix_socket_request(request):
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
assert request.transport
peername = request.transport.get_extra_info("peername")
if not peername or ip_address(peername[0]) != ip_address(hassio_ip):
_LOGGER.error("Invalid auth request from %s", request.remote)
raise HTTPUnauthorized

# Check caller token
if request[KEY_HASS_USER].id != self.user.id:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/holiday/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.95", "babel==2.15.0"]
"requirements": ["holidays==0.96", "babel==2.15.0"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower_ble",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["automower-ble==0.2.8", "gardena-bluetooth==2.4.0"]
"requirements": ["automower-ble==0.2.8", "gardena-bluetooth==2.8.1"]
}
5 changes: 5 additions & 0 deletions homeassistant/components/intellifire/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import asyncio

import aiohttp
from intellifire4py import UnifiedFireplace
from intellifire4py.cloud_interface import IntelliFireCloudInterface
from intellifire4py.const import IntelliFireApiMode
Expand Down Expand Up @@ -155,6 +156,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: IntellifireConfigEntry)
raise ConfigEntryNotReady(
"Initialization of fireplace timed out after 10 minutes"
) from err
except (aiohttp.ClientConnectionError, ConnectionError) as err:
raise ConfigEntryNotReady(
"Error communicating with fireplace during initialization"
) from err

# Construct coordinator
data_update_coordinator = IntellifireDataUpdateCoordinator(hass, entry, fireplace)
Expand Down
7 changes: 3 additions & 4 deletions homeassistant/components/intellifire/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntryState,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
Expand Down Expand Up @@ -289,10 +290,8 @@ async def async_step_init(
errors: dict[str, str] = {}

if user_input is not None:
# Validate connectivity for requested modes if runtime data is available
coordinator = self.config_entry.runtime_data
if coordinator is not None:
fireplace = coordinator.fireplace
if self.config_entry.state is ConfigEntryState.LOADED:
fireplace = self.config_entry.runtime_data.fireplace

# Refresh connectivity status before validating
await fireplace.async_validate_connectivity()
Expand Down
69 changes: 66 additions & 3 deletions homeassistant/components/overkiz/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,21 @@ class OverkizCoverDescription(CoverEntityDescription):
close_tilt_command=OverkizCommand.LOWER_CLOSE,
stop_tilt_command=OverkizCommand.STOP,
),
# Needs override to remove open/close commands
# Needs override to add support for very specific tilt commands
# uiClass is VenetianBlind
OverkizCoverDescription(
key=UIWidget.TILT_ONLY_VENETIAN_BLIND,
device_class=CoverDeviceClass.BLIND,
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
# Position commands fully open/close the tilt
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
stop_command=OverkizCommand.STOP,
# Tilt commands move the tilt with a few degrees
open_tilt_command=OverkizCommand.TILT_POSITIVE,
open_tilt_command_args=(1, 0),
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
close_tilt_command_args=(1, 0),
stop_tilt_command=OverkizCommand.STOP,
),
# Needs override to support very specific tilt commands (rts:ExteriorVenetianBlindRTSComponent)
Expand All @@ -125,6 +132,57 @@ class OverkizCoverDescription(CoverEntityDescription):
close_tilt_command_args=(15, 1), # position (1-127), speed (1-15)
stop_tilt_command=OverkizCommand.STOP,
),
# Needs override to support very specific tilt commands (rts:VenetianBlindRTSComponent)
# uiClass is VenetianBlind
OverkizCoverDescription(
key=UIWidget.UP_DOWN_VENETIAN_BLIND,
device_class=CoverDeviceClass.BLIND,
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
stop_command=OverkizCommand.STOP,
open_tilt_command=OverkizCommand.TILT_POSITIVE,
open_tilt_command_args=(15, 1), # position (1-127), speed (1-15)
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
close_tilt_command_args=(15, 1), # position (1-127), speed (1-15)
stop_tilt_command=OverkizCommand.STOP,
),
# Needs override since PositionableGarageDoor reports
# core:OpenClosedUnknownState instead of core:OpenClosedState
# uiClass is GarageDoor
OverkizCoverDescription(
key=UIWidget.POSITIONABLE_GARAGE_DOOR,
device_class=CoverDeviceClass.GARAGE,
current_position_state=OverkizState.CORE_CLOSURE,
set_position_command=OverkizCommand.SET_CLOSURE,
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
stop_command=OverkizCommand.STOP,
is_closed_state=OverkizState.CORE_OPEN_CLOSED_UNKNOWN,
),
# Needs override since PositionableGarageDoorWithPartialPosition reports
# core:OpenClosedPartialState instead of core:OpenClosedState
# uiClass is GarageDoor
OverkizCoverDescription(
key=UIWidget.POSITIONABLE_GARAGE_DOOR_WITH_PARTIAL_POSITION,
device_class=CoverDeviceClass.GARAGE,
current_position_state=OverkizState.CORE_CLOSURE,
set_position_command=OverkizCommand.SET_CLOSURE,
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
stop_command=OverkizCommand.STOP,
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PARTIAL,
),
# Needs override since DiscreteGateWithPedestrianPosition reports
# core:OpenClosedPedestrianState instead of core:OpenClosedState
# uiClass is Gate
OverkizCoverDescription(
key=UIWidget.DISCRETE_GATE_WITH_PEDESTRIAN_POSITION,
device_class=CoverDeviceClass.GATE,
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
stop_command=OverkizCommand.STOP,
),
# Needs override to support this Generic device (rts:GenericRTSComponent)
# uiClass is Generic (not mapped to cover as this is a Generic device class)
OverkizCoverDescription(
Expand Down Expand Up @@ -201,20 +259,23 @@ class OverkizCoverDescription(CoverEntityDescription):
set_position_command=OverkizCommand.SET_CLOSURE,
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
is_closed_state=OverkizState.CORE_OPEN_CLOSED_UNKNOWN,
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
stop_command=OverkizCommand.STOP,
),
OverkizCoverDescription(
key=UIClass.GATE,
device_class=CoverDeviceClass.GATE,
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
stop_command=OverkizCommand.STOP,
),
OverkizCoverDescription(
key=UIClass.PERGOLA,
device_class=CoverDeviceClass.AWNING,
open_command=OverkizCommand.OPEN,
close_command=OverkizCommand.CLOSE,
stop_command=OverkizCommand.STOP,
is_closed_state=OverkizState.CORE_SLATS_OPEN_CLOSED,
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
Expand Down Expand Up @@ -392,6 +453,8 @@ def is_closed(self) -> bool | None:
"""Return if the cover is closed."""
if is_closed_state := self.entity_description.is_closed_state:
if state := self.device.states.get(is_closed_state):
if state.value == OverkizCommandParam.UNKNOWN:
return None
return state.value == OverkizCommandParam.CLOSED

if (position := self.current_cover_position) is not None:
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/overkiz/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
OverkizCommand.ON,
OverkizCommand.ON_WITH_TIMER,
OverkizCommand.TEST,
OverkizCommand.TILT_POSITIVE,
OverkizCommand.TILT_NEGATIVE,
]


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/overkiz/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
"requirements": ["pyoverkiz==1.20.0"],
"requirements": ["pyoverkiz==1.20.3"],
"zeroconf": [
{
"name": "gateway*",
Expand Down
18 changes: 17 additions & 1 deletion homeassistant/components/overkiz/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pyoverkiz.types import StateType as OverkizStateType

from homeassistant.components.sensor import (
DEVICE_CLASS_UNITS,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
Expand Down Expand Up @@ -606,10 +607,25 @@ def native_unit_of_measurement(self) -> str | None:
if (unit := attrs[OverkizAttribute.CORE_MEASURED_VALUE_TYPE]) and (
unit_value := unit.value_as_str
):
return OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)
ha_unit = OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)
if self._is_unit_valid_for_device_class(ha_unit):
return ha_unit

return default_unit

def _is_unit_valid_for_device_class(self, unit: str) -> bool:
"""Check if a unit is valid for this sensor's device class.

The device-level core:MeasuredValueType attribute describes the primary
sensor (e.g. luminance/temperature), but must not override the unit of
unrelated sensors on the same device (e.g. RSSI).
"""
if not (device_class := self.entity_description.device_class):
return True
if (valid_units := DEVICE_CLASS_UNITS.get(device_class)) is None:
return True
return unit in valid_units


class OverkizHomeKitSetupCodeSensor(OverkizEntity, SensorEntity):
"""Representation of an Overkiz HomeKit Setup Code."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/serial/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"codeowners": ["@fabaff"],
"documentation": "https://www.home-assistant.io/integrations/serial",
"iot_class": "local_polling",
"requirements": ["serialx==1.7.0"]
"requirements": ["serialx==1.7.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/tibber/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["tibber"],
"requirements": ["pyTibber==0.37.4"]
"requirements": ["pyTibber==0.37.5"]
}
3 changes: 2 additions & 1 deletion homeassistant/components/todo/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import voluptuous as vol

from homeassistant.const import ATTR_ENTITY_ID, CONF_TARGET
from homeassistant.const import ATTR_ENTITY_ID, CONF_OPTIONS, CONF_TARGET
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
Expand All @@ -25,6 +25,7 @@
ITEM_TRIGGER_SCHEMA = vol.Schema(
{
vol.Required(CONF_TARGET): cv.TARGET_FIELDS,
vol.Required(CONF_OPTIONS, default={}): {},
}
)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/usb/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "system",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["aiousbwatcher==1.1.2", "serialx==1.7.0"]
"requirements": ["aiousbwatcher==1.1.2", "serialx==1.7.1"]
}
Loading
Loading