Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d628d23
Fix wrong integration type classification of EDL21 (#172230)
tr4nt0r May 26, 2026
5d75f1c
Use non-reloading entry update method in config flow of Habitica inte…
tr4nt0r May 26, 2026
2c5adae
Bump indevolt-api to 1.8.2 (#172201)
Xirt May 26, 2026
eca83fb
Switch to async_setup in coordinator for gardena setup (#172198)
elupus May 26, 2026
6cff433
Remove artificial throttling of push updates in EDL21 integration (#1…
tr4nt0r May 26, 2026
231ed34
Bump pysml to 0.1.7 (#172217)
tr4nt0r May 26, 2026
a2fbd2b
Migrate EDL21 to use SerialPortSelector (#172220)
tr4nt0r May 26, 2026
89fb856
Use non-reloading entry update method in config flow of Xbox integrat…
tr4nt0r May 26, 2026
7e67c53
Use non-reloading entry update method in config flow of PlayStation N…
tr4nt0r May 26, 2026
7db5e82
Use non-reloading entry update methods in config flow of ntfy integra…
tr4nt0r May 26, 2026
7566839
Add missing Miele program phase codes (#172144)
astrandb May 26, 2026
7655cb0
Fix blocking time_zone validation in config/core/update websocket com…
bdraco May 26, 2026
975e30c
Remove unreachable Hikvision Shelter Alarm binary sensor (#172152)
ptarjan May 26, 2026
6ec1146
Replace duplicate constants in bluetooth with homeassistant.const imp…
maxmichels May 26, 2026
5053392
Add in_zones property to mobile_app device tracker (#171814)
emontnemery May 26, 2026
2368a36
Remove support for advanced mode from schema config flow (#172117)
emontnemery May 26, 2026
78db1e3
Deprecate the FlowHandler show_advanced_options property (#171754)
emontnemery May 26, 2026
67e4f04
Add serial_number to HomeWizard device registry entries (#172233)
DCSBL May 26, 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/bluetooth/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import CONF_SOURCE
from homeassistant.core import callback
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
Expand All @@ -40,7 +41,6 @@
CONF_DETAILS,
CONF_MODE,
CONF_PASSIVE,
CONF_SOURCE,
CONF_SOURCE_CONFIG_ENTRY_ID,
CONF_SOURCE_DEVICE_ID,
CONF_SOURCE_DOMAIN,
Expand Down
3 changes: 0 additions & 3 deletions homeassistant/components/bluetooth/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@

DEFAULT_MODE = BluetoothScanningMode.AUTO.value


# pylint: disable-next=home-assistant-duplicate-const
CONF_SOURCE: Final = "source"
CONF_SOURCE_DOMAIN: Final = "source_domain"
CONF_SOURCE_MODEL: Final = "source_model"
CONF_SOURCE_CONFIG_ENTRY_ID: Final = "source_config_entry_id"
Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/bluetooth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
)

from homeassistant import config_entries
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, EVENT_LOGGING_CHANGED
from homeassistant.const import (
CONF_SOURCE,
EVENT_HOMEASSISTANT_STOP,
EVENT_LOGGING_CHANGED,
)
from homeassistant.core import (
CALLBACK_TYPE,
Event,
Expand All @@ -33,7 +37,6 @@
from homeassistant.util.package import is_docker_env

from .const import (
CONF_SOURCE,
CONF_SOURCE_CONFIG_ENTRY_ID,
CONF_SOURCE_DEVICE_ID,
CONF_SOURCE_DOMAIN,
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/config/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ async def post(self, request: web.Request) -> web.Response:
vol.Optional("location_name"): str,
vol.Optional("longitude"): cv.longitude,
vol.Optional("radius"): cv.positive_int,
vol.Optional("time_zone"): cv.time_zone,
# Validated by async_set_time_zone in the executor to avoid
# blocking I/O loading zoneinfo data on the event loop.
vol.Optional("time_zone"): str,
vol.Optional("update_units"): bool,
vol.Optional("unit_system"): unit_system.validate_unit_system,
}
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/edl21/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.helpers.selector import SerialPortSelector

from .const import CONF_SERIAL_PORT, DEFAULT_TITLE, DOMAIN

DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_SERIAL_PORT): str,
vol.Required(CONF_SERIAL_PORT): SerialPortSelector(),
}
)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/edl21/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/edl21",
"integration_type": "hub",
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["sml"],
"requirements": ["pysml==0.1.5"]
"requirements": ["pysml==0.1.7"]
}
11 changes: 0 additions & 11 deletions homeassistant/components/edl21/sensor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Support for EDL21 Smart Meters."""

from collections.abc import Mapping
from datetime import timedelta
from typing import Any

from sml import SmlGetListResponse
Expand Down Expand Up @@ -29,7 +28,6 @@
async_dispatcher_send,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.dt import utcnow

from .const import (
CONF_SERIAL_PORT,
Expand All @@ -39,8 +37,6 @@
SIGNAL_EDL21_TELEGRAM,
)

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)

# OBIS format: A-B:C.D.E*F
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
# A=1: Electricity
Expand Down Expand Up @@ -391,8 +387,6 @@ def __init__(self, electricity_id, obis, entity_description, telegram):
self._electricity_id = electricity_id
self._obis = obis
self._telegram = telegram
self._min_time = MIN_TIME_BETWEEN_UPDATES
self._last_update = utcnow()
self._async_remove_dispatcher = None
self.entity_description = entity_description
self._attr_unique_id = f"{electricity_id}_{obis}"
Expand All @@ -414,12 +408,7 @@ def handle_telegram(electricity_id, telegram):
if self._telegram == telegram:
return

now = utcnow()
if now - self._last_update < self._min_time:
return

self._telegram = telegram
self._last_update = now
self.async_write_ha_state()

self._async_remove_dispatcher = async_dispatcher_connect(
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/edl21/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"step": {
"user": {
"data": {
"serial_port": "[%key:common::config_flow::data::usb_path%]"
"serial_port": "[%key:common::config_flow::data::port%]"
},
"data_description": {
"serial_port": "Serial port path to connect to"
},
"title": "Add your EDL21 smart meter"
}
Expand Down
72 changes: 15 additions & 57 deletions homeassistant/components/gardena_bluetooth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,14 @@

from bleak.backends.device import BLEDevice
from gardena_bluetooth.client import CachedConnection, Client
from gardena_bluetooth.const import AquaContour, DeviceConfiguration, DeviceInformation
from gardena_bluetooth.exceptions import (
CharacteristicNoAccess,
CharacteristicNotFound,
CommunicationFailure,
)
from gardena_bluetooth.parse import CharacteristicTime, ProductType
from gardena_bluetooth.const import ProductType
from gardena_bluetooth.scan import async_get_manufacturer_data

from homeassistant.components import bluetooth
from homeassistant.const import CONF_ADDRESS, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.util import dt as dt_util

from .const import DOMAIN
from .coordinator import (
DeviceUnavailable,
GardenaBluetoothConfigEntry,
Expand All @@ -39,7 +29,6 @@
Platform.VALVE,
]
LOGGER = logging.getLogger(__name__)
TIMEOUT = 20.0
DISCONNECT_DELAY = 5


Expand All @@ -57,73 +46,42 @@ def _device_lookup() -> BLEDevice:
return CachedConnection(DISCONNECT_DELAY, _device_lookup)


async def _update_timestamp(client: Client, characteristics: CharacteristicTime):
try:
await client.update_timestamp(characteristics, dt_util.now())
except CharacteristicNotFound:
pass
except CharacteristicNoAccess:
LOGGER.debug("No access to update internal time")


async def async_setup_entry(
hass: HomeAssistant, entry: GardenaBluetoothConfigEntry
) -> bool:
"""Set up Gardena Bluetooth from a config entry."""

address = entry.data[CONF_ADDRESS]

mfg_data = await async_get_manufacturer_data({address})
try:
mfg_data = await async_get_manufacturer_data({address})
except TimeoutError as exc:
raise ConfigEntryNotReady("Unable to find product type") from exc

product_type = mfg_data[address].product_type
if product_type is ProductType.UNKNOWN:
raise ConfigEntryNotReady("Unable to find product type")

client = Client(get_connection(hass, address), product_type)
try:
chars = await client.get_all_characteristics()

sw_version = await client.read_char(DeviceInformation.firmware_version, None)
manufacturer = await client.read_char(DeviceInformation.manufacturer_name, None)
model = await client.read_char(DeviceInformation.model_number, None)

name = entry.title
name = await client.read_char(DeviceConfiguration.custom_device_name, name)
name = await client.read_char(AquaContour.custom_device_name, name)

await _update_timestamp(client, DeviceConfiguration.unix_timestamp)
await _update_timestamp(client, AquaContour.unix_timestamp)

except (TimeoutError, CommunicationFailure, DeviceUnavailable) as exception:
await client.disconnect()
raise ConfigEntryNotReady(
f"Unable to connect to device {address} due to {exception}"
) from exception

device = DeviceInfo(
identifiers={(DOMAIN, address)},
connections={(dr.CONNECTION_BLUETOOTH, address)},
name=name,
sw_version=sw_version,
manufacturer=manufacturer,
model=model,
)

coordinator = GardenaBluetoothCoordinator(
hass, entry, LOGGER, client, set(chars.keys()), device, address
hass,
entry,
LOGGER,
client,
address,
)

await coordinator.async_config_entry_first_refresh()

entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await coordinator.async_refresh()

await coordinator.async_request_refresh()
return True


async def async_unload_entry(
hass: HomeAssistant, entry: GardenaBluetoothConfigEntry
) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await entry.runtime_data.async_shutdown()

return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
68 changes: 63 additions & 5 deletions homeassistant/components/gardena_bluetooth/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,28 @@
import logging

from gardena_bluetooth.client import Client
from gardena_bluetooth.const import AquaContour, DeviceConfiguration, DeviceInformation
from gardena_bluetooth.exceptions import (
CharacteristicNoAccess,
CharacteristicNotFound,
CommunicationFailure,
GardenaBluetoothException,
)
from gardena_bluetooth.parse import Characteristic, CharacteristicType
from gardena_bluetooth.parse import (
Characteristic,
CharacteristicTime,
CharacteristicType,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util

from .const import DOMAIN

SCAN_INTERVAL = timedelta(seconds=60)
LOGGER = logging.getLogger(__name__)
Expand All @@ -37,8 +48,6 @@ def __init__(
config_entry: GardenaBluetoothConfigEntry,
logger: logging.Logger,
client: Client,
characteristics: set[str],
device_info: DeviceInfo,
address: str,
) -> None:
"""Initialize global data updater."""
Expand All @@ -52,14 +61,63 @@ def __init__(
self.address = address
self.data = {}
self.client = client
self.characteristics = characteristics
self.device_info = device_info
self.characteristics: set[str] = set()
self.device_info = DeviceInfo(
identifiers={(DOMAIN, address)},
connections={(dr.CONNECTION_BLUETOOTH, address)},
name=config_entry.title,
)

async def async_shutdown(self) -> None:
"""Shutdown coordinator and any connection."""
await super().async_shutdown()
await self.client.disconnect()

async def _async_setup(self) -> None:
"""Set up the coordinator and read initial device metadata."""
try:
chars = await self.client.get_all_characteristics()

sw_version = await self.client.read_char(
DeviceInformation.firmware_version, None
)
manufacturer = await self.client.read_char(
DeviceInformation.manufacturer_name, None
)
model = await self.client.read_char(DeviceInformation.model_number, None)

name = self.config_entry.title
name = await self.client.read_char(
DeviceConfiguration.custom_device_name, name
)
name = await self.client.read_char(AquaContour.custom_device_name, name)

await self._update_timestamp(DeviceConfiguration.unix_timestamp)
await self._update_timestamp(AquaContour.unix_timestamp)

self.characteristics = set(chars.keys())
self.device_info = DeviceInfo(
{
**self.device_info,
"name": name,
"sw_version": sw_version,
"manufacturer": manufacturer,
"model": model,
}
)
except (TimeoutError, CommunicationFailure, DeviceUnavailable) as exception:
raise UpdateFailed(
f"Unable to set up Gardena Bluetooth device due to {exception}"
) from exception

async def _update_timestamp(self, char: CharacteristicTime) -> None:
try:
await self.client.update_timestamp(char, dt_util.now())
except CharacteristicNotFound:
pass
except CharacteristicNoAccess:
LOGGER.debug("No access to update internal time")

async def _async_update_data(self) -> dict[str, bytes]:
"""Poll the device."""
uuids: set[str] = {
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/habitica/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ async def async_step_reauth_confirm(
if not errors and login is not None:
await self.async_set_unique_id(str(login.id))
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
return self.async_update_and_abort(
reauth_entry,
data_updates={CONF_API_KEY: login.apiToken},
)
Expand All @@ -261,7 +261,7 @@ async def async_step_reauth_confirm(
}
)
if not errors and user is not None:
return self.async_update_reload_and_abort(
return self.async_update_and_abort(
reauth_entry, data_updates=user_input[SECTION_REAUTH_API_KEY]
)
else:
Expand Down Expand Up @@ -309,7 +309,7 @@ async def async_step_reconfigure(
}
)
if not errors and user is not None:
return self.async_update_reload_and_abort(
return self.async_update_and_abort(
reconf_entry,
data_updates={
CONF_API_KEY: user_input[CONF_API_KEY],
Expand Down
Loading
Loading