diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b52cced901b3e8..aaefd47a5536e3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ env: MYPY_CACHE_VERSION: 1 HA_SHORT_VERSION: "2025.11" DEFAULT_PYTHON: "3.13" - ALL_PYTHON_VERSIONS: "['3.13']" + ALL_PYTHON_VERSIONS: "['3.13', '3.14']" # 10.3 is the oldest supported version # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) # 10.6 is the current long-term-support diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index b911b3cec998f2..7c0004d44c52e9 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -5,14 +5,9 @@ import asyncio import logging from random import randrange +import sys from typing import Any, cast -from pyatv import connect, exceptions, scan -from pyatv.conf import AppleTV -from pyatv.const import DeviceModel, Protocol -from pyatv.convert import model_str -from pyatv.interface import AppleTV as AppleTVInterface, DeviceListener - from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -29,7 +24,11 @@ Platform, ) from homeassistant.core import Event, HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ( + ConfigEntryAuthFailed, + ConfigEntryNotReady, + HomeAssistantError, +) from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -43,6 +42,18 @@ SIGNAL_DISCONNECTED, ) +if sys.version_info < (3, 14): + from pyatv import connect, exceptions, scan + from pyatv.conf import AppleTV + from pyatv.const import DeviceModel, Protocol + from pyatv.convert import model_str + from pyatv.interface import AppleTV as AppleTVInterface, DeviceListener +else: + + class DeviceListener: + """Dummy class.""" + + _LOGGER = logging.getLogger(__name__) DEFAULT_NAME_TV = "Apple TV" @@ -53,31 +64,41 @@ PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE] -AUTH_EXCEPTIONS = ( - exceptions.AuthenticationError, - exceptions.InvalidCredentialsError, - exceptions.NoCredentialsError, -) -CONNECTION_TIMEOUT_EXCEPTIONS = ( - OSError, - asyncio.CancelledError, - TimeoutError, - exceptions.ConnectionLostError, - exceptions.ConnectionFailedError, -) -DEVICE_EXCEPTIONS = ( - exceptions.ProtocolError, - exceptions.NoServiceError, - exceptions.PairingError, - exceptions.BackOffError, - exceptions.DeviceIdMissingError, -) +if sys.version_info < (3, 14): + AUTH_EXCEPTIONS = ( + exceptions.AuthenticationError, + exceptions.InvalidCredentialsError, + exceptions.NoCredentialsError, + ) + CONNECTION_TIMEOUT_EXCEPTIONS = ( + OSError, + asyncio.CancelledError, + TimeoutError, + exceptions.ConnectionLostError, + exceptions.ConnectionFailedError, + ) + DEVICE_EXCEPTIONS = ( + exceptions.ProtocolError, + exceptions.NoServiceError, + exceptions.PairingError, + exceptions.BackOffError, + exceptions.DeviceIdMissingError, + ) +else: + AUTH_EXCEPTIONS = () + CONNECTION_TIMEOUT_EXCEPTIONS = () + DEVICE_EXCEPTIONS = () + type AppleTvConfigEntry = ConfigEntry[AppleTVManager] async def async_setup_entry(hass: HomeAssistant, entry: AppleTvConfigEntry) -> bool: """Set up a config entry for Apple TV.""" + if sys.version_info >= (3, 14): + raise HomeAssistantError( + "Apple TV is not supported on Python 3.14. Please use Python 3.13." + ) manager = AppleTVManager(hass, entry) if manager.is_on: diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index fe500d2bfb0813..ac618221526f8c 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -7,7 +7,7 @@ "documentation": "https://www.home-assistant.io/integrations/apple_tv", "iot_class": "local_push", "loggers": ["pyatv", "srptools"], - "requirements": ["pyatv==0.16.1"], + "requirements": ["pyatv==0.16.1;python_version<'3.14'"], "zeroconf": [ "_mediaremotetv._tcp.local.", "_companion-link._tcp.local.", diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index 5198c98db1eac3..92763d57d92c74 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -196,7 +196,7 @@ def __init__( self._attr_name = name if name is not None else f"{source_entity} derivative" self._attr_extra_state_attributes = {ATTR_SOURCE_ID: source_entity} - + self._unit_template: str | None = None if unit_of_measurement is None: final_unit_prefix = "" if unit_prefix is None else unit_prefix self._unit_template = f"{final_unit_prefix}{{}}/{unit_time}" @@ -217,6 +217,23 @@ def __init__( lambda *args: None ) + def _derive_and_set_attributes_from_state(self, source_state: State | None) -> None: + if self._unit_template and source_state: + original_unit = self._attr_native_unit_of_measurement + source_unit = source_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + self._attr_native_unit_of_measurement = self._unit_template.format( + "" if source_unit is None else source_unit + ) + if original_unit != self._attr_native_unit_of_measurement: + _LOGGER.debug( + "%s: Derivative sensor switched UoM from %s to %s, resetting state to 0", + self.entity_id, + original_unit, + self._attr_native_unit_of_measurement, + ) + self._state_list = [] + self._attr_native_value = round(Decimal(0), self._round_digits) + def _calc_derivative_from_state_list(self, current_time: datetime) -> Decimal: def calculate_weight(start: datetime, end: datetime, now: datetime) -> float: window_start = now - timedelta(seconds=self._time_window) @@ -285,6 +302,9 @@ async def async_added_to_hass(self) -> None: except (InvalidOperation, TypeError): self._attr_native_value = None + source_state = self.hass.states.get(self._sensor_source_id) + self._derive_and_set_attributes_from_state(source_state) + def schedule_max_sub_interval_exceeded(source_state: State | None) -> None: """Schedule calculation using the source state and max_sub_interval. @@ -358,10 +378,18 @@ def on_state_changed(event: Event[EventStateChangedData]) -> None: _LOGGER.debug("%s: New state changed event: %s", self.entity_id, event.data) self._cancel_max_sub_interval_exceeded_callback() new_state = event.data["new_state"] + if not self._handle_invalid_source_state(new_state): return assert new_state + + original_unit = self._attr_native_unit_of_measurement + self._derive_and_set_attributes_from_state(new_state) + if original_unit != self._attr_native_unit_of_measurement: + self.async_write_ha_state() + return + schedule_max_sub_interval_exceeded(new_state) old_state = event.data["old_state"] if old_state is not None: @@ -391,12 +419,6 @@ def calc_derivative( self.async_write_ha_state() return - if self.native_unit_of_measurement is None: - unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - self._attr_native_unit_of_measurement = self._unit_template.format( - "" if unit is None else unit - ) - self._prune_state_list(new_timestamp) try: diff --git a/homeassistant/components/firefly_iii/entity.py b/homeassistant/components/firefly_iii/entity.py index 0281065a6e70ec..28003b661ae1fe 100644 --- a/homeassistant/components/firefly_iii/entity.py +++ b/homeassistant/components/firefly_iii/entity.py @@ -2,14 +2,14 @@ from __future__ import annotations +from pyfirefly.models import Account, Category from yarl import URL from homeassistant.const import CONF_URL from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo -from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, MANUFACTURER +from .const import DOMAIN, MANUFACTURER, NAME from .coordinator import FireflyDataUpdateCoordinator @@ -21,20 +21,65 @@ class FireflyBaseEntity(CoordinatorEntity[FireflyDataUpdateCoordinator]): def __init__( self, coordinator: FireflyDataUpdateCoordinator, - entity_description: EntityDescription, ) -> None: """Initialize a Firefly entity.""" super().__init__(coordinator) - - self.entity_description = entity_description self._attr_device_info = DeviceInfo( entry_type=DeviceEntryType.SERVICE, manufacturer=MANUFACTURER, + name=NAME, configuration_url=URL(coordinator.config_entry.data[CONF_URL]), + identifiers={(DOMAIN, f"{coordinator.config_entry.entry_id}_service")}, + ) + + +class FireflyAccountBaseEntity(FireflyBaseEntity): + """Base class for Firefly III account entity.""" + + def __init__( + self, + coordinator: FireflyDataUpdateCoordinator, + account: Account, + key: str, + ) -> None: + """Initialize a Firefly account entity.""" + super().__init__(coordinator) + self._account = account + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + manufacturer=MANUFACTURER, + name=account.attributes.name, + configuration_url=f"{URL(coordinator.config_entry.data[CONF_URL])}/accounts/show/{account.id}", identifiers={ - ( - DOMAIN, - f"{coordinator.config_entry.entry_id}_{self.entity_description.key}", - ) + (DOMAIN, f"{coordinator.config_entry.entry_id}_account_{account.id}") }, ) + self._attr_unique_id = ( + f"{coordinator.config_entry.unique_id}_account_{account.id}_{key}" + ) + + +class FireflyCategoryBaseEntity(FireflyBaseEntity): + """Base class for Firefly III category entity.""" + + def __init__( + self, + coordinator: FireflyDataUpdateCoordinator, + category: Category, + key: str, + ) -> None: + """Initialize a Firefly category entity.""" + super().__init__(coordinator) + self._category = category + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + manufacturer=MANUFACTURER, + name=category.attributes.name, + configuration_url=f"{URL(coordinator.config_entry.data[CONF_URL])}/categories/show/{category.id}", + identifiers={ + (DOMAIN, f"{coordinator.config_entry.entry_id}_category_{category.id}") + }, + ) + self._attr_unique_id = ( + f"{coordinator.config_entry.unique_id}_category_{category.id}_{key}" + ) diff --git a/homeassistant/components/firefly_iii/icons.json b/homeassistant/components/firefly_iii/icons.json index 9a8498041924d2..3007b6b8df676f 100644 --- a/homeassistant/components/firefly_iii/icons.json +++ b/homeassistant/components/firefly_iii/icons.json @@ -2,13 +2,13 @@ "entity": { "sensor": { "account_type": { - "default": "mdi:bank", - "state": { - "expense": "mdi:cash-minus", - "revenue": "mdi:cash-plus", - "asset": "mdi:account-cash", - "liability": "mdi:hand-coin" - } + "default": "mdi:bank" + }, + "account_balance": { + "default": "mdi:currency-usd" + }, + "account_role": { + "default": "mdi:account-circle" }, "category": { "default": "mdi:label" diff --git a/homeassistant/components/firefly_iii/sensor.py b/homeassistant/components/firefly_iii/sensor.py index e6facfb6b948bf..7e6a8ec863c29c 100644 --- a/homeassistant/components/firefly_iii/sensor.py +++ b/homeassistant/components/firefly_iii/sensor.py @@ -4,35 +4,33 @@ from pyfirefly.models import Account, Category -from homeassistant.components.sensor import ( - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) +from homeassistant.components.sensor import SensorEntity, SensorStateClass, StateType from homeassistant.components.sensor.const import SensorDeviceClass +from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .coordinator import FireflyConfigEntry, FireflyDataUpdateCoordinator -from .entity import FireflyBaseEntity - -ACCOUNT_SENSORS: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key="account_type", - translation_key="account", - device_class=SensorDeviceClass.MONETARY, - state_class=SensorStateClass.TOTAL, - ), -) - -CATEGORY_SENSORS: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key="category", - translation_key="category", - device_class=SensorDeviceClass.MONETARY, - state_class=SensorStateClass.TOTAL, - ), -) +from .entity import FireflyAccountBaseEntity, FireflyCategoryBaseEntity + +ACCOUNT_ROLE_MAPPING = { + "defaultAsset": "default_asset", + "sharedAsset": "shared_asset", + "savingAsset": "saving_asset", + "ccAsset": "cc_asset", + "cashWalletAsset": "cash_wallet_asset", +} +ACCOUNT_TYPE_ICONS = { + "expense": "mdi:cash-minus", + "asset": "mdi:account-cash", + "revenue": "mdi:cash-plus", + "liability": "mdi:hand-coin", +} + +ACCOUNT_BALANCE = "account_balance" +ACCOUNT_ROLE = "account_role" +ACCOUNT_TYPE = "account_type" +CATEGORY = "category" async def async_setup_entry( @@ -40,94 +38,137 @@ async def async_setup_entry( entry: FireflyConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: - """Set up the Firefly III sensor platform.""" + """Set up Firefly III sensors.""" coordinator = entry.runtime_data - entities: list[SensorEntity] = [ - FireflyAccountEntity( - coordinator=coordinator, - entity_description=description, - account=account, + entities: list[SensorEntity] = [] + + for account in coordinator.data.accounts: + entities.append( + FireflyAccountBalanceSensor(coordinator, account, ACCOUNT_BALANCE) ) - for account in coordinator.data.accounts - for description in ACCOUNT_SENSORS - ] + entities.append(FireflyAccountRoleSensor(coordinator, account, ACCOUNT_ROLE)) + entities.append(FireflyAccountTypeSensor(coordinator, account, ACCOUNT_TYPE)) entities.extend( - FireflyCategoryEntity( - coordinator=coordinator, - entity_description=description, - category=category, - ) - for category in coordinator.data.category_details - for description in CATEGORY_SENSORS + [ + FireflyCategorySensor(coordinator, category, CATEGORY) + for category in coordinator.data.category_details + ] ) async_add_entities(entities) -class FireflyAccountEntity(FireflyBaseEntity, SensorEntity): - """Entity for Firefly III account.""" +class FireflyAccountBalanceSensor(FireflyAccountBaseEntity, SensorEntity): + """Account balance sensor.""" + + _attr_translation_key = "account_balance" + _attr_device_class = SensorDeviceClass.MONETARY + _attr_state_class = SensorStateClass.TOTAL def __init__( self, coordinator: FireflyDataUpdateCoordinator, - entity_description: SensorEntityDescription, account: Account, + key: str, ) -> None: - """Initialize Firefly account entity.""" - super().__init__(coordinator, entity_description) + """Initialize the account balance sensor.""" + super().__init__(coordinator, account, key) self._account = account - self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{entity_description.key}_{account.id}" - self._attr_name = account.attributes.name self._attr_native_unit_of_measurement = ( coordinator.data.primary_currency.attributes.code ) - # Account type state doesn't go well with the icons.json. Need to fix it. - if account.attributes.type == "expense": - self._attr_icon = "mdi:cash-minus" - elif account.attributes.type == "asset": - self._attr_icon = "mdi:account-cash" - elif account.attributes.type == "revenue": - self._attr_icon = "mdi:cash-plus" - elif account.attributes.type == "liability": - self._attr_icon = "mdi:hand-coin" - else: - self._attr_icon = "mdi:bank" - @property - def native_value(self) -> str | None: - """Return the state of the sensor.""" + def native_value(self) -> StateType: + """Return current account balance.""" return self._account.attributes.current_balance -class FireflyCategoryEntity(FireflyBaseEntity, SensorEntity): - """Entity for Firefly III category.""" +class FireflyAccountRoleSensor(FireflyAccountBaseEntity, SensorEntity): + """Account role diagnostic sensor.""" + + _attr_translation_key = "account_role" + _attr_entity_category = EntityCategory.DIAGNOSTIC + _attr_entity_registry_enabled_default = True + + def __init__( + self, + coordinator: FireflyDataUpdateCoordinator, + account: Account, + key: str, + ) -> None: + """Initialize the account role sensor.""" + super().__init__(coordinator, account, key) + self._account = account + + @property + def native_value(self) -> StateType: + """Return account role.""" + + # An account can be empty and then should resort to Unknown + account_role: str | None = self._account.attributes.account_role + if account_role is None: + return None + + return ACCOUNT_ROLE_MAPPING.get(account_role, account_role) + + +class FireflyAccountTypeSensor(FireflyAccountBaseEntity, SensorEntity): + """Account type diagnostic sensor.""" + + _attr_translation_key = "account_type" + _attr_entity_category = EntityCategory.DIAGNOSTIC + _attr_entity_registry_enabled_default = True + + def __init__( + self, + coordinator: FireflyDataUpdateCoordinator, + account: Account, + key: str, + ) -> None: + """Initialize the account type sensor.""" + super().__init__(coordinator, account, key) + acc_type = account.attributes.type + self._attr_icon = ( + ACCOUNT_TYPE_ICONS.get(acc_type, "mdi:bank") + if acc_type is not None + else "mdi:bank" + ) + + @property + def native_value(self) -> StateType: + """Return account type.""" + return self._account.attributes.type + + +class FireflyCategorySensor(FireflyCategoryBaseEntity, SensorEntity): + """Category sensor.""" + + _attr_translation_key = "category" + _attr_device_class = SensorDeviceClass.MONETARY + _attr_state_class = SensorStateClass.TOTAL def __init__( self, coordinator: FireflyDataUpdateCoordinator, - entity_description: SensorEntityDescription, category: Category, + key: str, ) -> None: - """Initialize Firefly category entity.""" - super().__init__(coordinator, entity_description) + """Initialize the category sensor.""" + super().__init__(coordinator, category, key) self._category = category - self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{entity_description.key}_{category.id}" - self._attr_name = category.attributes.name self._attr_native_unit_of_measurement = ( coordinator.data.primary_currency.attributes.code ) @property - def native_value(self) -> float | None: - """Return the state of the sensor.""" + def native_value(self) -> StateType: + """Return net spent+earned value for this category in the period.""" spent_items = self._category.attributes.spent or [] earned_items = self._category.attributes.earned or [] - spent = sum(float(item.sum) for item in spent_items if item.sum is not None) earned = sum(float(item.sum) for item in earned_items if item.sum is not None) - if spent == 0 and earned == 0: return None return spent + earned diff --git a/homeassistant/components/firefly_iii/strings.json b/homeassistant/components/firefly_iii/strings.json index 4d5831d8d71681..7ab733c6b0e770 100644 --- a/homeassistant/components/firefly_iii/strings.json +++ b/homeassistant/components/firefly_iii/strings.json @@ -45,5 +45,34 @@ "timeout_connect": { "message": "A timeout occurred while trying to connect to the Firefly instance: {error}" } + }, + "entity": { + "sensor": { + "account_balance": { + "name": "Account Balance" + }, + "account_role": { + "name": "Account Role", + "state": { + "default_asset": "Default asset", + "shared_asset": "Shared asset", + "saving_asset": "Saving asset", + "cc_asset": "Credit card asset", + "cash_wallet_asset": "Cash wallet asset" + } + }, + "account_type": { + "name": "Account Type", + "state": { + "asset": "Asset", + "expense": "Expense", + "revenue": "Revenue", + "liability": "Liability" + } + }, + "category": { + "name": "Earned/Spent" + } + } } } diff --git a/homeassistant/components/fritz/models.py b/homeassistant/components/fritz/models.py index f66c1d338b9311..ee0b3d461f670b 100644 --- a/homeassistant/components/fritz/models.py +++ b/homeassistant/components/fritz/models.py @@ -5,7 +5,7 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import datetime -from typing import TypedDict +from typing import NotRequired, TypedDict from homeassistant.util import dt as dt_util @@ -55,7 +55,7 @@ class Interface(TypedDict): "X_AVM-DE_Guest": bool, "X_AVM-DE_RequestClient": str, "X_AVM-DE_VPN": bool, - "X_AVM-DE_WANAccess": str, + "X_AVM-DE_WANAccess": NotRequired[str], "X_AVM-DE_Disallow": bool, "X_AVM-DE_IsMeshable": str, "X_AVM-DE_Priority": str, diff --git a/homeassistant/components/hardkernel/hardware.py b/homeassistant/components/hardkernel/hardware.py index 86dcf0736804a2..45af8b4e146635 100644 --- a/homeassistant/components/hardkernel/hardware.py +++ b/homeassistant/components/hardkernel/hardware.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.hardware.models import BoardInfo, HardwareInfo +from homeassistant.components.hardware import BoardInfo, HardwareInfo from homeassistant.components.hassio import get_os_info from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/hardware/__init__.py b/homeassistant/components/hardware/__init__.py index 5db9671a4ed028..7d616ef4cefc57 100644 --- a/homeassistant/components/hardware/__init__.py +++ b/homeassistant/components/hardware/__init__.py @@ -11,7 +11,13 @@ from . import websocket_api from .const import DATA_HARDWARE, DOMAIN from .hardware import async_process_hardware_platforms -from .models import HardwareData, SystemStatus +from .models import BoardInfo, HardwareData, HardwareInfo, SystemStatus, USBInfo + +__all__ = [ + "BoardInfo", + "HardwareInfo", + "USBInfo", +] CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py index e4b6f1c7c2ca19..17b6cf08910217 100644 --- a/homeassistant/components/harmony/__init__.py +++ b/homeassistant/components/harmony/__init__.py @@ -1,15 +1,20 @@ """The Logitech Harmony Hub integration.""" +from __future__ import annotations + import logging +import sys from homeassistant.components.remote import ATTR_ACTIVITY, ATTR_DELAY_SECS from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import HARMONY_OPTIONS_UPDATE, PLATFORMS -from .data import HarmonyConfigEntry, HarmonyData +if sys.version_info < (3, 14): + from .const import HARMONY_OPTIONS_UPDATE, PLATFORMS + from .data import HarmonyConfigEntry, HarmonyData _LOGGER = logging.getLogger(__name__) @@ -20,6 +25,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: HarmonyConfigEntry) -> b # when setting up a config entry, we fallback to adding # the options to the config entry and pull them out here if # they are missing from the options + if sys.version_info >= (3, 14): + raise HomeAssistantError( + "Logitech Harmony Hub is not supported on Python 3.14. Please use Python 3.13." + ) _async_import_options_from_data_if_missing(hass, entry) address = entry.data[CONF_HOST] diff --git a/homeassistant/components/harmony/manifest.json b/homeassistant/components/harmony/manifest.json index f74bff314a454e..795c9c5ed882fa 100644 --- a/homeassistant/components/harmony/manifest.json +++ b/homeassistant/components/harmony/manifest.json @@ -7,7 +7,7 @@ "documentation": "https://www.home-assistant.io/integrations/harmony", "iot_class": "local_push", "loggers": ["aioharmony", "slixmpp"], - "requirements": ["aioharmony==0.5.3"], + "requirements": ["aioharmony==0.5.3;python_version<'3.14'"], "ssdp": [ { "manufacturer": "Logitech", diff --git a/homeassistant/components/homeassistant_connect_zbt2/hardware.py b/homeassistant/components/homeassistant_connect_zbt2/hardware.py index 8367df6501d47c..0d45e055407682 100644 --- a/homeassistant/components/homeassistant_connect_zbt2/hardware.py +++ b/homeassistant/components/homeassistant_connect_zbt2/hardware.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.hardware.models import HardwareInfo, USBInfo +from homeassistant.components.hardware import HardwareInfo, USBInfo from homeassistant.core import HomeAssistant, callback from .config_flow import HomeAssistantConnectZBT2ConfigFlow diff --git a/homeassistant/components/homeassistant_green/hardware.py b/homeassistant/components/homeassistant_green/hardware.py index bf0decb9d05ea8..825eede5653ea7 100644 --- a/homeassistant/components/homeassistant_green/hardware.py +++ b/homeassistant/components/homeassistant_green/hardware.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.hardware.models import BoardInfo, HardwareInfo +from homeassistant.components.hardware import BoardInfo, HardwareInfo from homeassistant.components.hassio import get_os_info from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py index 294ed83bad1df8..b32998f55b0cb2 100644 --- a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py +++ b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py @@ -309,6 +309,7 @@ class OptionsFlowHandler(OptionsFlow, ABC): def __init__(self, config_entry: ConfigEntry) -> None: """Set up the options flow.""" + # pylint: disable=hass-component-root-import from homeassistant.components.zha.radio_manager import ( # noqa: PLC0415 ZhaMultiPANMigrationHelper, ) @@ -450,6 +451,7 @@ async def async_step_configure_addon( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Configure the Silicon Labs Multiprotocol add-on.""" + # pylint: disable=hass-component-root-import from homeassistant.components.zha import DOMAIN as ZHA_DOMAIN # noqa: PLC0415 from homeassistant.components.zha.radio_manager import ( # noqa: PLC0415 ZhaMultiPANMigrationHelper, @@ -741,6 +743,7 @@ async def async_step_configure_flasher_addon( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Perform initial backup and reconfigure ZHA.""" + # pylint: disable=hass-component-root-import from homeassistant.components.zha import DOMAIN as ZHA_DOMAIN # noqa: PLC0415 from homeassistant.components.zha.radio_manager import ( # noqa: PLC0415 ZhaMultiPANMigrationHelper, diff --git a/homeassistant/components/homeassistant_sky_connect/hardware.py b/homeassistant/components/homeassistant_sky_connect/hardware.py index bf4ffefdc7508d..90ac80bf49a472 100644 --- a/homeassistant/components/homeassistant_sky_connect/hardware.py +++ b/homeassistant/components/homeassistant_sky_connect/hardware.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.hardware.models import HardwareInfo, USBInfo +from homeassistant.components.hardware import HardwareInfo, USBInfo from homeassistant.core import HomeAssistant, callback from .config_flow import HomeAssistantSkyConnectConfigFlow diff --git a/homeassistant/components/homeassistant_yellow/hardware.py b/homeassistant/components/homeassistant_yellow/hardware.py index 2064f33484cd10..0772b27f93640f 100644 --- a/homeassistant/components/homeassistant_yellow/hardware.py +++ b/homeassistant/components/homeassistant_yellow/hardware.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.hardware.models import BoardInfo, HardwareInfo +from homeassistant.components.hardware import BoardInfo, HardwareInfo from homeassistant.components.hassio import get_os_info from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/homee/__init__.py b/homeassistant/components/homee/__init__.py index d748d1dd809afa..6e90ecd97d88c5 100644 --- a/homeassistant/components/homee/__init__.py +++ b/homeassistant/components/homee/__init__.py @@ -3,6 +3,7 @@ import logging from pyHomee import Homee, HomeeAuthFailedException, HomeeConnectionFailedException +from pyHomee.model import HomeeNode from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform @@ -88,6 +89,40 @@ async def _connection_update_callback(connected: bool) -> None: sw_version=homee.settings.version, ) + # Remove devices that are no longer present in homee. + devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id) + for device in devices: + # Check if the device is still present in homee + device_identifiers = {identifier[1] for identifier in device.identifiers} + # homee itself uses just the uid, nodes use uid-nodeid + is_homee_hub = homee.settings.uid in device_identifiers + is_node_present = any( + f"{homee.settings.uid}-{node.id}" in device_identifiers + for node in homee.nodes + ) + if not is_node_present and not is_homee_hub: + _LOGGER.info("Removing device %s", device.name) + device_registry.async_update_device( + device_id=device.id, + remove_config_entry_id=entry.entry_id, + ) + + # Remove device at runtime when node is removed in homee + async def _remove_node_callback(node: HomeeNode, add: bool) -> None: + """Call when a node is removed.""" + if not add: + device = device_registry.async_get_device( + identifiers={(DOMAIN, f"{entry.runtime_data.settings.uid}-{node.id}")} + ) + if device: + _LOGGER.info("Removing device %s", device.name) + device_registry.async_update_device( + device_id=device.id, + remove_config_entry_id=entry.entry_id, + ) + + homee.add_nodes_listener(_remove_node_callback) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/homee/quality_scale.yaml b/homeassistant/components/homee/quality_scale.yaml index f27876b1725171..bf86abb7938ff4 100644 --- a/homeassistant/components/homee/quality_scale.yaml +++ b/homeassistant/components/homee/quality_scale.yaml @@ -63,7 +63,7 @@ rules: icon-translations: done reconfiguration-flow: done repair-issues: todo - stale-devices: todo + stale-devices: done # Platinum async-dependency: todo diff --git a/homeassistant/components/iron_os/manifest.json b/homeassistant/components/iron_os/manifest.json index fb4d3fc15cda74..c50ca23692879f 100644 --- a/homeassistant/components/iron_os/manifest.json +++ b/homeassistant/components/iron_os/manifest.json @@ -11,6 +11,7 @@ "config_flow": true, "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/iron_os", + "integration_type": "device", "iot_class": "local_polling", "loggers": ["pynecil"], "quality_scale": "platinum", diff --git a/homeassistant/components/onewire/binary_sensor.py b/homeassistant/components/onewire/binary_sensor.py index c1d34bad60ead7..50127f96af3c9c 100644 --- a/homeassistant/components/onewire/binary_sensor.py +++ b/homeassistant/components/onewire/binary_sensor.py @@ -2,7 +2,6 @@ from __future__ import annotations -from dataclasses import dataclass from datetime import timedelta import os @@ -16,8 +15,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, READ_MODE_INT -from .entity import OneWireEntity, OneWireEntityDescription +from .const import DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B +from .entity import OneWireEntity from .onewirehub import ( SIGNAL_NEW_DEVICE_CONNECTED, OneWireConfigEntry, @@ -31,25 +30,16 @@ SCAN_INTERVAL = timedelta(seconds=30) -@dataclass(frozen=True) -class OneWireBinarySensorEntityDescription( - OneWireEntityDescription, BinarySensorEntityDescription -): - """Class describing OneWire binary sensor entities.""" - - read_mode = READ_MODE_INT - - -DEVICE_BINARY_SENSORS: dict[str, tuple[OneWireBinarySensorEntityDescription, ...]] = { +DEVICE_BINARY_SENSORS: dict[str, tuple[BinarySensorEntityDescription, ...]] = { "05": ( - OneWireBinarySensorEntityDescription( + BinarySensorEntityDescription( key="sensed", entity_registry_enabled_default=False, translation_key="sensed", ), ), "12": tuple( - OneWireBinarySensorEntityDescription( + BinarySensorEntityDescription( key=f"sensed.{device_key}", entity_registry_enabled_default=False, translation_key="sensed_id", @@ -58,7 +48,7 @@ class OneWireBinarySensorEntityDescription( for device_key in DEVICE_KEYS_A_B ), "29": tuple( - OneWireBinarySensorEntityDescription( + BinarySensorEntityDescription( key=f"sensed.{device_key}", entity_registry_enabled_default=False, translation_key="sensed_id", @@ -67,7 +57,7 @@ class OneWireBinarySensorEntityDescription( for device_key in DEVICE_KEYS_0_7 ), "3A": tuple( - OneWireBinarySensorEntityDescription( + BinarySensorEntityDescription( key=f"sensed.{device_key}", entity_registry_enabled_default=False, translation_key="sensed_id", @@ -79,9 +69,9 @@ class OneWireBinarySensorEntityDescription( } # EF sensors are usually hobbyboards specialized sensors. -HOBBYBOARD_EF: dict[str, tuple[OneWireBinarySensorEntityDescription, ...]] = { +HOBBYBOARD_EF: dict[str, tuple[BinarySensorEntityDescription, ...]] = { "HB_HUB": tuple( - OneWireBinarySensorEntityDescription( + BinarySensorEntityDescription( key=f"hub/short.{device_key}", entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, @@ -96,7 +86,7 @@ class OneWireBinarySensorEntityDescription( def get_sensor_types( device_sub_type: str, -) -> dict[str, tuple[OneWireBinarySensorEntityDescription, ...]]: +) -> dict[str, tuple[BinarySensorEntityDescription, ...]]: """Return the proper info array for the device type.""" if "HobbyBoard" in device_sub_type: return HOBBYBOARD_EF @@ -160,11 +150,9 @@ def get_entities( class OneWireBinarySensorEntity(OneWireEntity, BinarySensorEntity): """Implementation of a 1-Wire binary sensor.""" - entity_description: OneWireBinarySensorEntityDescription - @property def is_on(self) -> bool | None: """Return true if sensor is on.""" - if self._state is None: + if (state := self._state) is None: return None - return self._state == 1 + return state == "1" diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index 7e4db9f94df17c..6a60e98eb87e2d 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -51,6 +51,3 @@ MANUFACTURER_MAXIM = "Maxim Integrated" MANUFACTURER_HOBBYBOARDS = "Hobby Boards" MANUFACTURER_EDS = "Embedded Data Systems" - -READ_MODE_FLOAT = "float" -READ_MODE_INT = "int" diff --git a/homeassistant/components/onewire/entity.py b/homeassistant/components/onewire/entity.py index 9adc046acc1fe8..acc6499b01daeb 100644 --- a/homeassistant/components/onewire/entity.py +++ b/homeassistant/components/onewire/entity.py @@ -2,7 +2,6 @@ from __future__ import annotations -from dataclasses import dataclass import logging from typing import Any @@ -12,28 +11,17 @@ from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity, EntityDescription -from .const import READ_MODE_INT - - -@dataclass(frozen=True) -class OneWireEntityDescription(EntityDescription): - """Class describing OneWire entities.""" - - read_mode: str | None = None - - _LOGGER = logging.getLogger(__name__) class OneWireEntity(Entity): """Implementation of a 1-Wire entity.""" - entity_description: OneWireEntityDescription _attr_has_entity_name = True def __init__( self, - description: OneWireEntityDescription, + description: EntityDescription, device_id: str, device_info: DeviceInfo, device_file: str, @@ -45,8 +33,7 @@ def __init__( self._attr_unique_id = f"/{device_id}/{description.key}" self._attr_device_info = device_info self._device_file = device_file - self._state: int | float | None = None - self._value_raw: float | None = None + self._state: str | None = None self._owproxy = owproxy @property @@ -56,10 +43,6 @@ def extra_state_attributes(self) -> dict[str, Any] | None: "device_file": self._device_file, } - async def _read_value(self) -> str: - """Read a value from the server.""" - return (await self._owproxy.read(self._device_file)).decode().lstrip() - async def _write_value(self, value: bytes) -> None: """Write a value to the server.""" await self._owproxy.write(self._device_file, value) @@ -67,7 +50,7 @@ async def _write_value(self, value: bytes) -> None: async def async_update(self) -> None: """Get the latest data from the device.""" try: - self._value_raw = float(await self._read_value()) + state = await self._owproxy.read(self._device_file) except OWServerError as exc: if self._last_update_success: _LOGGER.error("Error fetching %s data: %s", self.name, exc) @@ -77,7 +60,4 @@ async def async_update(self) -> None: if not self._last_update_success: self._last_update_success = True _LOGGER.debug("Fetching %s data recovered", self.name) - if self.entity_description.read_mode == READ_MODE_INT: - self._state = int(self._value_raw) - else: - self._state = self._value_raw + self._state = state.decode("ascii").strip() diff --git a/homeassistant/components/onewire/manifest.json b/homeassistant/components/onewire/manifest.json index 80d3a6fdc3c937..b7b6377e8b26f6 100644 --- a/homeassistant/components/onewire/manifest.json +++ b/homeassistant/components/onewire/manifest.json @@ -7,6 +7,6 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["aio_ownet"], - "requirements": ["aio-ownet==0.0.3"], + "requirements": ["aio-ownet==0.0.4"], "zeroconf": ["_owserver._tcp.local."] } diff --git a/homeassistant/components/onewire/select.py b/homeassistant/components/onewire/select.py index b3b3e166c36342..60c0c985ab006e 100644 --- a/homeassistant/components/onewire/select.py +++ b/homeassistant/components/onewire/select.py @@ -2,7 +2,6 @@ from __future__ import annotations -from dataclasses import dataclass from datetime import timedelta import os @@ -12,8 +11,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import READ_MODE_INT -from .entity import OneWireEntity, OneWireEntityDescription +from .entity import OneWireEntity from .onewirehub import ( SIGNAL_NEW_DEVICE_CONNECTED, OneWireConfigEntry, @@ -27,17 +25,11 @@ SCAN_INTERVAL = timedelta(seconds=30) -@dataclass(frozen=True) -class OneWireSelectEntityDescription(OneWireEntityDescription, SelectEntityDescription): - """Class describing OneWire select entities.""" - - -ENTITY_DESCRIPTIONS: dict[str, tuple[OneWireEntityDescription, ...]] = { +ENTITY_DESCRIPTIONS: dict[str, tuple[SelectEntityDescription, ...]] = { "28": ( - OneWireSelectEntityDescription( + SelectEntityDescription( key="tempres", entity_category=EntityCategory.CONFIG, - read_mode=READ_MODE_INT, options=["9", "10", "11", "12"], translation_key="tempres", entity_registry_enabled_default=False, @@ -99,12 +91,10 @@ def get_entities( class OneWireSelectEntity(OneWireEntity, SelectEntity): """Implementation of a 1-Wire switch.""" - entity_description: OneWireSelectEntityDescription - @property def current_option(self) -> str | None: """Return the selected entity option to represent the entity state.""" - return str(self._state) + return self._state async def async_select_option(self, option: str) -> None: """Change the selected option.""" diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index ee0a3cbacbbfb3..f54b66b059dc5a 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -27,7 +27,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from homeassistant.helpers.typing import StateType from .const import ( DEVICE_KEYS_0_3, @@ -36,10 +35,8 @@ OPTION_ENTRY_DEVICE_OPTIONS, OPTION_ENTRY_SENSOR_PRECISION, PRECISION_MAPPING_FAMILY_28, - READ_MODE_FLOAT, - READ_MODE_INT, ) -from .entity import OneWireEntity, OneWireEntityDescription +from .entity import OneWireEntity from .onewirehub import ( SIGNAL_NEW_DEVICE_CONNECTED, OneWireConfigEntry, @@ -54,7 +51,7 @@ @dataclasses.dataclass(frozen=True) -class OneWireSensorEntityDescription(OneWireEntityDescription, SensorEntityDescription): +class OneWireSensorEntityDescription(SensorEntityDescription): """Class describing OneWire sensor entities.""" override_key: Callable[[str, Mapping[str, Any]], str] | None = None @@ -81,7 +78,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) key="temperature", device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ) @@ -96,7 +92,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.TEMPERATURE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( @@ -104,7 +99,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.PRESSURE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfPressure.MBAR, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), ), @@ -115,7 +109,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.VOLTAGE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfElectricPotential.VOLT, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="latest_voltage_id", translation_placeholders={"id": str(device_key)}, @@ -127,7 +120,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) key=f"volt.{device_key}", device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=UnitOfElectricPotential.VOLT, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="voltage_id", translation_placeholders={"id": str(device_key)}, @@ -143,7 +135,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.HUMIDITY, entity_registry_enabled_default=False, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( @@ -151,7 +142,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.HUMIDITY, entity_registry_enabled_default=False, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="humidity_hih3600", ), @@ -160,7 +150,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.HUMIDITY, entity_registry_enabled_default=False, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="humidity_hih4000", ), @@ -169,7 +158,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.HUMIDITY, entity_registry_enabled_default=False, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="humidity_hih5030", ), @@ -178,7 +166,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.HUMIDITY, entity_registry_enabled_default=False, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="humidity_htm1735", ), @@ -187,7 +174,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.PRESSURE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfPressure.MBAR, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( @@ -195,7 +181,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.ILLUMINANCE, entity_registry_enabled_default=False, native_unit_of_measurement=LIGHT_LUX, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( @@ -203,7 +188,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.VOLTAGE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfElectricPotential.VOLT, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="voltage_vad", ), @@ -212,7 +196,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.VOLTAGE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfElectricPotential.VOLT, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="voltage_vdd", ), @@ -221,7 +204,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.VOLTAGE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfElectricPotential.VOLT, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="voltage_vis", ), @@ -232,7 +214,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, override_key=_get_sensor_precision_family_28, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), ), @@ -243,7 +224,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.TEMPERATURE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - read_mode=READ_MODE_FLOAT, override_key=lambda d, o: "typeK/temperature", state_class=SensorStateClass.MEASUREMENT, translation_key="thermocouple_temperature_k", @@ -253,7 +233,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.VOLTAGE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfElectricPotential.VOLT, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( @@ -261,7 +240,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) device_class=SensorDeviceClass.VOLTAGE, entity_registry_enabled_default=False, native_unit_of_measurement=UnitOfElectricPotential.VOLT, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="voltage_vis_gradient", ), @@ -271,7 +249,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) "1D": tuple( OneWireSensorEntityDescription( key=f"counter.{device_key}", - read_mode=READ_MODE_INT, state_class=SensorStateClass.TOTAL_INCREASING, translation_key="counter_id", translation_placeholders={"id": str(device_key)}, @@ -288,14 +265,12 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) key="humidity/humidity_corrected", device_class=SensorDeviceClass.HUMIDITY, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( key="humidity/humidity_raw", device_class=SensorDeviceClass.HUMIDITY, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="humidity_raw", ), @@ -303,7 +278,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) key="humidity/temperature", device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), ), @@ -312,7 +286,6 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) key=f"moisture/sensor.{device_key}", device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=UnitOfPressure.CBAR, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, translation_key="moisture_id", translation_placeholders={"id": str(device_key)}, @@ -329,14 +302,12 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) key="EDS0066/temperature", device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( key="EDS0066/pressure", device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=UnitOfPressure.MBAR, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), ), @@ -345,28 +316,24 @@ def _get_sensor_precision_family_28(device_id: str, options: Mapping[str, Any]) key="EDS0068/temperature", device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( key="EDS0068/pressure", device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=UnitOfPressure.MBAR, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( key="EDS0068/light", device_class=SensorDeviceClass.ILLUMINANCE, native_unit_of_measurement=LIGHT_LUX, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), OneWireSensorEntityDescription( key="EDS0068/humidity", device_class=SensorDeviceClass.HUMIDITY, native_unit_of_measurement=PERCENTAGE, - read_mode=READ_MODE_FLOAT, state_class=SensorStateClass.MEASUREMENT, ), ), @@ -484,9 +451,9 @@ async def get_entities( class OneWireSensorEntity(OneWireEntity, SensorEntity): """Implementation of a 1-Wire sensor.""" - entity_description: OneWireSensorEntityDescription - @property - def native_value(self) -> StateType: + def native_value(self) -> float | int | None: """Return the state of the entity.""" - return self._state + if (state := self._state) is None: + return None + return float(state) if "." in state else int(state) diff --git a/homeassistant/components/onewire/switch.py b/homeassistant/components/onewire/switch.py index 23f85714136d82..c86151100711b6 100644 --- a/homeassistant/components/onewire/switch.py +++ b/homeassistant/components/onewire/switch.py @@ -2,7 +2,6 @@ from __future__ import annotations -from dataclasses import dataclass from datetime import timedelta import os from typing import Any @@ -13,8 +12,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B, READ_MODE_INT -from .entity import OneWireEntity, OneWireEntityDescription +from .const import DEVICE_KEYS_0_3, DEVICE_KEYS_0_7, DEVICE_KEYS_A_B +from .entity import OneWireEntity from .onewirehub import ( SIGNAL_NEW_DEVICE_CONNECTED, OneWireConfigEntry, @@ -28,16 +27,9 @@ SCAN_INTERVAL = timedelta(seconds=30) -@dataclass(frozen=True) -class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescription): - """Class describing OneWire switch entities.""" - - read_mode = READ_MODE_INT - - -DEVICE_SWITCHES: dict[str, tuple[OneWireEntityDescription, ...]] = { +DEVICE_SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { "05": ( - OneWireSwitchEntityDescription( + SwitchEntityDescription( key="PIO", entity_registry_enabled_default=False, translation_key="pio", @@ -45,7 +37,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr ), "12": tuple( [ - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"PIO.{device_key}", entity_registry_enabled_default=False, translation_key="pio_id", @@ -54,7 +46,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr for device_key in DEVICE_KEYS_A_B ] + [ - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"latch.{device_key}", entity_registry_enabled_default=False, translation_key="latch_id", @@ -64,7 +56,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr ] ), "26": ( - OneWireSwitchEntityDescription( + SwitchEntityDescription( key="IAD", entity_registry_enabled_default=False, entity_category=EntityCategory.CONFIG, @@ -73,7 +65,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr ), "29": tuple( [ - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"PIO.{device_key}", entity_registry_enabled_default=False, translation_key="pio_id", @@ -82,7 +74,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr for device_key in DEVICE_KEYS_0_7 ] + [ - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"latch.{device_key}", entity_registry_enabled_default=False, translation_key="latch_id", @@ -92,7 +84,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr ] ), "3A": tuple( - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"PIO.{device_key}", entity_registry_enabled_default=False, translation_key="pio_id", @@ -105,9 +97,9 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr # EF sensors are usually hobbyboards specialized sensors. -HOBBYBOARD_EF: dict[str, tuple[OneWireEntityDescription, ...]] = { +HOBBYBOARD_EF: dict[str, tuple[SwitchEntityDescription, ...]] = { "HB_HUB": tuple( - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"hub/branch.{device_key}", entity_registry_enabled_default=False, entity_category=EntityCategory.CONFIG, @@ -118,7 +110,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr ), "HB_MOISTURE_METER": tuple( [ - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"moisture/is_leaf.{device_key}", entity_registry_enabled_default=False, entity_category=EntityCategory.CONFIG, @@ -128,7 +120,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr for device_key in DEVICE_KEYS_0_3 ] + [ - OneWireSwitchEntityDescription( + SwitchEntityDescription( key=f"moisture/is_moisture.{device_key}", entity_registry_enabled_default=False, entity_category=EntityCategory.CONFIG, @@ -143,7 +135,7 @@ class OneWireSwitchEntityDescription(OneWireEntityDescription, SwitchEntityDescr def get_sensor_types( device_sub_type: str, -) -> dict[str, tuple[OneWireEntityDescription, ...]]: +) -> dict[str, tuple[SwitchEntityDescription, ...]]: """Return the proper info array for the device type.""" if "HobbyBoard" in device_sub_type: return HOBBYBOARD_EF @@ -211,14 +203,12 @@ def get_entities( class OneWireSwitchEntity(OneWireEntity, SwitchEntity): """Implementation of a 1-Wire switch.""" - entity_description: OneWireSwitchEntityDescription - @property def is_on(self) -> bool | None: """Return true if switch is on.""" - if self._state is None: + if (state := self._state) is None: return None - return self._state == 1 + return state == "1" async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py index 66b35eaff210e1..6a8ce39b32b725 100644 --- a/homeassistant/components/profiler/__init__.py +++ b/homeassistant/components/profiler/__init__.py @@ -453,6 +453,10 @@ async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall) # Imports deferred to avoid loading modules # in memory since usually only one part of this # integration is used at a time + if sys.version_info >= (3, 14): + raise HomeAssistantError( + "Memory profiling is not supported on Python 3.14. Please use Python 3.13." + ) from guppy import hpy # noqa: PLC0415 start_time = int(time.time() * 1000000) diff --git a/homeassistant/components/profiler/manifest.json b/homeassistant/components/profiler/manifest.json index 814b00a16d4702..68eb7179a297cb 100644 --- a/homeassistant/components/profiler/manifest.json +++ b/homeassistant/components/profiler/manifest.json @@ -7,7 +7,7 @@ "quality_scale": "internal", "requirements": [ "pyprof2calltree==1.4.5", - "guppy3==3.1.5", + "guppy3==3.1.5;python_version<'3.14'", "objgraph==3.5.0" ], "single_config_entry": true diff --git a/homeassistant/components/python_script/manifest.json b/homeassistant/components/python_script/manifest.json index c8cb1da40c90bb..d25515fb45475c 100644 --- a/homeassistant/components/python_script/manifest.json +++ b/homeassistant/components/python_script/manifest.json @@ -5,5 +5,8 @@ "documentation": "https://www.home-assistant.io/integrations/python_script", "loggers": ["RestrictedPython"], "quality_scale": "internal", - "requirements": ["RestrictedPython==8.0"] + "requirements": [ + "RestrictedPython==8.0;python_version<'3.14'", + "RestrictedPython==8.1a1.dev0;python_version>='3.14'" + ] } diff --git a/homeassistant/components/raspberry_pi/hardware.py b/homeassistant/components/raspberry_pi/hardware.py index 54d375c7b6e48d..1386f8628b3145 100644 --- a/homeassistant/components/raspberry_pi/hardware.py +++ b/homeassistant/components/raspberry_pi/hardware.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.hardware.models import BoardInfo, HardwareInfo +from homeassistant.components.hardware import BoardInfo, HardwareInfo from homeassistant.components.hassio import get_os_info from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/recorder/executor.py b/homeassistant/components/recorder/executor.py index 6b8192d1e146bd..082be5787c8568 100644 --- a/homeassistant/components/recorder/executor.py +++ b/homeassistant/components/recorder/executor.py @@ -4,6 +4,7 @@ from collections.abc import Callable from concurrent.futures.thread import _threads_queues, _worker +import sys import threading from typing import Any import weakref @@ -53,6 +54,18 @@ def weakref_cb( # type: ignore[no-untyped-def] ) -> None: q.put(None) + if sys.version_info >= (3, 14): + additional_args = ( + self._create_worker_context(), + self._work_queue, + ) + else: + additional_args = ( + self._work_queue, + self._initializer, + self._initargs, + ) + num_threads = len(self._threads) if num_threads < self._max_workers: thread_name = f"{self._thread_name_prefix or self}_{num_threads}" @@ -63,9 +76,7 @@ def weakref_cb( # type: ignore[no-untyped-def] self._shutdown_hook, self.recorder_and_worker_thread_ids, weakref.ref(self, weakref_cb), - self._work_queue, - self._initializer, - self._initargs, + *(additional_args), ), ) executor_thread.start() diff --git a/homeassistant/components/shelly/button.py b/homeassistant/components/shelly/button.py index b921255fbc1e8c..f5e0e315fd8af5 100644 --- a/homeassistant/components/shelly/button.py +++ b/homeassistant/components/shelly/button.py @@ -66,7 +66,7 @@ class RpcButtonDescription(RpcEntityDescription, ButtonEntityDescription): BUTTONS: Final[list[ShellyButtonDescription[Any]]] = [ ShellyButtonDescription[ShellyBlockCoordinator | ShellyRpcCoordinator]( key="reboot", - name="Reboot", + name="Restart", device_class=ButtonDeviceClass.RESTART, entity_category=EntityCategory.CONFIG, press_action="trigger_reboot", diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 8cb3736e09063b..5a6592a8f71b0f 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -388,7 +388,7 @@ def __init__( ), ("sensor", "luminosity"): BlockSensorDescription( key="sensor|luminosity", - name="Luminosity", + name="Illuminance", native_unit_of_measurement=LIGHT_LUX, device_class=SensorDeviceClass.ILLUMINANCE, state_class=SensorStateClass.MEASUREMENT, @@ -470,7 +470,7 @@ def __init__( REST_SENSORS: Final = { "rssi": RestSensorDescription( key="rssi", - name="RSSI", + name="Signal strength", native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, value=lambda status, _: status["wifi_sta"]["rssi"], device_class=SensorDeviceClass.SIGNAL_STRENGTH, @@ -480,7 +480,7 @@ def __init__( ), "uptime": RestSensorDescription( key="uptime", - name="Uptime", + name="Last restart", value=lambda status, last: get_device_uptime(status["uptime"], last), device_class=SensorDeviceClass.TIMESTAMP, entity_registry_enabled_default=False, @@ -1297,7 +1297,7 @@ def __init__( "rssi": RpcSensorDescription( key="wifi", sub_key="rssi", - name="RSSI", + name="Signal strength", native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, device_class=SensorDeviceClass.SIGNAL_STRENGTH, state_class=SensorStateClass.MEASUREMENT, @@ -1309,7 +1309,7 @@ def __init__( "uptime": RpcSensorDescription( key="sys", sub_key="uptime", - name="Uptime", + name="Last restart", value=get_device_uptime, device_class=SensorDeviceClass.TIMESTAMP, entity_registry_enabled_default=False, @@ -1389,7 +1389,7 @@ def __init__( "counter_value": RpcSensorDescription( key="input", sub_key="counts", - name="counter value", + name="pulse counter value", value=lambda status, _: status["xtotal"], removal_condition=lambda config, status, key: ( config[key]["type"] != "count" diff --git a/homeassistant/components/subaru/device_tracker.py b/homeassistant/components/subaru/device_tracker.py index 6bcca848ef22b8..f8b1b0f5aad867 100644 --- a/homeassistant/components/subaru/device_tracker.py +++ b/homeassistant/components/subaru/device_tracker.py @@ -6,7 +6,7 @@ from subarulink.const import LATITUDE, LONGITUDE, TIMESTAMP -from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.components.device_tracker import TrackerEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index f5fbfafa02b175..0d9059e8d98959 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -75,6 +75,7 @@ ATTR_REPLYMARKUP, ATTR_RESIZE_KEYBOARD, ATTR_STICKER_ID, + ATTR_TARGET, ATTR_TEXT, ATTR_TIMEOUT, ATTR_TITLE, @@ -95,7 +96,6 @@ PARSER_PLAIN_TEXT, SERVICE_EDIT_CAPTION, SERVICE_EDIT_MESSAGE, - SERVICE_SEND_ANIMATION, SERVICE_SEND_DOCUMENT, SERVICE_SEND_PHOTO, SERVICE_SEND_STICKER, @@ -103,6 +103,7 @@ SERVICE_SEND_VOICE, ) +_FILE_TYPES = ("animation", "document", "photo", "sticker", "video", "voice") _LOGGER = logging.getLogger(__name__) type TelegramBotConfigEntry = ConfigEntry[TelegramNotificationService] @@ -380,7 +381,10 @@ def _make_row_inline_keyboard(row_keyboard: Any) -> list[InlineKeyboardButton]: InlineKeyboardButton(text_btn, callback_data=data_btn) ) else: - raise TypeError(str(row_keyboard)) + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="invalid_inline_keyboard", + ) return buttons # Defaults @@ -432,6 +436,48 @@ def _make_row_inline_keyboard(row_keyboard: Any) -> list[InlineKeyboardButton]: params[ATTR_PARSER] = None return params + async def _send_msgs( + self, + func_send: Callable, + msg_error: str, + message_tag: str | None, + *args_msg: Any, + context: Context | None = None, + **kwargs_msg: Any, + ) -> dict[int, int]: + """Sends a message to each of the targets. + + If there is only 1 targtet, an error is raised if the send fails. + For multiple targets, errors are logged and the caller is responsible for checking which target is successful/failed based on the return value. + + :return: dict with chat_id keys and message_id values for successful sends + """ + chat_ids = self.get_target_chat_ids(kwargs_msg.pop(ATTR_TARGET, None)) + msg_ids = {} + for chat_id in chat_ids: + _LOGGER.debug("%s to chat ID %s", func_send.__name__, chat_id) + + for file_type in _FILE_TYPES: + if file_type in kwargs_msg and isinstance( + kwargs_msg[file_type], io.BytesIO + ): + kwargs_msg[file_type].seek(0) + + response: Message = await self._send_msg( + func_send, + msg_error, + message_tag, + chat_id, + *args_msg, + context=context, + suppress_error=len(chat_ids) > 1, + **kwargs_msg, + ) + if response: + msg_ids[chat_id] = response.id + + return msg_ids + async def _send_msg( self, func_send: Callable, @@ -439,6 +485,7 @@ async def _send_msg( message_tag: str | None, *args_msg: Any, context: Context | None = None, + suppress_error: bool = False, **kwargs_msg: Any, ) -> Any: """Send one message.""" @@ -471,9 +518,17 @@ async def _send_msg( EVENT_TELEGRAM_SENT, event_data, context=context ) except TelegramError as exc: + if not suppress_error: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="action_failed", + translation_placeholders={"error": str(exc)}, + ) from exc + _LOGGER.error( "%s: %s. Args: %s, kwargs: %s", msg_error, exc, args_msg, kwargs_msg ) + return None return out @@ -488,27 +543,20 @@ async def send_message( title = kwargs.get(ATTR_TITLE) text = f"{title}\n{message}" if title else message params = self._get_msg_kwargs(kwargs) - msg_ids = {} - for chat_id in self.get_target_chat_ids(target): - _LOGGER.debug("Send message in chat ID %s with params: %s", chat_id, params) - msg = await self._send_msg( - self.bot.send_message, - "Error sending message", - params[ATTR_MESSAGE_TAG], - chat_id, - text, - parse_mode=params[ATTR_PARSER], - disable_web_page_preview=params[ATTR_DISABLE_WEB_PREV], - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) - if msg is not None: - msg_ids[chat_id] = msg.id - return msg_ids + return await self._send_msgs( + self.bot.send_message, + "Error sending message", + params[ATTR_MESSAGE_TAG], + text, + parse_mode=params[ATTR_PARSER], + disable_web_page_preview=params[ATTR_DISABLE_WEB_PREV], + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) async def delete_message( self, @@ -706,7 +754,6 @@ async def send_chat_action( async def send_file( self, file_type: str, - target: Any = None, context: Context | None = None, **kwargs: Any, ) -> dict[int, int]: @@ -726,117 +773,107 @@ async def send_file( ), ) - msg_ids = {} - if file_content: - for chat_id in self.get_target_chat_ids(target): - _LOGGER.debug("Sending file to chat ID %s", chat_id) - - if file_type == SERVICE_SEND_PHOTO: - msg = await self._send_msg( - self.bot.send_photo, - "Error sending photo", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - photo=file_content, - caption=kwargs.get(ATTR_CAPTION), - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - parse_mode=params[ATTR_PARSER], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) + if file_type == SERVICE_SEND_PHOTO: + return await self._send_msgs( + self.bot.send_photo, + "Error sending photo", + params[ATTR_MESSAGE_TAG], + target=kwargs.get(ATTR_TARGET), + photo=file_content, + caption=kwargs.get(ATTR_CAPTION), + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) - elif file_type == SERVICE_SEND_STICKER: - msg = await self._send_msg( - self.bot.send_sticker, - "Error sending sticker", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - sticker=file_content, - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) + if file_type == SERVICE_SEND_STICKER: + return await self._send_msgs( + self.bot.send_sticker, + "Error sending sticker", + params[ATTR_MESSAGE_TAG], + target=kwargs.get(ATTR_TARGET), + sticker=file_content, + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) - elif file_type == SERVICE_SEND_VIDEO: - msg = await self._send_msg( - self.bot.send_video, - "Error sending video", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - video=file_content, - caption=kwargs.get(ATTR_CAPTION), - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - parse_mode=params[ATTR_PARSER], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) - elif file_type == SERVICE_SEND_DOCUMENT: - msg = await self._send_msg( - self.bot.send_document, - "Error sending document", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - document=file_content, - caption=kwargs.get(ATTR_CAPTION), - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - parse_mode=params[ATTR_PARSER], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) - elif file_type == SERVICE_SEND_VOICE: - msg = await self._send_msg( - self.bot.send_voice, - "Error sending voice", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - voice=file_content, - caption=kwargs.get(ATTR_CAPTION), - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) - elif file_type == SERVICE_SEND_ANIMATION: - msg = await self._send_msg( - self.bot.send_animation, - "Error sending animation", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - animation=file_content, - caption=kwargs.get(ATTR_CAPTION), - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - parse_mode=params[ATTR_PARSER], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) + if file_type == SERVICE_SEND_VIDEO: + return await self._send_msgs( + self.bot.send_video, + "Error sending video", + params[ATTR_MESSAGE_TAG], + target=kwargs.get(ATTR_TARGET), + video=file_content, + caption=kwargs.get(ATTR_CAPTION), + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) - msg_ids[chat_id] = msg.id - file_content.seek(0) - else: - _LOGGER.error("Can't send file with kwargs: %s", kwargs) + if file_type == SERVICE_SEND_DOCUMENT: + return await self._send_msgs( + self.bot.send_document, + "Error sending document", + params[ATTR_MESSAGE_TAG], + target=kwargs.get(ATTR_TARGET), + document=file_content, + caption=kwargs.get(ATTR_CAPTION), + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) - return msg_ids + if file_type == SERVICE_SEND_VOICE: + return await self._send_msgs( + self.bot.send_voice, + "Error sending voice", + params[ATTR_MESSAGE_TAG], + target=kwargs.get(ATTR_TARGET), + voice=file_content, + caption=kwargs.get(ATTR_CAPTION), + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) + + # SERVICE_SEND_ANIMATION + return await self._send_msgs( + self.bot.send_animation, + "Error sending animation", + params[ATTR_MESSAGE_TAG], + target=kwargs.get(ATTR_TARGET), + animation=file_content, + caption=kwargs.get(ATTR_CAPTION), + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) async def send_sticker( self, - target: Any = None, context: Context | None = None, **kwargs: Any, ) -> dict[int, int]: @@ -844,25 +881,20 @@ async def send_sticker( params = self._get_msg_kwargs(kwargs) stickerid = kwargs.get(ATTR_STICKER_ID) - msg_ids = {} if stickerid: - for chat_id in self.get_target_chat_ids(target): - msg = await self._send_msg( - self.bot.send_sticker, - "Error sending sticker", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - sticker=stickerid, - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - reply_markup=params[ATTR_REPLYMARKUP], - read_timeout=params[ATTR_TIMEOUT], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) - msg_ids[chat_id] = msg.id - return msg_ids - return await self.send_file(SERVICE_SEND_STICKER, target, context, **kwargs) + return await self._send_msgs( + self.bot.send_sticker, + "Error sending sticker", + params[ATTR_MESSAGE_TAG], + sticker=stickerid, + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + reply_markup=params[ATTR_REPLYMARKUP], + read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) + return await self.send_file(SERVICE_SEND_STICKER, context, **kwargs) async def send_location( self, @@ -876,26 +908,18 @@ async def send_location( latitude = float(latitude) longitude = float(longitude) params = self._get_msg_kwargs(kwargs) - msg_ids = {} - for chat_id in self.get_target_chat_ids(target): - _LOGGER.debug( - "Send location %s/%s to chat ID %s", latitude, longitude, chat_id - ) - msg = await self._send_msg( - self.bot.send_location, - "Error sending location", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - latitude=latitude, - longitude=longitude, - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - read_timeout=params[ATTR_TIMEOUT], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) - msg_ids[chat_id] = msg.id - return msg_ids + return await self._send_msgs( + self.bot.send_location, + "Error sending location", + params[ATTR_MESSAGE_TAG], + latitude=latitude, + longitude=longitude, + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) async def send_poll( self, @@ -910,27 +934,21 @@ async def send_poll( """Send a poll.""" params = self._get_msg_kwargs(kwargs) openperiod = kwargs.get(ATTR_OPEN_PERIOD) - msg_ids = {} - for chat_id in self.get_target_chat_ids(target): - _LOGGER.debug("Send poll '%s' to chat ID %s", question, chat_id) - msg = await self._send_msg( - self.bot.send_poll, - "Error sending poll", - params[ATTR_MESSAGE_TAG], - chat_id=chat_id, - question=question, - options=options, - is_anonymous=is_anonymous, - allows_multiple_answers=allows_multiple_answers, - open_period=openperiod, - disable_notification=params[ATTR_DISABLE_NOTIF], - reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - read_timeout=params[ATTR_TIMEOUT], - message_thread_id=params[ATTR_MESSAGE_THREAD_ID], - context=context, - ) - msg_ids[chat_id] = msg.id - return msg_ids + return await self._send_msgs( + self.bot.send_poll, + "Error sending poll", + params[ATTR_MESSAGE_TAG], + question=question, + options=options, + is_anonymous=is_anonymous, + allows_multiple_answers=allows_multiple_answers, + open_period=openperiod, + disable_notification=params[ATTR_DISABLE_NOTIF], + reply_to_message_id=params[ATTR_REPLY_TO_MSGID], + read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], + context=context, + ) async def leave_chat( self, diff --git a/homeassistant/components/telegram_bot/strings.json b/homeassistant/components/telegram_bot/strings.json index 1a4d8c0b30a13e..0a7aa610dd6dc3 100644 --- a/homeassistant/components/telegram_bot/strings.json +++ b/homeassistant/components/telegram_bot/strings.json @@ -1027,6 +1027,9 @@ "invalid_chat_ids": { "message": "Invalid chat IDs: {chat_ids}. Please configure the chat IDs for {bot_name}." }, + "invalid_inline_keyboard": { + "message": "Invalid value for inline keyboard. Only strings or lists are accepted." + }, "failed_chat_ids": { "message": "Failed targets: {chat_ids}. Please verify that the chat IDs for {bot_name} have been configured." }, @@ -1041,6 +1044,9 @@ }, "failed_to_load_file": { "message": "Failed to load file: {error}" + }, + "action_failed": { + "message": "Action failed. {error}" } }, "issues": { diff --git a/homeassistant/components/tesla_fleet/device_tracker.py b/homeassistant/components/tesla_fleet/device_tracker.py index 19bf353c62d459..55f3564bd9ff6a 100644 --- a/homeassistant/components/tesla_fleet/device_tracker.py +++ b/homeassistant/components/tesla_fleet/device_tracker.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.components.device_tracker import TrackerEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_HOME from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/teslemetry/device_tracker.py b/homeassistant/components/teslemetry/device_tracker.py index 0e1b3edf69ada7..84be1d742dcc6d 100644 --- a/homeassistant/components/teslemetry/device_tracker.py +++ b/homeassistant/components/teslemetry/device_tracker.py @@ -9,7 +9,7 @@ from teslemetry_stream import TeslemetryStreamVehicle from teslemetry_stream.const import TeslaLocation -from homeassistant.components.device_tracker.config_entry import ( +from homeassistant.components.device_tracker import ( TrackerEntity, TrackerEntityDescription, ) diff --git a/homeassistant/components/tessie/device_tracker.py b/homeassistant/components/tessie/device_tracker.py index fe81ed673371f9..154bf8c3eb340c 100644 --- a/homeassistant/components/tessie/device_tracker.py +++ b/homeassistant/components/tessie/device_tracker.py @@ -2,7 +2,7 @@ from __future__ import annotations -from homeassistant.components.device_tracker.config_entry import TrackerEntity +from homeassistant.components.device_tracker import TrackerEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType diff --git a/homeassistant/components/volvo/device_tracker.py b/homeassistant/components/volvo/device_tracker.py index 44078c9387deea..62e90032da4199 100644 --- a/homeassistant/components/volvo/device_tracker.py +++ b/homeassistant/components/volvo/device_tracker.py @@ -4,7 +4,7 @@ from volvocarsapi.models import VolvoCarsApiBaseModel, VolvoCarsLocation -from homeassistant.components.device_tracker.config_entry import ( +from homeassistant.components.device_tracker import ( TrackerEntity, TrackerEntityDescription, ) diff --git a/homeassistant/components/xmpp/manifest.json b/homeassistant/components/xmpp/manifest.json index d128e3e511127f..8c3a56da2bf0e7 100644 --- a/homeassistant/components/xmpp/manifest.json +++ b/homeassistant/components/xmpp/manifest.json @@ -6,5 +6,8 @@ "iot_class": "cloud_push", "loggers": ["pyasn1", "slixmpp"], "quality_scale": "legacy", - "requirements": ["slixmpp==1.10.0", "emoji==2.8.0"] + "requirements": [ + "slixmpp==1.10.0;python_version<'3.14'", + "emoji==2.8.0;python_version<'3.14'" + ] } diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index ee57abd769d545..ca622ec670abba 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -9,16 +9,9 @@ import pathlib import random import string +import sys import requests -import slixmpp -from slixmpp.exceptions import IqError, IqTimeout, XMPPError -from slixmpp.plugins.xep_0363.http_upload import ( - FileTooBig, - FileUploadError, - UploadServiceNotFound, -) -from slixmpp.xmlstream.xmlstream import NotConnectedError import voluptuous as vol from homeassistant.components.notify import ( @@ -35,9 +28,20 @@ CONF_SENDER, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, template as template_helper from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +if sys.version_info < (3, 14): + import slixmpp + from slixmpp.exceptions import IqError, IqTimeout, XMPPError + from slixmpp.plugins.xep_0363.http_upload import ( + FileTooBig, + FileUploadError, + UploadServiceNotFound, + ) + from slixmpp.xmlstream.xmlstream import NotConnectedError + _LOGGER = logging.getLogger(__name__) ATTR_DATA = "data" @@ -74,6 +78,10 @@ async def async_get_service( discovery_info: DiscoveryInfoType | None = None, ) -> XmppNotificationService: """Get the Jabber (XMPP) notification service.""" + if sys.version_info >= (3, 14): + raise HomeAssistantError( + "Jabber (XMPP) is not supported on Python 3.14. Please use Python 3.13." + ) return XmppNotificationService( config.get(CONF_SENDER), config.get(CONF_RESOURCE), diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index bd49c9d1e04ed8..0a036ae1dc2570 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3138,7 +3138,7 @@ }, "iron_os": { "name": "IronOS", - "integration_type": "hub", + "integration_type": "device", "config_flow": true, "iot_class": "local_polling" }, diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 36e60447720a08..9af0efd4ab9617 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -132,8 +132,8 @@ backoff>=2.0 # ensure pydantic version does not float since it might have breaking changes pydantic==2.12.2 -# Required for Python 3.12.4 compatibility (#119223). -mashumaro>=3.13.1 +# Required for Python 3.14.0 compatibility (#119223). +mashumaro>=3.17.0 # Breaks asyncio # https://github.com/pubnub/python/issues/130 diff --git a/homeassistant/util/frozen_dataclass_compat.py b/homeassistant/util/frozen_dataclass_compat.py index 518515d4f857c5..a727edf88d5801 100644 --- a/homeassistant/util/frozen_dataclass_compat.py +++ b/homeassistant/util/frozen_dataclass_compat.py @@ -10,6 +10,11 @@ import sys from typing import TYPE_CHECKING, Any, cast, dataclass_transform +if sys.version_info >= (3, 14): + from annotationlib import Format, get_annotations +else: + from typing_extensions import Format, get_annotations + if TYPE_CHECKING: from _typeshed import DataclassInstance @@ -19,7 +24,7 @@ def _class_fields(cls: type, kw_only: bool) -> list[tuple[str, Any, Any]]: Extracted from dataclasses._process_class. """ - cls_annotations = cls.__dict__.get("__annotations__", {}) + cls_annotations = get_annotations(cls, format=Format.FORWARDREF) cls_fields: list[dataclasses.Field[Any]] = [] @@ -96,8 +101,16 @@ class will be a real dataclass, i.e. it's decorated with @dataclass. for parent in cls.__mro__[::-1]: if parent is object: continue - annotations |= parent.__annotations__ - cls.__annotations__ = annotations + annotations |= get_annotations(parent, format=Format.FORWARDREF) + + if "__annotations__" in cls.__dict__ or sys.version_info < (3, 14): + cls.__annotations__ = annotations + else: + + def wrapped_annotate(format: Format) -> dict: + return annotations + + cls.__annotate__ = wrapped_annotate return # First try without setting the kw_only flag, and if that fails, try setting it diff --git a/pylint/plugins/hass_imports.py b/pylint/plugins/hass_imports.py index 38dbf0356047ea..ede92875e1bf5b 100644 --- a/pylint/plugins/hass_imports.py +++ b/pylint/plugins/hass_imports.py @@ -136,7 +136,6 @@ class ObsoleteImportMatch: "ffmpeg", "ffmpeg_motion", "google_assistant", - "hardware", "homeassistant", "homeassistant_hardware", "http", @@ -147,7 +146,6 @@ class ObsoleteImportMatch: "script", "sensor", "stream", - "zha", ) diff --git a/pyproject.toml b/pyproject.toml index 99c4f3a7a53118..d1d78d939552bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ classifiers = [ "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Home Automation", ] requires-python = ">=3.13.2" @@ -569,6 +570,11 @@ filterwarnings = [ # https://pypi.org/project/opuslib/ - v3.0.1 - 2018-01-16 # https://pypi.org/project/pyiss/ - v1.0.1 - 2016-12-19 "ignore:\"is.*\" with '.*' literal:SyntaxWarning:importlib._bootstrap", + # - SyntaxWarning - return in finally + # https://github.com/nextcord/nextcord/pull/1268 - v3.1.1 - 2025-08-16 + # https://github.com/Python-roborock/python-roborock/ - >=2.50.4 + # https://pypi.org/project/sleekxmppfs/ - v1.4.1 - 2022-08-18 + "ignore:'return' in a 'finally' block:SyntaxWarning:importlib._bootstrap", # -- New in Python 3.13 # https://github.com/youknowone/python-deadlib - Backports for aifc, telnetlib @@ -577,6 +583,28 @@ filterwarnings = [ "ignore:telnetlib was removed in Python 3.13.*'standard-telnetlib':DeprecationWarning:ndms2_client.connection", "ignore:telnetlib was removed in Python 3.13.*'standard-telnetlib':DeprecationWarning:pyws66i", + # -- New in Python 3.14 + # https://github.com/kumaraditya303/aioshutil - v1.5 - 2024-07-20 + "ignore:'shutil.ExecError' is deprecated and slated for removal in Python 3.16:DeprecationWarning:aioshutil", + # https://github.com/litl/backoff/pull/220 - v2.2.1 - 2022-10-05 (archived) + "ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:(backoff._decorator|backoff._async)", + # https://github.com/albertogeniola/elmax-api - v0.0.6.3 - 2024-11-30 + "ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:elmax_api.http", + # https://github.com/py-mine/mcstatus - v12.0.5 - 2025-08-13 + "ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:mcstatus.utils", + # https://github.com/nextcord/nextcord/pull/1269 - v3.1.1 - 2025-08-16 + "ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:nextcord.member", + # https://github.com/andrewsayre/pyheos/pull/124 - >1.0.5 + "ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:pyheos.dispatch", + # https://github.com/SteveEasley/pykaleidescape/pull/7 - v2022.2.6 - 2022-03-07 + "ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:kaleidescape.dispatcher", + # https://github.com/svinota/pyroute2 + "ignore:Due to '_pack_', the '.*' Structure will use memory layout compatible with MSVC:DeprecationWarning:pyroute2.ethtool.ioctl", + # https://github.com/googleapis/python-genai + "ignore:'_UnionGenericAlias' is deprecated and slated for removal in Python 3.17:DeprecationWarning:google.genai.types", + # https://github.com/pyusb/pyusb + "ignore:Due to '_pack_', the '.*' Structure will use memory layout compatible with MSVC:DeprecationWarning:usb.backend.libusb0", + # -- Websockets 14.1 # https://websockets.readthedocs.io/en/stable/howto/upgrade.html "ignore:websockets.legacy is deprecated:DeprecationWarning:websockets.legacy", diff --git a/requirements_all.txt b/requirements_all.txt index 399383d681af0d..57d5c0fe903f2a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -109,7 +109,10 @@ PyXiaomiGateway==0.14.3 RachioPy==1.1.0 # homeassistant.components.python_script -RestrictedPython==8.0 +RestrictedPython==8.0;python_version<'3.14' + +# homeassistant.components.python_script +RestrictedPython==8.1a1.dev0;python_version>='3.14' # homeassistant.components.remember_the_milk RtmAPI==0.7.2 @@ -176,7 +179,7 @@ aio-geojson-usgs-earthquakes==0.3 aio-georss-gdacs==0.10 # homeassistant.components.onewire -aio-ownet==0.0.3 +aio-ownet==0.0.4 # homeassistant.components.acaia aioacaia==0.1.17 @@ -268,7 +271,7 @@ aiogithubapi==24.6.0 aioguardian==2022.07.0 # homeassistant.components.harmony -aioharmony==0.5.3 +aioharmony==0.5.3;python_version<'3.14' # homeassistant.components.hassio aiohasupervisor==0.3.3 @@ -883,7 +886,7 @@ elmax-api==0.0.6.4rc0 elvia==0.1.0 # homeassistant.components.xmpp -emoji==2.8.0 +emoji==2.8.0;python_version<'3.14' # homeassistant.components.emulated_roku emulated-roku==0.3.0 @@ -1127,7 +1130,7 @@ gspread==5.5.0 gstreamer-player==1.1.2 # homeassistant.components.profiler -guppy3==3.1.5 +guppy3==3.1.5;python_version<'3.14' # homeassistant.components.iaqualink h2==4.3.0 @@ -1885,7 +1888,7 @@ pyatag==0.3.5.3 pyatmo==9.2.3 # homeassistant.components.apple_tv -pyatv==0.16.1 +pyatv==0.16.1;python_version<'3.14' # homeassistant.components.aussie_broadband pyaussiebb==0.1.5 @@ -2846,7 +2849,7 @@ skyboxremote==0.0.6 slack_sdk==3.33.4 # homeassistant.components.xmpp -slixmpp==1.10.0 +slixmpp==1.10.0;python_version<'3.14' # homeassistant.components.smart_meter_texas smart-meter-texas==0.5.5 diff --git a/requirements_test.txt b/requirements_test.txt index ddcb51ef5f532e..1135d258535e97 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -18,7 +18,7 @@ mock-open==1.4.0 mypy-dev==1.19.0a4 pre-commit==4.2.0 pydantic==2.12.2 -pylint==4.0.0 +pylint==4.0.1 pylint-per-file-ignores==1.4.0 pipdeptree==2.26.1 pytest-asyncio==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b4600989f1a6eb..74f9f2fff0c1f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -103,7 +103,10 @@ PyXiaomiGateway==0.14.3 RachioPy==1.1.0 # homeassistant.components.python_script -RestrictedPython==8.0 +RestrictedPython==8.0;python_version<'3.14' + +# homeassistant.components.python_script +RestrictedPython==8.1a1.dev0;python_version>='3.14' # homeassistant.components.remember_the_milk RtmAPI==0.7.2 @@ -164,7 +167,7 @@ aio-geojson-usgs-earthquakes==0.3 aio-georss-gdacs==0.10 # homeassistant.components.onewire -aio-ownet==0.0.3 +aio-ownet==0.0.4 # homeassistant.components.acaia aioacaia==0.1.17 @@ -253,7 +256,7 @@ aiogithubapi==24.6.0 aioguardian==2022.07.0 # homeassistant.components.harmony -aioharmony==0.5.3 +aioharmony==0.5.3;python_version<'3.14' # homeassistant.components.hassio aiohasupervisor==0.3.3 @@ -988,7 +991,7 @@ gspread==5.5.0 gstreamer-player==1.1.2 # homeassistant.components.profiler -guppy3==3.1.5 +guppy3==3.1.5;python_version<'3.14' # homeassistant.components.iaqualink h2==4.3.0 @@ -1593,7 +1596,7 @@ pyatag==0.3.5.3 pyatmo==9.2.3 # homeassistant.components.apple_tv -pyatv==0.16.1 +pyatv==0.16.1;python_version<'3.14' # homeassistant.components.aussie_broadband pyaussiebb==0.1.5 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 749cbe99783bb3..4c4941851170c1 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -157,8 +157,8 @@ # ensure pydantic version does not float since it might have breaking changes pydantic==2.12.2 -# Required for Python 3.12.4 compatibility (#119223). -mashumaro>=3.13.1 +# Required for Python 3.14.0 compatibility (#119223). +mashumaro>=3.17.0 # Breaks asyncio # https://github.com/pubnub/python/issues/130 diff --git a/tests/components/apple_tv/__init__.py b/tests/components/apple_tv/__init__.py index 514d77bde4da4f..e06e5deaa1524b 100644 --- a/tests/components/apple_tv/__init__.py +++ b/tests/components/apple_tv/__init__.py @@ -1,6 +1,9 @@ """Tests for Apple TV.""" +import sys + import pytest -# Make asserts in the common module display differences -pytest.register_assert_rewrite("tests.components.apple_tv.common") +if sys.version_info < (3, 14): + # Make asserts in the common module display differences + pytest.register_assert_rewrite("tests.components.apple_tv.common") diff --git a/tests/components/apple_tv/conftest.py b/tests/components/apple_tv/conftest.py index 78982a8d51cfd9..22311f91b7796a 100644 --- a/tests/components/apple_tv/conftest.py +++ b/tests/components/apple_tv/conftest.py @@ -1,14 +1,20 @@ """Fixtures for component.""" from collections.abc import Generator +import sys from unittest.mock import AsyncMock, MagicMock, patch -from pyatv import conf -from pyatv.const import PairingRequirement, Protocol -from pyatv.support import http import pytest -from .common import MockPairingHandler, airplay_service, create_conf, mrp_service +if sys.version_info < (3, 14): + from pyatv import conf + from pyatv.const import PairingRequirement, Protocol + from pyatv.support import http + + from .common import MockPairingHandler, airplay_service, create_conf, mrp_service + +if sys.version_info >= (3, 14): + collect_ignore_glob = ["test_*.py"] @pytest.fixture(autouse=True, name="mock_scan") diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index c782605aab33fb..f220b233b1dafb 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -44,13 +44,12 @@ from tests.common import MockConfigEntry, async_fire_time_changed -SENSORS_DEFAULT = [*SENSORS_BYTES, *SENSORS_RATES] +SENSORS_DEFAULT = [*SENSORS_BYTES, *SENSORS_LOAD_AVG, *SENSORS_RATES] -SENSORS_ALL_LEGACY = [*SENSORS_DEFAULT, *SENSORS_LOAD_AVG, *SENSORS_TEMPERATURES_LEGACY] +SENSORS_ALL_LEGACY = [*SENSORS_DEFAULT, *SENSORS_TEMPERATURES_LEGACY] SENSORS_ALL_HTTP = [ - *SENSORS_DEFAULT, *SENSORS_CPU, - *SENSORS_LOAD_AVG, + *SENSORS_DEFAULT, *SENSORS_MEMORY, *SENSORS_TEMPERATURES, *SENSORS_UPTIME, @@ -148,6 +147,9 @@ async def _test_sensors( assert hass.states.get(f"{sensor_prefix}_sensor_tx_rates").state == "80.0" assert hass.states.get(f"{sensor_prefix}_sensor_tx_bytes").state == "50.0" assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2" + assert hass.states.get(f"{sensor_prefix}_sensor_load_avg1").state == "1.1" + assert hass.states.get(f"{sensor_prefix}_sensor_load_avg5").state == "1.2" + assert hass.states.get(f"{sensor_prefix}_sensor_load_avg15").state == "1.3" # remove first tracked device mock_devices.pop(MOCK_MACS[0]) @@ -609,13 +611,17 @@ async def test_decorator_errors( mock_available_temps, ) -> None: """Test AsusWRT sensors are unavailable on decorator type check error.""" - sensors = [*SENSORS_BYTES, *SENSORS_TEMPERATURES_LEGACY] + sensors = SENSORS_ALL_LEGACY config_entry, sensor_prefix = _setup_entry(hass, CONFIG_DATA_TELNET, sensors) config_entry.add_to_hass(hass) mock_available_temps[1] = True connect_legacy.return_value.async_get_bytes_total.return_value = "bad_response" + connect_legacy.return_value.async_get_current_transfer_rates.return_value = ( + "bad_response" + ) connect_legacy.return_value.async_get_temperature.return_value = "bad_response" + connect_legacy.return_value.async_get_loadavg.return_value = "bad_response" # initial devices setup assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/derivative/test_config_flow.py b/tests/components/derivative/test_config_flow.py index 09e9fc9f07dfce..13a6307c914d66 100644 --- a/tests/components/derivative/test_config_flow.py +++ b/tests/components/derivative/test_config_flow.py @@ -102,6 +102,7 @@ async def test_options( hass.states.async_set("sensor.input", 10, {"unit_of_measurement": "dog"}) hass.states.async_set("sensor.valid", 10, {"unit_of_measurement": "dog"}) hass.states.async_set("sensor.invalid", 10, {"unit_of_measurement": "cat"}) + await hass.async_block_till_done() result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] is FlowResultType.FORM @@ -119,6 +120,11 @@ async def test_options( "sensor.valid", ] + state = hass.states.get(f"{platform}.my_derivative") + assert state.attributes["unit_of_measurement"] == f"{unit_prefix_used}dog/min" + hass.states.async_set("sensor.valid", 10, {"unit_of_measurement": "cat"}) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ @@ -188,12 +194,11 @@ async def test_update_unit(hass: HomeAssistant) -> None: time = dt_util.utcnow() with freeze_time(time) as freezer: # First state update of the source. - # Derivative does not learn the unit yet. hass.states.async_set(source_id, 5, {"unit_of_measurement": "dogs"}) await hass.async_block_till_done() state = hass.states.get(derivative_id) assert state.state == "0.0" - assert state.attributes.get("unit_of_measurement") is None + assert state.attributes.get("unit_of_measurement") == "dogs/min" # Second state update of the source. time += timedelta(minutes=1) @@ -217,10 +222,10 @@ async def test_update_unit(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - # Check the state after reconfigure. Neither unit or state has changed. + # Check the state after reconfigure. state = hass.states.get(derivative_id) - assert state.state == "2.0" - assert state.attributes.get("unit_of_measurement") == "dogs/min" + assert state.state == "0.0" + assert state.attributes.get("unit_of_measurement") == "dogs/s" # Third state update of the source. time += timedelta(seconds=1) @@ -229,9 +234,7 @@ async def test_update_unit(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get(derivative_id) assert state.state == "3.0" - # While the state is correctly reporting a state of 3 dogs per second, it incorrectly keeps - # the unit as dogs/min - assert state.attributes.get("unit_of_measurement") == "dogs/min" + assert state.attributes.get("unit_of_measurement") == "dogs/s" # Fourth state update of the source. time += timedelta(seconds=1) @@ -240,4 +243,4 @@ async def test_update_unit(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get(derivative_id) assert state.state == "10.0" - assert state.attributes.get("unit_of_measurement") == "dogs/min" + assert state.attributes.get("unit_of_measurement") == "dogs/s" diff --git a/tests/components/derivative/test_init.py b/tests/components/derivative/test_init.py index f2f505bd2e719a..0132f4055fc3d8 100644 --- a/tests/components/derivative/test_init.py +++ b/tests/components/derivative/test_init.py @@ -36,7 +36,9 @@ async def test_setup_and_remove_config_entry( input_sensor_entity_id = "sensor.input" derivative_entity_id = "sensor.my_derivative" - hass.states.async_set(input_sensor_entity_id, "10.0", {}) + hass.states.async_set( + input_sensor_entity_id, "10.0", {"unit_of_measurement": "dog"} + ) await hass.async_block_till_done() # Setup the config entry @@ -63,7 +65,7 @@ async def test_setup_and_remove_config_entry( # Check the platform is setup correctly state = hass.states.get(derivative_entity_id) assert state.state == "0.0" - assert "unit_of_measurement" not in state.attributes + assert state.attributes["unit_of_measurement"] == "kdog/min" assert state.attributes["source"] == "sensor.input" hass.states.async_set(input_sensor_entity_id, 10, {"unit_of_measurement": "dog"}) diff --git a/tests/components/derivative/test_sensor.py b/tests/components/derivative/test_sensor.py index 52b7ae725ea750..95fe4b5cf846ab 100644 --- a/tests/components/derivative/test_sensor.py +++ b/tests/components/derivative/test_sensor.py @@ -332,8 +332,8 @@ def _capture_event(event: Event) -> None: await hass.async_block_till_done() await hass.async_block_till_done() - assert len(events[2:]) == len(times) - for time, event in zip(times, events[2:], strict=True): + assert len(events[1:]) == len(times) + for time, event in zip(times, events[1:], strict=True): state = event.data["new_state"] derivative = round(float(state.state), config["sensor"]["round"]) @@ -882,12 +882,12 @@ async def test_unavailable_boot( "sensor.power", restore_state, { - "unit_of_measurement": "W", + "unit_of_measurement": "kWh/s", }, ), { "native_value": restore_state, - "native_unit_of_measurement": "W", + "native_unit_of_measurement": "kWh/s", }, ), ], @@ -903,7 +903,7 @@ async def test_unavailable_boot( config = {"sensor": config} entity_id = config["sensor"]["source"] - hass.states.async_set(entity_id, STATE_UNAVAILABLE, {}) + hass.states.async_set(entity_id, STATE_UNAVAILABLE, {"unit_of_measurement": "kWh"}) await hass.async_block_till_done() assert await async_setup_component(hass, "sensor", config) @@ -917,7 +917,7 @@ async def test_unavailable_boot( base = dt_util.utcnow() with freeze_time(base) as freezer: freezer.move_to(base + timedelta(seconds=1)) - hass.states.async_set(entity_id, 10, {}) + hass.states.async_set(entity_id, 10, {"unit_of_measurement": "kWh"}) await hass.async_block_till_done() state = hass.states.get("sensor.power") @@ -927,13 +927,14 @@ async def test_unavailable_boot( assert state.state == restore_state freezer.move_to(base + timedelta(seconds=2)) - hass.states.async_set(entity_id, 15, {}) + hass.states.async_set(entity_id, 15, {"unit_of_measurement": "kWh"}) await hass.async_block_till_done() state = hass.states.get("sensor.power") assert state is not None # Now that the source sensor has two valid datapoints, we can calculate derivative assert state.state == "5.00" + assert state.attributes.get("unit_of_measurement") == "kWh/s" async def test_source_unit_change( @@ -966,7 +967,7 @@ async def test_source_unit_change( await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == "0.000" - assert state.attributes.get("unit_of_measurement") is None + assert state.attributes.get("unit_of_measurement") == "cats/s" # Second state update of the source. time += timedelta(seconds=1) @@ -978,21 +979,22 @@ async def test_source_unit_change( assert state.attributes.get("unit_of_measurement") == "cats/s" # Third state update of the source, source unit changes to dogs. - # Ignored by derivative which continues reporting cats. + # Derivative switches to dogs/s, and resets state to zero, as we + # don't want to generate bogus data from the change. time += timedelta(seconds=1) freezer.move_to(time) hass.states.async_set(source_id, "12", {"unit_of_measurement": "dogs"}) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert state.state == "5.000" - assert state.attributes.get("unit_of_measurement") == "cats/s" + assert state.state == "0.000" + assert state.attributes.get("unit_of_measurement") == "dogs/s" # Fourth state update of the source, still dogs. - # Ignored by derivative which continues reporting cats. + # Now correctly updating derivative as dogs/s. time += timedelta(seconds=1) freezer.move_to(time) hass.states.async_set(source_id, "20", {"unit_of_measurement": "dogs"}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == "8.000" - assert state.attributes.get("unit_of_measurement") == "cats/s" + assert state.attributes.get("unit_of_measurement") == "dogs/s" diff --git a/tests/components/firefly_iii/conftest.py b/tests/components/firefly_iii/conftest.py index 18250624ca7383..7c9aa4c2bbdd1f 100644 --- a/tests/components/firefly_iii/conftest.py +++ b/tests/components/firefly_iii/conftest.py @@ -92,4 +92,5 @@ def mock_config_entry() -> MockConfigEntry: title="Firefly III test", data=MOCK_TEST_CONFIG, entry_id="firefly_iii_test_entry_123", + unique_id="firefly_iii_test_unique_id_123", ) diff --git a/tests/components/firefly_iii/snapshots/test_sensor.ambr b/tests/components/firefly_iii/snapshots/test_sensor.ambr index bccd54746ec67b..143905fcfdb9a0 100644 --- a/tests/components/firefly_iii/snapshots/test_sensor.ambr +++ b/tests/components/firefly_iii/snapshots/test_sensor.ambr @@ -1,5 +1,5 @@ # serializer version: 1 -# name: test_all_entities[sensor.firefly_iii_test_credit_card-entry] +# name: test_all_entities[sensor.credit_card_account_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -14,7 +14,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.firefly_iii_test_credit_card', + 'entity_id': 'sensor.credit_card_account_balance', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -25,35 +25,131 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:hand-coin', - 'original_name': 'Credit Card', + 'original_icon': None, + 'original_name': 'Account Balance', 'platform': 'firefly_iii', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'account', - 'unique_id': 'None_account_type_4', + 'translation_key': 'account_balance', + 'unique_id': 'firefly_iii_test_unique_id_123_account_4_account_balance', 'unit_of_measurement': 'AMS', }) # --- -# name: test_all_entities[sensor.firefly_iii_test_credit_card-state] +# name: test_all_entities[sensor.credit_card_account_balance-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'monetary', - 'friendly_name': 'Firefly III test Credit Card', - 'icon': 'mdi:hand-coin', + 'friendly_name': 'Credit Card Account Balance', 'state_class': , 'unit_of_measurement': 'AMS', }), 'context': , - 'entity_id': 'sensor.firefly_iii_test_credit_card', + 'entity_id': 'sensor.credit_card_account_balance', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '-250.00', }) # --- -# name: test_all_entities[sensor.firefly_iii_test_lunch-entry] +# name: test_all_entities[sensor.credit_card_account_role-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.credit_card_account_role', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Account Role', + 'platform': 'firefly_iii', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'account_role', + 'unique_id': 'firefly_iii_test_unique_id_123_account_4_account_role', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.credit_card_account_role-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Credit Card Account Role', + }), + 'context': , + 'entity_id': 'sensor.credit_card_account_role', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'creditCard', + }) +# --- +# name: test_all_entities[sensor.credit_card_account_type-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.credit_card_account_type', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:hand-coin', + 'original_name': 'Account Type', + 'platform': 'firefly_iii', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'account_type', + 'unique_id': 'firefly_iii_test_unique_id_123_account_4_account_type', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.credit_card_account_type-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Credit Card Account Type', + 'icon': 'mdi:hand-coin', + }), + 'context': , + 'entity_id': 'sensor.credit_card_account_type', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'liability', + }) +# --- +# name: test_all_entities[sensor.lunch_earned_spent-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -68,7 +164,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.firefly_iii_test_lunch', + 'entity_id': 'sensor.lunch_earned_spent', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -80,33 +176,33 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Lunch', + 'original_name': 'Earned/Spent', 'platform': 'firefly_iii', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'category', - 'unique_id': 'None_category_2', + 'unique_id': 'firefly_iii_test_unique_id_123_category_2_category', 'unit_of_measurement': 'AMS', }) # --- -# name: test_all_entities[sensor.firefly_iii_test_lunch-state] +# name: test_all_entities[sensor.lunch_earned_spent-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'monetary', - 'friendly_name': 'Firefly III test Lunch', + 'friendly_name': 'Lunch Earned/Spent', 'state_class': , 'unit_of_measurement': 'AMS', }), 'context': , - 'entity_id': 'sensor.firefly_iii_test_lunch', + 'entity_id': 'sensor.lunch_earned_spent', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '-12300.0', }) # --- -# name: test_all_entities[sensor.firefly_iii_test_my_checking_account-entry] +# name: test_all_entities[sensor.my_checking_account_account_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -121,7 +217,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.firefly_iii_test_my_checking_account', + 'entity_id': 'sensor.my_checking_account_account_balance', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -132,35 +228,131 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:account-cash', - 'original_name': 'My checking account', + 'original_icon': None, + 'original_name': 'Account Balance', 'platform': 'firefly_iii', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'account', - 'unique_id': 'None_account_type_2', + 'translation_key': 'account_balance', + 'unique_id': 'firefly_iii_test_unique_id_123_account_2_account_balance', 'unit_of_measurement': 'AMS', }) # --- -# name: test_all_entities[sensor.firefly_iii_test_my_checking_account-state] +# name: test_all_entities[sensor.my_checking_account_account_balance-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'monetary', - 'friendly_name': 'Firefly III test My checking account', - 'icon': 'mdi:account-cash', + 'friendly_name': 'My checking account Account Balance', 'state_class': , 'unit_of_measurement': 'AMS', }), 'context': , - 'entity_id': 'sensor.firefly_iii_test_my_checking_account', + 'entity_id': 'sensor.my_checking_account_account_balance', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '123.45', }) # --- -# name: test_all_entities[sensor.firefly_iii_test_savings_account-entry] +# name: test_all_entities[sensor.my_checking_account_account_role-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_checking_account_account_role', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Account Role', + 'platform': 'firefly_iii', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'account_role', + 'unique_id': 'firefly_iii_test_unique_id_123_account_2_account_role', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.my_checking_account_account_role-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'My checking account Account Role', + }), + 'context': , + 'entity_id': 'sensor.my_checking_account_account_role', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'default_asset', + }) +# --- +# name: test_all_entities[sensor.my_checking_account_account_type-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_checking_account_account_type', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:account-cash', + 'original_name': 'Account Type', + 'platform': 'firefly_iii', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'account_type', + 'unique_id': 'firefly_iii_test_unique_id_123_account_2_account_type', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.my_checking_account_account_type-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'My checking account Account Type', + 'icon': 'mdi:account-cash', + }), + 'context': , + 'entity_id': 'sensor.my_checking_account_account_type', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'asset', + }) +# --- +# name: test_all_entities[sensor.savings_account_account_balance-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -175,7 +367,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.firefly_iii_test_savings_account', + 'entity_id': 'sensor.savings_account_account_balance', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -186,31 +378,127 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:cash-minus', - 'original_name': 'Savings Account', + 'original_icon': None, + 'original_name': 'Account Balance', 'platform': 'firefly_iii', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'account', - 'unique_id': 'None_account_type_3', + 'translation_key': 'account_balance', + 'unique_id': 'firefly_iii_test_unique_id_123_account_3_account_balance', 'unit_of_measurement': 'AMS', }) # --- -# name: test_all_entities[sensor.firefly_iii_test_savings_account-state] +# name: test_all_entities[sensor.savings_account_account_balance-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'monetary', - 'friendly_name': 'Firefly III test Savings Account', - 'icon': 'mdi:cash-minus', + 'friendly_name': 'Savings Account Account Balance', 'state_class': , 'unit_of_measurement': 'AMS', }), 'context': , - 'entity_id': 'sensor.firefly_iii_test_savings_account', + 'entity_id': 'sensor.savings_account_account_balance', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '5000.00', }) # --- +# name: test_all_entities[sensor.savings_account_account_role-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.savings_account_account_role', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Account Role', + 'platform': 'firefly_iii', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'account_role', + 'unique_id': 'firefly_iii_test_unique_id_123_account_3_account_role', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.savings_account_account_role-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Savings Account Account Role', + }), + 'context': , + 'entity_id': 'sensor.savings_account_account_role', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'savingsAsset', + }) +# --- +# name: test_all_entities[sensor.savings_account_account_type-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.savings_account_account_type', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:cash-minus', + 'original_name': 'Account Type', + 'platform': 'firefly_iii', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'account_type', + 'unique_id': 'firefly_iii_test_unique_id_123_account_3_account_type', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.savings_account_account_type-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Savings Account Account Type', + 'icon': 'mdi:cash-minus', + }), + 'context': , + 'entity_id': 'sensor.savings_account_account_type', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'expense', + }) +# --- diff --git a/tests/components/firefly_iii/test_sensor.py b/tests/components/firefly_iii/test_sensor.py index aa674c27910385..bcc4907051a8d0 100644 --- a/tests/components/firefly_iii/test_sensor.py +++ b/tests/components/firefly_iii/test_sensor.py @@ -66,5 +66,6 @@ async def test_refresh_exceptions( async_fire_time_changed(hass, dt_util.utcnow()) await hass.async_block_till_done() - state = hass.states.get("sensor.firefly_iii_test_credit_card") + state = hass.states.get("sensor.credit_card_account_balance") + assert state is not None assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index 759770e9746573..701a6410e739c4 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -1,9 +1,11 @@ """Fixtures for harmony tests.""" +from __future__ import annotations + from collections.abc import Generator +import sys from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch -from aioharmony.const import ClientCallbackType import pytest from homeassistant.components.harmony.const import ACTIVITY_POWER_OFF, DOMAIN @@ -18,6 +20,13 @@ from tests.common import MockConfigEntry +if sys.version_info < (3, 14): + from aioharmony.const import ClientCallbackType + +if sys.version_info >= (3, 14): + collect_ignore_glob = ["test_*.py"] + + ACTIVITIES_TO_IDS = { ACTIVITY_POWER_OFF: -1, "Watch TV": WATCH_TV_ACTIVITY_ID, diff --git a/tests/components/homee/test_binary_sensor.py b/tests/components/homee/test_binary_sensor.py index ef3cf8ecee350e..9cfca38467659a 100644 --- a/tests/components/homee/test_binary_sensor.py +++ b/tests/components/homee/test_binary_sensor.py @@ -46,7 +46,7 @@ async def test_add_device( added_node = build_mock_node("add_device.json") mock_homee.nodes.append(added_node) mock_homee.get_node_by_id.return_value = mock_homee.nodes[1] - await mock_homee.add_nodes_listener.call_args_list[0][0][0](added_node, True) + await mock_homee.add_nodes_listener.call_args_list[1][0][0](added_node, True) await hass.async_block_till_done() await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) diff --git a/tests/components/homee/test_init.py b/tests/components/homee/test_init.py index c24cb39295dda8..8dde59e967f1f1 100644 --- a/tests/components/homee/test_init.py +++ b/tests/components/homee/test_init.py @@ -143,3 +143,60 @@ async def test_unload_entry( await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +async def test_remove_stale_device_on_startup( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test removal of stale device on startup.""" + mock_homee.nodes = [ + build_mock_node("homee.json"), + build_mock_node("light_single.json"), # id 2 + build_mock_node("add_device.json"), # id 3 + ] + mock_homee.get_node_by_id = lambda node_id: mock_homee.nodes[node_id - 1] + await setup_integration(hass, mock_config_entry) + + device = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}-3")}) + assert device is not None + + mock_homee.nodes.pop() # Remove node with id 3 + # Reload integration + await hass.config_entries.async_reload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + # Stale device should be removed + device = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}-3")}) + assert device is None + + +async def test_remove_node_callback( + hass: HomeAssistant, + mock_homee: MagicMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test removal of device when node is removed in homee.""" + mock_homee.nodes = [ + build_mock_node("homee.json"), + build_mock_node("light_single.json"), # id 2 + build_mock_node("add_device.json"), # id 3 + ] + mock_homee.get_node_by_id = lambda node_id: mock_homee.nodes[node_id - 1] + await setup_integration(hass, mock_config_entry) + + device = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}-3")}) + assert device is not None + + # Simulate removal of node with id 3 in homee + await mock_homee.add_nodes_listener.call_args_list[0][0][0]( + mock_homee.nodes[2], add=False + ) + await hass.async_block_till_done() + + # Device should be removed + device = device_registry.async_get_device(identifiers={(DOMAIN, f"{HOMEE_ID}-3")}) + assert device is None diff --git a/tests/components/playstation_network/test_init.py b/tests/components/playstation_network/test_init.py index f3fc37a7264264..571808d17ff02f 100644 --- a/tests/components/playstation_network/test_init.py +++ b/tests/components/playstation_network/test_init.py @@ -130,7 +130,7 @@ async def test_trophy_title_coordinator( assert config_entry.state is ConfigEntryState.LOADED assert len(mock_psnawpapi.user.return_value.trophy_titles.mock_calls) == 1 - freezer.tick(timedelta(days=1)) + freezer.tick(timedelta(days=1, seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done() @@ -155,7 +155,7 @@ async def test_trophy_title_coordinator_auth_failed( PSNAWPAuthenticationError ) - freezer.tick(timedelta(days=1)) + freezer.tick(timedelta(days=1, seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True) @@ -192,7 +192,7 @@ async def test_trophy_title_coordinator_update_data_failed( mock_psnawpapi.user.return_value.trophy_titles.side_effect = exception - freezer.tick(timedelta(days=1)) + freezer.tick(timedelta(days=1, seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True) @@ -223,7 +223,7 @@ async def test_trophy_title_coordinator_doesnt_update( assert config_entry.state is ConfigEntryState.LOADED assert len(mock_psnawpapi.user.return_value.trophy_titles.mock_calls) == 1 - freezer.tick(timedelta(days=1)) + freezer.tick(timedelta(days=1, seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done() @@ -252,7 +252,7 @@ async def test_trophy_title_coordinator_play_new_game( mock_psnawpapi.user.return_value.trophy_titles.return_value = _tmp - freezer.tick(timedelta(days=1)) + freezer.tick(timedelta(days=1, seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True) diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index 941d639a419a34..de90f8498310aa 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -6,6 +6,7 @@ import os from pathlib import Path import socket +import sys from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory @@ -72,6 +73,9 @@ def _mock_path(filename: str) -> str: await hass.async_block_till_done() +@pytest.mark.skipif( + sys.version_info >= (3, 14), reason="not yet available on Python 3.14" +) async def test_memory_usage(hass: HomeAssistant, tmp_path: Path) -> None: """Test we can setup and the service is registered.""" test_dir = tmp_path / "profiles" @@ -103,6 +107,24 @@ def _mock_path(filename: str) -> str: await hass.async_block_till_done() +@pytest.mark.skipif(sys.version_info < (3, 14), reason="still works on python 3.13") +async def test_memory_usage_py313(hass: HomeAssistant, tmp_path: Path) -> None: + """Test raise an error on python3.13.""" + entry = MockConfigEntry(domain=DOMAIN) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert hass.services.has_service(DOMAIN, SERVICE_MEMORY) + with pytest.raises( + HomeAssistantError, + match="Memory profiling is not supported on Python 3.14. Please use Python 3.13.", + ): + await hass.services.async_call( + DOMAIN, SERVICE_MEMORY, {CONF_SECONDS: 0.000001}, blocking=True + ) + + async def test_object_growth_logging( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, diff --git a/tests/components/shelly/snapshots/test_button.ambr b/tests/components/shelly/snapshots/test_button.ambr index 7ec15e7b1db02b..2f890ab215273f 100644 --- a/tests/components/shelly/snapshots/test_button.ambr +++ b/tests/components/shelly/snapshots/test_button.ambr @@ -47,7 +47,7 @@ 'state': 'unknown', }) # --- -# name: test_rpc_button[button.test_name_reboot-entry] +# name: test_rpc_button[button.test_name_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -60,7 +60,7 @@ 'disabled_by': None, 'domain': 'button', 'entity_category': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -72,7 +72,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Reboot', + 'original_name': 'Restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -82,14 +82,14 @@ 'unit_of_measurement': None, }) # --- -# name: test_rpc_button[button.test_name_reboot-state] +# name: test_rpc_button[button.test_name_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'restart', - 'friendly_name': 'Test name Reboot', + 'friendly_name': 'Test name Restart', }), 'context': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'last_changed': , 'last_reported': , 'last_updated': , diff --git a/tests/components/shelly/snapshots/test_devices.ambr b/tests/components/shelly/snapshots/test_devices.ambr index 4a8efa560b36cb..6f1370db6570cf 100644 --- a/tests/components/shelly/snapshots/test_devices.ambr +++ b/tests/components/shelly/snapshots/test_devices.ambr @@ -391,7 +391,7 @@ 'state': 'off', }) # --- -# name: test_shelly_2pm_gen3_cover[button.test_name_reboot-entry] +# name: test_shelly_2pm_gen3_cover[button.test_name_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -404,7 +404,7 @@ 'disabled_by': None, 'domain': 'button', 'entity_category': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -416,7 +416,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Reboot', + 'original_name': 'Restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -426,14 +426,14 @@ 'unit_of_measurement': None, }) # --- -# name: test_shelly_2pm_gen3_cover[button.test_name_reboot-state] +# name: test_shelly_2pm_gen3_cover[button.test_name_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'restart', - 'friendly_name': 'Test name Reboot', + 'friendly_name': 'Test name Restart', }), 'context': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'last_changed': , 'last_reported': , 'last_updated': , @@ -546,13 +546,13 @@ 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_frequency-entry] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -561,7 +561,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_frequency', + 'entity_id': 'sensor.test_name_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -571,38 +571,41 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Frequency', + 'original_name': 'Energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-cover:0-freq', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-cover:0-energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_frequency-state] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Test name Frequency', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_frequency', + 'entity_id': 'sensor.test_name_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '50.0', + 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_power-entry] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_frequency-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -617,7 +620,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_power', + 'entity_id': 'sensor.test_name_frequency', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -630,42 +633,40 @@ 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Frequency', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-cover:0-power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-cover:0-freq', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_power-state] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_frequency-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test name Power', + 'device_class': 'frequency', + 'friendly_name': 'Test name Frequency', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_power', + 'entity_id': 'sensor.test_name_frequency', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '50.0', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_rssi-entry] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_last_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -673,7 +674,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_id': 'sensor.test_name_last_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -683,35 +684,33 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'RSSI', + 'original_name': 'Last restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-wifi-rssi', - 'unit_of_measurement': 'dBm', + 'unique_id': '123456789ABC-sys-uptime', + 'unit_of_measurement': None, }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_rssi-state] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_last_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'signal_strength', - 'friendly_name': 'Test name RSSI', - 'state_class': , - 'unit_of_measurement': 'dBm', + 'device_class': 'timestamp', + 'friendly_name': 'Test name Last restart', }), 'context': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_id': 'sensor.test_name_last_restart', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-53', + 'state': '2025-05-26T15:57:39+00:00', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_temperature-entry] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -725,8 +724,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_name_temperature', + 'entity_category': None, + 'entity_id': 'sensor.test_name_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -736,44 +735,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 1, + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-cover:0-temperature', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-cover:0-power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_temperature-state] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Test name Temperature', + 'device_class': 'power', + 'friendly_name': 'Test name Power', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_temperature', + 'entity_id': 'sensor.test_name_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '36.4', + 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_energy-entry] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_signal_strength-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -781,8 +780,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_energy', + 'entity_category': , + 'entity_id': 'sensor.test_name_signal_strength', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -791,47 +790,43 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Signal strength', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-cover:0-energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-wifi-rssi', + 'unit_of_measurement': 'dBm', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_energy-state] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_signal_strength-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'signal_strength', + 'friendly_name': 'Test name Signal strength', + 'state_class': , + 'unit_of_measurement': 'dBm', }), 'context': , - 'entity_id': 'sensor.test_name_energy', + 'entity_id': 'sensor.test_name_signal_strength', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '-53', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_uptime-entry] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + 'state_class': , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -839,7 +834,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_id': 'sensor.test_name_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -848,31 +843,36 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Uptime', + 'original_name': 'Temperature', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-sys-uptime', - 'unit_of_measurement': None, + 'unique_id': '123456789ABC-cover:0-temperature', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_uptime-state] +# name: test_shelly_2pm_gen3_cover[sensor.test_name_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'timestamp', - 'friendly_name': 'Test name Uptime', + 'device_class': 'temperature', + 'friendly_name': 'Test name Temperature', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_id': 'sensor.test_name_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2025-05-26T15:57:39+00:00', + 'state': '36.4', }) # --- # name: test_shelly_2pm_gen3_cover[sensor.test_name_voltage-entry] @@ -1641,7 +1641,7 @@ 'state': 'off', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[button.test_name_reboot-entry] +# name: test_shelly_2pm_gen3_no_relay_names[button.test_name_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1654,7 +1654,7 @@ 'disabled_by': None, 'domain': 'button', 'entity_category': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -1666,7 +1666,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Reboot', + 'original_name': 'Restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -1676,28 +1676,26 @@ 'unit_of_measurement': None, }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[button.test_name_reboot-state] +# name: test_shelly_2pm_gen3_no_relay_names[button.test_name_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'restart', - 'friendly_name': 'Test name Reboot', + 'friendly_name': 'Test name Restart', }), 'context': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'unknown', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_rssi-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_last_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -1705,7 +1703,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_id': 'sensor.test_name_last_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -1715,41 +1713,39 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'RSSI', + 'original_name': 'Last restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-wifi-rssi', - 'unit_of_measurement': 'dBm', + 'unique_id': '123456789ABC-sys-uptime', + 'unit_of_measurement': None, }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_rssi-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_last_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'signal_strength', - 'friendly_name': 'Test name RSSI', - 'state_class': , - 'unit_of_measurement': 'dBm', + 'device_class': 'timestamp', + 'friendly_name': 'Test name Last restart', }), 'context': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_id': 'sensor.test_name_last_restart', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-52', + 'state': '2025-05-26T16:02:17+00:00', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy_consumed-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_signal_strength-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -1757,8 +1753,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_0_energy_consumed', + 'entity_category': , + 'entity_id': 'sensor.test_name_signal_strength', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -1767,39 +1763,33 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy consumed', + 'original_name': 'Signal strength', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-consumed_energy_switch', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-wifi-rssi', + 'unit_of_measurement': 'dBm', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy_consumed-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_signal_strength-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Switch 0 Energy consumed', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'signal_strength', + 'friendly_name': 'Test name Signal strength', + 'state_class': , + 'unit_of_measurement': 'dBm', }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_energy_consumed', + 'entity_id': 'sensor.test_name_signal_strength', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '-52', }) # --- # name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_current-entry] @@ -1858,13 +1848,13 @@ 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_frequency-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -1873,7 +1863,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_0_frequency', + 'entity_id': 'sensor.test_name_switch_0_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -1883,44 +1873,47 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Frequency', + 'original_name': 'Energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-freq', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_frequency-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Test name Switch 0 Frequency', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 0 Energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_frequency', + 'entity_id': 'sensor.test_name_switch_0_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '50.0', + 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_power-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy_consumed-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -1929,7 +1922,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_0_power', + 'entity_id': 'sensor.test_name_switch_0_energy_consumed', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -1939,31 +1932,34 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Energy consumed', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-consumed_energy_switch', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_power-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy_consumed-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test name Switch 0 Power', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 0 Energy consumed', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_power', + 'entity_id': 'sensor.test_name_switch_0_energy_consumed', 'last_changed': , 'last_reported': , 'last_updated': , @@ -2029,7 +2025,7 @@ 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_temperature-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_frequency-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2043,8 +2039,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_name_switch_0_temperature', + 'entity_category': None, + 'entity_id': 'sensor.test_name_switch_0_frequency', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2054,44 +2050,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 1, + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Frequency', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-temperature', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-freq', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_temperature-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_frequency-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Test name Switch 0 Temperature', + 'device_class': 'frequency', + 'friendly_name': 'Test name Switch 0 Frequency', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_temperature', + 'entity_id': 'sensor.test_name_switch_0_frequency', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '40.6', + 'state': '50.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -2100,7 +2096,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_0_energy', + 'entity_id': 'sensor.test_name_switch_0_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2110,41 +2106,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Switch 0 Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'power', + 'friendly_name': 'Test name Switch 0 Power', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_energy', + 'entity_id': 'sensor.test_name_switch_0_power', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_voltage-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2158,8 +2151,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_0_voltage', + 'entity_category': , + 'entity_id': 'sensor.test_name_switch_0_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2172,41 +2165,41 @@ 'suggested_display_precision': 1, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Voltage', + 'original_name': 'Temperature', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-voltage', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-temperature', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_voltage-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Test name Switch 0 Voltage', + 'device_class': 'temperature', + 'friendly_name': 'Test name Switch 0 Temperature', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_voltage', + 'entity_id': 'sensor.test_name_switch_0_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '216.2', + 'state': '40.6', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy_consumed-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -2215,7 +2208,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_1_energy_consumed', + 'entity_id': 'sensor.test_name_switch_0_voltage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2225,38 +2218,35 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 1, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy consumed', + 'original_name': 'Voltage', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-consumed_energy_switch', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-voltage', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy_consumed-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_voltage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Switch 1 Energy consumed', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'voltage', + 'friendly_name': 'Test name Switch 0 Voltage', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_energy_consumed', + 'entity_id': 'sensor.test_name_switch_0_voltage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '216.2', }) # --- # name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_current-entry] @@ -2315,13 +2305,13 @@ 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_frequency-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -2330,7 +2320,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_1_frequency', + 'entity_id': 'sensor.test_name_switch_1_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2340,44 +2330,47 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Frequency', + 'original_name': 'Energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-freq', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:1-energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_frequency-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Test name Switch 1 Frequency', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 1 Energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_frequency', + 'entity_id': 'sensor.test_name_switch_1_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '50.0', + 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_power-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy_consumed-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -2386,7 +2379,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_1_power', + 'entity_id': 'sensor.test_name_switch_1_energy_consumed', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2396,31 +2389,34 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Energy consumed', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:1-consumed_energy_switch', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_power-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy_consumed-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test name Switch 1 Power', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 1 Energy consumed', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_power', + 'entity_id': 'sensor.test_name_switch_1_energy_consumed', 'last_changed': , 'last_reported': , 'last_updated': , @@ -2486,7 +2482,7 @@ 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_temperature-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_frequency-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2500,8 +2496,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_name_switch_1_temperature', + 'entity_category': None, + 'entity_id': 'sensor.test_name_switch_1_frequency', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2511,44 +2507,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 1, + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Frequency', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-temperature', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:1-freq', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_temperature-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_frequency-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Test name Switch 1 Temperature', + 'device_class': 'frequency', + 'friendly_name': 'Test name Switch 1 Frequency', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_temperature', + 'entity_id': 'sensor.test_name_switch_1_frequency', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '40.6', + 'state': '50.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -2557,7 +2553,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_1_energy', + 'entity_id': 'sensor.test_name_switch_1_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2567,41 +2563,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:1-power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Switch 1 Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'power', + 'friendly_name': 'Test name Switch 1 Power', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_energy', + 'entity_id': 'sensor.test_name_switch_1_power', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_voltage-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2615,9 +2608,9 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_1_voltage', - 'has_entity_name': True, + 'entity_category': , + 'entity_id': 'sensor.test_name_switch_1_temperature', + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -2629,48 +2622,50 @@ 'suggested_display_precision': 1, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Voltage', + 'original_name': 'Temperature', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-voltage', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:1-temperature', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_voltage-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Test name Switch 1 Voltage', + 'device_class': 'temperature', + 'friendly_name': 'Test name Switch 1 Temperature', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_voltage', + 'entity_id': 'sensor.test_name_switch_1_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '216.3', + 'state': '40.6', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_uptime-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + 'state_class': , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_category': None, + 'entity_id': 'sensor.test_name_switch_1_voltage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2679,31 +2674,36 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Uptime', + 'original_name': 'Voltage', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-sys-uptime', - 'unit_of_measurement': None, + 'unique_id': '123456789ABC-switch:1-voltage', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_uptime-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_voltage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'timestamp', - 'friendly_name': 'Test name Uptime', + 'device_class': 'voltage', + 'friendly_name': 'Test name Switch 1 Voltage', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_id': 'sensor.test_name_switch_1_voltage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2025-05-26T16:02:17+00:00', + 'state': '216.3', }) # --- # name: test_shelly_2pm_gen3_no_relay_names[switch.test_name_switch_0-entry] @@ -3022,7 +3022,7 @@ 'state': 'off', }) # --- -# name: test_shelly_pro_3em[button.test_name_reboot-entry] +# name: test_shelly_pro_3em[button.test_name_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3035,7 +3035,7 @@ 'disabled_by': None, 'domain': 'button', 'entity_category': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3047,7 +3047,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Reboot', + 'original_name': 'Restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -3057,21 +3057,21 @@ 'unit_of_measurement': None, }) # --- -# name: test_shelly_pro_3em[button.test_name_reboot-state] +# name: test_shelly_pro_3em[button.test_name_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'restart', - 'friendly_name': 'Test name Reboot', + 'friendly_name': 'Test name Restart', }), 'context': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'unknown', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_apparent_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3086,7 +3086,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_power', + 'entity_id': 'sensor.test_name_apparent_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3099,35 +3099,35 @@ 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Apparent power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-a_act_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-total_aprt_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_power-state] +# name: test_shelly_pro_3em[sensor.test_name_apparent_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test name Phase A Power', + 'device_class': 'apparent_power', + 'friendly_name': 'Test name Apparent power', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_power', + 'entity_id': 'sensor.test_name_apparent_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2166.2', + 'state': '2525.779', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_apparent_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3142,7 +3142,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_apparent_power', + 'entity_id': 'sensor.test_name_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3152,44 +3152,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Apparent power', + 'original_name': 'Current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-a_aprt_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-total_current', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_apparent_power-state] +# name: test_shelly_pro_3em[sensor.test_name_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Test name Phase A Apparent power', + 'device_class': 'current', + 'friendly_name': 'Test name Current', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_apparent_power', + 'entity_id': 'sensor.test_name_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2175.9', + 'state': '11.116', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_current-entry] +# name: test_shelly_pro_3em[sensor.test_name_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3198,7 +3198,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_current', + 'entity_id': 'sensor.test_name_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3210,42 +3210,45 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Current', + 'original_name': 'Energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-a_current', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-emdata:0-total_act', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_current-state] +# name: test_shelly_pro_3em[sensor.test_name_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test name Phase A Current', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_current', + 'entity_id': 'sensor.test_name_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '9.592', + 'state': '5415.41419', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_frequency-entry] +# name: test_shelly_pro_3em[sensor.test_name_energy_returned-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3254,7 +3257,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_frequency', + 'entity_id': 'sensor.test_name_energy_returned', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3264,53 +3267,54 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Frequency', + 'original_name': 'Energy returned', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-a_freq', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-emdata:0-total_act_ret', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_frequency-state] +# name: test_shelly_pro_3em[sensor.test_name_energy_returned-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Test name Phase A Frequency', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Energy returned', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_frequency', + 'entity_id': 'sensor.test_name_energy_returned', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '49.9', + 'state': '0.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_power_factor-entry] +# name: test_shelly_pro_3em[sensor.test_name_last_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_power_factor', + 'entity_category': , + 'entity_id': 'sensor.test_name_last_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3320,40 +3324,39 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power factor', + 'original_name': 'Last restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-a_pf', + 'unique_id': '123456789ABC-sys-uptime', 'unit_of_measurement': None, }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_power_factor-state] +# name: test_shelly_pro_3em[sensor.test_name_last_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power_factor', - 'friendly_name': 'Test name Phase A Power factor', - 'state_class': , + 'device_class': 'timestamp', + 'friendly_name': 'Test name Last restart', }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_power_factor', + 'entity_id': 'sensor.test_name_last_restart', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.99', + 'state': '2025-05-20T20:42:37+00:00', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy-entry] +# name: test_shelly_pro_3em[sensor.test_name_neutral_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3362,7 +3365,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_energy', + 'entity_id': 'sensor.test_name_neutral_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3374,45 +3377,42 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Neutral current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-a_total_act_energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-n_current', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy-state] +# name: test_shelly_pro_3em[sensor.test_name_neutral_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Phase A Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'current', + 'friendly_name': 'Test name Neutral current', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_energy', + 'entity_id': 'sensor.test_name_neutral_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '3105.57642', + 'state': '3.124', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy_returned-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_apparent_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3421,7 +3421,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_energy_returned', + 'entity_id': 'sensor.test_name_phase_a_apparent_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3431,41 +3431,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy returned', + 'original_name': 'Apparent power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-a_total_act_ret_energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-a_aprt_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy_returned-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_apparent_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Phase A Energy returned', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'apparent_power', + 'friendly_name': 'Test name Phase A Apparent power', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_energy_returned', + 'entity_id': 'sensor.test_name_phase_a_apparent_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '2175.9', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_voltage-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3480,7 +3477,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_voltage', + 'entity_id': 'sensor.test_name_phase_a_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3490,44 +3487,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Voltage', + 'original_name': 'Current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-a_voltage', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-a_current', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_voltage-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Test name Phase A Voltage', + 'device_class': 'current', + 'friendly_name': 'Test name Phase A Current', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_voltage', + 'entity_id': 'sensor.test_name_phase_a_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '227.0', + 'state': '9.592', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3536,7 +3533,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_power', + 'entity_id': 'sensor.test_name_phase_a_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3546,44 +3543,47 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-b_act_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-emdata:0-a_total_act_energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test name Phase B Power', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Phase A Energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_power', + 'entity_id': 'sensor.test_name_phase_a_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '3.6', + 'state': '3105.57642', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_apparent_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy_returned-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3592,7 +3592,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_apparent_power', + 'entity_id': 'sensor.test_name_phase_a_energy_returned', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3602,38 +3602,41 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Apparent power', + 'original_name': 'Energy returned', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-b_aprt_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-emdata:0-a_total_act_ret_energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_apparent_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_energy_returned-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Test name Phase B Apparent power', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Phase A Energy returned', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_apparent_power', + 'entity_id': 'sensor.test_name_phase_a_energy_returned', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '10.1', + 'state': '0.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_current-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_frequency-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3648,7 +3651,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_current', + 'entity_id': 'sensor.test_name_phase_a_frequency', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3658,38 +3661,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Current', + 'original_name': 'Frequency', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-b_current', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-a_freq', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_current-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_frequency-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test name Phase B Current', + 'device_class': 'frequency', + 'friendly_name': 'Test name Phase A Frequency', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_current', + 'entity_id': 'sensor.test_name_phase_a_frequency', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.044', + 'state': '49.9', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_frequency-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3704,7 +3707,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_frequency', + 'entity_id': 'sensor.test_name_phase_a_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3717,35 +3720,35 @@ 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Frequency', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-b_freq', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-a_act_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_frequency-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Test name Phase B Frequency', + 'device_class': 'power', + 'friendly_name': 'Test name Phase A Power', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_frequency', + 'entity_id': 'sensor.test_name_phase_a_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '49.9', + 'state': '2166.2', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_power_factor-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_power_factor-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3760,7 +3763,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_power_factor', + 'entity_id': 'sensor.test_name_phase_a_power_factor', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3778,32 +3781,32 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-b_pf', + 'unique_id': '123456789ABC-em:0-a_pf', 'unit_of_measurement': None, }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_power_factor-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_power_factor-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', - 'friendly_name': 'Test name Phase B Power factor', + 'friendly_name': 'Test name Phase A Power factor', 'state_class': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_power_factor', + 'entity_id': 'sensor.test_name_phase_a_power_factor', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.36', + 'state': '0.99', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3812,7 +3815,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_energy', + 'entity_id': 'sensor.test_name_phase_a_voltage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3822,47 +3825,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Voltage', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-b_total_act_energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-a_voltage', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_voltage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Phase B Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'voltage', + 'friendly_name': 'Test name Phase A Voltage', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_energy', + 'entity_id': 'sensor.test_name_phase_a_voltage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '195.76572', + 'state': '227.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy_returned-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_apparent_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3871,7 +3871,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_energy_returned', + 'entity_id': 'sensor.test_name_phase_b_apparent_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3881,41 +3881,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy returned', + 'original_name': 'Apparent power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-b_total_act_ret_energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-b_aprt_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy_returned-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_apparent_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Phase B Energy returned', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'apparent_power', + 'friendly_name': 'Test name Phase B Apparent power', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_energy_returned', + 'entity_id': 'sensor.test_name_phase_b_apparent_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '10.1', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_voltage-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3930,7 +3927,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_voltage', + 'entity_id': 'sensor.test_name_phase_b_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3940,44 +3937,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Voltage', + 'original_name': 'Current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-b_voltage', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-b_current', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_voltage-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Test name Phase B Voltage', + 'device_class': 'current', + 'friendly_name': 'Test name Phase B Current', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_voltage', + 'entity_id': 'sensor.test_name_phase_b_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '230.0', + 'state': '0.044', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -3986,7 +3983,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_power', + 'entity_id': 'sensor.test_name_phase_b_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3996,44 +3993,47 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-c_act_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-emdata:0-b_total_act_energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test name Phase C Power', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Phase B Energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_power', + 'entity_id': 'sensor.test_name_phase_b_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '244.0', + 'state': '195.76572', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_apparent_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy_returned-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -4042,7 +4042,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_apparent_power', + 'entity_id': 'sensor.test_name_phase_b_energy_returned', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4052,38 +4052,41 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Apparent power', + 'original_name': 'Energy returned', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-c_aprt_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-emdata:0-b_total_act_ret_energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_apparent_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_energy_returned-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Test name Phase C Apparent power', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Phase B Energy returned', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_apparent_power', + 'entity_id': 'sensor.test_name_phase_b_energy_returned', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '339.7', + 'state': '0.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_current-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_frequency-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4098,7 +4101,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_current', + 'entity_id': 'sensor.test_name_phase_b_frequency', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4108,38 +4111,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Current', + 'original_name': 'Frequency', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-c_current', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-b_freq', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_current-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_frequency-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test name Phase C Current', + 'device_class': 'frequency', + 'friendly_name': 'Test name Phase B Frequency', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_current', + 'entity_id': 'sensor.test_name_phase_b_frequency', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1.479', + 'state': '49.9', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_frequency-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4154,7 +4157,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_frequency', + 'entity_id': 'sensor.test_name_phase_b_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4167,35 +4170,35 @@ 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Frequency', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-c_freq', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-b_act_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_frequency-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Test name Phase C Frequency', + 'device_class': 'power', + 'friendly_name': 'Test name Phase B Power', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_frequency', + 'entity_id': 'sensor.test_name_phase_b_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '49.9', + 'state': '3.6', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_power_factor-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_power_factor-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4210,7 +4213,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_power_factor', + 'entity_id': 'sensor.test_name_phase_b_power_factor', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4228,32 +4231,32 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-c_pf', + 'unique_id': '123456789ABC-em:0-b_pf', 'unit_of_measurement': None, }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_power_factor-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_power_factor-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', - 'friendly_name': 'Test name Phase C Power factor', + 'friendly_name': 'Test name Phase B Power factor', 'state_class': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_power_factor', + 'entity_id': 'sensor.test_name_phase_b_power_factor', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.72', + 'state': '0.36', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -4262,7 +4265,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_energy', + 'entity_id': 'sensor.test_name_phase_b_voltage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4272,47 +4275,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Voltage', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-c_total_act_energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-b_voltage', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_voltage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Phase C Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'voltage', + 'friendly_name': 'Test name Phase B Voltage', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_energy', + 'entity_id': 'sensor.test_name_phase_b_voltage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2114.07205', + 'state': '230.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy_returned-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_apparent_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -4321,7 +4321,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_energy_returned', + 'entity_id': 'sensor.test_name_phase_c_apparent_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4331,41 +4331,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy returned', + 'original_name': 'Apparent power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-c_total_act_ret_energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-c_aprt_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy_returned-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_apparent_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Phase C Energy returned', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'apparent_power', + 'friendly_name': 'Test name Phase C Apparent power', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_energy_returned', + 'entity_id': 'sensor.test_name_phase_c_apparent_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '339.7', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_voltage-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4380,7 +4377,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_voltage', + 'entity_id': 'sensor.test_name_phase_c_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4390,44 +4387,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Voltage', + 'original_name': 'Current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-c_voltage', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-c_current', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_voltage-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Test name Phase C Voltage', + 'device_class': 'current', + 'friendly_name': 'Test name Phase C Current', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_voltage', + 'entity_id': 'sensor.test_name_phase_c_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '230.2', + 'state': '1.479', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_neutral_current-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -4436,7 +4433,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_neutral_current', + 'entity_id': 'sensor.test_name_phase_c_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4448,42 +4445,45 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Neutral current', + 'original_name': 'Energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-n_current', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-emdata:0-c_total_act_energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_neutral_current-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test name Neutral current', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Phase C Energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_neutral_current', + 'entity_id': 'sensor.test_name_phase_c_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '3.124', + 'state': '2114.07205', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_rssi-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy_returned-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -4491,8 +4491,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_category': None, + 'entity_id': 'sensor.test_name_phase_c_energy_returned', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4501,36 +4501,42 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'RSSI', + 'original_name': 'Energy returned', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-wifi-rssi', - 'unit_of_measurement': 'dBm', + 'unique_id': '123456789ABC-emdata:0-c_total_act_ret_energy', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_rssi-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_energy_returned-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'signal_strength', - 'friendly_name': 'Test name RSSI', - 'state_class': , - 'unit_of_measurement': 'dBm', + 'device_class': 'energy', + 'friendly_name': 'Test name Phase C Energy returned', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_id': 'sensor.test_name_phase_c_energy_returned', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-57', + 'state': '0.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_temperature-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_frequency-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4545,7 +4551,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_temperature', + 'entity_id': 'sensor.test_name_phase_c_frequency', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4555,44 +4561,44 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 1, + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Frequency', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-temperature:0-temperature_0', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-c_freq', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_temperature-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_frequency-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Test name Temperature', + 'device_class': 'frequency', + 'friendly_name': 'Test name Phase C Frequency', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_temperature', + 'entity_id': 'sensor.test_name_phase_c_frequency', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '46.3', + 'state': '49.9', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_energy-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -4601,7 +4607,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_energy', + 'entity_id': 'sensor.test_name_phase_c_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4611,41 +4617,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-total_act', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-c_act_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_energy-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'power', + 'friendly_name': 'Test name Phase C Power', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_energy', + 'entity_id': 'sensor.test_name_phase_c_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '5415.41419', + 'state': '244.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_power_factor-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4660,7 +4663,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_power', + 'entity_id': 'sensor.test_name_phase_c_power_factor', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4669,45 +4672,41 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Power factor', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-total_act_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-c_pf', + 'unit_of_measurement': None, }) # --- -# name: test_shelly_pro_3em[sensor.test_name_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_power_factor-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test name Power', + 'device_class': 'power_factor', + 'friendly_name': 'Test name Phase C Power factor', 'state_class': , - 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_power', + 'entity_id': 'sensor.test_name_phase_c_power_factor', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2413.825', + 'state': '0.72', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_energy_returned-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -4716,7 +4715,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_energy_returned', + 'entity_id': 'sensor.test_name_phase_c_voltage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4726,41 +4725,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy returned', + 'original_name': 'Voltage', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-emdata:0-total_act_ret', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-c_voltage', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_energy_returned-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_voltage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Energy returned', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'voltage', + 'friendly_name': 'Test name Phase C Voltage', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_energy_returned', + 'entity_id': 'sensor.test_name_phase_c_voltage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '230.2', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_apparent_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4775,7 +4771,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_apparent_power', + 'entity_id': 'sensor.test_name_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4788,35 +4784,35 @@ 'suggested_display_precision': 0, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Apparent power', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-total_aprt_power', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-em:0-total_act_power', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_apparent_power-state] +# name: test_shelly_pro_3em[sensor.test_name_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Test name Apparent power', + 'device_class': 'power', + 'friendly_name': 'Test name Power', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_apparent_power', + 'entity_id': 'sensor.test_name_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2525.779', + 'state': '2413.825', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_current-entry] +# name: test_shelly_pro_3em[sensor.test_name_signal_strength-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4830,8 +4826,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_current', + 'entity_category': , + 'entity_id': 'sensor.test_name_signal_strength', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4840,52 +4836,51 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Current', + 'original_name': 'Signal strength', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-em:0-total_current', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-wifi-rssi', + 'unit_of_measurement': 'dBm', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_current-state] +# name: test_shelly_pro_3em[sensor.test_name_signal_strength-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test name Current', + 'device_class': 'signal_strength', + 'friendly_name': 'Test name Signal strength', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': 'dBm', }), 'context': , - 'entity_id': 'sensor.test_name_current', + 'entity_id': 'sensor.test_name_signal_strength', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '11.116', + 'state': '-57', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_uptime-entry] +# name: test_shelly_pro_3em[sensor.test_name_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + 'state_class': , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_category': None, + 'entity_id': 'sensor.test_name_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4894,31 +4889,36 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Uptime', + 'original_name': 'Temperature', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-sys-uptime', - 'unit_of_measurement': None, + 'unique_id': '123456789ABC-temperature:0-temperature_0', + 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_uptime-state] +# name: test_shelly_pro_3em[sensor.test_name_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'timestamp', - 'friendly_name': 'Test name Uptime', + 'device_class': 'temperature', + 'friendly_name': 'Test name Temperature', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_id': 'sensor.test_name_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2025-05-20T20:42:37+00:00', + 'state': '46.3', }) # --- # name: test_shelly_pro_3em[update.test_name_beta_firmware-entry] @@ -5239,7 +5239,7 @@ 'state': 'off', }) # --- -# name: test_wall_display_xl[button.test_name_reboot-entry] +# name: test_wall_display_xl[button.test_name_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5252,7 +5252,7 @@ 'disabled_by': None, 'domain': 'button', 'entity_category': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5264,7 +5264,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Reboot', + 'original_name': 'Restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -5274,14 +5274,14 @@ 'unit_of_measurement': None, }) # --- -# name: test_wall_display_xl[button.test_name_reboot-state] +# name: test_wall_display_xl[button.test_name_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'restart', - 'friendly_name': 'Test name Reboot', + 'friendly_name': 'Test name Restart', }), 'context': , - 'entity_id': 'button.test_name_reboot', + 'entity_id': 'button.test_name_restart', 'last_changed': , 'last_reported': , 'last_updated': , @@ -5634,14 +5634,12 @@ 'state': 'twilight', }) # --- -# name: test_wall_display_xl[sensor.test_name_rssi-entry] +# name: test_wall_display_xl[sensor.test_name_last_restart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -5649,7 +5647,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_id': 'sensor.test_name_last_restart', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5659,35 +5657,33 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'RSSI', + 'original_name': 'Last restart', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-wifi-rssi', - 'unit_of_measurement': 'dBm', + 'unique_id': '123456789ABC-sys-uptime', + 'unit_of_measurement': None, }) # --- -# name: test_wall_display_xl[sensor.test_name_rssi-state] +# name: test_wall_display_xl[sensor.test_name_last_restart-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'signal_strength', - 'friendly_name': 'Test name RSSI', - 'state_class': , - 'unit_of_measurement': 'dBm', + 'device_class': 'timestamp', + 'friendly_name': 'Test name Last restart', }), 'context': , - 'entity_id': 'sensor.test_name_rssi', + 'entity_id': 'sensor.test_name_last_restart', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-48', + 'state': '2025-05-15T21:33:41+00:00', }) # --- -# name: test_wall_display_xl[sensor.test_name_temperature-entry] +# name: test_wall_display_xl[sensor.test_name_signal_strength-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5701,8 +5697,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_temperature', + 'entity_category': , + 'entity_id': 'sensor.test_name_signal_strength', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5711,52 +5707,51 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Signal strength', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-temperature:0-temperature_0', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-wifi-rssi', + 'unit_of_measurement': 'dBm', }) # --- -# name: test_wall_display_xl[sensor.test_name_temperature-state] +# name: test_wall_display_xl[sensor.test_name_signal_strength-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Test name Temperature', + 'device_class': 'signal_strength', + 'friendly_name': 'Test name Signal strength', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': 'dBm', }), 'context': , - 'entity_id': 'sensor.test_name_temperature', + 'entity_id': 'sensor.test_name_signal_strength', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-275.149993896484', + 'state': '-48', }) # --- -# name: test_wall_display_xl[sensor.test_name_uptime-entry] +# name: test_wall_display_xl[sensor.test_name_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + 'state_class': , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_category': None, + 'entity_id': 'sensor.test_name_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5765,31 +5760,36 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Uptime', + 'original_name': 'Temperature', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-sys-uptime', - 'unit_of_measurement': None, + 'unique_id': '123456789ABC-temperature:0-temperature_0', + 'unit_of_measurement': , }) # --- -# name: test_wall_display_xl[sensor.test_name_uptime-state] +# name: test_wall_display_xl[sensor.test_name_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'timestamp', - 'friendly_name': 'Test name Uptime', + 'device_class': 'temperature', + 'friendly_name': 'Test name Temperature', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_uptime', + 'entity_id': 'sensor.test_name_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2025-05-15T21:33:41+00:00', + 'state': '-275.149993896484', }) # --- # name: test_wall_display_xl[switch.test_name-entry] diff --git a/tests/components/shelly/test_button.py b/tests/components/shelly/test_button.py index b59227acf7f00c..287f32e8c7a725 100644 --- a/tests/components/shelly/test_button.py +++ b/tests/components/shelly/test_button.py @@ -39,7 +39,7 @@ async def test_block_button( """Test block device reboot button.""" await init_integration(hass, 1) - entity_id = "button.test_name_reboot" + entity_id = "button.test_name_restart" # reboot button assert (state := hass.states.get(entity_id)) @@ -66,7 +66,7 @@ async def test_rpc_button( """Test rpc device OTA button.""" await init_integration(hass, 2) - entity_id = "button.test_name_reboot" + entity_id = "button.test_name_restart" # reboot button assert (state := hass.states.get(entity_id)) @@ -89,11 +89,11 @@ async def test_rpc_button( [ ( DeviceConnectionError, - "Device communication error occurred while calling action for button.test_name_reboot of Test name", + "Device communication error occurred while calling action for button.test_name_restart of Test name", ), ( RpcCallError(999), - "RPC call error occurred while calling action for button.test_name_reboot of Test name", + "RPC call error occurred while calling action for button.test_name_restart of Test name", ), ], ) @@ -112,7 +112,7 @@ async def test_rpc_button_exc( await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.test_name_reboot"}, + {ATTR_ENTITY_ID: "button.test_name_restart"}, blocking=True, ) @@ -128,7 +128,7 @@ async def test_rpc_button_reauth_error( await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, - {ATTR_ENTITY_ID: "button.test_name_reboot"}, + {ATTR_ENTITY_ID: "button.test_name_restart"}, blocking=True, ) @@ -169,7 +169,7 @@ async def test_migrate_unique_id( entry = await init_integration(hass, gen, skip_setup=True) entity = entity_registry.async_get_or_create( - suggested_object_id="test_name_reboot", + suggested_object_id="test_name_restart", disabled_by=None, domain=BUTTON_DOMAIN, platform=DOMAIN, @@ -181,12 +181,12 @@ async def test_migrate_unique_id( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - entity_entry = entity_registry.async_get("button.test_name_reboot") + entity_entry = entity_registry.async_get("button.test_name_restart") assert entity_entry assert entity_entry.unique_id == new_unique_id assert ( - bool("Migrating unique_id for button.test_name_reboot" in caplog.text) + bool("Migrating unique_id for button.test_name_restart" in caplog.text) == migration ) diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 802d6858f9f124..00c08cb7868bd2 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -486,7 +486,7 @@ async def test_rpc_rssi_sensor_removal( monkeypatch: pytest.MonkeyPatch, ) -> None: """Test RPC RSSI sensor removal if no WiFi stations enabled.""" - entity_id = f"{SENSOR_DOMAIN}.test_name_rssi" + entity_id = f"{SENSOR_DOMAIN}.test_name_signal_strength" entry = await init_integration(hass, 2) # WiFi1 enabled, do not remove sensor @@ -926,7 +926,7 @@ async def test_rpc_pulse_counter_sensors( assert (entry := entity_registry.async_get(entity_id)) assert entry.unique_id == "123456789ABC-input:2-pulse_counter" - entity_id = f"{SENSOR_DOMAIN}.test_name_gas_counter_value" + entity_id = f"{SENSOR_DOMAIN}.test_name_gas_pulse_counter_value" assert (state := hass.states.get(entity_id)) assert state.state == "561.74" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == expected_unit @@ -948,7 +948,7 @@ async def test_rpc_disabled_pulse_counter_sensors( entity_id = f"{SENSOR_DOMAIN}.gas_pulse_counter" assert hass.states.get(entity_id) is None - entity_id = f"{SENSOR_DOMAIN}.gas_counter_value" + entity_id = f"{SENSOR_DOMAIN}.gas_pulse_counter_value" assert hass.states.get(entity_id) is None diff --git a/tests/components/telegram_bot/conftest.py b/tests/components/telegram_bot/conftest.py index 5ec33d99012f63..8220e50d8745ad 100644 --- a/tests/components/telegram_bot/conftest.py +++ b/tests/components/telegram_bot/conftest.py @@ -265,10 +265,17 @@ def mock_broadcast_config_entry() -> MockConfigEntry: ConfigSubentryData( unique_id="123456", data={CONF_CHAT_ID: 123456}, - subentry_id="mock_id", + subentry_id="mock_id1", subentry_type=CONF_ALLOWED_CHAT_IDS, - title="mock chat", - ) + title="mock chat 1", + ), + ConfigSubentryData( + unique_id="654321", + data={CONF_CHAT_ID: 654321}, + subentry_id="mock_id2", + subentry_type=CONF_ALLOWED_CHAT_IDS, + title="mock chat 2", + ), ], ) diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index ba8bf41b52be4e..45db89a77a9822 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -283,6 +283,93 @@ async def test_send_message_with_inline_keyboard( assert (response["chats"][0]["message_id"]) == 12345 +async def test_send_sticker_partial_error( + hass: HomeAssistant, + mock_broadcast_config_entry: MockConfigEntry, + mock_external_calls: None, +) -> None: + """Test the send_sticker service with multiple targets.""" + + mock_broadcast_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_broadcast_config_entry.entry_id) + await hass.async_block_till_done() + + with ( + patch( + "homeassistant.components.telegram_bot.bot.load_data", + ) as mock_load_data, + patch( + "homeassistant.components.telegram_bot.bot.Bot.send_sticker" + ) as mock_send_sticker, + ): + mock_send_sticker.side_effect = NetworkError("mock network error") + + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_STICKER, + { + ATTR_URL: "https://mock_sticker_url", + ATTR_TARGET: [123456, 654321], + }, + blocking=True, + return_response=True, + ) + + await hass.async_block_till_done() + + assert mock_load_data.call_count == 1 + assert mock_send_sticker.call_count == 2 + assert err.value.translation_key == "failed_chat_ids" + assert err.value.args[0] == "Failed targets: [123456, 654321]" + + +async def test_send_sticker_error(hass: HomeAssistant, webhook_platform) -> None: + """Test the send_message service with an error.""" + with patch( + "homeassistant.components.telegram_bot.bot.Bot.send_sticker", + ) as mock_bot: + mock_bot.side_effect = NetworkError("mock network error") + + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_STICKER, + { + ATTR_STICKER_ID: "mock sticker id", + }, + blocking=True, + ) + + await hass.async_block_till_done() + + mock_bot.assert_called_once() + assert err.value.translation_domain == DOMAIN + assert err.value.translation_key == "action_failed" + + +async def test_send_message_with_invalid_inline_keyboard( + hass: HomeAssistant, + webhook_platform, +) -> None: + """Test the send_message service with invalid inline keyboard.""" + + with pytest.raises(ServiceValidationError) as err: + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_MESSAGE, + { + ATTR_MESSAGE: "test_message", + ATTR_KEYBOARD_INLINE: 1, + }, + blocking=True, + return_response=True, + ) + + await hass.async_block_till_done() + assert err.value.translation_key == "invalid_inline_keyboard" + + @patch( "builtins.open", mock_open( diff --git a/tests/components/todoist/test_calendar.py b/tests/components/todoist/test_calendar.py index 071a14a70aea77..e342df5f6b79e6 100644 --- a/tests/components/todoist/test_calendar.py +++ b/tests/components/todoist/test_calendar.py @@ -1,6 +1,6 @@ """Unit tests for the Todoist calendar platform.""" -from datetime import timedelta +from datetime import datetime, timedelta from http import HTTPStatus from typing import Any from unittest.mock import AsyncMock, patch @@ -141,7 +141,12 @@ async def test_update_entity_for_custom_project_no_due_date_on( [ Due( # Note: This runs before the test fixture that sets the timezone - date=(dt_util.now(TIMEZONE) + timedelta(days=3)).strftime("%Y-%m-%d"), + date=( + datetime( + day=15, month=10, year=2025, hour=23, minute=45, tzinfo=TIMEZONE + ) + + timedelta(days=3) + ).strftime("%Y-%m-%d"), is_recurring=False, string="3 days from today", ) @@ -158,9 +163,9 @@ async def test_update_entity_for_calendar_with_due_date_in_the_future( assert state.state == "on" # The end time should be in the user's timezone - expected_end_time = (dt_util.now() + timedelta(days=3)).strftime( - "%Y-%m-%d 00:00:00" - ) + expected_end_time = ( + datetime(day=15, month=10, year=2025, hour=23, minute=45) + timedelta(days=3) + ).strftime("%Y-%m-%d 00:00:00") assert state.attributes["end_time"] == expected_end_time diff --git a/tests/helpers/template/extensions/test_math.py b/tests/helpers/template/extensions/test_math.py index 5a8730951811b1..4cf26cdf517769 100644 --- a/tests/helpers/template/extensions/test_math.py +++ b/tests/helpers/template/extensions/test_math.py @@ -10,10 +10,7 @@ from homeassistant.exceptions import TemplateError from homeassistant.helpers import template - -def render(hass: HomeAssistant, template_str: str) -> str: - """Render template and return result.""" - return template.Template(template_str, hass).async_render() +from tests.helpers.template.helpers import render def test_math_constants(hass: HomeAssistant) -> None: diff --git a/tests/helpers/template/helpers.py b/tests/helpers/template/helpers.py new file mode 100644 index 00000000000000..f15e63b0b090d0 --- /dev/null +++ b/tests/helpers/template/helpers.py @@ -0,0 +1,61 @@ +"""Helpers for tests around template rendering.""" + +from __future__ import annotations + +from collections.abc import Iterable +from typing import Any + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import template +from homeassistant.helpers.typing import TemplateVarsType + + +def render( + hass: HomeAssistant, + template_str: str, + variables: TemplateVarsType | None = None, + **render_kwargs: Any, +) -> Any: + """Render template and return result.""" + return template.Template(template_str, hass).async_render( + variables, **render_kwargs + ) + + +def render_to_info( + hass: HomeAssistant, template_str: str, variables: TemplateVarsType | None = None +) -> template.RenderInfo: + """Create render info from template.""" + return template.Template(template_str, hass).async_render_to_info(variables) + + +def extract_entities( + hass: HomeAssistant, template_str: str, variables: TemplateVarsType | None = None +) -> set[str]: + """Extract entities from a template.""" + return render_to_info(hass, template_str, variables).entities + + +def assert_result_info( + info: template.RenderInfo, + result: Any, + entities: Iterable[str] | None = None, + domains: Iterable[str] | None = None, + all_states: bool = False, +) -> None: + """Check result info.""" + assert info.result() == result + assert info.all_states == all_states + assert info.filter("invalid_entity_name.somewhere") == all_states + if entities is not None: + assert info.entities == frozenset(entities) + assert all(info.filter(entity) for entity in entities) + if not all_states: + assert not info.filter("invalid_entity_name.somewhere") + else: + assert not info.entities + if domains is not None: + assert info.domains == frozenset(domains) + assert all(info.filter(domain + ".entity") for domain in domains) + else: + assert not hasattr(info, "_domains") diff --git a/tests/helpers/template/test_init.py b/tests/helpers/template/test_init.py index 44399869ef826e..c4e7c058a8300c 100644 --- a/tests/helpers/template/test_init.py +++ b/tests/helpers/template/test_init.py @@ -52,12 +52,13 @@ ALL_STATES_RATE_LIMIT, DOMAIN_STATES_RATE_LIMIT, ) -from homeassistant.helpers.typing import TemplateVarsType from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from homeassistant.util.read_only_dict import ReadOnlyDict from homeassistant.util.unit_system import UnitSystem +from .helpers import assert_result_info, render, render_to_info + from tests.common import MockConfigEntry, async_fire_time_changed @@ -77,55 +78,6 @@ def _set_up_units(hass: HomeAssistant) -> None: ) -def render( - hass: HomeAssistant, template_str: str, variables: TemplateVarsType | None = None -) -> Any: - """Create render info from template.""" - tmp = template.Template(template_str, hass) - return tmp.async_render(variables) - - -def render_to_info( - hass: HomeAssistant, template_str: str, variables: TemplateVarsType | None = None -) -> template.RenderInfo: - """Create render info from template.""" - tmp = template.Template(template_str, hass) - return tmp.async_render_to_info(variables) - - -def extract_entities( - hass: HomeAssistant, template_str: str, variables: TemplateVarsType | None = None -) -> set[str]: - """Extract entities from a template.""" - info = render_to_info(hass, template_str, variables) - return info.entities - - -def assert_result_info( - info: template.RenderInfo, - result: Any, - entities: Iterable[str] | None = None, - domains: Iterable[str] | None = None, - all_states: bool = False, -) -> None: - """Check result info.""" - assert info.result() == result - assert info.all_states == all_states - assert info.filter("invalid_entity_name.somewhere") == all_states - if entities is not None: - assert info.entities == frozenset(entities) - assert all(info.filter(entity) for entity in entities) - if not all_states: - assert not info.filter("invalid_entity_name.somewhere") - else: - assert not info.entities - if domains is not None: - assert info.domains == frozenset(domains) - assert all(info.filter(domain + ".entity") for domain in domains) - else: - assert not hasattr(info, "_domains") - - async def test_template_render_missing_hass(hass: HomeAssistant) -> None: """Test template render when hass is not set.""" hass.states.async_set("sensor.test", "23")