Compare commits

...

27 Commits

Author SHA1 Message Date
Franck Nijhof
42ea7ecbd6 Bump version to 2026.1.0b2 2025-12-31 15:34:05 +00:00
tronikos
d58d08c350 Filter out duplicate voices without language code in Google Cloud (#160046) 2025-12-31 15:33:49 +00:00
Paul Tarjan
65a259b9df Fix Hikvision thread safety issue when calling async_write_ha_state (#160027) 2025-12-31 15:33:48 +00:00
Luke Lashley
cbfbfbee13 Don't prefer cache for Roborock device fetching (#160022) 2025-12-31 15:33:47 +00:00
David Knowles
e503b37ddc Use WATER device_class for Hydrawise sensors (#160018) 2025-12-31 15:33:45 +00:00
Simone Chemelli
217eef39f3 Bump aioamazondevices to 11.0.2 (#160016)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-12-31 15:33:44 +00:00
Manu
dcdbce9b21 Convert store image URLs to https in Xbox media resolver (#160015) 2025-12-31 15:33:42 +00:00
Erwin Douna
71db8fe185 Bump portainer 1.0.19 (#160014) 2025-12-31 15:33:41 +00:00
Anders Melchiorsen
9b96cb66d5 Fix netgear_lte unloading (#160008) 2025-12-31 15:33:39 +00:00
Anders Melchiorsen
78bccbbbc2 Move async_setup_services to async_setup for netgear_lte (#160007) 2025-12-31 15:33:38 +00:00
Anders Melchiorsen
b0a8f9575c Bump eternalegypt to 0.0.18 (#160006) 2025-12-31 15:33:36 +00:00
Matthias Alphart
61104a9970 Update knx-frontend to 2025.12.30.151231 (#159999) 2025-12-31 15:33:35 +00:00
Franck Nijhof
8d13dbdd0c Bump version to 2026.1.0b1 2025-12-30 09:14:36 +00:00
Erwin Douna
9afb41004e Portainer fix stopped container for stats (#159964) 2025-12-30 09:14:24 +00:00
Luke Lashley
cdd542f6e6 Bump Python-Roborock to 4.1.0 (#159963) 2025-12-30 09:14:22 +00:00
Joost Lekkerkerker
f520686002 Small cleanup in Feedreader (#159962) 2025-12-30 09:14:20 +00:00
J. Nick Koston
e4d09bb615 Bump aioesphomeapi to 43.9.1 (#159960) 2025-12-30 09:14:19 +00:00
Matthias Alphart
10f6ccf6cc Fix KNX translation references (#159959) 2025-12-30 09:14:17 +00:00
Ernst Klamer
d9fa67b16f bump xiaomi-ble to 1.4.1 (#159954) 2025-12-30 09:14:15 +00:00
Joost Lekkerkerker
cf228ae02b Inject session in Switchbot cloud (#159942) 2025-12-30 09:14:14 +00:00
Joost Lekkerkerker
cb4d62ab9a Add integration_type device to ps4 (#159892) 2025-12-30 09:14:12 +00:00
Joost Lekkerkerker
d2f75aec04 Add integration_type hub to poolsense (#159881) 2025-12-30 09:14:11 +00:00
Joost Lekkerkerker
a609fbc07b Add integration_type hub to pooldose (#159880) 2025-12-30 09:14:09 +00:00
Joost Lekkerkerker
1b9c7ae0ac Add integration_type hub to permobil (#159872) 2025-12-30 09:14:07 +00:00
Joost Lekkerkerker
492f2117fb Add integration_type service to nuheat (#159845) 2025-12-30 09:14:06 +00:00
Joost Lekkerkerker
2346f83635 Add integration_type device to netgear (#159816) 2025-12-30 09:14:04 +00:00
Kamil Breguła
8925bfb182 Add translation of exceptions in met (#155765)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-12-30 09:12:18 +00:00
45 changed files with 146 additions and 103 deletions

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==10.0.0"]
"requirements": ["aioamazondevices==11.0.2"]
}

View File

@@ -17,7 +17,7 @@
"mqtt": ["esphome/discover/#"],
"quality_scale": "platinum",
"requirements": [
"aioesphomeapi==43.9.0",
"aioesphomeapi==43.9.1",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==3.4.0"
],

View File

@@ -9,14 +9,12 @@ from homeassistant.util.hass_dict import HassKey
from .const import DOMAIN
from .coordinator import FeedReaderConfigEntry, FeedReaderCoordinator, StoredData
CONF_URLS = "urls"
MY_KEY: HassKey[StoredData] = HassKey(DOMAIN)
FEEDREADER_KEY: HassKey[StoredData] = HassKey(DOMAIN)
async def async_setup_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry) -> bool:
"""Set up Feedreader from a config entry."""
storage = hass.data.setdefault(MY_KEY, StoredData(hass))
storage = hass.data.setdefault(FEEDREADER_KEY, StoredData(hass))
if not storage.is_initialized:
await storage.async_setup()
@@ -42,5 +40,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry)
)
# if this is the last entry, remove the storage
if len(entries) == 1:
hass.data.pop(MY_KEY)
hass.data.pop(FEEDREADER_KEY)
return await hass.config_entries.async_unload_platforms(entry, [Platform.EVENT])

View File

@@ -42,16 +42,15 @@ class FeedReaderEvent(CoordinatorEntity[FeedReaderCoordinator], EventEntity):
_attr_event_types = [EVENT_FEEDREADER]
_attr_name = None
_attr_has_entity_name = True
_attr_translation_key = "latest_feed"
_unrecorded_attributes = frozenset(
{ATTR_CONTENT, ATTR_DESCRIPTION, ATTR_TITLE, ATTR_LINK}
)
coordinator: FeedReaderCoordinator
def __init__(self, coordinator: FeedReaderCoordinator) -> None:
"""Initialize the feedreader event."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_latest_feed"
self._attr_translation_key = "latest_feed"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
name=coordinator.config_entry.title,

View File

@@ -21,12 +21,6 @@
}
}
},
"issues": {
"import_yaml_error_url_error": {
"description": "Configuring the Feedreader using YAML is being removed but there was a connection error when trying to import the YAML configuration for `{url}`.\n\nPlease verify that the URL is reachable and accessible for Home Assistant and restart Home Assistant to try again or remove the Feedreader YAML configuration from your configuration.yaml file and continue to set up the integration manually.",
"title": "The Feedreader YAML configuration import failed"
}
},
"options": {
"step": {
"init": {

View File

@@ -48,6 +48,8 @@ async def async_tts_voices(
list_voices_response = await client.list_voices()
for voice in list_voices_response.voices:
language_code = voice.language_codes[0]
if not voice.name.startswith(language_code):
continue
if language_code not in voices:
voices[language_code] = []
voices[language_code].append(voice.name)

View File

@@ -24,7 +24,7 @@ from homeassistant.const import (
CONF_SSL,
CONF_USERNAME,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.device_registry import DeviceInfo
@@ -227,7 +227,10 @@ class HikvisionBinarySensor(BinarySensorEntity):
# Register callback with pyhik
self._camera.add_update_callback(self._update_callback, self._callback_id)
@callback
def _update_callback(self, msg: str) -> None:
"""Update the sensor's state when callback is triggered."""
self.async_write_ha_state()
"""Update the sensor's state when callback is triggered.
This is called from pyhik's event stream thread, so we use
schedule_update_ha_state which is thread-safe.
"""
self.schedule_update_ha_state()

View File

@@ -67,21 +67,21 @@ FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = (
HydrawiseSensorEntityDescription(
key="daily_total_water_use",
translation_key="daily_total_water_use",
device_class=SensorDeviceClass.VOLUME,
device_class=SensorDeviceClass.WATER,
suggested_display_precision=1,
value_fn=lambda sensor: _get_water_use(sensor).total_use,
),
HydrawiseSensorEntityDescription(
key="daily_active_water_use",
translation_key="daily_active_water_use",
device_class=SensorDeviceClass.VOLUME,
device_class=SensorDeviceClass.WATER,
suggested_display_precision=1,
value_fn=lambda sensor: _get_water_use(sensor).total_active_use,
),
HydrawiseSensorEntityDescription(
key="daily_inactive_water_use",
translation_key="daily_inactive_water_use",
device_class=SensorDeviceClass.VOLUME,
device_class=SensorDeviceClass.WATER,
suggested_display_precision=1,
value_fn=lambda sensor: _get_water_use(sensor).total_inactive_use,
),
@@ -91,7 +91,7 @@ FLOW_ZONE_SENSORS: tuple[SensorEntityDescription, ...] = (
HydrawiseSensorEntityDescription(
key="daily_active_water_use",
translation_key="daily_active_water_use",
device_class=SensorDeviceClass.VOLUME,
device_class=SensorDeviceClass.WATER,
suggested_display_precision=1,
value_fn=lambda sensor: float(
_get_water_use(sensor).active_use_by_zone_id.get(sensor.zone.id, 0.0)
@@ -204,7 +204,7 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity):
@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit_of_measurement of the sensor."""
if self.entity_description.device_class != SensorDeviceClass.VOLUME:
if self.entity_description.device_class != SensorDeviceClass.WATER:
return self.entity_description.native_unit_of_measurement
return (
UnitOfVolume.GALLONS
@@ -217,7 +217,7 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity):
"""Icon of the entity based on the value."""
if (
self.entity_description.key in FLOW_MEASUREMENT_KEYS
and self.entity_description.device_class == SensorDeviceClass.VOLUME
and self.entity_description.device_class == SensorDeviceClass.WATER
and round(self.state, 2) == 0.0
):
return "mdi:water-outline"

View File

@@ -13,7 +13,7 @@
"requirements": [
"xknx==3.13.0",
"xknxproject==3.8.2",
"knx-frontend==2025.12.28.215221"
"knx-frontend==2025.12.30.151231"
],
"single_config_entry": true
}

View File

@@ -154,6 +154,27 @@
}
},
"config_panel": {
"dashboard": {
"connection_flow": {
"description": "Reconfigure KNX connection or import a new KNX keyring file",
"title": "Connection settings"
},
"options_flow": {
"description": "Configure integration settings",
"title": "Integration options"
},
"project_upload": {
"description": "Import a KNX project file to help configure group addresses and datapoint types",
"title": "[%key:component::knx::config_panel::dialogs::project_upload::title%]"
}
},
"dialogs": {
"project_upload": {
"description": "Details such as group address names, datapoint types, devices and group objects are extracted from your project file. The ETS project file itself and its optional password are not stored.\n\n`.knxproj` files exported by ETS 4, 5 or 6 are supported.",
"file_upload_label": "ETS project file",
"title": "Import ETS project"
}
},
"dpt": {
"options": {
"5": "Generic 1-byte unsigned integer",
@@ -845,9 +866,9 @@
},
"mode": {
"description": "Select how the entity is displayed in Home Assistant.",
"label": "[%common::config_flow::data::mode%]",
"label": "[%key:common::config_flow::data::mode%]",
"options": {
"password": "[%common::config_flow::data::password%]",
"password": "[%key:common::config_flow::data::password%]",
"text": "[%key:component::text::entity_component::_::state_attributes::mode::state::text%]"
}
}

View File

@@ -80,8 +80,6 @@ async def register_panel(hass: HomeAssistant) -> None:
hass=hass,
frontend_url_path=DOMAIN,
webcomponent_name=knx_panel.webcomponent_name,
sidebar_title=DOMAIN.upper(),
sidebar_icon="mdi:bus-electric",
module_url=f"{URL_BASE}/{knx_panel.entrypoint_js}",
embed_iframe=True,
require_admin=True,

View File

@@ -116,8 +116,12 @@ class MetDataUpdateCoordinator(DataUpdateCoordinator[MetWeatherData]):
"""Fetch data from Met."""
try:
return await self.weather.fetch_data()
except Exception as err:
raise UpdateFailed(f"Update failed: {err}") from err
except CannotConnect as err:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_failed",
translation_placeholders={"error": str(err)},
) from err
def track_home(self) -> None:
"""Start tracking changes to HA home setting."""

View File

@@ -19,6 +19,11 @@
}
}
},
"exceptions": {
"update_failed": {
"message": "Update of data from the web site failed: {error}"
}
},
"options": {
"step": {
"init": {

View File

@@ -4,6 +4,7 @@
"codeowners": ["@hacf-fr", "@Quentame", "@starkillerOG"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/netgear",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["pynetgear"],
"requirements": ["pynetgear==0.10.10"],

View File

@@ -51,7 +51,6 @@ ALL_BINARY_SENSORS = [
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.NOTIFY,
Platform.SENSOR,
]
@@ -61,6 +60,7 @@ CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Netgear LTE component."""
hass.data[DATA_HASS_CONFIG] = config
async_setup_services(hass)
return True
@@ -96,19 +96,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: NetgearLTEConfigEntry) -
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
async_setup_services(hass)
await discovery.async_load_platform(
hass,
Platform.NOTIFY,
DOMAIN,
{CONF_NAME: entry.title, "modem": modem},
{CONF_NAME: entry.title, "modem": modem, "entry": entry},
hass.data[DATA_HASS_CONFIG],
)
await hass.config_entries.async_forward_entry_setups(
entry, [platform for platform in PLATFORMS if platform != Platform.NOTIFY]
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@@ -118,7 +114,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: NetgearLTEConfigEntry)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if not hass.config_entries.async_loaded_entries(DOMAIN):
hass.data.pop(DOMAIN, None)
for service_name in hass.services.async_services()[DOMAIN]:
hass.services.async_remove(DOMAIN, service_name)
return unload_ok

View File

@@ -14,7 +14,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import DEFAULT_HOST, DOMAIN, LOGGER, MANUFACTURER
from .const import DEFAULT_HOST, DOMAIN, MANUFACTURER
class NetgearLTEFlowHandler(ConfigFlow, domain=DOMAIN):
@@ -72,9 +72,6 @@ class NetgearLTEFlowHandler(ConfigFlow, domain=DOMAIN):
info = await modem.information()
except Error as ex:
raise InputValidationError("cannot_connect") from ex
except Exception as ex:
LOGGER.exception("Unexpected exception")
raise InputValidationError("unknown") from ex
await modem.logout()
return info

View File

@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["eternalegypt"],
"requirements": ["eternalegypt==0.0.16"]
"requirements": ["eternalegypt==0.0.18"]
}

View File

@@ -38,6 +38,7 @@ class NetgearNotifyService(BaseNotificationService):
"""Initialize the service."""
self.config = config
self.modem: Modem = discovery_info["modem"]
discovery_info["entry"].async_on_unload(self.async_unregister_services)
async def async_send_message(self, message="", **kwargs):
"""Send a message to a user."""

View File

@@ -4,6 +4,7 @@ import voluptuous as vol
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from .const import (
@@ -14,7 +15,6 @@ from .const import (
AUTOCONNECT_MODES,
DOMAIN,
FAILOVER_MODES,
LOGGER,
)
from .coordinator import NetgearLTEConfigEntry
@@ -56,8 +56,11 @@ async def _service_handler(call: ServiceCall) -> None:
break
if not entry or not (modem := entry.runtime_data.modem).token:
LOGGER.error("%s: host %s unavailable", call.service, host)
return
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="config_entry_not_found",
translation_placeholders={"service": call.service},
)
if call.service == SERVICE_DELETE_SMS:
for sms_id in call.data[ATTR_SMS_ID]:

View File

@@ -71,6 +71,11 @@
}
}
},
"exceptions": {
"config_entry_not_found": {
"message": "Failed to perform action \"{service}\". Config entry for target not found"
}
},
"services": {
"connect_lte": {
"description": "Asks the modem to establish the LTE connection.",

View File

@@ -10,6 +10,7 @@
}
],
"documentation": "https://www.home-assistant.io/integrations/nuheat",
"integration_type": "device",
"iot_class": "cloud_polling",
"loggers": ["nuheat"],
"requirements": ["nuheat==1.0.1"]

View File

@@ -4,6 +4,7 @@
"codeowners": ["@IsakNyberg"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/permobil",
"integration_type": "device",
"iot_class": "cloud_polling",
"requirements": ["mypermobil==0.1.8"]
}

View File

@@ -9,6 +9,7 @@
}
],
"documentation": "https://www.home-assistant.io/integrations/pooldose",
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["python-pooldose==0.8.1"]

View File

@@ -4,6 +4,7 @@
"codeowners": ["@haemishkyd"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/poolsense",
"integration_type": "device",
"iot_class": "cloud_polling",
"loggers": ["poolsense"],
"requirements": ["poolsense==0.0.8"]

View File

@@ -15,6 +15,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import PortainerConfigEntry
from .const import CONTAINER_STATE_RUNNING
from .coordinator import PortainerContainerData, PortainerCoordinator
from .entity import (
PortainerContainerEntity,
@@ -41,7 +42,7 @@ CONTAINER_SENSORS: tuple[PortainerContainerBinarySensorEntityDescription, ...] =
PortainerContainerBinarySensorEntityDescription(
key="status",
translation_key="status",
state_fn=lambda data: data.container.state == "running",
state_fn=lambda data: data.container.state == CONTAINER_STATE_RUNNING,
device_class=BinarySensorDeviceClass.RUNNING,
entity_category=EntityCategory.DIAGNOSTIC,
),

View File

@@ -4,3 +4,5 @@ DOMAIN = "portainer"
DEFAULT_NAME = "Portainer"
ENDPOINT_STATUS_DOWN = 2
CONTAINER_STATE_RUNNING = "running"

View File

@@ -24,7 +24,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, ENDPOINT_STATUS_DOWN
from .const import CONTAINER_STATE_RUNNING, DOMAIN, ENDPOINT_STATUS_DOWN
type PortainerConfigEntry = ConfigEntry[PortainerCoordinator]
@@ -158,10 +158,11 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
),
)
for container in containers
if container.state == CONTAINER_STATE_RUNNING
]
container_stats_gather = await asyncio.gather(
*[task for _, task in container_stats_task],
*[task for _, task in container_stats_task]
)
for (container, _), container_stats in zip(
container_stats_task, container_stats_gather, strict=False

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["pyportainer==1.0.17"]
"requirements": ["pyportainer==1.0.19"]
}

View File

@@ -4,6 +4,7 @@
"codeowners": ["@ktnrg45"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ps4",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["pyps4_2ndscreen"],
"requirements": ["pyps4-2ndscreen==1.3.1"]

View File

@@ -79,6 +79,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
map_scale=MAP_SCALE,
),
mqtt_session_unauthorized_hook=lambda: entry.async_start_reauth(hass),
prefer_cache=False,
)
except RoborockInvalidCredentials as err:
raise ConfigEntryAuthFailed(

View File

@@ -20,7 +20,7 @@
"loggers": ["roborock"],
"quality_scale": "silver",
"requirements": [
"python-roborock==3.21.0",
"python-roborock==4.1.0",
"vacuum-map-parser-roborock==0.1.4"
]
}

View File

@@ -20,6 +20,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN, CONF_WEBHOOK_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN, ENTRY_TITLE
from .coordinator import SwitchBotCoordinator
@@ -309,7 +310,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
token = entry.data[CONF_API_TOKEN]
secret = entry.data[CONF_API_KEY]
api = SwitchBotAPI(token=token, secret=secret)
api = SwitchBotAPI(
token=token, secret=secret, session=async_get_clientsession(hass)
)
try:
devices = await api.list_devices()
except SwitchBotAuthenticationError as ex:

View File

@@ -209,7 +209,7 @@ class XboxSource(MediaSource):
if images is not None:
try:
return PlayMedia(
images[int(identifier.media_id)].url,
to_https(images[int(identifier.media_id)].url),
MIME_TYPE_MAP[ATTR_SCREENSHOTS],
)
except (ValueError, IndexError):

View File

@@ -25,5 +25,5 @@
"documentation": "https://www.home-assistant.io/integrations/xiaomi_ble",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["xiaomi-ble==1.2.0"]
"requirements": ["xiaomi-ble==1.4.1"]
}

View File

@@ -17,7 +17,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2026
MINOR_VERSION: Final = 1
PATCH_VERSION: Final = "0b0"
PATCH_VERSION: Final = "0b2"
__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

@@ -4584,7 +4584,7 @@
},
"nuheat": {
"name": "NuHeat",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "cloud_polling"
},
@@ -5014,7 +5014,7 @@
},
"permobil": {
"name": "MyPermobil",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "cloud_polling"
},
@@ -5152,13 +5152,13 @@
},
"pooldose": {
"name": "SEKO PoolDose",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
"poolsense": {
"name": "PoolSense",
"integration_type": "hub",
"integration_type": "device",
"config_flow": true,
"iot_class": "cloud_polling"
},

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2026.1.0b0"
version = "2026.1.0b2"
license = "Apache-2.0"
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
description = "Open-source home automation platform running on Python 3."

14
requirements_all.txt generated
View File

@@ -190,7 +190,7 @@ aioairzone-cloud==0.7.2
aioairzone==1.0.4
# homeassistant.components.alexa_devices
aioamazondevices==10.0.0
aioamazondevices==11.0.2
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@@ -252,7 +252,7 @@ aioelectricitymaps==1.1.1
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==43.9.0
aioesphomeapi==43.9.1
# homeassistant.components.matrix
# homeassistant.components.slack
@@ -929,7 +929,7 @@ esphome-dashboard-api==1.3.0
essent-dynamic-pricing==0.2.7
# homeassistant.components.netgear_lte
eternalegypt==0.0.16
eternalegypt==0.0.18
# homeassistant.components.eufylife_ble
eufylife-ble-client==0.1.8
@@ -1349,7 +1349,7 @@ kiwiki-client==0.1.1
knocki==0.4.2
# homeassistant.components.knx
knx-frontend==2025.12.28.215221
knx-frontend==2025.12.30.151231
# homeassistant.components.konnected
konnected==1.2.0
@@ -2318,7 +2318,7 @@ pyplaato==0.0.19
pypoint==3.0.0
# homeassistant.components.portainer
pyportainer==1.0.17
pyportainer==1.0.19
# homeassistant.components.probe_plus
pyprobeplus==1.1.2
@@ -2581,7 +2581,7 @@ python-rabbitair==0.0.8
python-ripple-api==0.0.3
# homeassistant.components.roborock
python-roborock==3.21.0
python-roborock==4.1.0
# homeassistant.components.smarttub
python-smarttub==0.0.46
@@ -3213,7 +3213,7 @@ wsdot==0.0.1
wyoming==1.7.2
# homeassistant.components.xiaomi_ble
xiaomi-ble==1.2.0
xiaomi-ble==1.4.1
# homeassistant.components.knx
xknx==3.13.0

View File

@@ -181,7 +181,7 @@ aioairzone-cloud==0.7.2
aioairzone==1.0.4
# homeassistant.components.alexa_devices
aioamazondevices==10.0.0
aioamazondevices==11.0.2
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@@ -243,7 +243,7 @@ aioelectricitymaps==1.1.1
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==43.9.0
aioesphomeapi==43.9.1
# homeassistant.components.matrix
# homeassistant.components.slack
@@ -820,7 +820,7 @@ esphome-dashboard-api==1.3.0
essent-dynamic-pricing==0.2.7
# homeassistant.components.netgear_lte
eternalegypt==0.0.16
eternalegypt==0.0.18
# homeassistant.components.eufylife_ble
eufylife-ble-client==0.1.8
@@ -1183,7 +1183,7 @@ kegtron-ble==1.0.2
knocki==0.4.2
# homeassistant.components.knx
knx-frontend==2025.12.28.215221
knx-frontend==2025.12.30.151231
# homeassistant.components.konnected
konnected==1.2.0
@@ -1959,7 +1959,7 @@ pyplaato==0.0.19
pypoint==3.0.0
# homeassistant.components.portainer
pyportainer==1.0.17
pyportainer==1.0.19
# homeassistant.components.probe_plus
pyprobeplus==1.1.2
@@ -2165,7 +2165,7 @@ python-pooldose==0.8.1
python-rabbitair==0.0.8
# homeassistant.components.roborock
python-roborock==3.21.0
python-roborock==4.1.0
# homeassistant.components.smarttub
python-smarttub==0.0.46
@@ -2683,7 +2683,7 @@ wsdot==0.0.1
wyoming==1.7.2
# homeassistant.components.xiaomi_ble
xiaomi-ble==1.2.0
xiaomi-ble==1.4.1
# homeassistant.components.knx
xknx==3.13.0

View File

@@ -81,6 +81,7 @@ def mock_api_tts() -> AsyncMock:
voices=[
cloud_tts.Voice(language_codes=["en-US"], name="en-US-Standard-A"),
cloud_tts.Voice(language_codes=["en-US"], name="en-US-Standard-B"),
cloud_tts.Voice(language_codes=["en-US"], name="Standard-A"),
cloud_tts.Voice(language_codes=["el-GR"], name="el-GR-Standard-A"),
]
)

View File

@@ -294,6 +294,10 @@ async def test_binary_sensor_update_callback(
callback_func = add_callback_call[0][0]
callback_func("motion detected")
# Wait for the event loop to process the scheduled state update
# (callback uses call_soon_threadsafe to schedule update in event loop)
await hass.async_block_till_done()
# Verify state was updated
state = hass.states.get("binary_sensor.front_camera_motion")
assert state is not None

View File

@@ -28,7 +28,7 @@
'suggested_unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
}),
'original_device_class': <SensorDeviceClass.VOLUME: 'volume'>,
'original_device_class': <SensorDeviceClass.WATER: 'water'>,
'original_icon': None,
'original_name': 'Daily active water use',
'platform': 'hydrawise',
@@ -44,7 +44,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by hydrawise.com',
'device_class': 'volume',
'device_class': 'water',
'friendly_name': 'Home Controller Daily active water use',
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
@@ -139,7 +139,7 @@
'suggested_unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
}),
'original_device_class': <SensorDeviceClass.VOLUME: 'volume'>,
'original_device_class': <SensorDeviceClass.WATER: 'water'>,
'original_icon': None,
'original_name': 'Daily inactive water use',
'platform': 'hydrawise',
@@ -155,7 +155,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by hydrawise.com',
'device_class': 'volume',
'device_class': 'water',
'friendly_name': 'Home Controller Daily inactive water use',
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
@@ -196,7 +196,7 @@
'suggested_unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
}),
'original_device_class': <SensorDeviceClass.VOLUME: 'volume'>,
'original_device_class': <SensorDeviceClass.WATER: 'water'>,
'original_icon': None,
'original_name': 'Daily total water use',
'platform': 'hydrawise',
@@ -212,7 +212,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by hydrawise.com',
'device_class': 'volume',
'device_class': 'water',
'friendly_name': 'Home Controller Daily total water use',
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
@@ -253,7 +253,7 @@
'suggested_unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
}),
'original_device_class': <SensorDeviceClass.VOLUME: 'volume'>,
'original_device_class': <SensorDeviceClass.WATER: 'water'>,
'original_icon': None,
'original_name': 'Daily active water use',
'platform': 'hydrawise',
@@ -269,7 +269,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by hydrawise.com',
'device_class': 'volume',
'device_class': 'water',
'friendly_name': 'Zone One Daily active water use',
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
@@ -464,7 +464,7 @@
'suggested_unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
}),
'original_device_class': <SensorDeviceClass.VOLUME: 'volume'>,
'original_device_class': <SensorDeviceClass.WATER: 'water'>,
'original_icon': 'mdi:water-outline',
'original_name': 'Daily active water use',
'platform': 'hydrawise',
@@ -480,7 +480,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by hydrawise.com',
'device_class': 'volume',
'device_class': 'water',
'friendly_name': 'Zone Two Daily active water use',
'icon': 'mdi:water-outline',
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,

View File

@@ -65,18 +65,3 @@ async def test_flow_user_cannot_connect(
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == "cannot_connect"
async def test_flow_user_unknown_error(hass: HomeAssistant, unknown: None) -> None:
"""Test unknown error."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONF_DATA,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == "unknown"

View File

@@ -2,9 +2,12 @@
from unittest.mock import patch
import pytest
from homeassistant.components.netgear_lte.const import DOMAIN
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from .conftest import HOST
@@ -54,3 +57,15 @@ async def test_set_option(hass: HomeAssistant, setup_integration: None) -> None:
blocking=True,
)
assert len(mock_client.mock_calls) == 1
entry = hass.config_entries.async_entries(DOMAIN)[0]
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
"delete_sms",
{CONF_HOST: "no-match", "sms_id": 1},
blocking=True,
)

View File

@@ -275,7 +275,7 @@ async def test_browse_media_account_not_configured_exception(
),
(
"/271958441785640/1297287135/game_media/0",
"http://store-images.s-microsoft.com/image/apps.35725.65457035095819016.56f55216-1bb9-40aa-8796-068cf3075fc1.c4bf34f8-ad40-4af3-914e-a85e75a76bed",
"https://store-images.s-microsoft.com/image/apps.35725.65457035095819016.56f55216-1bb9-40aa-8796-068cf3075fc1.c4bf34f8-ad40-4af3-914e-a85e75a76bed",
"image/png",
),
],