mirror of
https://github.com/home-assistant/core.git
synced 2025-12-31 20:28:41 +00:00
Compare commits
27 Commits
2026.1.0b0
...
rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42ea7ecbd6 | ||
|
|
d58d08c350 | ||
|
|
65a259b9df | ||
|
|
cbfbfbee13 | ||
|
|
e503b37ddc | ||
|
|
217eef39f3 | ||
|
|
dcdbce9b21 | ||
|
|
71db8fe185 | ||
|
|
9b96cb66d5 | ||
|
|
78bccbbbc2 | ||
|
|
b0a8f9575c | ||
|
|
61104a9970 | ||
|
|
8d13dbdd0c | ||
|
|
9afb41004e | ||
|
|
cdd542f6e6 | ||
|
|
f520686002 | ||
|
|
e4d09bb615 | ||
|
|
10f6ccf6cc | ||
|
|
d9fa67b16f | ||
|
|
cf228ae02b | ||
|
|
cb4d62ab9a | ||
|
|
d2f75aec04 | ||
|
|
a609fbc07b | ||
|
|
1b9c7ae0ac | ||
|
|
492f2117fb | ||
|
|
2346f83635 | ||
|
|
8925bfb182 |
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==10.0.0"]
|
||||
"requirements": ["aioamazondevices==11.0.2"]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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%]"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"update_failed": {
|
||||
"message": "Update of data from the web site failed: {error}"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["eternalegypt"],
|
||||
"requirements": ["eternalegypt==0.0.16"]
|
||||
"requirements": ["eternalegypt==0.0.18"]
|
||||
}
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -4,3 +4,5 @@ DOMAIN = "portainer"
|
||||
DEFAULT_NAME = "Portainer"
|
||||
|
||||
ENDPOINT_STATUS_DOWN = 2
|
||||
|
||||
CONTAINER_STATE_RUNNING = "running"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyportainer==1.0.17"]
|
||||
"requirements": ["pyportainer==1.0.19"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
14
requirements_all.txt
generated
@@ -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
|
||||
|
||||
14
requirements_test_all.txt
generated
14
requirements_test_all.txt
generated
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user