master 2025.6.1
Franck Nijhof 2025-06-13 22:15:26 +02:00 committed by GitHub
commit a75646d047
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 120 additions and 104 deletions

View File

@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "bronze",
"requirements": ["aioamazondevices==3.0.6"]
"requirements": ["aioamazondevices==3.1.2"]
}

View File

@ -7,6 +7,7 @@ from dataclasses import dataclass
from typing import Any, Final
from aioamazondevices.api import AmazonDevice, AmazonEchoApi
from aioamazondevices.const import SPEAKER_GROUP_FAMILY
from homeassistant.components.notify import NotifyEntity, NotifyEntityDescription
from homeassistant.core import HomeAssistant
@ -22,6 +23,7 @@ PARALLEL_UPDATES = 1
class AmazonNotifyEntityDescription(NotifyEntityDescription):
"""Alexa Devices notify entity description."""
is_supported: Callable[[AmazonDevice], bool] = lambda _device: True
method: Callable[[AmazonEchoApi, AmazonDevice, str], Awaitable[None]]
subkey: str
@ -31,6 +33,7 @@ NOTIFY: Final = (
key="speak",
translation_key="speak",
subkey="AUDIO_PLAYER",
is_supported=lambda _device: _device.device_family != SPEAKER_GROUP_FAMILY,
method=lambda api, device, message: api.call_alexa_speak(device, message),
),
AmazonNotifyEntityDescription(
@ -58,6 +61,7 @@ async def async_setup_entry(
for sensor_desc in NOTIFY
for serial_num in coordinator.data
if sensor_desc.subkey in coordinator.data[serial_num].capabilities
and sensor_desc.is_supported(coordinator.data[serial_num])
)

View File

@ -89,7 +89,7 @@ class ArubaDeviceScanner(DeviceScanner):
def get_aruba_data(self) -> dict[str, dict[str, str]] | None:
"""Retrieve data from Aruba Access Point and return parsed result."""
connect = f"ssh {self.username}@{self.host} -o HostKeyAlgorithms=ssh-rsa"
connect = f"ssh {self.username}@{self.host}"
ssh: pexpect.spawn[str] = pexpect.spawn(connect, encoding="utf-8")
query = ssh.expect(
[

View File

@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dnsip",
"iot_class": "cloud_polling",
"requirements": ["aiodns==3.4.0"]
"requirements": ["aiodns==3.5.0"]
}

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250531.2"]
"requirements": ["home-assistant-frontend==20250531.3"]
}

View File

@ -128,6 +128,7 @@ class HomematicipHAP:
self.config_entry.data.get(HMIPC_AUTHTOKEN),
self.config_entry.data.get(HMIPC_NAME),
)
except HmipcConnectionError as err:
raise ConfigEntryNotReady from err
except Exception as err: # noqa: BLE001
@ -210,41 +211,13 @@ class HomematicipHAP:
for device in self.home.devices:
device.fire_update_event()
async def async_connect(self) -> None:
"""Start WebSocket connection."""
tries = 0
while True:
retry_delay = 2 ** min(tries, 8)
async def async_connect(self, home: AsyncHome) -> None:
"""Connect to HomematicIP Cloud Websocket."""
await home.enable_events()
try:
await self.home.get_current_state_async()
hmip_events = self.home.enable_events()
self.home.set_on_connected_handler(self.ws_connected_handler)
self.home.set_on_disconnected_handler(self.ws_disconnected_handler)
tries = 0
await hmip_events
except HmipConnectionError:
_LOGGER.error(
(
"Error connecting to HomematicIP with HAP %s. "
"Retrying in %d seconds"
),
self.config_entry.unique_id,
retry_delay,
)
if self._ws_close_requested:
break
self._ws_close_requested = False
tries += 1
try:
self._retry_task = self.hass.async_create_task(
asyncio.sleep(retry_delay)
)
await self._retry_task
except asyncio.CancelledError:
break
home.set_on_connected_handler(self.ws_connected_handler)
home.set_on_disconnected_handler(self.ws_disconnected_handler)
home.set_on_reconnect_handler(self.ws_reconnected_handler)
async def async_reset(self) -> bool:
"""Close the websocket connection."""
@ -272,14 +245,22 @@ class HomematicipHAP:
async def ws_connected_handler(self) -> None:
"""Handle websocket connected."""
_LOGGER.debug("WebSocket connection to HomematicIP established")
_LOGGER.info("Websocket connection to HomematicIP Cloud established")
if self._ws_connection_closed.is_set():
await self.get_state()
self._ws_connection_closed.clear()
async def ws_disconnected_handler(self) -> None:
"""Handle websocket disconnection."""
_LOGGER.warning("WebSocket connection to HomematicIP closed")
_LOGGER.warning("Websocket connection to HomematicIP Cloud closed")
self._ws_connection_closed.set()
async def ws_reconnected_handler(self, reason: str) -> None:
"""Handle websocket reconnection."""
_LOGGER.info(
"Websocket connection to HomematicIP Cloud re-established due to reason: %s",
reason,
)
self._ws_connection_closed.set()
async def get_hap(
@ -306,6 +287,6 @@ class HomematicipHAP:
home.on_update(self.async_update)
home.on_create(self.async_create_entity)
hass.loop.create_task(self.async_connect())
await self.async_connect(home)
return home

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/homematicip_cloud",
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"requirements": ["homematicip==2.0.4"]
"requirements": ["homematicip==2.0.5"]
}

