Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2cda081
Add illuminance sensor for Shelly Plug US Gen4 (#150681)
chemelli74 Sep 10, 2025
ceeeb22
Add integration for Droplet (#149989)
sarahseidman Sep 10, 2025
6f00f8a
Update feedreader to 6.0.12 (#152054)
cdce8p Sep 10, 2025
6a482b1
Remove stale devices for Alexa Devices (#151909)
chemelli74 Sep 10, 2025
ccef31a
Set PARALLEL_UPDATES in Whirlpool integration (#152065)
abmantis Sep 10, 2025
4592d63
Bump PySwitchBot to 0.70.0 (#152072)
bdraco Sep 10, 2025
83f3b3e
Bump ruff to 0.13.0 (#152067)
joostlek Sep 10, 2025
7d471f9
Add rgbicww light for switchbot integration (#151129)
zerzhang Sep 10, 2025
b496637
Add Portainer integration (#142875)
erwindouna Sep 10, 2025
c4649fc
Avoid cleanup/recreate of device_trackers not linked to a device for …
chemelli74 Sep 10, 2025
0a35fd0
Add key reconfigure to UptimeRobot config flow (#151562)
chemelli74 Sep 10, 2025
9082637
Raise on service calls in Whirlpool (#152057)
abmantis Sep 10, 2025
46c38f1
Adapt AccuWeather to new paid API plans (#152056)
bieniu Sep 10, 2025
e3c0cfd
Enable RUF059 and fix violations (#152071)
joostlek Sep 10, 2025
e73c670
Enable PYI061 and fix violations (#152070)
joostlek Sep 10, 2025
8852562
Enable PYI059 and fix violations (#152069)
joostlek Sep 10, 2025
214925e
Refactor unifiprotect RTSP repair flow to use publicapi create_rtsps_…
RaHehl Sep 10, 2025
4c54883
Use state selector for select option service (#148960)
piitaya Sep 10, 2025
720ecde
Support for Matter MountedDimmableLoadControl device type (#151330)
lboue Sep 10, 2025
002493c
Openuv protection window internal update (#146409)
wbyoung Sep 10, 2025
4ad664a
Add huawei_lte quality scale YAML (#143347)
scop Sep 10, 2025
d71b124
Add DHCP discovery to Aladdin Connect (#151532)
joostlek Sep 10, 2025
8367930
Jewish Calendar quality scale (#143763)
tsvi Sep 10, 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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.1
rev: v0.13.0
hooks:
- id: ruff-check
args:
Expand Down
2 changes: 2 additions & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ homeassistant.components.dnsip.*
homeassistant.components.doorbird.*
homeassistant.components.dormakaba_dkey.*
homeassistant.components.downloader.*
homeassistant.components.droplet.*
homeassistant.components.dsmr.*
homeassistant.components.duckdns.*
homeassistant.components.dunehd.*
Expand Down Expand Up @@ -401,6 +402,7 @@ homeassistant.components.person.*
homeassistant.components.pi_hole.*
homeassistant.components.ping.*
homeassistant.components.plugwise.*
homeassistant.components.portainer.*
homeassistant.components.powerfox.*
homeassistant.components.powerwall.*
homeassistant.components.private_ble_device.*
Expand Down
4 changes: 4 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions homeassistant/components/accuweather/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ async def async_step_user(
await self.async_set_unique_id(
accuweather.location_key, raise_on_progress=False
)
self._abort_if_unique_id_configured()

return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/accuweather/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@
4: "very_high",
5: "extreme",
}
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=10)
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)
3 changes: 1 addition & 2 deletions homeassistant/components/accuweather/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["accuweather"],
"requirements": ["accuweather==4.2.1"],
"single_config_entry": true
"requirements": ["accuweather==4.2.1"]
}
3 changes: 3 additions & 0 deletions homeassistant/components/accuweather/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
"requests_exceeded": "The allowed number of requests to the AccuWeather API has been exceeded. You have to wait or change the API key."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]"
}
},
"entity": {
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/aladdin_connect/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"codeowners": ["@swcloudgenie"],
"config_flow": true,
"dependencies": ["application_credentials"],
"dhcp": [
{
"hostname": "gdocntl-*"
}
],
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
"integration_type": "hub",
"iot_class": "cloud_polling",
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/aladdin_connect/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "Aladdin Connect needs to re-authenticate your account"
},
"oauth_discovery": {
"description": "Home Assistant has found an Aladdin Connect device on your network. Press **Submit** to continue setting up Aladdin Connect."
}
},
"abort": {
Expand Down
32 changes: 31 additions & 1 deletion homeassistant/components/alexa_devices/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import _LOGGER, CONF_LOGIN_DATA, DOMAIN
Expand Down Expand Up @@ -48,12 +49,13 @@ def __init__(
entry.data[CONF_PASSWORD],
entry.data[CONF_LOGIN_DATA],
)
self.previous_devices: set[str] = set()

async def _async_update_data(self) -> dict[str, AmazonDevice]:
"""Update device data."""
try:
await self.api.login_mode_stored_data()
return await self.api.get_devices_data()
data = await self.api.get_devices_data()
except CannotConnect as err:
raise UpdateFailed(
translation_domain=DOMAIN,
Expand All @@ -72,3 +74,31 @@ async def _async_update_data(self) -> dict[str, AmazonDevice]:
translation_key="invalid_auth",
translation_placeholders={"error": repr(err)},
) from err
else:
current_devices = set(data.keys())
if stale_devices := self.previous_devices - current_devices:
await self._async_remove_device_stale(stale_devices)

self.previous_devices = current_devices
return data

async def _async_remove_device_stale(
self,
stale_devices: set[str],
) -> None:
"""Remove stale device."""
device_registry = dr.async_get(self.hass)

for serial_num in stale_devices:
_LOGGER.debug(
"Detected change in devices: serial %s removed",
serial_num,
)
device = device_registry.async_get_device(
identifiers={(DOMAIN, serial_num)}
)
if device:
device_registry.async_update_device(
device_id=device.id,
remove_config_entry_id=self.config_entry.entry_id,
)
4 changes: 1 addition & 3 deletions homeassistant/components/alexa_devices/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ rules:
repair-issues:
status: exempt
comment: no known use cases for repair issues or flows, yet
stale-devices:
status: todo
comment: automate the cleanup process
stale-devices: done

# Platinum
async-dependency: done
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/deconz/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@


@dataclass(frozen=True, kw_only=True)
class DeconzSensorDescription(Generic[T], SensorEntityDescription):
class DeconzSensorDescription(SensorEntityDescription, Generic[T]):
"""Class describing deCONZ binary sensor entities."""

instance_check: type[T] | None = None
Expand Down
37 changes: 37 additions & 0 deletions homeassistant/components/droplet/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""The Droplet integration."""

from __future__ import annotations

import logging

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant

from .coordinator import DropletConfigEntry, DropletDataCoordinator

PLATFORMS: list[Platform] = [
Platform.SENSOR,
]

logger = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant, config_entry: DropletConfigEntry
) -> bool:
"""Set up Droplet from a config entry."""

droplet_coordinator = DropletDataCoordinator(hass, config_entry)
await droplet_coordinator.async_config_entry_first_refresh()
config_entry.runtime_data = droplet_coordinator
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)

return True


async def async_unload_entry(
hass: HomeAssistant, config_entry: DropletConfigEntry
) -> bool:
"""Unload a config entry."""

return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
118 changes: 118 additions & 0 deletions homeassistant/components/droplet/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Config flow for Droplet integration."""

from __future__ import annotations

from typing import Any

from pydroplet.droplet import DropletConnection, DropletDiscovery
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_CODE, CONF_DEVICE_ID, CONF_IP_ADDRESS, CONF_PORT
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import DOMAIN


class DropletConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle Droplet config flow."""

_droplet_discovery: DropletDiscovery

async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
self._droplet_discovery = DropletDiscovery(
discovery_info.host,
discovery_info.port,
discovery_info.name,
)
if not self._droplet_discovery.is_valid():
return self.async_abort(reason="invalid_discovery_info")

# In this case, device ID was part of the zeroconf discovery info
device_id: str = await self._droplet_discovery.get_device_id()
await self.async_set_unique_id(device_id)

self._abort_if_unique_id_configured(
updates={CONF_IP_ADDRESS: self._droplet_discovery.host},
)

self.context.update({"title_placeholders": {"name": device_id}})
return await self.async_step_confirm()

async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm the setup."""
errors: dict[str, str] = {}
device_id: str = await self._droplet_discovery.get_device_id()
if user_input is not None:
# Test if we can connect before returning
session = async_get_clientsession(self.hass)
if await self._droplet_discovery.try_connect(
session, user_input[CONF_CODE]
):
device_data = {
CONF_IP_ADDRESS: self._droplet_discovery.host,
CONF_PORT: self._droplet_discovery.port,
CONF_DEVICE_ID: device_id,
CONF_CODE: user_input[CONF_CODE],
}

return self.async_create_entry(
title=device_id,
data=device_data,
)
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="confirm",
data_schema=vol.Schema(
{
vol.Required(CONF_CODE): str,
}
),
description_placeholders={
"device_name": device_id,
},
errors=errors,
)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input is not None:
self._droplet_discovery = DropletDiscovery(
user_input[CONF_IP_ADDRESS], DropletConnection.DEFAULT_PORT, ""
)
session = async_get_clientsession(self.hass)
if await self._droplet_discovery.try_connect(
session, user_input[CONF_CODE]
) and (device_id := await self._droplet_discovery.get_device_id()):
device_data = {
CONF_IP_ADDRESS: self._droplet_discovery.host,
CONF_PORT: self._droplet_discovery.port,
CONF_DEVICE_ID: device_id,
CONF_CODE: user_input[CONF_CODE],
}
await self.async_set_unique_id(device_id, raise_on_progress=False)
self._abort_if_unique_id_configured(
description_placeholders={CONF_DEVICE_ID: device_id},
)

return self.async_create_entry(
title=device_id,
data=device_data,
)
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_CODE): str}
),
errors=errors,
)
11 changes: 11 additions & 0 deletions homeassistant/components/droplet/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Constants for the droplet integration."""

CONNECT_DELAY = 5

DOMAIN = "droplet"
DEVICE_NAME = "Droplet"

KEY_CURRENT_FLOW_RATE = "current_flow_rate"
KEY_VOLUME = "volume"
KEY_SIGNAL_QUALITY = "signal_quality"
KEY_SERVER_CONNECTIVITY = "server_connectivity"
Loading
Loading