View File

@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/jewish_calendar",
"iot_class": "calculated",
"loggers": ["hdate"],
"requirements": ["hdate[astral]==1.1.1"],
"requirements": ["hdate[astral]==1.1.2"],
"single_config_entry": true
}

View File

@ -73,7 +73,7 @@ INFO_SENSORS: tuple[JewishCalendarSensorDescription, ...] = (
translation_key="weekly_portion",
device_class=SensorDeviceClass.ENUM,
options_fn=lambda _: [str(p) for p in Parasha],
value_fn=lambda results: str(results.after_tzais_date.upcoming_shabbat.parasha),
value_fn=lambda results: results.after_tzais_date.upcoming_shabbat.parasha,
),
JewishCalendarSensorDescription(
key="holiday",
@ -98,17 +98,13 @@ INFO_SENSORS: tuple[JewishCalendarSensorDescription, ...] = (
key="omer_count",
translation_key="omer_count",
entity_registry_enabled_default=False,
value_fn=lambda results: (
results.after_shkia_date.omer.total_days
if results.after_shkia_date.omer
else 0
),
value_fn=lambda results: results.after_shkia_date.omer.total_days,
),
JewishCalendarSensorDescription(
key="daf_yomi",
translation_key="daf_yomi",
entity_registry_enabled_default=False,
value_fn=lambda results: str(results.daytime_date.daf_yomi),
value_fn=lambda results: results.daytime_date.daf_yomi,
),
)

View File

@ -5,7 +5,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable
from datetime import timedelta
from typing import Any
from typing import TYPE_CHECKING, Any
import aiolifx_effects
from aiolifx_themes.painter import ThemePainter
@ -31,9 +31,12 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_extract_referenced_entity_ids
from .const import _ATTR_COLOR_TEMP, ATTR_THEME, DATA_LIFX_MANAGER, DOMAIN
from .coordinator import LIFXUpdateCoordinator, Light
from .coordinator import LIFXUpdateCoordinator
from .util import convert_8_to_16, find_hsbk
if TYPE_CHECKING:
from aiolifx.aiolifx import Light
SCAN_INTERVAL = timedelta(seconds=10)
SERVICE_EFFECT_COLORLOOP = "effect_colorloop"
@ -426,8 +429,8 @@ class LIFXManager:
) -> None:
"""Start the firmware-based Sky effect."""
palette = kwargs.get(ATTR_PALETTE)
theme = Theme()
if palette is not None:
theme = Theme()
for hsbk in palette:
theme.add_hsbk(hsbk[0], hsbk[1], hsbk[2], hsbk[3])

View File

@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["linkplay"],
"requirements": ["python-linkplay==0.2.11"],
"requirements": ["python-linkplay==0.2.12"],
"zeroconf": ["_linkplay._tcp.local."]
}

View File

@ -36,11 +36,6 @@ _LOGGER = logging.getLogger(__name__)
PRODID = "-//homeassistant.io//local_calendar 1.0//EN"
# The calendar on disk is only changed when this entity is updated, so there
# is no need to poll for changes. The calendar enttiy base class will handle
# refreshing the entity state based on the start or end time of the event.
SCAN_INTERVAL = timedelta(days=1)
async def async_setup_entry(
hass: HomeAssistant,

View File

@ -10,6 +10,7 @@ from opower import (
CannotConnect,
InvalidAuth,
Opower,
create_cookie_jar,
get_supported_utility_names,
select_utility,
)
@ -39,7 +40,7 @@ async def _validate_login(
) -> dict[str, str]:
"""Validate login data and return any errors."""
api = Opower(
async_create_clientsession(hass),
async_create_clientsession(hass, cookie_jar=create_cookie_jar()),
login_data[CONF_UTILITY],
login_data[CONF_USERNAME],
login_data[CONF_PASSWORD],

View File

@ -12,6 +12,7 @@ from opower import (
MeterType,
Opower,
ReadResolution,
create_cookie_jar,
)
from opower.exceptions import ApiException, CannotConnect, InvalidAuth
@ -30,7 +31,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, UnitOfEnergy, UnitOfVolume
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import aiohttp_client, issue_registry as ir
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util
@ -62,7 +64,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
update_interval=timedelta(hours=12),
)
self.api = Opower(
aiohttp_client.async_get_clientsession(hass),
async_create_clientsession(hass, cookie_jar=create_cookie_jar()),
config_entry.data[CONF_UTILITY],
config_entry.data[CONF_USERNAME],
config_entry.data[CONF_PASSWORD],

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/opower",
"iot_class": "cloud_polling",
"loggers": ["opower"],
"requirements": ["opower==0.12.3"]
"requirements": ["opower==0.12.4"]
}

View File

@ -4,6 +4,7 @@ from datetime import datetime
import logging
from ical.event import Event
from ical.timeline import Timeline
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.core import HomeAssistant
@ -48,12 +49,18 @@ class RemoteCalendarEntity(
super().__init__(coordinator)
self._attr_name = entry.data[CONF_CALENDAR_NAME]
self._attr_unique_id = entry.entry_id
self._event: CalendarEvent | None = None
self._timeline: Timeline | None = None
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return self._event
if self._timeline is None:
return None
now = dt_util.now()
events = self._timeline.active_after(now)
if event := next(events, None):
return _get_calendar_event(event)
return None
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
@ -79,15 +86,12 @@ class RemoteCalendarEntity(
"""
await super().async_update()
def next_timeline_event() -> CalendarEvent | None:
def _get_timeline() -> Timeline | None:
"""Return the next active event."""
now = dt_util.now()
events = self.coordinator.data.timeline_tz(now.tzinfo).active_after(now)
if event := next(events, None):
return _get_calendar_event(event)
return None
return self.coordinator.data.timeline_tz(now.tzinfo)
self._event = await self.hass.async_add_executor_job(next_timeline_event)
self._timeline = await self.hass.async_add_executor_job(_get_timeline)
def _get_calendar_event(event: Event) -> CalendarEvent:

View File

@ -76,10 +76,10 @@ class SamsungTVEntity(CoordinatorEntity[SamsungTVDataUpdateCoordinator], Entity)
def _wake_on_lan(self) -> None:
"""Wake the device via wake on lan."""
send_magic_packet(self._mac, ip_address=self._host)
send_magic_packet(self._mac, ip_address=self._host) # type: ignore[arg-type]
# If the ip address changed since we last saw the device
# broadcast a packet as well
send_magic_packet(self._mac)
send_magic_packet(self._mac) # type: ignore[arg-type]
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""

View File

@ -38,7 +38,7 @@
"getmac==0.9.5",
"samsungctl[websocket]==0.7.1",
"samsungtvws[async,encrypted]==2.7.2",
"wakeonlan==2.1.0",
"wakeonlan==3.1.0",
"async-upnp-client==0.44.0"
],
"ssdp": [

View File

@ -9,5 +9,5 @@ from homeassistant.helpers import aiohttp_client
async def async_client_session(hass: HomeAssistant) -> ClientSession:
"""Return a new aiohttp session."""
return aiohttp_client.async_create_clientsession(
hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True)
hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True, quote_cookie=False)
)

View File

@ -52,7 +52,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
await hass.async_add_executor_job(
partial(wakeonlan.send_magic_packet, mac_address, **service_kwargs)
partial(wakeonlan.send_magic_packet, mac_address, **service_kwargs) # type: ignore[arg-type]
)
hass.services.async_register(

View File

@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/wake_on_lan",
"iot_class": "local_push",
"requirements": ["wakeonlan==2.1.0"]
"requirements": ["wakeonlan==3.1.0"]
}

View File

@ -330,6 +330,12 @@ class XiaomiGenericDevice(
"""Return the percentage based speed of the fan."""
return None
@property
def is_on(self) -> bool | None:
"""Return true if device is on."""
# Base FanEntity uses percentage to determine if the device is on.
return self._attr_is_on
async def async_turn_on(
self,
percentage: int | None = None,

View File

@ -25,7 +25,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2025
MINOR_VERSION: Final = 6
PATCH_VERSION: Final = "0"
PATCH_VERSION: Final = "1"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)

View File

@ -2,7 +2,7 @@
aiodhcpwatcher==1.2.0
aiodiscover==2.7.0
aiodns==3.4.0
aiodns==3.5.0
aiofiles==24.1.0
aiohasupervisor==0.3.1
aiohttp-asyncmdnsresolver==0.1.1
@ -39,7 +39,7 @@ habluetooth==3.49.0
hass-nabucasa==0.101.0
hassil==2.2.3
home-assistant-bluetooth==1.13.1
home-assistant-frontend==20250531.2
home-assistant-frontend==20250531.3
home-assistant-intents==2025.6.10
httpx==0.28.1
ifaddr==0.2.0

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2025.6.0"
version = "2025.6.1"
license = "Apache-2.0"
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
description = "Open-source home automation platform running on Python 3."
@ -23,7 +23,7 @@ classifiers = [
]
requires-python = ">=3.13.2"
dependencies = [
"aiodns==3.4.0",
"aiodns==3.5.0",
"aiofiles==24.1.0",
# Integrations may depend on hassio integration without listing it to
# change behavior based on presence of supervisor. Deprecated with #127228

2
requirements.txt generated
View File

@ -3,7 +3,7 @@
-c homeassistant/package_constraints.txt
# Home Assistant Core
aiodns==3.4.0
aiodns==3.5.0
aiofiles==24.1.0
aiohasupervisor==0.3.1
aiohttp==3.12.12

16
requirements_all.txt generated
View File

@ -182,7 +182,7 @@ aioairzone-cloud==0.6.12
aioairzone==1.0.0
# homeassistant.components.alexa_devices
aioamazondevices==3.0.6
aioamazondevices==3.1.2
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@ -223,7 +223,7 @@ aiodhcpwatcher==1.2.0
aiodiscover==2.7.0
# homeassistant.components.dnsip
aiodns==3.4.0
aiodns==3.5.0
# homeassistant.components.duke_energy
aiodukeenergy==0.3.0
@ -1133,7 +1133,7 @@ hass-splunk==0.1.1
hassil==2.2.3
# homeassistant.components.jewish_calendar
hdate[astral]==1.1.1
hdate[astral]==1.1.2
# homeassistant.components.heatmiser
heatmiserV3==2.0.3
@ -1164,13 +1164,13 @@ hole==0.8.0
holidays==0.74
# homeassistant.components.frontend
home-assistant-frontend==20250531.2
home-assistant-frontend==20250531.3
# homeassistant.components.conversation
home-assistant-intents==2025.6.10
# homeassistant.components.homematicip_cloud
homematicip==2.0.4
homematicip==2.0.5
# homeassistant.components.horizon
horimote==0.4.1
@ -1617,7 +1617,7 @@ openwrt-luci-rpc==1.1.17
openwrt-ubus-rpc==0.0.2
# homeassistant.components.opower
opower==0.12.3
opower==0.12.4
# homeassistant.components.oralb
oralb-ble==0.17.6
@ -2452,7 +2452,7 @@ python-juicenet==1.1.0
python-kasa[speedups]==0.10.2
# homeassistant.components.linkplay
python-linkplay==0.2.11
python-linkplay==0.2.12
# homeassistant.components.lirc
# python-lirc==1.2.3
@ -3059,7 +3059,7 @@ vultr==0.1.2
# homeassistant.components.samsungtv
# homeassistant.components.wake_on_lan
wakeonlan==2.1.0
wakeonlan==3.1.0
# homeassistant.components.wallbox
wallbox==0.9.0

View File

@ -170,7 +170,7 @@ aioairzone-cloud==0.6.12
aioairzone==1.0.0
# homeassistant.components.alexa_devices
aioamazondevices==3.0.6
aioamazondevices==3.1.2
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@ -211,7 +211,7 @@ aiodhcpwatcher==1.2.0
aiodiscover==2.7.0
# homeassistant.components.dnsip
aiodns==3.4.0
aiodns==3.5.0
# homeassistant.components.duke_energy
aiodukeenergy==0.3.0
@ -988,7 +988,7 @@ hass-nabucasa==0.101.0
hassil==2.2.3
# homeassistant.components.jewish_calendar
hdate[astral]==1.1.1
hdate[astral]==1.1.2
# homeassistant.components.here_travel_time
here-routing==1.0.1
@ -1010,13 +1010,13 @@ hole==0.8.0
holidays==0.74
# homeassistant.components.frontend
home-assistant-frontend==20250531.2
home-assistant-frontend==20250531.3
# homeassistant.components.conversation
home-assistant-intents==2025.6.10
# homeassistant.components.homematicip_cloud
homematicip==2.0.4
homematicip==2.0.5
# homeassistant.components.remember_the_milk
httplib2==0.20.4
@ -1370,7 +1370,7 @@ openhomedevice==2.2.0
openwebifpy==4.3.1
# homeassistant.components.opower
opower==0.12.3
opower==0.12.4
# homeassistant.components.oralb
oralb-ble==0.17.6
@ -2022,7 +2022,7 @@ python-juicenet==1.1.0
python-kasa[speedups]==0.10.2
# homeassistant.components.linkplay
python-linkplay==0.2.11
python-linkplay==0.2.12
# homeassistant.components.lirc
# python-lirc==1.2.3
@ -2518,7 +2518,7 @@ vultr==0.1.2
# homeassistant.components.samsungtv
# homeassistant.components.wake_on_lan
wakeonlan==2.1.0
wakeonlan==3.1.0
# homeassistant.components.wallbox
wallbox==0.9.0

View File

@ -56,6 +56,9 @@ def mock_amazon_devices_client() -> Generator[AsyncMock]:
do_not_disturb=False,
response_style=None,
bluetooth_state=True,
entity_id="11111111-2222-3333-4444-555555555555",
appliance_id="G1234567890123456789012345678A",
sensors={},
)
}
client.get_model_details = lambda device: DEVICE_TYPE_TO_MODEL.get(

View File

@ -1,6 +1,6 @@
"""Test HomematicIP Cloud accesspoint."""
from unittest.mock import Mock, patch
from unittest.mock import AsyncMock, Mock, patch
from homematicip.auth import Auth
from homematicip.connection.connection_context import ConnectionContext
@ -16,6 +16,7 @@ from homeassistant.components.homematicip_cloud.const import (
)
from homeassistant.components.homematicip_cloud.errors import HmipcConnectionError
from homeassistant.components.homematicip_cloud.hap import (
AsyncHome,
HomematicipAuth,
HomematicipHAP,
)
@ -251,3 +252,21 @@ async def test_get_state_after_disconnect(
assert hap._ws_connection_closed.is_set()
await hap.ws_connected_handler()
mock_get_state.assert_called_once()
async def test_async_connect(
hass: HomeAssistant, hmip_config_entry: MockConfigEntry, simple_mock_home
) -> None:
"""Test async_connect."""
hass.config.components.add(HMIPC_DOMAIN)
hap = HomematicipHAP(hass, hmip_config_entry)
assert hap
simple_mock_home = AsyncMock(spec=AsyncHome, autospec=True)
await hap.async_connect(simple_mock_home)
simple_mock_home.set_on_connected_handler.assert_called_once()
simple_mock_home.set_on_disconnected_handler.assert_called_once()
simple_mock_home.set_on_reconnect_handler.assert_called_once()
simple_mock_home.enable_events.assert_called_once()

View File

@ -843,7 +843,7 @@ async def test_sky_effect(hass: HomeAssistant) -> None:
SERVICE_EFFECT_SKY,
{
ATTR_ENTITY_ID: entity_id,
ATTR_PALETTE: [],
ATTR_PALETTE: None,
ATTR_SKY_TYPE: "Clouds",
ATTR_CLOUD_SATURATION_MAX: 180,
ATTR_CLOUD_SATURATION_MIN: 50,
@ -854,7 +854,7 @@ async def test_sky_effect(hass: HomeAssistant) -> None:
bulb.power_level = 65535
bulb.effect = {
"effect": "SKY",
"palette": [],
"palette": None,
"sky_type": 2,
"cloud_saturation_min": 50,
"cloud_saturation_max": 180,

View File

@ -382,8 +382,10 @@ def verify_cleanup(
# Verify no threads where left behind.
threads = frozenset(threading.enumerate()) - threads_before
for thread in threads:
assert isinstance(thread, threading._DummyThread) or thread.name.startswith(
"waitpid-"
assert (
isinstance(thread, threading._DummyThread)
or thread.name.startswith("waitpid-")
or "_run_safe_shutdown_loop" in thread.name
)
try: