Compare commits

..

61 Commits

Author SHA1 Message Date
Ludovic BOUÉ
28f70fab8d Add setpoint_change_source icon with states for external, manual, and schedule 2026-01-13 20:23:07 +00:00
Ludovic BOUÉ
289490faa3 Refactor setpoint_change_timestamp device_to_ha conversion to use matter_epoch_seconds_to_utc 2026-01-12 20:57:39 +00:00
Ludovic BOUÉ
dea46f7b2e Add tests for Eve Thermo v5 SetpointChangeSource, timestamp, and amount sensors 2026-01-12 20:54:52 +00:00
Ludovic BOUÉ
82e3221126 Update Matter Eve Thermo sensor entries to reflect last change and change amount attributes 2026-01-12 19:28:15 +00:00
Ludovic BOUÉ
47e8fbc1ed Add Matter Eve Thermo 20ECD1701 sensor entries and update mock thermostat configurations 2026-01-12 19:25:10 +00:00
Ludovic BOUÉ
0428d0b97f Merge branch 'dev' into setpoint_change_source 2026-01-12 20:19:09 +01:00
Ludovic BOUÉ
514b6e243c Rename Matter Eve Thermostat Fixture to eve_thermo_v4 (#160796) 2026-01-12 20:16:13 +01:00
Ludovic BOUÉ
45344c04c1 Refactor setpoint change source mapping and add utility functions for Matter epoch conversion 2026-01-12 19:14:54 +00:00
Krisjanis Lejejs
742230c7be Bump hass-nabucasa from 1.8.0 to 1.9.0 (#160788) 2026-01-12 19:50:48 +01:00
Ludovic BOUÉ
acb6b1444e Add fixture for Matter Eve Thermo 20ECD1701 (v5) with detailed attributes (#160795) 2026-01-12 18:52:18 +01:00
Erwin Douna
f358b2231a Add match case in perform action (#160150) 2026-01-12 18:25:51 +01:00
Joakim Sørensen
fd24cffa6b Block untill done while setting up cloud in tests (#160780) 2026-01-12 17:32:06 +01:00
Yuxin Wang
0b5d6ee538 Add TIMESTAMP device classes to corresponding sensors in APCUPSD (#160577) 2026-01-12 17:10:25 +01:00
DeerMaximum
d125bb88d1 Use load_json_object_fixture in tests for NINA (#160690)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-01-12 17:09:18 +01:00
Ludovic BOUÉ
2ab51f582a Add Matter occupied setback for thermostats (#155439) 2026-01-12 16:47:43 +01:00
epenet
f9b32811b2 Move typed ConfigEntry to coordinator module in point (#160786) 2026-01-12 16:34:38 +01:00
seppwabala
41a423e140 Add support for eds0065 in onewire (#160094)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-01-12 16:21:00 +01:00
Xiangxuan Qu
f717867657 Pass config_entry explicitly to Point coordinator (#160578) 2026-01-12 15:55:41 +01:00
J. Nick Koston
ab202a03db Handle deleted issue during repair flow translation check (#160698) 2026-01-12 15:52:36 +01:00
Álvaro Fernández Rojas
46a3e5e5b5 Fix Airzone Q-Adapt select entities (#160695)
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2026-01-12 15:48:07 +01:00
Krisjanis Lejejs
0163a4d289 Bump hass-nabucasa from 1.7.0 to 1.8.0 (#160775) 2026-01-12 15:46:49 +01:00
Willem-Jan van Rootselaar
6c1bf31a3c Bump python-bsblan to version 4.1.0 (#160676) 2026-01-12 15:44:03 +01:00
Michael
a434760a80 Complete entity name and icon translations in FRITZ!Box Tools (#160746)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:43:28 +01:00
Jevgeni Kiski
798990fadc Bump vallox-websocket-api to 6.0.0 (#160742) 2026-01-12 15:30:17 +01:00
Glenn de Haan
b3d9d92e4a Add HDFury diagnostics (#160641) 2026-01-12 15:08:19 +01:00
Lukas
1082a9ca69 Pooldose: Sync with docs update (#160190) 2026-01-12 14:41:46 +01:00
Joost Lekkerkerker
c247f56658 Fix fitbit icon (#160750) 2026-01-12 11:08:59 +01:00
Paul Tarjan
e7f71781f1 Fix Hikvision NVR binary sensors not being detected (#160254)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 11:04:30 +01:00
Josef Zweck
c4b2c5e621 Fix missing key for brew by weight in lamarzocco (#160722) 2026-01-12 11:03:36 +01:00
Thomas55555
7779609a76 Add more pollutants to Google Air Quality (#160738) 2026-01-12 11:02:18 +01:00
Duco Sebel
7b9a5f897c Bump python-homewizard-energy to 10.0.1 (#160736) 2026-01-12 10:59:55 +01:00
epenet
6eccbfc1cf Fix Requirement parsing in RequirementsManager (#160485) 2026-01-12 10:55:39 +01:00
Artur Pragacz
0da518e951 Fix scrape sensor device name (#160765) 2026-01-12 10:53:25 +01:00
Bram Kragten
e5851b7920 Update frontend to 20260107.1 (#160644) 2026-01-12 10:51:49 +01:00
Artur Pragacz
1b9364e8b5 Assign device_entry earlier in entity platform (#160767) 2026-01-12 10:49:01 +01:00
Carter Green
8460d4f5e2 Yolink diagnostic sensors (#160749) 2026-01-12 10:33:49 +01:00
Artur Pragacz
8fd35cd70d Rename registry imports in entity platform (#160766) 2026-01-12 10:27:03 +01:00
MarkGodwin
88be115699 Bump tplink_omada quality scale to bronze (#160762) 2026-01-12 09:52:46 +01:00
Ludovic BOUÉ
c472b6ac5e Add support for RoomAirConditioner device type 2025-12-01 15:12:33 +00:00
Ludovic BOUÉ
58f533feb6 Add device_type attribute for Thermostat sensors 2025-11-30 21:43:43 +01:00
Ludovic BOUÉ
0af8c8fd8c Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-30 21:37:01 +01:00
Ludovic BOUÉ
b9d6c3b9fe Update homeassistant/components/matter/strings.json
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-30 21:36:08 +01:00
Ludovic BOUÉ
b700940bb9 Merge branch 'dev' into setpoint_change_source 2025-11-30 21:31:19 +01:00
Ludovic BOUÉ
3b73f6d37e Update thermostat setpoint change timestamp to January 1, 2025 2025-11-30 20:21:45 +00:00
Ludovic BOUÉ
2812bb21da Add offset for Matter 2000 epoch in timestamp conversion 2025-11-30 20:20:13 +00:00
Ludovic BOUÉ
5d474675e8 Update mock thermostat state timestamp to January 1, 2025 2025-11-30 20:15:31 +00:00
Ludovic BOUÉ
ea7bcf6cda Update mock thermostat JSON to correct timestamp for attribute 1/513/50 2025-11-30 20:11:19 +00:00
Ludovic BOUÉ
725bd3d671 Add mock thermostat entity and state snapshots for temperature display mode 2025-11-21 12:38:04 +00:00
Ludovic BOUÉ
cfc4fa6342 Merge branch 'dev' into setpoint_change_source 2025-11-21 13:35:31 +01:00
Ludovic BOUÉ
b650e71660 Update mock thermostat snapshots with new attributes and state values 2025-11-18 18:05:29 +00:00
Ludovic BOUÉ
9ddf15e348 Update mock thermostat JSON with additional attributes and values 2025-11-18 18:03:46 +00:00
Ludovic BOUÉ
15082f9111 Merge branch 'dev' into setpoint_change_source 2025-11-18 16:45:05 +01:00
Ludovic BOUÉ
12f16611ff Rename mock thermostat entity IDs and friendly names in snapshots for consistency 2025-11-18 15:30:39 +00:00
Ludovic BOUÉ
8041be3d08 Merge branch 'dev' into setpoint_change_source 2025-11-18 14:08:38 +01:00
Ludovic BOUÉ
40b021e755 Add tests for Thermostat SetpointChangeSource, Timestamp, and Amount sensors 2025-11-18 13:02:44 +00:00
Ludovic BOUÉ
aab57eda96 Update mock thermostat product name to "Mock Thermostat" 2025-11-18 13:00:26 +00:00
Ludovic BOUÉ
f0dd37caa5 Add mock thermostat sensors and states for testing 2025-11-18 12:49:32 +00:00
Ludovic BOUÉ
662b178495 Remove unused attribute from thermostat fixture 2025-11-18 12:48:56 +00:00
Ludovic BOUÉ
cb3d30884a Add mock thermostat fixture for integration tests 2025-11-18 12:48:21 +00:00
Ludovic BOUÉ
49e6f20372 Add Setpoint Change Source timestamp and amount sensors with localization strings 2025-11-18 12:39:28 +00:00
Ludovic BOUÉ
75d02661eb Add Setpoint Change Source sensor and localization strings 2025-11-14 17:19:28 +00:00
97 changed files with 2976 additions and 436 deletions

View File

@@ -85,6 +85,22 @@ class AirzoneSystemEntity(AirzoneEntity):
value = system[key]
return value
async def _async_update_sys_params(self, params: dict[str, Any]) -> None:
"""Send system parameters to API."""
_params = {
API_SYSTEM_ID: self.system_id,
**params,
}
_LOGGER.debug("update_sys_params=%s", _params)
try:
await self.coordinator.airzone.set_sys_parameters(_params)
except AirzoneError as error:
raise HomeAssistantError(
f"Failed to set system {self.entity_id}: {error}"
) from error
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
class AirzoneHotWaterEntity(AirzoneEntity):
"""Define an Airzone Hot Water entity."""

View File

@@ -20,6 +20,7 @@ from aioairzone.const import (
AZD_MODES,
AZD_Q_ADAPT,
AZD_SLEEP,
AZD_SYSTEMS,
AZD_ZONES,
)
@@ -30,7 +31,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AirzoneConfigEntry, AirzoneUpdateCoordinator
from .entity import AirzoneEntity, AirzoneZoneEntity
from .entity import AirzoneEntity, AirzoneSystemEntity, AirzoneZoneEntity
@dataclass(frozen=True, kw_only=True)
@@ -85,14 +86,7 @@ def main_zone_options(
return [k for k, v in options.items() if v in modes]
MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_MODE,
key=AZD_MODE,
options_dict=MODE_DICT,
options_fn=main_zone_options,
translation_key="modes",
),
SYSTEM_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_Q_ADAPT,
entity_category=EntityCategory.CONFIG,
@@ -104,6 +98,17 @@ MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
)
MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_MODE,
key=AZD_MODE,
options_dict=MODE_DICT,
options_fn=main_zone_options,
translation_key="modes",
),
)
ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_COLD_ANGLE,
@@ -140,16 +145,37 @@ async def async_setup_entry(
"""Add Airzone select from a config_entry."""
coordinator = entry.runtime_data
added_systems: set[str] = set()
added_zones: set[str] = set()
def _async_entity_listener() -> None:
"""Handle additions of select."""
entities: list[AirzoneBaseSelect] = []
systems_data = coordinator.data.get(AZD_SYSTEMS, {})
received_systems = set(systems_data)
new_systems = received_systems - added_systems
if new_systems:
entities.extend(
AirzoneSystemSelect(
coordinator,
description,
entry,
system_id,
systems_data.get(system_id),
)
for system_id in new_systems
for description in SYSTEM_SELECT_TYPES
if description.key in systems_data.get(system_id)
)
added_systems.update(new_systems)
zones_data = coordinator.data.get(AZD_ZONES, {})
received_zones = set(zones_data)
new_zones = received_zones - added_zones
if new_zones:
entities: list[AirzoneZoneSelect] = [
entities.extend(
AirzoneZoneSelect(
coordinator,
description,
@@ -161,8 +187,8 @@ async def async_setup_entry(
for description in MAIN_ZONE_SELECT_TYPES
if description.key in zones_data.get(system_zone_id)
and zones_data.get(system_zone_id).get(AZD_MASTER) is True
]
entities += [
)
entities.extend(
AirzoneZoneSelect(
coordinator,
description,
@@ -173,10 +199,11 @@ async def async_setup_entry(
for system_zone_id in new_zones
for description in ZONE_SELECT_TYPES
if description.key in zones_data.get(system_zone_id)
]
async_add_entities(entities)
)
added_zones.update(new_zones)
async_add_entities(entities)
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
_async_entity_listener()
@@ -203,6 +230,38 @@ class AirzoneBaseSelect(AirzoneEntity, SelectEntity):
self._attr_current_option = self._get_current_option()
class AirzoneSystemSelect(AirzoneSystemEntity, AirzoneBaseSelect):
"""Define an Airzone System select."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
description: AirzoneSelectDescription,
entry: ConfigEntry,
system_id: str,
system_data: dict[str, Any],
) -> None:
"""Initialize."""
super().__init__(coordinator, entry, system_data)
self._attr_unique_id = f"{self._attr_unique_id}_{system_id}_{description.key}"
self.entity_description = description
self._attr_options = self.entity_description.options_fn(
system_data, description.options_dict
)
self.values_dict = {v: k for k, v in description.options_dict.items()}
self._async_update_attrs()
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
param = self.entity_description.api_param
value = self.entity_description.options_dict[option]
await self._async_update_sys_params({param: value})
class AirzoneZoneSelect(AirzoneZoneEntity, AirzoneBaseSelect):
"""Define an Airzone Zone select."""

View File

@@ -4,6 +4,8 @@ from __future__ import annotations
import logging
import dateutil
from homeassistant.components.automation import automations_with_entity
from homeassistant.components.script import scripts_with_entity
from homeassistant.components.sensor import (
@@ -179,6 +181,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
LAST_S_TEST: SensorEntityDescription(
key=LAST_S_TEST,
translation_key="last_self_test",
device_class=SensorDeviceClass.TIMESTAMP,
),
"lastxfer": SensorEntityDescription(
key="lastxfer",
@@ -232,6 +235,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"masterupd": SensorEntityDescription(
key="masterupd",
translation_key="master_update",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
),
"maxlinev": SensorEntityDescription(
@@ -365,6 +369,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"starttime": SensorEntityDescription(
key="starttime",
translation_key="startup_time",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
),
"statflag": SensorEntityDescription(
@@ -416,16 +421,19 @@ SENSORS: dict[str, SensorEntityDescription] = {
"xoffbat": SensorEntityDescription(
key="xoffbat",
translation_key="transfer_from_battery",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
),
"xoffbatt": SensorEntityDescription(
key="xoffbatt",
translation_key="transfer_from_battery",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
),
"xonbatt": SensorEntityDescription(
key="xonbatt",
translation_key="transfer_to_battery",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
),
}
@@ -529,7 +537,13 @@ class APCUPSdSensor(APCUPSdEntity, SensorEntity):
self._attr_native_value = None
return
self._attr_native_value, inferred_unit = infer_unit(self.coordinator.data[key])
data = self.coordinator.data[key]
if self.entity_description.device_class == SensorDeviceClass.TIMESTAMP:
self._attr_native_value = dateutil.parser.parse(data)
return
self._attr_native_value, inferred_unit = infer_unit(data)
if not self.native_unit_of_measurement:
self._attr_native_unit_of_measurement = inferred_unit

View File

@@ -111,11 +111,17 @@ class BSBLANClimate(BSBLanEntity, ClimateEntity):
return None
return self.coordinator.data.state.target_temperature.value
@property
def _hvac_mode_value(self) -> int | str | None:
"""Return the raw hvac_mode value from the coordinator."""
if (hvac_mode := self.coordinator.data.state.hvac_mode) is None:
return None
return hvac_mode.value
@property
def hvac_mode(self) -> HVACMode | None:
"""Return hvac operation ie. heat, cool mode."""
hvac_mode_value = self.coordinator.data.state.hvac_mode.value
if hvac_mode_value is None:
if (hvac_mode_value := self._hvac_mode_value) is None:
return None
# BSB-Lan returns integer values: 0=off, 1=auto, 2=eco, 3=heat
if isinstance(hvac_mode_value, int):
@@ -125,9 +131,8 @@ class BSBLANClimate(BSBLanEntity, ClimateEntity):
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode."""
hvac_mode_value = self.coordinator.data.state.hvac_mode.value
# BSB-Lan mode 2 is eco/reduced mode
if hvac_mode_value == 2:
if self._hvac_mode_value == 2:
return PRESET_ECO
return PRESET_NONE

View File

@@ -29,7 +29,11 @@ class BSBLanEntityBase[_T: BSBLanCoordinator](CoordinatorEntity[_T]):
connections={(CONNECTION_NETWORK_MAC, format_mac(mac))},
name=data.device.name,
manufacturer="BSBLAN Inc.",
model=data.info.device_identification.value,
model=(
data.info.device_identification.value
if data.info.device_identification
else None
),
sw_version=data.device.version,
configuration_url=f"http://{host}",
)

View File

@@ -7,7 +7,7 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["bsblan"],
"requirements": ["python-bsblan==3.1.6"],
"requirements": ["python-bsblan==4.1.0"],
"zeroconf": [
{
"name": "bsb-lan*",

View File

@@ -50,7 +50,6 @@ from . import (
from .client import CloudClient
from .const import (
CONF_ACCOUNT_LINK_SERVER,
CONF_ACCOUNTS_SERVER,
CONF_ACME_SERVER,
CONF_ALEXA,
CONF_ALIASES,
@@ -138,7 +137,6 @@ _BASE_CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA,
vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA,
vol.Optional(CONF_ACCOUNT_LINK_SERVER): str,
vol.Optional(CONF_ACCOUNTS_SERVER): str,
vol.Optional(CONF_ACME_SERVER): str,
vol.Optional(CONF_API_SERVER): str,
vol.Optional(CONF_RELAYER_SERVER): str,

View File

@@ -76,7 +76,6 @@ CONF_GOOGLE_ACTIONS = "google_actions"
CONF_USER_POOL_ID = "user_pool_id"
CONF_ACCOUNT_LINK_SERVER = "account_link_server"
CONF_ACCOUNTS_SERVER = "accounts_server"
CONF_ACME_SERVER = "acme_server"
CONF_API_SERVER = "api_server"
CONF_DISCOVERY_SERVICE_ACTIONS = "discovery_service_actions"

View File

@@ -13,6 +13,6 @@
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["acme", "hass_nabucasa", "snitun"],
"requirements": ["hass-nabucasa==1.7.0"],
"requirements": ["hass-nabucasa==1.9.0"],
"single_config_entry": true
}

View File

@@ -461,7 +461,7 @@ FITBIT_RESOURCES_LIST: Final[tuple[FitbitSensorEntityDescription, ...]] = (
key="sleep/timeInBed",
translation_key="sleep_time_in_bed",
native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:hotel",
icon="mdi:bed",
device_class=SensorDeviceClass.DURATION,
scope=FitbitScope.SLEEP,
state_class=SensorStateClass.TOTAL_INCREASING,

View File

@@ -164,13 +164,12 @@ def _async_wol_buttons_list(
class FritzBoxWOLButton(FritzDeviceBase, ButtonEntity):
"""Defines a FRITZ!Box Tools Wake On LAN button."""
_attr_icon = "mdi:lan-pending"
_attr_entity_registry_enabled_default = False
_attr_translation_key = "wake_on_lan"
def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None:
"""Initialize Fritz!Box WOL button."""
super().__init__(avm_wrapper, device)
self._name = f"{self.hostname} Wake on LAN"
self._attr_unique_id = f"{self._mac}_wake_on_lan"
self._is_available = True

View File

@@ -10,6 +10,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DEFAULT_DEVICE_NAME
from .coordinator import FRITZ_DATA_KEY, AvmWrapper, FritzConfigEntry, FritzData
from .entity import FritzDeviceBase
from .helpers import device_filter_out_from_trackers
@@ -71,6 +72,7 @@ class FritzBoxTracker(FritzDeviceBase, ScannerEntity):
def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None:
"""Initialize a FRITZ!Box device."""
super().__init__(avm_wrapper, device)
self._attr_name: str = device.hostname or DEFAULT_DEVICE_NAME
self._last_activity: datetime.datetime | None = device.last_activity
@property

View File

@@ -13,7 +13,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_DEVICE_NAME, DOMAIN
from .const import DOMAIN
from .coordinator import AvmWrapper
from .models import FritzDevice
@@ -21,21 +21,17 @@ from .models import FritzDevice
class FritzDeviceBase(CoordinatorEntity[AvmWrapper]):
"""Entity base class for a device connected to a FRITZ!Box device."""
_attr_has_entity_name = True
def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None:
"""Initialize a FRITZ!Box device."""
super().__init__(avm_wrapper)
self._avm_wrapper = avm_wrapper
self._mac: str = device.mac_address
self._name: str = device.hostname or DEFAULT_DEVICE_NAME
self._attr_device_info = DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, device.mac_address)}
)
@property
def name(self) -> str:
"""Return device name."""
return self._name
@property
def ip_address(self) -> str | None:
"""Return the primary ip address of the device."""

View File

@@ -3,6 +3,9 @@
"button": {
"cleanup": {
"default": "mdi:broom"
},
"wake_on_lan": {
"default": "mdi:lan-pending"
}
},
"sensor": {
@@ -48,6 +51,11 @@
"max_kb_s_sent": {
"default": "mdi:upload"
}
},
"switch": {
"internet_access": {
"default": "mdi:router-wireless-settings"
}
}
},
"services": {

View File

@@ -8,6 +8,7 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["fritzconnection"],
"quality_scale": "bronze",
"requirements": ["fritzconnection[qr]==1.15.0", "xmltodict==1.0.2"],
"ssdp": [
{

View File

@@ -13,9 +13,7 @@ rules:
docs-removal-instructions: done
entity-event-setup: done
entity-unique-id: done
has-entity-name:
status: todo
comment: partially done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done

View File

@@ -108,6 +108,9 @@
},
"reconnect": {
"name": "Reconnect"
},
"wake_on_lan": {
"name": "Wake on LAN"
}
},
"sensor": {
@@ -162,6 +165,11 @@
"max_kb_s_sent": {
"name": "Max connection upload throughput"
}
},
"switch": {
"internet_access": {
"name": "Internet access"
}
}
},
"exceptions": {

View File

@@ -499,13 +499,12 @@ class FritzBoxDeflectionSwitch(FritzBoxBaseCoordinatorSwitch):
class FritzBoxProfileSwitch(FritzDeviceBase, SwitchEntity):
"""Defines a FRITZ!Box Tools DeviceProfile switch."""
_attr_icon = "mdi:router-wireless-settings"
_attr_translation_key = "internet_access"
def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None:
"""Init Fritz profile."""
super().__init__(avm_wrapper, device)
self._attr_is_on: bool = False
self._name = f"{device.hostname} Internet Access"
self._attr_unique_id = f"{self._mac}_internet_access"
self._attr_entity_category = EntityCategory.CONFIG

View File

@@ -23,5 +23,5 @@
"winter_mode": {}
},
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20260107.0"]
"requirements": ["home-assistant-frontend==20260107.1"]
}

View File

@@ -1,9 +1,21 @@
{
"entity": {
"sensor": {
"ammonia": {
"default": "mdi:molecule"
},
"benzene": {
"default": "mdi:molecule"
},
"nitrogen_dioxide": {
"default": "mdi:molecule"
},
"nitrogen_monoxide": {
"default": "mdi:molecule"
},
"non_methane_hydrocarbons": {
"default": "mdi:molecule"
},
"ozone": {
"default": "mdi:molecule"
},

View File

@@ -99,6 +99,14 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
"local_aqi": data.indexes[1].display_name
},
),
AirQualitySensorEntityDescription(
key="c6h6",
translation_key="benzene",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda x: x.pollutants.c6h6.concentration.units,
value_fn=lambda x: x.pollutants.c6h6.concentration.value,
exists_fn=lambda x: "c6h6" in {p.code for p in x.pollutants},
),
AirQualitySensorEntityDescription(
key="co",
state_class=SensorStateClass.MEASUREMENT,
@@ -106,6 +114,30 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
native_unit_of_measurement_fn=lambda x: x.pollutants.co.concentration.units,
value_fn=lambda x: x.pollutants.co.concentration.value,
),
AirQualitySensorEntityDescription(
key="nh3",
translation_key="ammonia",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda x: x.pollutants.nh3.concentration.units,
value_fn=lambda x: x.pollutants.nh3.concentration.value,
exists_fn=lambda x: "nh3" in {p.code for p in x.pollutants},
),
AirQualitySensorEntityDescription(
key="nmhc",
translation_key="non_methane_hydrocarbons",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda x: x.pollutants.nmhc.concentration.units,
value_fn=lambda x: x.pollutants.nmhc.concentration.value,
exists_fn=lambda x: "nmhc" in {p.code for p in x.pollutants},
),
AirQualitySensorEntityDescription(
key="no",
translation_key="nitrogen_monoxide",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda x: x.pollutants.no.concentration.units,
value_fn=lambda x: x.pollutants.no.concentration.value,
exists_fn=lambda x: "no" in {p.code for p in x.pollutants},
),
AirQualitySensorEntityDescription(
key="no2",
translation_key="nitrogen_dioxide",

View File

@@ -76,6 +76,12 @@
},
"entity": {
"sensor": {
"ammonia": {
"name": "Ammonia"
},
"benzene": {
"name": "Benzene"
},
"local_aqi": {
"name": "{local_aqi} AQI"
},
@@ -189,6 +195,9 @@
"name": "{local_aqi} dominant pollutant",
"state": {
"co": "[%key:component::sensor::entity_component::carbon_monoxide::name%]",
"nh3": "[%key:component::google_air_quality::entity::sensor::ammonia::name%]",
"nmhc": "[%key:component::google_air_quality::entity::sensor::non_methane_hydrocarbons::name%]",
"no": "[%key:component::sensor::entity_component::nitrogen_monoxide::name%]",
"no2": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]",
"o3": "[%key:component::sensor::entity_component::ozone::name%]",
"pm10": "[%key:component::sensor::entity_component::pm10::name%]",
@@ -199,6 +208,12 @@
"nitrogen_dioxide": {
"name": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]"
},
"nitrogen_monoxide": {
"name": "[%key:component::sensor::entity_component::nitrogen_monoxide::name%]"
},
"non_methane_hydrocarbons": {
"name": "Non-methane hydrocarbons"
},
"ozone": {
"name": "[%key:component::sensor::entity_component::ozone::name%]"
},

View File

@@ -0,0 +1,21 @@
"""Diagnostics for HDFury Integration."""
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .coordinator import HDFuryCoordinator
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: HDFuryCoordinator = entry.runtime_data
return {
"board": coordinator.data.board,
"info": coordinator.data.info,
"config": coordinator.data.config,
}

View File

@@ -43,7 +43,7 @@ rules:
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info: todo
discovery: todo
docs-data-update: todo

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
from dataclasses import dataclass
import logging
from pyhik.constants import SENSOR_MAP
from pyhik.hikvision import HikCamera
import requests
@@ -70,13 +71,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: HikvisionConfigEntry) ->
device_type=device_type,
)
_LOGGER.debug(
"Device %s (type=%s) initial event_states: %s",
device_name,
device_type,
camera.current_event_states,
)
# For NVRs or devices with no detected events, try to fetch events from ISAPI
# Use broader notification methods for NVRs since they often use 'record' etc.
if device_type == "NVR" or not camera.current_event_states:
nvr_notification_methods = {"center", "HTTP", "record", "email", "beep"}
def fetch_and_inject_nvr_events() -> None:
"""Fetch and inject NVR events in a single executor job."""
if nvr_events := camera.get_event_triggers():
camera.inject_events(nvr_events)
nvr_events = camera.get_event_triggers(nvr_notification_methods)
_LOGGER.debug("NVR events fetched with extended methods: %s", nvr_events)
if nvr_events:
# Map raw event type names to friendly names using SENSOR_MAP
mapped_events: dict[str, list[int]] = {}
for event_type, channels in nvr_events.items():
friendly_name = SENSOR_MAP.get(event_type.lower(), event_type)
if friendly_name in mapped_events:
mapped_events[friendly_name].extend(channels)
else:
mapped_events[friendly_name] = list(channels)
_LOGGER.debug("Mapped NVR events: %s", mapped_events)
camera.inject_events(mapped_events)
await hass.async_add_executor_job(fetch_and_inject_nvr_events)

View File

@@ -13,6 +13,6 @@
"iot_class": "local_polling",
"loggers": ["homewizard_energy"],
"quality_scale": "platinum",
"requirements": ["python-homewizard-energy==10.0.0"],
"requirements": ["python-homewizard-energy==10.0.1"],
"zeroconf": ["_hwenergy._tcp.local.", "_homewizard._tcp.local."]
}

View File

@@ -256,6 +256,8 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (ModelName.LINEA_MINI, ModelName.LINEA_MINI_R)
and WidgetType.CM_BREW_BY_WEIGHT_DOSES
in coordinator.device.dashboard.config
),
),
LaMarzoccoNumberEntityDescription(
@@ -289,6 +291,8 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (ModelName.LINEA_MINI, ModelName.LINEA_MINI_R)
and WidgetType.CM_BREW_BY_WEIGHT_DOSES
in coordinator.device.dashboard.config
),
),
)

View File

@@ -149,6 +149,8 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = (
supported_fn=(
lambda coordinator: coordinator.device.dashboard.model_name
in (ModelName.LINEA_MINI, ModelName.LINEA_MINI_R)
and WidgetType.CM_BREW_BY_WEIGHT_DOSES
in coordinator.device.dashboard.config
),
),
)

View File

@@ -140,6 +140,14 @@
"pump_status": {
"default": "mdi:pump"
},
"setpoint_change_source": {
"default": "mdi:hand-back-right",
"state": {
"external": "mdi:webhook",
"manual": "mdi:hand-back-right",
"schedule": "mdi:calendar-clock"
}
},
"tank_percentage": {
"default": "mdi:water-boiler"
},

View File

@@ -66,8 +66,9 @@ class MatterRangeNumberEntityDescription(
format_max_value: Callable[[float], float] = lambda x: x
# command: a custom callback to create the command to send to the device
# the callback's argument will be the index of the selected list value
command: Callable[[int], ClusterCommand]
# the callback's argument will be the converted device value from ha_to_device
# if omitted the command will just be a write_attribute command to the primary attribute
command: Callable[[int], ClusterCommand] | None = None
class MatterNumber(MatterEntity, NumberEntity):
@@ -99,9 +100,15 @@ class MatterRangeNumber(MatterEntity, NumberEntity):
async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
send_value = self.entity_description.ha_to_device(value)
# custom command defined to set the new value
await self.send_device_command(
self.entity_description.command(send_value),
if self.entity_description.command:
# custom command defined to set the new value
await self.send_device_command(
self.entity_description.command(send_value),
)
return
# regular write attribute to set the new value
await self.write_attribute(
value=send_value,
)
@callback
@@ -253,6 +260,30 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterNumber,
required_attributes=(custom_clusters.EveCluster.Attributes.Altitude,),
),
MatterDiscoverySchema(
platform=Platform.NUMBER,
entity_description=MatterRangeNumberEntityDescription(
key="ThermostatOccupiedSetback",
entity_category=EntityCategory.CONFIG,
translation_key="occupied_setback",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_to_ha=lambda x: None if x is None else x / 10,
ha_to_device=lambda x: round(x * 10),
format_min_value=lambda x: x / 10,
format_max_value=lambda x: x / 10,
min_attribute=clusters.Thermostat.Attributes.OccupiedSetbackMin,
max_attribute=clusters.Thermostat.Attributes.OccupiedSetbackMax,
native_step=0.5,
mode=NumberMode.BOX,
),
entity_class=MatterRangeNumber,
required_attributes=(
clusters.Thermostat.Attributes.OccupiedSetback,
clusters.Thermostat.Attributes.OccupiedSetbackMin,
clusters.Thermostat.Attributes.OccupiedSetbackMax,
),
featuremap_contains=(clusters.Thermostat.Bitmaps.Feature.kSetback),
),
MatterDiscoverySchema(
platform=Platform.NUMBER,
entity_description=MatterNumberEntityDescription(

View File

@@ -173,6 +173,13 @@ EVSE_FAULT_STATE_MAP = {
clusters.EnergyEvse.Enums.FaultStateEnum.kOther: "other",
}
SETPOINT_CHANGE_SOURCE_MAP = {
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kManual: "manual",
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kSchedule: "schedule",
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kExternal: "external",
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kUnknownEnumValue: None,
}
PUMP_CONTROL_MODE_MAP = {
clusters.PumpConfigurationAndControl.Enums.ControlModeEnum.kConstantSpeed: "constant_speed",
clusters.PumpConfigurationAndControl.Enums.ControlModeEnum.kConstantPressure: "constant_pressure",
@@ -1541,4 +1548,48 @@ DISCOVERY_SCHEMAS = [
required_attributes=(clusters.DoorLock.Attributes.DoorClosedEvents,),
featuremap_contains=clusters.DoorLock.Bitmaps.Feature.kDoorPositionSensor,
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="SetpointChangeSource",
translation_key="setpoint_change_source",
device_class=SensorDeviceClass.ENUM,
state_class=None,
options=[x for x in SETPOINT_CHANGE_SOURCE_MAP.values() if x is not None],
device_to_ha=lambda x: SETPOINT_CHANGE_SOURCE_MAP[x],
),
entity_class=MatterSensor,
required_attributes=(clusters.Thermostat.Attributes.SetpointChangeSource,),
device_type=(device_types.Thermostat, device_types.RoomAirConditioner),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="SetpointChangeSourceTimestamp",
translation_key="setpoint_change_timestamp",
device_class=SensorDeviceClass.TIMESTAMP,
state_class=None,
device_to_ha=matter_epoch_seconds_to_utc,
),
entity_class=MatterSensor,
required_attributes=(
clusters.Thermostat.Attributes.SetpointChangeSourceTimestamp,
),
device_type=(device_types.Thermostat, device_types.RoomAirConditioner),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="ThermostatSetpointChangeAmount",
translation_key="setpoint_change_amount",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=1,
device_class=SensorDeviceClass.TEMPERATURE,
device_to_ha=lambda x: x / TEMPERATURE_SCALING_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
required_attributes=(clusters.Thermostat.Attributes.SetpointChangeAmount,),
device_type=(device_types.Thermostat, device_types.RoomAirConditioner),
),
]

View File

@@ -217,6 +217,9 @@
"led_indicator_intensity_on": {
"name": "LED on intensity"
},
"occupied_setback": {
"name": "Occupied setback"
},
"off_transition_time": {
"name": "Off transition time"
},
@@ -546,6 +549,20 @@
"rms_voltage": {
"name": "Effective voltage"
},
"setpoint_change_amount": {
"name": "Last change amount"
},
"setpoint_change_source": {
"name": "Last change source",
"state": {
"external": "External",
"manual": "Manual",
"schedule": "Schedule"
}
},
"setpoint_change_timestamp": {
"name": "Last change"
},
"switch_current_position": {
"name": "Current switch position"
},

View File

@@ -47,7 +47,6 @@ rules:
test-coverage:
status: todo
comment: |
Use load_json_object_fixture in tests
Patch the library instead of the HTTP requests
Create a shared fixture for the mock config entry
Use init_integration in tests

View File

@@ -28,7 +28,7 @@ DEVICE_SUPPORT = {
"3A": (),
"3B": (),
"42": (),
"7E": ("EDS0066", "EDS0068"),
"7E": ("EDS0065", "EDS0066", "EDS0068"),
"A6": (),
"EF": ("HB_HUB", "HB_MOISTURE_METER", "HobbyBoards_EF"),
}

View File

@@ -297,6 +297,20 @@ HOBBYBOARD_EF: dict[str, tuple[OneWireSensorEntityDescription, ...]] = {
# 7E sensors are special sensors by Embedded Data Systems
EDS_SENSORS: dict[str, tuple[OneWireSensorEntityDescription, ...]] = {
"EDS0065": (
OneWireSensorEntityDescription(
key="EDS0065/temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
OneWireSensorEntityDescription(
key="EDS0065/humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
),
"EDS0066": (
OneWireSensorEntityDescription(
key="EDS0066/temperature",

View File

@@ -7,7 +7,6 @@ from aiohttp import ClientError, ClientResponseError, web
from pypoint import PointSession
from homeassistant.components import webhook
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_WEBHOOK_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
@@ -21,14 +20,12 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
from . import api
from .const import CONF_WEBHOOK_URL, DOMAIN, EVENT_RECEIVED, SIGNAL_WEBHOOK
from .coordinator import PointDataUpdateCoordinator
from .coordinator import PointConfigEntry, PointDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
type PointConfigEntry = ConfigEntry[PointDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: PointConfigEntry) -> bool:
"""Set up Minut Point from a config entry."""
@@ -59,7 +56,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: PointConfigEntry) -> boo
point_session = PointSession(auth)
coordinator = PointDataUpdateCoordinator(hass, point_session)
coordinator = PointDataUpdateCoordinator(hass, point_session, entry)
await coordinator.async_config_entry_first_refresh()

View File

@@ -16,8 +16,8 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import PointConfigEntry
from .const import DOMAIN, SIGNAL_WEBHOOK
from .coordinator import PointConfigEntry
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,9 +15,8 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import PointConfigEntry
from .const import SIGNAL_WEBHOOK
from .coordinator import PointDataUpdateCoordinator
from .coordinator import PointConfigEntry, PointDataUpdateCoordinator
from .entity import MinutPointEntity
_LOGGER = logging.getLogger(__name__)

View File

@@ -7,6 +7,7 @@ from typing import Any
from pypoint import PointSession
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.dt import parse_datetime
@@ -15,17 +16,24 @@ from .const import DOMAIN, SCAN_INTERVAL
_LOGGER = logging.getLogger(__name__)
type PointConfigEntry = ConfigEntry[PointDataUpdateCoordinator]
class PointDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
"""Class to manage fetching Point data from the API."""
def __init__(self, hass: HomeAssistant, point: PointSession) -> None:
config_entry: PointConfigEntry
def __init__(
self, hass: HomeAssistant, point: PointSession, config_entry: PointConfigEntry
) -> None:
"""Initialize."""
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
config_entry=config_entry,
)
self.point = point
self.device_updates: dict[str, datetime] = {}

View File

@@ -14,8 +14,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import PointConfigEntry
from .coordinator import PointDataUpdateCoordinator
from .coordinator import PointConfigEntry, PointDataUpdateCoordinator
from .entity import MinutPointEntity
_LOGGER = logging.getLogger(__name__)

View File

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

View File

@@ -45,12 +45,12 @@ rules:
discovery-update-info: done
discovery: done
docs-data-update: done
docs-examples: todo
docs-examples: done
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: todo
docs-use-cases: done
dynamic-devices:
status: exempt
comment: This integration does not support dynamic device discovery, as each config entry represents a single PoolDose device with all available entities.

View File

@@ -40,12 +40,13 @@ class PortainerSwitchEntityDescription(SwitchEntityDescription):
async def perform_action(
action: str, portainer: Portainer, endpoint_id: int, container_id: str
) -> None:
"""Stop a container."""
"""Perform an action on a container."""
try:
if action == "start":
await portainer.start_container(endpoint_id, container_id)
elif action == "stop":
await portainer.stop_container(endpoint_id, container_id)
match action:
case "start":
await portainer.start_container(endpoint_id, container_id)
case "stop":
await portainer.stop_container(endpoint_id, container_id)
except PortainerAuthenticationError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,

View File

@@ -142,6 +142,8 @@ async def async_setup_entry(
class ScrapeSensor(CoordinatorEntity[ScrapeCoordinator], ManualTriggerSensorEntity):
"""Representation of a web scrape sensor."""
_sensor_name: str | None = None
def __init__(
self,
hass: HomeAssistant,
@@ -162,14 +164,26 @@ class ScrapeSensor(CoordinatorEntity[ScrapeCoordinator], ManualTriggerSensorEnti
self._value_template = value_template
self._attr_native_value = None
if not yaml and (unique_id := trigger_entity_config.get(CONF_UNIQUE_ID)):
self._attr_name = None
self._sensor_name = None
self._attr_has_entity_name = True
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, unique_id)},
manufacturer="Scrape",
name=self.name,
name=self._rendered[CONF_NAME],
)
else:
self._sensor_name = self._rendered.get(CONF_NAME)
@property
def name(self) -> str | None:
"""Return the name of the sensor.
Override needed because TriggerBaseEntity.name always returns the
rendered name, ignoring _attr_name. When has_entity_name is True,
we need name to return None to use the device name instead.
"""
return self._sensor_name
def _extract_value(self) -> Any:
"""Parse the html extraction in the executor."""

View File

@@ -6,5 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/tplink_omada",
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["tplink-omada-client==1.5.3"]
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/vallox",
"iot_class": "local_polling",
"loggers": ["vallox_websocket_api"],
"requirements": ["vallox-websocket-api==5.3.0"]
"requirements": ["vallox-websocket-api==6.0.0"]
}

View File

@@ -212,6 +212,7 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = (
key="battery",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
exists_fn=lambda device: device.device_type in BATTERY_POWER_SENSOR,
should_update_entity=lambda value: value is not None,
@@ -251,9 +252,11 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = (
# mcu temperature
YoLinkSensorEntityDescription(
key="devTemperature",
translation_key="device_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
exists_fn=lambda device: device.device_type in MCU_DEV_TEMPERATURE_SENSOR,
should_update_entity=lambda value: value is not None,
value=lambda device, data: data.get("devTemperature"),

View File

@@ -67,6 +67,9 @@
"current_power": {
"name": "Current power"
},
"device_temperature": {
"name": "Device temperature"
},
"power_consumption": {
"name": "Power consumption"
},

View File

@@ -25,7 +25,6 @@ from homeassistant.const import (
ATTR_ASSUMED_STATE,
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_ENTITY_PICTURE,
ATTR_FRIENDLY_NAME,
ATTR_ICON,
@@ -418,7 +417,6 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
"extra_state_attributes",
"force_update",
"icon",
"included_unique_ids",
"name",
"should_poll",
"state",
@@ -526,9 +524,6 @@ class Entity(
__capabilities_updated_at_reported: bool = False
__remove_future: asyncio.Future[None] | None = None
# A list of included entity IDs in case the entity represents a group
_included_entities: list[str] | None = None
# Entity Properties
_attr_assumed_state: bool = False
_attr_attribution: str | None = None
@@ -544,7 +539,6 @@ class Entity(
_attr_extra_state_attributes: dict[str, Any]
_attr_force_update: bool
_attr_icon: str | None
_attr_included_unique_ids: list[str]
_attr_name: str | None
_attr_should_poll: bool = True
_attr_state: StateType = STATE_UNKNOWN
@@ -1091,21 +1085,6 @@ class Entity(
available = self.available # only call self.available once per update cycle
state = self._stringify_state(available)
if available:
if self.included_unique_ids is not None:
entity_registry = er.async_get(self.hass)
self._included_entities = [
entity_id
for included_id in self.included_unique_ids
if (
entity_id := entity_registry.async_get_entity_id(
self.platform.domain,
self.platform.platform_name,
included_id,
)
)
is not None
]
attr[ATTR_ENTITY_ID] = self._included_entities.copy()
if state_attributes := self.state_attributes:
attr |= state_attributes
if extra_state_attributes := self.extra_state_attributes:
@@ -1395,30 +1374,6 @@ class Entity(
async def add_to_platform_finish(self) -> None:
"""Finish adding an entity to a platform."""
entity_registry = er.async_get(self.hass)
async def _handle_entity_registry_updated(event: Event[Any]) -> None:
"""Handle registry create or update event."""
if (
event.data["action"] in {"create", "update"}
and (entry := entity_registry.async_get(event.data["entity_id"]))
and self.included_unique_ids is not None
and entry.unique_id in self.included_unique_ids
) or (
event.data["action"] == "remove"
and self._included_entities is not None
and event.data["entity_id"] in self._included_entities
):
self.async_write_ha_state()
if self.included_unique_ids is not None:
self.async_on_remove(
self.hass.bus.async_listen(
er.EVENT_ENTITY_REGISTRY_UPDATED,
_handle_entity_registry_updated,
)
)
await self.async_internal_added_to_hass()
await self.async_added_to_hass()
self._platform_state = EntityPlatformState.ADDED
@@ -1680,16 +1635,6 @@ class Entity(
self.hass, integration_domain=platform_name, module=type(self).__module__
)
@cached_property
def included_unique_ids(self) -> list[str] | None:
"""Return the list of unique IDs if the entity represents a group.
The corresponding entities will be shown as members in the UI.
"""
if hasattr(self, "_attr_included_unique_ids"):
return self._attr_included_unique_ids
return None
class ToggleEntityDescription(EntityDescription, frozen_or_thawed=True):
"""A class that describes toggle entities."""

View File

@@ -38,12 +38,7 @@ from homeassistant.setup import SetupPhases, async_start_setup
from homeassistant.util.async_ import create_eager_task
from homeassistant.util.hass_dict import HassKey
from . import (
device_registry as dev_reg,
entity_registry as ent_reg,
service,
translation,
)
from . import device_registry as dr, entity_registry as er, service, translation
from .deprecation import deprecated_function
from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider
from .event import async_call_later
@@ -624,7 +619,7 @@ class EntityPlatform:
event loop and will finish faster if we run them concurrently.
"""
results: list[BaseException | None] | None = None
entity_registry = ent_reg.async_get(self.hass)
entity_registry = er.async_get(self.hass)
try:
async with self.hass.timeout.async_timeout(timeout, self.domain):
results = await asyncio.gather(
@@ -676,7 +671,7 @@ class EntityPlatform:
to the event loop so we can await the coros directly without
scheduling them as tasks.
"""
entity_registry = ent_reg.async_get(self.hass)
entity_registry = er.async_get(self.hass)
try:
async with self.hass.timeout.async_timeout(timeout, self.domain):
for entity in entities:
@@ -852,16 +847,16 @@ class EntityPlatform:
entity.add_to_platform_abort()
return
device: dev_reg.DeviceEntry | None
device: dr.DeviceEntry | None
if self.config_entry:
if device_info := entity.device_info:
try:
device = dev_reg.async_get(self.hass).async_get_or_create(
device = dr.async_get(self.hass).async_get_or_create(
config_entry_id=self.config_entry.entry_id,
config_subentry_id=config_subentry_id,
**device_info,
)
except dev_reg.DeviceInfoError as exc:
except dr.DeviceInfoError as exc:
self.logger.error(
"%s: Not adding entity with invalid device info: %s",
self.platform_name,
@@ -869,6 +864,8 @@ class EntityPlatform:
)
entity.add_to_platform_abort()
return
entity.device_entry = device
else:
device = entity.device_entry
else:
@@ -929,8 +926,6 @@ class EntityPlatform:
)
entity.registry_entry = entry
if device:
entity.device_entry = device
entity.entity_id = entry.entity_id
else: # entity.unique_id is None
@@ -1236,7 +1231,7 @@ class EntityPlatform:
@callback
def async_calculate_suggested_object_id(
entity: Entity, device: dev_reg.DeviceEntry | None
entity: Entity, device: dr.DeviceEntry | None
) -> str | None:
"""Calculate the suggested object ID for an entity."""
calculated_object_id: str | None = None

View File

@@ -36,10 +36,10 @@ fnv-hash-fast==1.6.0
go2rtc-client==0.4.0
ha-ffmpeg==3.2.2
habluetooth==5.8.0
hass-nabucasa==1.7.0
hass-nabucasa==1.9.0
hassil==3.5.0
home-assistant-bluetooth==1.13.1
home-assistant-frontend==20260107.0
home-assistant-frontend==20260107.1
home-assistant-intents==2026.1.6
httpx==0.28.1
ifaddr==0.2.0

View File

@@ -9,8 +9,6 @@ import logging
import os
from typing import Any
from packaging.requirements import Requirement
from .core import HomeAssistant, callback
from .exceptions import HomeAssistantError
from .helpers import singleton
@@ -260,8 +258,13 @@ class RequirementsManager:
"""
if DEPRECATED_PACKAGES or self.hass.config.skip_pip_packages:
all_requirements = {
requirement_string: Requirement(requirement_string)
requirement_string: requirement_details
for requirement_string in requirements
if (
requirement_details := pkg_util.parse_requirement_safe(
requirement_string
)
)
}
if DEPRECATED_PACKAGES:
for requirement_string, requirement_details in all_requirements.items():
@@ -272,9 +275,12 @@ class RequirementsManager:
"" if is_built_in else "custom ",
name,
f"has requirement '{requirement_string}' which {reason}",
f"This will stop working in Home Assistant {breaks_in_ha_version}, please"
if breaks_in_ha_version
else "Please",
(
"This will stop working in Home Assistant "
f"{breaks_in_ha_version}, please"
if breaks_in_ha_version
else "Please"
),
async_suggest_report_issue(
self.hass, integration_domain=name
),

View File

@@ -44,6 +44,39 @@ def get_installed_versions(specifiers: set[str]) -> set[str]:
return {specifier for specifier in specifiers if is_installed(specifier)}
def parse_requirement_safe(requirement_str: str) -> Requirement | None:
"""Parse a requirement string into a Requirement object.
expected input is a pip compatible package specifier (requirement string)
e.g. "package==1.0.0" or "package>=1.0.0,<2.0.0" or "package@git+https://..."
For backward compatibility, it also accepts a URL with a fragment
e.g. "git+https://github.com/pypa/pip#pip>=1"
Returns None on a badly-formed requirement string.
"""
try:
return Requirement(requirement_str)
except InvalidRequirement:
if "#" not in requirement_str:
_LOGGER.error("Invalid requirement '%s'", requirement_str)
return None
# This is likely a URL with a fragment
# example: git+https://github.com/pypa/pip#pip>=1
# fragment support was originally used to install zip files, and
# we no longer do this in Home Assistant. However, custom
# components started using it to install packages from git
# urls which would make it would be a breaking change to
# remove it.
try:
return Requirement(urlparse(requirement_str).fragment)
except InvalidRequirement:
_LOGGER.error("Invalid requirement '%s'", requirement_str)
return None
def is_installed(requirement_str: str) -> bool:
"""Check if a package is installed and will be loaded when we import it.
@@ -56,26 +89,8 @@ def is_installed(requirement_str: str) -> bool:
Returns True when the requirement is met.
Returns False when the package is not installed or doesn't meet req.
"""
try:
req = Requirement(requirement_str)
except InvalidRequirement:
if "#" not in requirement_str:
_LOGGER.error("Invalid requirement '%s'", requirement_str)
return False
# This is likely a URL with a fragment
# example: git+https://github.com/pypa/pip#pip>=1
# fragment support was originally used to install zip files, and
# we no longer do this in Home Assistant. However, custom
# components started using it to install packages from git
# urls which would make it would be a breaking change to
# remove it.
try:
req = Requirement(urlparse(requirement_str).fragment)
except InvalidRequirement:
_LOGGER.error("Invalid requirement '%s'", requirement_str)
return False
if (req := parse_requirement_safe(requirement_str)) is None:
return False
try:
if (installed_version := version(req.name)) is None:

View File

@@ -48,7 +48,7 @@ dependencies = [
"fnv-hash-fast==1.6.0",
# hass-nabucasa is imported by helpers which don't depend on the cloud
# integration
"hass-nabucasa==1.7.0",
"hass-nabucasa==1.9.0",
# When bumping httpx, please check the version pins of
# httpcore, anyio, and h11 in gen_requirements_all
"httpx==0.28.1",

2
requirements.txt generated
View File

@@ -24,7 +24,7 @@ cronsim==2.7
cryptography==46.0.2
fnv-hash-fast==1.6.0
ha-ffmpeg==3.2.2
hass-nabucasa==1.7.0
hass-nabucasa==1.9.0
hassil==3.5.0
home-assistant-bluetooth==1.13.1
home-assistant-intents==2026.1.6

10
requirements_all.txt generated
View File

@@ -1172,7 +1172,7 @@ habluetooth==5.8.0
hanna-cloud==0.0.7
# homeassistant.components.cloud
hass-nabucasa==1.7.0
hass-nabucasa==1.9.0
# homeassistant.components.splunk
hass-splunk==0.1.1
@@ -1216,7 +1216,7 @@ hole==0.9.0
holidays==0.84
# homeassistant.components.frontend
home-assistant-frontend==20260107.0
home-assistant-frontend==20260107.1
# homeassistant.components.conversation
home-assistant-intents==2026.1.6
@@ -2481,7 +2481,7 @@ python-awair==0.2.5
python-blockchain-api==0.0.2
# homeassistant.components.bsblan
python-bsblan==3.1.6
python-bsblan==4.1.0
# homeassistant.components.citybikes
python-citybikes==0.3.3
@@ -2520,7 +2520,7 @@ python-google-weather-api==0.0.4
python-homeassistant-analytics==0.9.0
# homeassistant.components.homewizard
python-homewizard-energy==10.0.0
python-homewizard-energy==10.0.1
# homeassistant.components.hp_ilo
python-hpilo==4.4.3
@@ -3116,7 +3116,7 @@ uvcclient==0.12.1
vacuum-map-parser-roborock==0.1.4
# homeassistant.components.vallox
vallox-websocket-api==5.3.0
vallox-websocket-api==6.0.0
# homeassistant.components.vegehub
vegehub==0.1.26

View File

@@ -1042,7 +1042,7 @@ habluetooth==5.8.0
hanna-cloud==0.0.7
# homeassistant.components.cloud
hass-nabucasa==1.7.0
hass-nabucasa==1.9.0
# homeassistant.components.assist_satellite
# homeassistant.components.conversation
@@ -1074,7 +1074,7 @@ hole==0.9.0
holidays==0.84
# homeassistant.components.frontend
home-assistant-frontend==20260107.0
home-assistant-frontend==20260107.1
# homeassistant.components.conversation
home-assistant-intents==2026.1.6
@@ -2098,7 +2098,7 @@ python-MotionMount==2.3.0
python-awair==0.2.5
# homeassistant.components.bsblan
python-bsblan==3.1.6
python-bsblan==4.1.0
# homeassistant.components.ecobee
python-ecobee-api==0.3.2
@@ -2116,7 +2116,7 @@ python-google-weather-api==0.0.4
python-homeassistant-analytics==0.9.0
# homeassistant.components.homewizard
python-homewizard-energy==10.0.0
python-homewizard-energy==10.0.1
# homeassistant.components.izone
python-izone==1.2.9
@@ -2604,7 +2604,7 @@ uvcclient==0.12.1
vacuum-map-parser-roborock==0.1.4
# homeassistant.components.vallox
vallox-websocket-api==5.3.0
vallox-websocket-api==6.0.0
# homeassistant.components.vegehub
vegehub==0.1.26

View File

@@ -1390,7 +1390,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
"freebox",
"freedns",
"freedompro",
"fritz",
"fritzbox",
"fritzbox_callmonitor",
"frontier_silicon",
@@ -2000,7 +1999,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
"touchline",
"touchline_sl",
"tplink_lte",
"tplink_omada",
"traccar",
"traccar_server",
"tractive",

View File

@@ -2,12 +2,13 @@
from unittest.mock import patch
from aioairzone.common import OperationMode
from aioairzone.common import OperationMode, QAdapt
from aioairzone.const import (
API_COLD_ANGLE,
API_DATA,
API_HEAT_ANGLE,
API_MODE,
API_Q_ADAPT,
API_SLEEP,
API_SYSTEM_ID,
API_ZONE_ID,
@@ -17,7 +18,7 @@ import pytest
from homeassistant.components.select import ATTR_OPTIONS, DOMAIN as SELECT_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, SERVICE_SELECT_OPTION
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from .util import async_init_integration
@@ -27,6 +28,11 @@ async def test_airzone_create_selects(hass: HomeAssistant) -> None:
await async_init_integration(hass)
# Systems
state = hass.states.get("select.system_1_q_adapt")
assert state.state == "standard"
# Zones
state = hass.states.get("select.despacho_cold_angle")
assert state.state == "90deg"
@@ -95,6 +101,71 @@ async def test_airzone_create_selects(hass: HomeAssistant) -> None:
assert state.state == "off"
async def test_airzone_select_sys_qadapt(hass: HomeAssistant) -> None:
"""Test select system Q-Adapt."""
await async_init_integration(hass)
put_q_adapt = {
API_DATA: {
API_SYSTEM_ID: 1,
API_Q_ADAPT: QAdapt.SILENCE,
}
}
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.system_1_q_adapt",
ATTR_OPTION: "Invalid",
},
blocking=True,
)
with patch(
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
return_value=put_q_adapt,
):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.system_1_q_adapt",
ATTR_OPTION: "silence",
},
blocking=True,
)
state = hass.states.get("select.system_1_q_adapt")
assert state.state == "silence"
put_q_adapt = {
API_DATA: {
API_SYSTEM_ID: 2,
API_Q_ADAPT: QAdapt.SILENCE,
}
}
with (
patch(
"homeassistant.components.airzone.AirzoneLocalApi.put_hvac",
return_value=put_q_adapt,
),
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.system_1_q_adapt",
ATTR_OPTION: "silence",
},
blocking=True,
)
async def test_airzone_select_sleep(hass: HomeAssistant) -> None:
"""Test select sleep."""

View File

@@ -10,7 +10,7 @@ CONF_DATA: Final = {CONF_HOST: "test", CONF_PORT: 1234}
MOCK_STATUS: Final = {
"APC": "001,038,0985",
"DATE": "1970-01-01 00:00:00 0000",
"DATE": "1970-01-01 00:00:00 +0000",
"VERSION": "3.14.14 (31 May 2016) unknown",
"CABLE": "USB Cable",
"DRIVER": "USB UPS Driver",
@@ -19,6 +19,7 @@ MOCK_STATUS: Final = {
"APCMODEL": "Back-UPS ES 600",
"MODEL": "Back-UPS ES 600",
"STATUS": "ONLINE",
"STARTTIME": "2006-01-01 00:00:00 +0500",
"LINEV": "124.0 Volts",
"LOADPCT": "14.0 Percent",
"BCHARGE": "100.0 Percent",
@@ -36,11 +37,11 @@ MOCK_STATUS: Final = {
"OUTCURNT": "0.88 Amps",
"LASTXFER": "Automatic or explicit self test",
"NUMXFERS": "1",
"XONBATT": "1970-01-01 00:00:00 0000",
"XONBATT": "1970-01-01 00:00:00 +0000",
"TONBATT": "0 Seconds",
"CUMONBATT": "8 Seconds",
"XOFFBATT": "1970-01-01 00:00:00 0000",
"LASTSTEST": "1970-01-01 00:00:00 0000",
"XOFFBATT": "1970-01-01 00:00:00 +0000",
"LASTSTEST": "1970-01-01 00:00:00 +0000",
"SELFTEST": "NO",
"STESTI": "7 days",
"STATFLAG": "0x05000008",
@@ -50,7 +51,8 @@ MOCK_STATUS: Final = {
"NOMBATTV": "12.0 Volts",
"NOMPOWER": "330 Watts",
"FIRMWARE": "928.a8 .D USB FW:a8",
"END APC": "1970-01-01 00:00:00 0000",
"MASTERUPD": "1970-01-01 00:00:00 +0000",
"END APC": "1970-01-01 00:00:00 +0000",
}
# Minimal status adapted from http://www.apcupsd.org/manual/manual.html#apcaccess-test.
@@ -58,13 +60,13 @@ MOCK_STATUS: Final = {
# of the integration to handle such cases.
MOCK_MINIMAL_STATUS: Final = {
"APC": "001,012,0319",
"DATE": "1970-01-01 00:00:00 0000",
"DATE": "1970-01-01 00:00:00 +0000",
"RELEASE": "3.8.5",
"CABLE": "APC Cable 940-0128A",
"UPSMODE": "Stand Alone",
"STARTTIME": "1970-01-01 00:00:00 0000",
"STARTTIME": "1970-01-01 00:00:00 +0000",
"LINEFAIL": "OK",
"BATTSTAT": "OK",
"STATFLAG": "0x008",
"END APC": "1970-01-01 00:00:00 0000",
"END APC": "1970-01-01 00:00:00 +0000",
}

View File

@@ -9,17 +9,18 @@
'BCHARGE': '100.0 Percent',
'CABLE': 'USB Cable',
'CUMONBATT': '8 Seconds',
'DATE': '1970-01-01 00:00:00 0000',
'DATE': '1970-01-01 00:00:00 +0000',
'DRIVER': 'USB UPS Driver',
'END APC': '1970-01-01 00:00:00 0000',
'END APC': '1970-01-01 00:00:00 +0000',
'FIRMWARE': '928.a8 .D USB FW:a8',
'HITRANS': '139.0 Volts',
'ITEMP': '34.6 C Internal',
'LASTSTEST': '1970-01-01 00:00:00 0000',
'LASTSTEST': '1970-01-01 00:00:00 +0000',
'LASTXFER': 'Automatic or explicit self test',
'LINEV': '124.0 Volts',
'LOADPCT': '14.0 Percent',
'LOTRANS': '92.0 Volts',
'MASTERUPD': '1970-01-01 00:00:00 +0000',
'MAXTIME': '0 Seconds',
'MBATTCHG': '5 Percent',
'MINTIMEL': '3 Minutes',
@@ -33,6 +34,7 @@
'SELFTEST': 'NO',
'SENSE': 'Medium',
'SERIALNO': '**REDACTED**',
'STARTTIME': '2006-01-01 00:00:00 +0500',
'STATFLAG': '0x05000008',
'STATUS': 'ONLINE',
'STESTI': '7 days',
@@ -41,7 +43,7 @@
'UPSMODE': 'Stand Alone',
'UPSNAME': 'MyUPS',
'VERSION': '3.14.14 (31 May 2016) unknown',
'XOFFBATT': '1970-01-01 00:00:00 0000',
'XONBATT': '1970-01-01 00:00:00 0000',
'XOFFBATT': '1970-01-01 00:00:00 +0000',
'XONBATT': '1970-01-01 00:00:00 +0000',
})
# ---

View File

@@ -737,7 +737,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1970-01-01 00:00:00 0000',
'state': '1970-01-01 00:00:00 +0000',
})
# ---
# name: test_sensor[sensor.myups_driver-entry]
@@ -971,7 +971,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Last self-test',
'platform': 'apcupsd',
@@ -986,6 +986,7 @@
# name: test_sensor[sensor.myups_last_self_test-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'MyUPS Last self-test',
}),
'context': <ANY>,
@@ -993,7 +994,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1970-01-01 00:00:00 0000',
'state': '1970-01-01T00:00:00+00:00',
})
# ---
# name: test_sensor[sensor.myups_last_transfer-entry]
@@ -1096,6 +1097,55 @@
'state': '14.0',
})
# ---
# name: test_sensor[sensor.myups_master_update-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.myups_master_update',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Master update',
'platform': 'apcupsd',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'master_update',
'unique_id': 'XXXXXXXXXXXX_masterupd',
'unit_of_measurement': None,
})
# ---
# name: test_sensor[sensor.myups_master_update-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'MyUPS Master update',
}),
'context': <ANY>,
'entity_id': 'sensor.myups_master_update',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1970-01-01T00:00:00+00:00',
})
# ---
# name: test_sensor[sensor.myups_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -1745,6 +1795,55 @@
'state': '3',
})
# ---
# name: test_sensor[sensor.myups_startup_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.myups_startup_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Startup time',
'platform': 'apcupsd',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'startup_time',
'unique_id': 'XXXXXXXXXXXX_starttime',
'unit_of_measurement': None,
})
# ---
# name: test_sensor[sensor.myups_startup_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'MyUPS Startup time',
}),
'context': <ANY>,
'entity_id': 'sensor.myups_startup_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2005-12-31T19:00:00+00:00',
})
# ---
# name: test_sensor[sensor.myups_status-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -1886,7 +1985,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1970-01-01 00:00:00 0000',
'state': '1970-01-01 00:00:00 +0000',
})
# ---
# name: test_sensor[sensor.myups_status_flag-entry]
@@ -2179,7 +2278,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Transfer from battery',
'platform': 'apcupsd',
@@ -2194,6 +2293,7 @@
# name: test_sensor[sensor.myups_transfer_from_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'MyUPS Transfer from battery',
}),
'context': <ANY>,
@@ -2201,7 +2301,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1970-01-01 00:00:00 0000',
'state': '1970-01-01T00:00:00+00:00',
})
# ---
# name: test_sensor[sensor.myups_transfer_high-entry]
@@ -2333,7 +2433,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Transfer to battery',
'platform': 'apcupsd',
@@ -2348,6 +2448,7 @@
# name: test_sensor[sensor.myups_transfer_to_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'MyUPS Transfer to battery',
}),
'context': <ANY>,
@@ -2355,6 +2456,6 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1970-01-01 00:00:00 0000',
'state': '1970-01-01T00:00:00+00:00',
})
# ---

View File

@@ -3,6 +3,7 @@
from datetime import timedelta
from unittest.mock import AsyncMock
import dateutil.parser
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -151,13 +152,17 @@ async def test_sensor_unknown(
# Simulate an event (a self test) such that "LASTSTEST" field is being reported, the state of
# the sensor should be properly updated with the corresponding value.
last_self_test_value = "1970-01-01 00:00:00 +0000"
mock_request_status.return_value = MOCK_MINIMAL_STATUS | {
"LASTSTEST": "1970-01-01 00:00:00 0000"
"LASTSTEST": last_self_test_value
}
future = utcnow() + timedelta(minutes=2)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert hass.states.get(last_self_test_id).state == "1970-01-01 00:00:00 0000"
assert (
hass.states.get(last_self_test_id).state
== dateutil.parser.parse(last_self_test_value).isoformat()
)
# Simulate another event (e.g., daemon restart) such that "LASTSTEST" is no longer reported.
mock_request_status.return_value = MOCK_MINIMAL_STATUS

View File

@@ -159,6 +159,30 @@ async def test_climate_hvac_mode_none_value(
assert state.state == "unknown"
async def test_climate_hvac_mode_object_none(
hass: HomeAssistant,
mock_bsblan: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test climate entity when hvac_mode object itself is None."""
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
# Set hvac_mode to None (the object itself, not just the value)
mock_bsblan.state.return_value.hvac_mode = None
freezer.tick(timedelta(minutes=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
# State should be unknown when hvac_mode object is None
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == "unknown"
# preset_mode should be "none" when hvac_mode object is None
assert state.attributes["preset_mode"] == PRESET_NONE
async def test_climate_hvac_mode_string_fallback(
hass: HomeAssistant,
mock_bsblan: AsyncMock,

View File

@@ -245,6 +245,7 @@ async def cloud_prefs(hass: HomeAssistant) -> CloudPreferences:
async def mock_cloud_setup(hass: HomeAssistant) -> None:
"""Set up the cloud."""
await mock_cloud(hass)
await hass.async_block_till_done()
@pytest.fixture

View File

@@ -130,7 +130,6 @@ async def setup_cloud_fixture(hass: HomeAssistant, cloud: MagicMock) -> None:
"relayer_server": "relayer",
"acme_server": "cert-server",
"api_server": "api-test.example.com",
"accounts_server": "api-test.hass.io",
"google_actions": {"filter": {"include_domains": "light"}},
"alexa": {
"filter": {"include_entities": ["light.kitchen", "switch.ac"]}

View File

@@ -45,7 +45,6 @@ async def test_constructor_loads_info_from_config(hass: HomeAssistant) -> None:
"region": "test-region",
"api_server": "test-api-server",
"relayer_server": "test-relayer-server",
"accounts_server": "test-acounts-server",
"acme_server": "test-acme-server",
"remotestate_server": "test-remotestate-server",
"discovery_service_actions": {
@@ -63,7 +62,6 @@ async def test_constructor_loads_info_from_config(hass: HomeAssistant) -> None:
assert cl.region == "test-region"
assert cl.relayer_server == "test-relayer-server"
assert cl.iot.ws_server_url == "wss://test-relayer-server/websocket"
assert cl.accounts_server == "test-acounts-server"
assert cl.acme_server == "test-acme-server"
assert cl.api_server == "test-api-server"
assert cl.remotestate_server == "test-remotestate-server"

View File

@@ -48,7 +48,7 @@ async def test_create_repair_issues_at_startup_if_logged_in(
) -> None:
"""Test that we create repair issue at startup if we are logged in."""
aioclient_mock.get(
"https://accounts.nabucasa.com/payments/subscription_info",
"https://api.nabucasa.com/account/payments/subscription_info",
json={"provider": "legacy"},
)
@@ -88,11 +88,11 @@ async def test_legacy_subscription_repair_flow(
) -> None:
"""Test desired flow of the fix flow for legacy subscription."""
aioclient_mock.get(
"https://accounts.nabucasa.com/payments/subscription_info",
"https://api.nabucasa.com/account/payments/subscription_info",
json={"provider": None},
)
aioclient_mock.post(
"https://accounts.nabucasa.com/payments/migrate_paypal_agreement",
"https://api.nabucasa.com/account/payments/migrate_paypal_agreement",
json={"url": "https://paypal.com"},
)

View File

@@ -20,7 +20,6 @@ from tests.test_util.aiohttp import AiohttpClientMocker
async def mocked_cloud_object(hass: HomeAssistant) -> Cloud:
"""Mock cloud object."""
return Mock(
accounts_server="accounts.nabucasa.com",
auth=Mock(async_check_token=AsyncMock()),
websession=async_get_clientsession(hass),
payments=Mock(

View File

@@ -345,10 +345,10 @@ async def test_get_tts_audio_logged_out(
@pytest.mark.parametrize(
("mock_process_tts_side_effect"),
"mock_process_tts_side_effect",
[
(None,),
(VoiceError("Boom!"),),
None,
VoiceError("Boom!"),
],
)
async def test_tts_entity(

View File

@@ -823,6 +823,9 @@ async def _check_config_flow_result_translations(
integration = flow.handler
issue_id = flow.issue_id
issue = ir.async_get(flow.hass).async_get_issue(integration, issue_id)
if issue is None:
# Issue was deleted mid-flow (e.g., config entry removed), skip check
return
key_prefix = f"{issue.translation_key}.fix_flow."
description_placeholders = {
# Both are used in issue translations, and description_placeholders

View File

@@ -281,7 +281,7 @@
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'First L. Sleep time in bed',
'icon': 'mdi:hotel',
'icon': 'mdi:bed',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),

View File

@@ -208,7 +208,7 @@
'domain': 'button',
'entity_category': None,
'entity_id': 'button.printer_wake_on_lan',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@@ -218,13 +218,13 @@
'options': dict({
}),
'original_device_class': None,
'original_icon': 'mdi:lan-pending',
'original_name': 'printer Wake on LAN',
'original_icon': None,
'original_name': 'Wake on LAN',
'platform': 'fritz',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'wake_on_lan',
'unique_id': 'AA:BB:CC:00:11:22_wake_on_lan',
'unit_of_measurement': None,
})
@@ -233,7 +233,6 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'printer Wake on LAN',
'icon': 'mdi:lan-pending',
}),
'context': <ANY>,
'entity_id': 'button.printer_wake_on_lan',

View File

@@ -111,7 +111,7 @@
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.printer_internet_access',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@@ -121,13 +121,13 @@
'options': dict({
}),
'original_device_class': None,
'original_icon': 'mdi:router-wireless-settings',
'original_name': 'printer Internet Access',
'original_icon': None,
'original_name': 'Internet access',
'platform': 'fritz',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'internet_access',
'unique_id': 'AA:BB:CC:00:11:22_internet_access',
'unit_of_measurement': None,
})
@@ -135,8 +135,7 @@
# name: test_switch_setup[fc_data0][switch.printer_internet_access-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'printer Internet Access',
'icon': 'mdi:router-wireless-settings',
'friendly_name': 'printer Internet access',
}),
'context': <ANY>,
'entity_id': 'switch.printer_internet_access',
@@ -258,7 +257,7 @@
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.printer_internet_access',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@@ -268,13 +267,13 @@
'options': dict({
}),
'original_device_class': None,
'original_icon': 'mdi:router-wireless-settings',
'original_name': 'printer Internet Access',
'original_icon': None,
'original_name': 'Internet access',
'platform': 'fritz',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'internet_access',
'unique_id': 'AA:BB:CC:00:11:22_internet_access',
'unit_of_measurement': None,
})
@@ -282,8 +281,7 @@
# name: test_switch_setup[fc_data1][switch.printer_internet_access-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'printer Internet Access',
'icon': 'mdi:router-wireless-settings',
'friendly_name': 'printer Internet access',
}),
'context': <ANY>,
'entity_id': 'switch.printer_internet_access',
@@ -405,7 +403,7 @@
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.printer_internet_access',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@@ -415,13 +413,13 @@
'options': dict({
}),
'original_device_class': None,
'original_icon': 'mdi:router-wireless-settings',
'original_name': 'printer Internet Access',
'original_icon': None,
'original_name': 'Internet access',
'platform': 'fritz',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'internet_access',
'unique_id': 'AA:BB:CC:00:11:22_internet_access',
'unit_of_measurement': None,
})
@@ -429,8 +427,7 @@
# name: test_switch_setup[fc_data2][switch.printer_internet_access-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'printer Internet Access',
'icon': 'mdi:router-wireless-settings',
'friendly_name': 'printer Internet access',
}),
'context': <ANY>,
'entity_id': 'switch.printer_internet_access',
@@ -558,7 +555,7 @@
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.printer_internet_access',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@@ -568,13 +565,13 @@
'options': dict({
}),
'original_device_class': None,
'original_icon': 'mdi:router-wireless-settings',
'original_name': 'printer Internet Access',
'original_icon': None,
'original_name': 'Internet access',
'platform': 'fritz',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'internet_access',
'unique_id': 'AA:BB:CC:00:11:22_internet_access',
'unit_of_measurement': None,
})
@@ -582,8 +579,7 @@
# name: test_switch_setup[fc_data3][switch.printer_internet_access-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'printer Internet Access',
'icon': 'mdi:router-wireless-settings',
'friendly_name': 'printer Internet access',
}),
'context': <ANY>,
'entity_id': 'switch.printer_internet_access',

View File

@@ -81,6 +81,42 @@
"value": 1.2,
"units": "PARTS_PER_BILLION"
}
},
{
"code": "nh3",
"displayName": "NH3",
"fullName": "Ammonia",
"concentration": {
"value": 81.41,
"units": "PARTS_PER_BILLION"
}
},
{
"code": "no",
"displayName": "NO",
"fullName": "Nitrogen monoxide",
"concentration": {
"value": 0.62,
"units": "PARTS_PER_BILLION"
}
},
{
"code": "nmhc",
"displayName": "NMHC",
"fullName": "Non-methane hydrocarbons",
"concentration": {
"value": 52.66,
"units": "PARTS_PER_BILLION"
}
},
{
"code": "c6h6",
"displayName": "C6H6",
"fullName": "Benzene",
"concentration": {
"value": 0.24,
"units": "MICROGRAMS_PER_CUBIC_METER"
}
}
]
}

View File

@@ -1,4 +1,110 @@
# serializer version: 1
# name: test_sensor_snapshot[sensor.home_ammonia-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.home_ammonia',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Ammonia',
'platform': 'google_air_quality',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'ammonia',
'unique_id': 'nh3_10.1_20.1',
'unit_of_measurement': 'ppb',
})
# ---
# name: test_sensor_snapshot[sensor.home_ammonia-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by Google Air Quality',
'friendly_name': 'Home Ammonia',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'ppb',
}),
'context': <ANY>,
'entity_id': 'sensor.home_ammonia',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '81.41',
})
# ---
# name: test_sensor_snapshot[sensor.home_benzene-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.home_benzene',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Benzene',
'platform': 'google_air_quality',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'benzene',
'unique_id': 'c6h6_10.1_20.1',
'unit_of_measurement': 'μg/m³',
})
# ---
# name: test_sensor_snapshot[sensor.home_benzene-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by Google Air Quality',
'friendly_name': 'Home Benzene',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'μg/m³',
}),
'context': <ANY>,
'entity_id': 'sensor.home_benzene',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.24',
})
# ---
# name: test_sensor_snapshot[sensor.home_carbon_monoxide-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -234,6 +340,112 @@
'state': '14.18',
})
# ---
# name: test_sensor_snapshot[sensor.home_nitrogen_monoxide-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.home_nitrogen_monoxide',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Nitrogen monoxide',
'platform': 'google_air_quality',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'nitrogen_monoxide',
'unique_id': 'no_10.1_20.1',
'unit_of_measurement': 'ppb',
})
# ---
# name: test_sensor_snapshot[sensor.home_nitrogen_monoxide-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by Google Air Quality',
'friendly_name': 'Home Nitrogen monoxide',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'ppb',
}),
'context': <ANY>,
'entity_id': 'sensor.home_nitrogen_monoxide',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.62',
})
# ---
# name: test_sensor_snapshot[sensor.home_non_methane_hydrocarbons-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.home_non_methane_hydrocarbons',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Non-methane hydrocarbons',
'platform': 'google_air_quality',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'non_methane_hydrocarbons',
'unique_id': 'nmhc_10.1_20.1',
'unit_of_measurement': 'ppb',
})
# ---
# name: test_sensor_snapshot[sensor.home_non_methane_hydrocarbons-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by Google Air Quality',
'friendly_name': 'Home Non-methane hydrocarbons',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'ppb',
}),
'context': <ANY>,
'entity_id': 'sensor.home_non_methane_hydrocarbons',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '52.66',
})
# ---
# name: test_sensor_snapshot[sensor.home_ozone-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -0,0 +1,30 @@
# serializer version: 1
# name: test_diagnostics
dict({
'board': dict({
'hostname': 'VRROOM-02',
'ipaddress': '192.168.1.123',
'pcbv': '3',
'serial': '000123456789',
'version': 'FW: 0.61',
}),
'config': dict({
'autosw': '1',
'htpcmode0': '0',
'htpcmode1': '0',
'htpcmode2': '0',
'htpcmode3': '0',
'iractive': '1',
'macaddr': 'c7:1c:df:9d:f6:40',
'mutetx0': '1',
'mutetx1': '1',
'oled': '1',
'relay': '0',
}),
'info': dict({
'opmode': '0',
'portseltx0': '0',
'portseltx1': '4',
}),
})
# ---

View File

@@ -0,0 +1,31 @@
"""Tests for the HDFury diagnostics."""
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.hdfury import PLATFORMS
from homeassistant.core import HomeAssistant
import homeassistant.helpers.entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test HDFury diagnostics."""
await setup_integration(hass, mock_config_entry, PLATFORMS)
diagnostics = await get_diagnostics_for_config_entry(
hass, hass_client, mock_config_entry
)
assert diagnostics == snapshot

View File

@@ -96,7 +96,8 @@ async def integration_fixture(
"eve_energy_20ecn4101",
"eve_energy_plug",
"eve_energy_plug_patched",
"eve_thermo",
"eve_thermo_v4",
"eve_thermo_v5",
"eve_shutter",
"eve_weather_sensor",
"extended_color_light",

View File

@@ -61,7 +61,7 @@
"0/40/0": 17,
"0/40/1": "Eve Systems",
"0/40/2": 4874,
"0/40/3": "Eve Thermo",
"0/40/3": "Eve Thermo 20EBP1701",
"0/40/4": 79,
"0/40/5": "",
"0/40/6": "**REDACTED**",

View File

@@ -0,0 +1,593 @@
{
"node_id": 12,
"date_commissioned": "2026-01-12T17:05:18.823583",
"last_interview": "2026-01-12T17:12:42.428644",
"interview_version": 6,
"available": true,
"is_bridge": false,
"attributes": {
"0/29/0": [
{
"0": 18,
"1": 1
},
{
"0": 17,
"1": 1
},
{
"0": 22,
"1": 3
}
],
"0/29/1": [
29, 31, 40, 42, 47, 48, 49, 50, 51, 52, 53, 56, 60, 62, 63, 70, 323615744
],
"0/29/2": [41],
"0/29/3": [1],
"0/29/65532": 0,
"0/29/65533": 2,
"0/29/65528": [],
"0/29/65529": [],
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/31/0": [
{
"254": 1
},
{
"254": 1
},
{
"254": 2
},
{
"254": 3
},
{
"1": 5,
"2": 2,
"3": [112233],
"4": null,
"254": 4
}
],
"0/31/1": [],
"0/31/2": 10,
"0/31/3": 3,
"0/31/4": 5,
"0/31/65532": 1,
"0/31/65533": 2,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/40/0": 18,
"0/40/1": "Eve Systems",
"0/40/2": 4874,
"0/40/3": "Eve Thermo 20ECD1701",
"0/40/4": 125,
"0/40/5": "",
"0/40/6": "**REDACTED**",
"0/40/7": 1,
"0/40/8": "1.1",
"0/40/9": 10287,
"0/40/10": "3.6.5",
"0/40/15": "FX46O1M01234",
"0/40/18": "DF3D0B4137A71234",
"0/40/19": {
"0": 3,
"1": 3
},
"0/40/21": 17039616,
"0/40/22": 1,
"0/40/65532": 0,
"0/40/65533": 4,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 18, 19, 21, 22, 65528, 65529, 65531,
65532, 65533
],
"0/42/0": [
{
"1": 556220604,
"2": 0,
"254": 1
}
],
"0/42/1": true,
"0/42/2": 1,
"0/42/3": null,
"0/42/65532": 0,
"0/42/65533": 1,
"0/42/65528": [],
"0/42/65529": [0],
"0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/47/0": 1,
"0/47/1": 0,
"0/47/2": "Battery",
"0/47/11": null,
"0/47/12": 200,
"0/47/14": 0,
"0/47/15": false,
"0/47/16": 2,
"0/47/18": [],
"0/47/19": "",
"0/47/20": 2,
"0/47/25": 2,
"0/47/31": [],
"0/47/65532": 10,
"0/47/65533": 3,
"0/47/65528": [],
"0/47/65529": [],
"0/47/65531": [
0, 1, 2, 11, 12, 14, 15, 16, 18, 19, 20, 25, 31, 65528, 65529, 65531,
65532, 65533
],
"0/48/0": 0,
"0/48/1": {
"0": 60,
"1": 900
},
"0/48/2": 0,
"0/48/3": 0,
"0/48/4": true,
"0/48/65532": 0,
"0/48/65533": 2,
"0/48/65528": [1, 3, 5],
"0/48/65529": [0, 2, 4],
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/49/0": 1,
"0/49/1": [
{
"0": "p0jbsOzJRNw=",
"1": true
}
],
"0/49/2": 10,
"0/49/3": 20,
"0/49/4": true,
"0/49/5": 0,
"0/49/6": "p0jbsOzJRNw=",
"0/49/7": null,
"0/49/9": 4,
"0/49/10": 5,
"0/49/65532": 2,
"0/49/65533": 2,
"0/49/65528": [1, 5, 7],
"0/49/65529": [0, 3, 4, 6, 8],
"0/49/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 65528, 65529, 65531, 65532, 65533
],
"0/50/65532": 0,
"0/50/65533": 1,
"0/50/65528": [1],
"0/50/65529": [0],
"0/50/65531": [65528, 65529, 65531, 65532, 65533],
"0/51/0": [
{
"0": "MyHome",
"1": true,
"2": null,
"3": null,
"4": "3jP5Rq6mlcw=",
"5": [],
"6": [
"/akBUIsgAAB2ykzh+Z7oEA==",
"/QANuACgAAAAAAD//gAsAw==",
"/QANuACgAADNhSwQ0KnuNg==",
"/oAAAAAAAADcM/lGrqaVzA=="
],
"7": 4
}
],
"0/51/1": 9,
"0/51/2": 11,
"0/51/3": 0,
"0/51/5": [],
"0/51/6": [],
"0/51/7": [],
"0/51/8": false,
"0/51/65532": 0,
"0/51/65533": 2,
"0/51/65528": [2],
"0/51/65529": [0, 1],
"0/51/65531": [0, 1, 2, 3, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533],
"0/52/1": 10172,
"0/52/2": 1948,
"0/52/65532": 0,
"0/52/65533": 1,
"0/52/65528": [],
"0/52/65529": [],
"0/52/65531": [1, 2, 65528, 65529, 65531, 65532, 65533],
"0/53/0": 25,
"0/53/1": 2,
"0/53/2": "MyHome",
"0/53/3": 4660,
"0/53/4": 12054125955590472924,
"0/53/5": "QP0ADbgAoAAA",
"0/53/6": 0,
"0/53/7": [
{
"0": 12864791528929066571,
"1": 12,
"2": 11264,
"3": 1787623,
"4": 77786,
"5": 3,
"6": -50,
"7": -51,
"8": 28,
"9": 0,
"10": true,
"11": true,
"12": true,
"13": false
}
],
"0/53/8": [
{
"0": 12864791528929066571,
"1": 11264,
"2": 11,
"3": 0,
"4": 0,
"5": 3,
"6": 0,
"7": 12,
"8": true,
"9": true
}
],
"0/53/9": 1775826714,
"0/53/10": 64,
"0/53/11": 96,
"0/53/12": 247,
"0/53/13": 57,
"0/53/14": 1,
"0/53/15": 1,
"0/53/16": 0,
"0/53/17": 0,
"0/53/18": 0,
"0/53/19": 1,
"0/53/20": 0,
"0/53/21": 0,
"0/53/22": 795,
"0/53/23": 795,
"0/53/24": 0,
"0/53/25": 796,
"0/53/26": 797,
"0/53/27": 0,
"0/53/28": 687,
"0/53/29": 161,
"0/53/30": 0,
"0/53/31": 0,
"0/53/32": 0,
"0/53/33": 466,
"0/53/34": 0,
"0/53/35": 0,
"0/53/36": 0,
"0/53/37": 0,
"0/53/38": 0,
"0/53/39": 251,
"0/53/40": 143,
"0/53/41": 0,
"0/53/42": 142,
"0/53/43": 0,
"0/53/44": 0,
"0/53/45": 0,
"0/53/46": 0,
"0/53/47": 0,
"0/53/48": 98,
"0/53/49": 1,
"0/53/50": 2,
"0/53/51": 0,
"0/53/52": 0,
"0/53/53": 0,
"0/53/54": 7,
"0/53/55": 1,
"0/53/59": {
"0": 672,
"1": 143
},
"0/53/60": "AB//4A==",
"0/53/61": {
"0": true,
"1": false,
"2": true,
"3": true,
"4": true,
"5": true,
"6": false,
"7": true,
"8": true,
"9": true,
"10": true,
"11": true
},
"0/53/62": [],
"0/53/65532": 15,
"0/53/65533": 3,
"0/53/65528": [],
"0/53/65529": [0],
"0/53/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 59,
60, 61, 62, 65528, 65529, 65531, 65532, 65533
],
"0/56/0": null,
"0/56/1": 0,
"0/56/2": 0,
"0/56/3": null,
"0/56/5": [
{
"0": 3600,
"1": 0,
"2": "Europe/Paris"
}
],
"0/56/6": [
{
"0": 0,
"1": 0,
"2": 828061200000000
},
{
"0": 3600,
"1": 828061200000000,
"2": 846205200000000
}
],
"0/56/7": null,
"0/56/8": 2,
"0/56/10": 2,
"0/56/11": 2,
"0/56/65532": 9,
"0/56/65533": 2,
"0/56/65528": [3],
"0/56/65529": [0, 1, 2, 4],
"0/56/65531": [
0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 65528, 65529, 65531, 65532, 65533
],
"0/60/0": 0,
"0/60/1": null,
"0/60/2": null,
"0/60/65532": 1,
"0/60/65533": 1,
"0/60/65528": [],
"0/60/65529": [0, 1, 2],
"0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"0/62/0": [
{
"254": 1
},
{
"254": 2
},
{
"254": 3
},
{
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRDBgkBwEkCAEwCUEEjpEdjgSj9HS+HEJVD/GpyTr4aD+5fAti/w8n4eIrPgWZGhqCV0qnqaWVnQ15JLw/y001clUJvTA0F6aotXHi6zcKNQEoARgkAgE2AwQCBAEYMAQU93OvKOKKLhOjzDp+3jm7VZEuC/MwBRRa34d1hFPuca7UFWclq9cFnlPhShgwC0DDZdbO0KEk7s3FtbyASnf25X/Rwj9BNpBNviVDFPpR2hnkqttW8rmplsec7DeAiYNDGqxt5shN8rNfJHpr9+Q2GA==",
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEAV5qZprx2HWOKSP2iCzsI7A0CHgZVtbwsQ/y4ssETfB9z00733STIN0AfD552Vi1h6fJSeEg0/pA82bJL/y0azcKNQEpARgkAmAwBBRa34d1hFPuca7UFWclq9cFnlPhSjAFFG9oKFV1nAO5dx/+jKvq8o8oKcZbGDALQDD8OnB1NcHRxx387f9wZeFDYf32VZ3ZENQrlWBTQZqEKP+K6XjWmjTWttDEeW1kiNtB1T5ZBIaJUxVdqMuNQx8Y",
"254": 4
}
],
"0/62/1": [
{
"1": "BIGMMa0wfrcohBn60cI5V0xt+DIkLSV24OUKndKIXUVuzH8GGO72Yl/9IfYSPDKlK2pRWlT3J4IQD9DEiZtWK6k=",
"2": 4937,
"3": 3003885711,
"4": 3179312192,
"5": "Home",
"254": 1
}
],
"0/62/2": 5,
"0/62/3": 4,
"0/62/4": [
"FTABAQAkAgE3AyYUyakYCSYVj6gLsxgmBMfk9zAkBQA3BiYUyakYCSYVj6gLsxgkBwEkCAEwCUEEgYwxrTB+tyiEGfrRwjlXTG34MiQtJXbg5Qqd0ohdRW7MfwYY7vZiX/0h9hI8MqUralFaVPcnghAP0MSJm1YrqTcKNQEpARgkAmAwBBS3BS9aJzt+p6i28Nj+trB2Uu+vdzAFFLcFL1onO36nqLbw2P62sHZS7693GDALQCm96olCh4FdOmdpai/048NktfVtRdSntFc2qDrwkfljr0v13vTxADZ8mUF2TxEmi0EpXiYLp6rcLm7SNOdQlSgY",
"FTABAQAkAgE3AycUQhmZbaIbYjokFQIYJgRWZLcqJAUANwYnFEIZmW2iG2I6JBUCGCQHASQIATAJQQT2AlKGW/kOMjqayzeO0md523/fuhrhGEUU91uQpTiKo0I7wcPpKnmrwfQNPX6g0kEQl+VGaXa3e22lzfu5Tzp0Nwo1ASkBGCQCYDAEFOOMk13ScMKuT2hlaydi1yEJnhTqMAUU44yTXdJwwq5PaGVrJ2LXIQmeFOoYMAtAv2jJd1qd5miXbYesH1XrJ+vgyY0hzGuZ78N6Jw4Cb1oN1sLSpA+PNM0u7+hsEqcSvvn2eSV8EaRR+hg5YQjHDxg=",
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEiuu42juvSBfqPqWrV0OLnN4rePFxNq+O3ajhp0IJIJi1vE5qR9vsLcZeqBXgvO6UVKKdt7CZiR2oUEeqbmnG9TcKNQEpARgkAmAwBBTjAjvCZO2QpJyarhRj7T8yYjarAzAFFOMCO8Jk7ZCknJquFGPtPzJiNqsDGDALQE7hTxTRg92QOxwA1hK3xv8DaxvxL71r6ZHcNRzug9wNnonJ+NC84SFKvKDxwcBxHYqFdIyDiDgwJNTQIBgasmIY",
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEbUU9qPxT8hkwnSWRhFacvs82vrsjsaZsqqvM48qn3YZZmQwtvEeyRKl6EDEzFbqd6lAdav4Sr0sunvDLgIHtrjcKNQEpARgkAmAwBBRvaChVdZwDuXcf/oyr6vKPKCnGWzAFFG9oKFV1nAO5dx/+jKvq8o8oKcZbGDALQLa3jnnqN0/o6VG8wM4V9FDzrgDfKPd5cn3BBz77K80Jzo/aNotaTNOa6zX//yIvOkBZfGyq1Dh1vXZ4g2NKcXoY"
],
"0/62/5": 4,
"0/62/65532": 0,
"0/62/65533": 1,
"0/62/65528": [1, 3, 5, 8],
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
"0/63/0": [],
"0/63/1": [],
"0/63/2": 4,
"0/63/3": 3,
"0/63/65532": 0,
"0/63/65533": 2,
"0/63/65528": [2, 5],
"0/63/65529": [0, 1, 3, 4],
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/70/0": 120,
"0/70/1": 300,
"0/70/2": 2000,
"0/70/65532": 0,
"0/70/65533": 3,
"0/70/65528": [],
"0/70/65529": [],
"0/70/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"0/323615744/1": true,
"0/323615744/65532": 0,
"0/323615744/65533": 1,
"0/323615744/65528": [],
"0/323615744/65529": [],
"0/323615744/65531": [1, 65528, 65529, 65531, 65532, 65533],
"1/3/0": 0,
"1/3/1": 4,
"1/3/65532": 0,
"1/3/65533": 5,
"1/3/65528": [],
"1/3/65529": [0],
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/29/0": [
{
"0": 769,
"1": 4
}
],
"1/29/1": [3, 29, 30, 513, 516, 319486977],
"1/29/2": [1026],
"1/29/3": [],
"1/29/65532": 0,
"1/29/65533": 2,
"1/29/65528": [],
"1/29/65529": [],
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"1/30/0": [],
"1/30/65532": 0,
"1/30/65533": 1,
"1/30/65528": [],
"1/30/65529": [],
"1/30/65531": [0, 65528, 65529, 65531, 65532, 65533],
"1/513/0": 1620,
"1/513/3": 1000,
"1/513/4": 3000,
"1/513/16": -15,
"1/513/18": 1750,
"1/513/21": 1000,
"1/513/22": 3000,
"1/513/26": 0,
"1/513/27": 2,
"1/513/28": 4,
"1/513/35": 0,
"1/513/36": 0,
"1/513/41": 0,
"1/513/48": 0,
"1/513/49": 0,
"1/513/50": 0,
"1/513/72": [
{
"0": 1,
"1": 1,
"2": 2
},
{
"0": 2,
"1": 1,
"2": 2
},
{
"0": 3,
"1": 1,
"2": 2
},
{
"0": 4,
"1": 1,
"2": 2
},
{
"0": 5,
"1": 1,
"2": 2
},
{
"0": 6,
"1": 1,
"2": 2
},
{
"0": 254,
"1": 1,
"2": 2
}
],
"1/513/74": 8,
"1/513/78": null,
"1/513/80": [
{
"0": "AQ==",
"1": 1,
"2": "Home",
"4": 2200,
"5": true
},
{
"0": "Ag==",
"1": 2,
"2": "Away",
"4": 1800,
"5": true
},
{
"0": "Aw==",
"1": 3,
"2": "Sleep",
"4": 2000,
"5": false
},
{
"0": "BA==",
"1": 4,
"2": "Wake",
"4": 2300,
"5": false
},
{
"0": "BQ==",
"1": 5,
"2": "Vacation",
"4": 1600,
"5": false
},
{
"0": "Bg==",
"1": 6,
"2": "GoingToSleep",
"4": 2100,
"5": false
},
{
"0": "/g==",
"1": 254,
"2": "Eco",
"4": 1600,
"5": false
}
],
"1/513/82": 0,
"1/513/65532": 257,
"1/513/65533": 8,
"1/513/65528": [253],
"1/513/65529": [0, 6, 254],
"1/513/65531": [
0, 3, 4, 16, 18, 21, 22, 26, 27, 28, 35, 36, 41, 48, 49, 50, 72, 74, 78,
80, 82, 65528, 65529, 65531, 65532, 65533
],
"1/516/0": 0,
"1/516/1": 0,
"1/516/2": 0,
"1/516/65532": 0,
"1/516/65533": 2,
"1/516/65528": [],
"1/516/65529": [],
"1/516/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/319486977/319422464": "AAJ9AAsCAAADAjYoBAxGWDQ2TzFNMDM2NDecAQD/BAECAyz5AQEdAQj/BCUCVQY7DTEuMC4xNS8yLjIuMAA8AQA3AQA/AQAmAQAnAQBPBgAAICAoKP8DIwHx/wYmBCXZAiD/BDQCmAhFBQUAAAAARgkFAAAADgAAQgZJBgUMCBCAAUQRBQAABQMAAAAAAAAAAAAAAABHEQUAAAAAAAAAAAAAAAAAAAAASAYFAAAAAABKBgUAAAAAAP8LIgkQAAAAAAAAAAA=",
"1/319486977/319422466": "bwIAAAAAAAAAAAAABgECEQIQARIBHQE0Ag0AABAAAAAAAgAAAAEBAA==",
"1/319486977/319422467": "FQQAAAACAAAAgAAAAAAAAAAAAAAA",
"1/319486977/319422476": 0,
"1/319486977/319422482": 11267,
"1/319486977/319422487": false,
"1/319486977/319422488": 0,
"1/319486977/319422489": 30240,
"1/319486977/319422490": 262144,
"1/319486977/65532": 0,
"1/319486977/65533": 1,
"1/319486977/65528": [],
"1/319486977/65529": [319422464],
"1/319486977/65531": [
65528, 65529, 65531, 319422464, 319422465, 319422466, 319422467,
319422468, 319422469, 319422476, 319422482, 319422487, 319422488,
319422489, 319422490, 65532, 65533
]
},
"attribute_subscriptions": []
}

View File

@@ -391,7 +391,7 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_local_temperature_remote_sensing-entry]
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_local_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -404,7 +404,7 @@
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_local_temperature_remote_sensing',
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_local_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -426,20 +426,20 @@
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_local_temperature_remote_sensing-state]
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_local_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo Local temperature remote sensing',
'friendly_name': 'Eve Thermo 20EBP1701 Local temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_local_temperature_remote_sensing',
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_local_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_outdoor_temperature_remote_sensing-entry]
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -452,7 +452,7 @@
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_outdoor_temperature_remote_sensing',
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -474,13 +474,109 @@
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_outdoor_temperature_remote_sensing-state]
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo Outdoor temperature remote sensing',
'friendly_name': 'Eve Thermo 20EBP1701 Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_outdoor_temperature_remote_sensing',
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_local_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_local_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Local temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_local_temperature',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-ThermostatRemoteSensing_LocalTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_local_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20ECD1701 Local temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_local_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Outdoor temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20ECD1701 Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,

View File

@@ -1418,7 +1418,7 @@
'state': 'unknown',
})
# ---
# name: test_buttons[eve_thermo][button.eve_thermo_identify-entry]
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_identify-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -1431,7 +1431,7 @@
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.eve_thermo_identify',
'entity_id': 'button.eve_thermo_20ebp1701_identify',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -1453,14 +1453,63 @@
'unit_of_measurement': None,
})
# ---
# name: test_buttons[eve_thermo][button.eve_thermo_identify-state]
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_identify-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'Eve Thermo Identify',
'friendly_name': 'Eve Thermo 20EBP1701 Identify',
}),
'context': <ANY>,
'entity_id': 'button.eve_thermo_identify',
'entity_id': 'button.eve_thermo_20ebp1701_identify',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_identify-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.eve_thermo_20ecd1701_identify',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': 'Identify',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-IdentifyButton-3-1',
'unit_of_measurement': None,
})
# ---
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_identify-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'Eve Thermo 20ECD1701 Identify',
}),
'context': <ANY>,
'entity_id': 'button.eve_thermo_20ecd1701_identify',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,

View File

@@ -191,7 +191,7 @@
'state': 'heat',
})
# ---
# name: test_climates[eve_thermo][climate.eve_thermo-entry]
# name: test_climates[eve_thermo_v4][climate.eve_thermo_20ebp1701-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -211,7 +211,7 @@
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.eve_thermo',
'entity_id': 'climate.eve_thermo_20ebp1701',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -233,11 +233,11 @@
'unit_of_measurement': None,
})
# ---
# name: test_climates[eve_thermo][climate.eve_thermo-state]
# name: test_climates[eve_thermo_v4][climate.eve_thermo_20ebp1701-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 21.0,
'friendly_name': 'Eve Thermo',
'friendly_name': 'Eve Thermo 20EBP1701',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
@@ -248,7 +248,71 @@
'temperature': 17.0,
}),
'context': <ANY>,
'entity_id': 'climate.eve_thermo',
'entity_id': 'climate.eve_thermo_20ebp1701',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
})
# ---
# name: test_climates[eve_thermo_v5][climate.eve_thermo_20ecd1701-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 30.0,
'min_temp': 10.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.eve_thermo_20ecd1701',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
# ---
# name: test_climates[eve_thermo_v5][climate.eve_thermo_20ecd1701-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 16.2,
'friendly_name': 'Eve Thermo 20ECD1701',
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
]),
'max_temp': 30.0,
'min_temp': 10.0,
'supported_features': <ClimateEntityFeature: 385>,
'temperature': 17.5,
}),
'context': <ANY>,
'entity_id': 'climate.eve_thermo_20ecd1701',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,

View File

@@ -115,6 +115,64 @@
'state': '10',
})
# ---
# name: test_numbers[aqara_thermostat_w500][number.floor_heating_thermostat_occupied_setback-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 3.0,
'min': 0.5,
'mode': <NumberMode.BOX: 'box'>,
'step': 0.5,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.floor_heating_thermostat_occupied_setback',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Occupied setback',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'occupied_setback',
'unique_id': '00000000000004D2-0000000000000064-MatterNodeDevice-1-ThermostatOccupiedSetback-513-52',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_numbers[aqara_thermostat_w500][number.floor_heating_thermostat_occupied_setback-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Floor Heating Thermostat Occupied setback',
'max': 3.0,
'min': 0.5,
'mode': <NumberMode.BOX: 'box'>,
'step': 0.5,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'number.floor_heating_thermostat_occupied_setback',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.5',
})
# ---
# name: test_numbers[aqara_u200][number.aqara_smart_lock_u200_user_code_temporary_disable_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -979,7 +1037,7 @@
'state': '3',
})
# ---
# name: test_numbers[eve_thermo][number.eve_thermo_temperature_offset-entry]
# name: test_numbers[eve_thermo_v4][number.eve_thermo_20ebp1701_temperature_offset-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -997,7 +1055,7 @@
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.eve_thermo_temperature_offset',
'entity_id': 'number.eve_thermo_20ebp1701_temperature_offset',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -1019,11 +1077,11 @@
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_numbers[eve_thermo][number.eve_thermo_temperature_offset-state]
# name: test_numbers[eve_thermo_v4][number.eve_thermo_20ebp1701_temperature_offset-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Eve Thermo Temperature offset',
'friendly_name': 'Eve Thermo 20EBP1701 Temperature offset',
'max': 50,
'min': -50,
'mode': <NumberMode.BOX: 'box'>,
@@ -1031,13 +1089,72 @@
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'number.eve_thermo_temperature_offset',
'entity_id': 'number.eve_thermo_20ebp1701_temperature_offset',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_numbers[eve_thermo_v5][number.eve_thermo_20ecd1701_temperature_offset-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 50,
'min': -50,
'mode': <NumberMode.BOX: 'box'>,
'step': 0.5,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.eve_thermo_20ecd1701_temperature_offset',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <NumberDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature offset',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'temperature_offset',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-EveTemperatureOffset-513-16',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_numbers[eve_thermo_v5][number.eve_thermo_20ecd1701_temperature_offset-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Eve Thermo 20ECD1701 Temperature offset',
'max': 50,
'min': -50,
'mode': <NumberMode.BOX: 'box'>,
'step': 0.5,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'number.eve_thermo_20ecd1701_temperature_offset',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '-1.5',
})
# ---
# name: test_numbers[eve_weather_sensor][number.eve_weather_altitude_above_sea_level-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -1400,7 +1400,7 @@
'state': 'previous',
})
# ---
# name: test_selects[eve_thermo][select.eve_thermo_temperature_display_mode-entry]
# name: test_selects[eve_thermo_v4][select.eve_thermo_20ebp1701_temperature_display_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -1418,7 +1418,7 @@
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.eve_thermo_temperature_display_mode',
'entity_id': 'select.eve_thermo_20ebp1701_temperature_display_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -1440,17 +1440,74 @@
'unit_of_measurement': None,
})
# ---
# name: test_selects[eve_thermo][select.eve_thermo_temperature_display_mode-state]
# name: test_selects[eve_thermo_v4][select.eve_thermo_20ebp1701_temperature_display_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo Temperature display mode',
'friendly_name': 'Eve Thermo 20EBP1701 Temperature display mode',
'options': list([
'Celsius',
'Fahrenheit',
]),
}),
'context': <ANY>,
'entity_id': 'select.eve_thermo_temperature_display_mode',
'entity_id': 'select.eve_thermo_20ebp1701_temperature_display_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'Celsius',
})
# ---
# name: test_selects[eve_thermo_v5][select.eve_thermo_20ecd1701_temperature_display_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'Celsius',
'Fahrenheit',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.eve_thermo_20ecd1701_temperature_display_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Temperature display mode',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'temperature_display_mode',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-TrvTemperatureDisplayMode-516-0',
'unit_of_measurement': None,
})
# ---
# name: test_selects[eve_thermo_v5][select.eve_thermo_20ecd1701_temperature_display_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20ECD1701 Temperature display mode',
'options': list([
'Celsius',
'Fahrenheit',
]),
}),
'context': <ANY>,
'entity_id': 'select.eve_thermo_20ecd1701_temperature_display_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,

View File

@@ -4904,7 +4904,7 @@
'state': '100',
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_battery-entry]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -4919,7 +4919,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.eve_thermo_battery',
'entity_id': 'sensor.eve_thermo_20ebp1701_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -4941,23 +4941,23 @@
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_battery-state]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Eve Thermo Battery',
'friendly_name': 'Eve Thermo 20EBP1701 Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_battery',
'entity_id': 'sensor.eve_thermo_20ebp1701_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_battery_voltage-entry]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_battery_voltage-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -4972,7 +4972,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.eve_thermo_battery_voltage',
'entity_id': 'sensor.eve_thermo_20ebp1701_battery_voltage',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -5000,23 +5000,23 @@
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_battery_voltage-state]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_battery_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'voltage',
'friendly_name': 'Eve Thermo Battery voltage',
'friendly_name': 'Eve Thermo 20EBP1701 Battery voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_battery_voltage',
'entity_id': 'sensor.eve_thermo_20ebp1701_battery_voltage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '3.05',
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_temperature-entry]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -5031,7 +5031,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.eve_thermo_temperature',
'entity_id': 'sensor.eve_thermo_20ebp1701_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -5056,23 +5056,23 @@
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_temperature-state]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Eve Thermo Temperature',
'friendly_name': 'Eve Thermo 20EBP1701 Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_temperature',
'entity_id': 'sensor.eve_thermo_20ebp1701_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '21.0',
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_valve_position-entry]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_valve_position-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -5085,7 +5085,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.eve_thermo_valve_position',
'entity_id': 'sensor.eve_thermo_20ebp1701_valve_position',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -5107,20 +5107,343 @@
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo][sensor.eve_thermo_valve_position-state]
# name: test_sensors[eve_thermo_v4][sensor.eve_thermo_20ebp1701_valve_position-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo Valve position',
'friendly_name': 'Eve Thermo 20EBP1701 Valve position',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_valve_position',
'entity_id': 'sensor.eve_thermo_20ebp1701_valve_position',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '10',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.eve_thermo_20ecd1701_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'Battery',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-0-PowerSource-47-12',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Eve Thermo 20ECD1701 Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_last_change-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.eve_thermo_20ecd1701_last_change',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Last change',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'setpoint_change_timestamp',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-SetpointChangeSourceTimestamp-513-50',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_last_change-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'Eve Thermo 20ECD1701 Last change',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_last_change',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_last_change_amount-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.eve_thermo_20ecd1701_last_change_amount',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Last change amount',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'setpoint_change_amount',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-ThermostatSetpointChangeAmount-513-49',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_last_change_amount-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Eve Thermo 20ECD1701 Last change amount',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_last_change_amount',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_last_change_source-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'manual',
'schedule',
'external',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.eve_thermo_20ecd1701_last_change_source',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Last change source',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'setpoint_change_source',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-SetpointChangeSource-513-48',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_last_change_source-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Eve Thermo 20ECD1701 Last change source',
'options': list([
'manual',
'schedule',
'external',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_last_change_source',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'manual',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.eve_thermo_20ecd1701_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-ThermostatLocalTemperature-513-0',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Eve Thermo 20ECD1701 Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '16.2',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_valve_position-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.eve_thermo_20ecd1701_valve_position',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Valve position',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'valve_position',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-EveThermoValvePosition-319486977-319422488',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eve_thermo_v5][sensor.eve_thermo_20ecd1701_valve_position-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20ECD1701 Valve position',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.eve_thermo_20ecd1701_valve_position',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_sensors[eve_weather_sensor][sensor.eve_weather_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -8583,6 +8906,171 @@
'state': '25',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.mock_thermostat_last_change',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Last change',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'setpoint_change_timestamp',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-SetpointChangeSourceTimestamp-513-50',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'Mock Thermostat Last change',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_thermostat_last_change',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2025-01-01T00:00:00+00:00',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_amount-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.mock_thermostat_last_change_amount',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Last change amount',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'setpoint_change_amount',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatSetpointChangeAmount-513-49',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_amount-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Mock Thermostat Last change amount',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.mock_thermostat_last_change_amount',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1.5',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_source-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'manual',
'schedule',
'external',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.mock_thermostat_last_change_source',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Last change source',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'setpoint_change_source',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-SetpointChangeSource-513-48',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_source-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Mock Thermostat Last change source',
'options': list([
'manual',
'schedule',
'external',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.mock_thermostat_last_change_source',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'manual',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_outdoor_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -487,7 +487,7 @@
'state': 'off',
})
# ---
# name: test_switches[eve_thermo][switch.eve_thermo_child_lock-entry]
# name: test_switches[eve_thermo_v4][switch.eve_thermo_20ebp1701_child_lock-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -500,7 +500,7 @@
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.eve_thermo_child_lock',
'entity_id': 'switch.eve_thermo_20ebp1701_child_lock',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -522,13 +522,61 @@
'unit_of_measurement': None,
})
# ---
# name: test_switches[eve_thermo][switch.eve_thermo_child_lock-state]
# name: test_switches[eve_thermo_v4][switch.eve_thermo_20ebp1701_child_lock-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo Child lock',
'friendly_name': 'Eve Thermo 20EBP1701 Child lock',
}),
'context': <ANY>,
'entity_id': 'switch.eve_thermo_child_lock',
'entity_id': 'switch.eve_thermo_20ebp1701_child_lock',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switches[eve_thermo_v5][switch.eve_thermo_20ecd1701_child_lock-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.eve_thermo_20ecd1701_child_lock',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Child lock',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'child_lock',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-EveTrvChildLock-516-1',
'unit_of_measurement': None,
})
# ---
# name: test_switches[eve_thermo_v5][switch.eve_thermo_20ecd1701_child_lock-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20ECD1701 Child lock',
}),
'context': <ANY>,
'entity_id': 'switch.eve_thermo_20ecd1701_child_lock',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,

View File

@@ -236,6 +236,50 @@ async def test_microwave_oven(
)
@pytest.mark.parametrize("node_fixture", ["aqara_thermostat_w500"])
async def test_thermostat_occupied_setback(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test thermostat occupied setback number entity."""
entity_id = "number.floor_heating_thermostat_occupied_setback"
# Initial value comes from 1/513/52 with scale /10 (5 -> 0.5 °C)
state = hass.states.get(entity_id)
assert state
assert state.state == "0.5"
# Update attribute to 30 (-> 3.0 °C)
set_node_attribute(matter_node, 1, 513, 52, 30)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "3.0"
# Setting value to 2.0 °C writes 20 to OccupiedSetback (scale x10)
await hass.services.async_call(
"number",
"set_value",
{
"entity_id": entity_id,
"value": 2.0,
},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args == call(
node_id=matter_node.node_id,
attribute_path=create_attribute_path_from_attribute(
endpoint_id=1,
attribute=clusters.Thermostat.Attributes.OccupiedSetback,
),
value=20,
)
@pytest.mark.parametrize("node_fixture", ["door_lock"])
async def test_lock_attributes(
hass: HomeAssistant,

View File

@@ -201,7 +201,7 @@ async def test_battery_sensor_description(
state = hass.states.get("sensor.smoke_sensor_battery_type") is None
@pytest.mark.parametrize("node_fixture", ["eve_thermo"])
@pytest.mark.parametrize("node_fixture", ["eve_thermo_v4"])
async def test_eve_thermo_sensor(
hass: HomeAssistant,
matter_client: MagicMock,
@@ -209,30 +209,124 @@ async def test_eve_thermo_sensor(
) -> None:
"""Test Eve Thermo."""
# Valve position
state = hass.states.get("sensor.eve_thermo_valve_position")
state = hass.states.get("sensor.eve_thermo_20ebp1701_valve_position")
assert state
assert state.state == "10"
set_node_attribute(matter_node, 1, 319486977, 319422488, 0)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.eve_thermo_valve_position")
state = hass.states.get("sensor.eve_thermo_20ebp1701_valve_position")
assert state
assert state.state == "0"
# LocalTemperature
state = hass.states.get("sensor.eve_thermo_temperature")
state = hass.states.get("sensor.eve_thermo_20ebp1701_temperature")
assert state
assert state.state == "21.0"
set_node_attribute(matter_node, 1, 513, 0, 1800)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.eve_thermo_temperature")
state = hass.states.get("sensor.eve_thermo_20ebp1701_temperature")
assert state
assert state.state == "18.0"
@pytest.mark.parametrize("node_fixture", ["eve_thermo_v5"])
async def test_eve_thermo_v5_setpoint_change_source(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test Eve Thermo v5 SetpointChangeSource sensor."""
entity_id = "sensor.eve_thermo_20ecd1701_last_change_source"
# Initial state and options
state = hass.states.get(entity_id)
assert state
assert state.state == "manual"
assert state.attributes["options"] == ["manual", "schedule", "external"]
# Change to schedule
set_node_attribute(matter_node, 1, 513, 48, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "schedule"
# Change to external
set_node_attribute(matter_node, 1, 513, 48, 2)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "external"
@pytest.mark.parametrize("node_fixture", ["eve_thermo_v5"])
async def test_eve_thermo_v5_setpoint_change_timestamp(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test Eve Thermo v5 SetpointChangeSourceTimestamp sensor."""
entity_id = "sensor.eve_thermo_20ecd1701_last_change"
# Initial is unknown per snapshot
state = hass.states.get(entity_id)
assert state
assert state.state == "unknown"
# Update to 2024-01-01 00:00:00+00:00 (Matter epoch seconds since 2000)
set_node_attribute(matter_node, 1, 513, 50, 757382400)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "2024-01-01T00:00:00+00:00"
# Set to zero should yield unknown
set_node_attribute(matter_node, 1, 513, 50, 0)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "unknown"
@pytest.mark.parametrize("node_fixture", ["eve_thermo_v5"])
async def test_eve_thermo_v5_setpoint_change_amount(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test Eve Thermo v5 SetpointChangeAmount sensor."""
entity_id = "sensor.eve_thermo_20ecd1701_last_change_amount"
# Initial per snapshot
state = hass.states.get(entity_id)
assert state
assert state.state == "0.0"
# Update to 2.0°C (200 in Matter units)
set_node_attribute(matter_node, 1, 513, 49, 200)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "2.0"
# Update to -0.5°C (-50 in Matter units)
set_node_attribute(matter_node, 1, 513, 49, -50)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "-0.5"
@pytest.mark.parametrize("node_fixture", ["longan_link_thermostat"])
async def test_thermostat_outdoor(
hass: HomeAssistant,

View File

@@ -116,32 +116,32 @@ async def test_power_switch(hass: HomeAssistant, matter_node: MatterNode) -> Non
assert state.attributes["friendly_name"] == "Room AirConditioner Power"
@pytest.mark.parametrize("node_fixture", ["eve_thermo"])
@pytest.mark.parametrize("node_fixture", ["eve_thermo_v4"])
async def test_numeric_switch(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test numeric switch entity is discovered and working using an Eve Thermo fixture ."""
state = hass.states.get("switch.eve_thermo_child_lock")
state = hass.states.get("switch.eve_thermo_20ebp1701_child_lock")
assert state
assert state.state == "off"
# name should be derived from description attribute
assert state.attributes["friendly_name"] == "Eve Thermo Child lock"
assert state.attributes["friendly_name"] == "Eve Thermo 20EBP1701 Child lock"
# test attribute changes
set_node_attribute(matter_node, 1, 516, 1, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("switch.eve_thermo_child_lock")
state = hass.states.get("switch.eve_thermo_20ebp1701_child_lock")
assert state.state == "on"
set_node_attribute(matter_node, 1, 516, 1, 0)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("switch.eve_thermo_child_lock")
state = hass.states.get("switch.eve_thermo_20ebp1701_child_lock")
assert state.state == "off"
# test switch service
await hass.services.async_call(
"switch",
"turn_on",
{"entity_id": "switch.eve_thermo_child_lock"},
{"entity_id": "switch.eve_thermo_20ebp1701_child_lock"},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
@@ -156,7 +156,7 @@ async def test_numeric_switch(
await hass.services.async_call(
"switch",
"turn_off",
{"entity_id": "switch.eve_thermo_child_lock"},
{"entity_id": "switch.eve_thermo_20ebp1701_child_lock"},
blocking=True,
)
assert matter_client.write_attribute.call_count == 2

View File

@@ -1,13 +1,17 @@
"""Tests for the Nina integration."""
import json
from typing import Any
from unittest.mock import patch
from homeassistant.components.nina.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
from tests.common import (
MockConfigEntry,
load_json_array_fixture,
load_json_object_fixture,
)
async def setup_platform(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
@@ -24,20 +28,20 @@ async def setup_platform(hass: HomeAssistant, config_entry: MockConfigEntry) ->
def mocked_request_function(url: str) -> dict[str, Any]:
"""Mock of the request function."""
dummy_response: dict[str, Any] = json.loads(
load_fixture("sample_warnings.json", "nina")
dummy_response: list[dict[str, Any]] = load_json_array_fixture(
"sample_warnings.json", DOMAIN
)
dummy_response_details: dict[str, Any] = json.loads(
load_fixture("sample_warning_details.json", "nina")
dummy_response_details: dict[str, Any] = load_json_object_fixture(
"sample_warning_details.json", DOMAIN
)
dummy_response_regions: dict[str, Any] = json.loads(
load_fixture("sample_regions.json", "nina")
dummy_response_regions: dict[str, Any] = load_json_object_fixture(
"sample_regions.json", DOMAIN
)
dummy_response_labels: dict[str, Any] = json.loads(
load_fixture("sample_labels.json", "nina")
dummy_response_labels: dict[str, Any] = load_json_object_fixture(
"sample_labels.json", DOMAIN
)
if "https://warnung.bund.de/api31/dashboard/" in url: # codespell:ignore bund

View File

@@ -260,4 +260,12 @@ MOCK_OWPROXY_DEVICES = {
"/EDS0066/pressure": [b" 1012.21"],
},
},
"7E.333333333333": {
ATTR_INJECT_READS: {
"/type": [b"EDS"],
"/device_type": [b"EDS0065"],
"/EDS0065/temperature": [b" 13.9375"],
"/EDS0065/humidity": [b" 41.375"],
},
},
}

View File

@@ -588,6 +588,37 @@
'via_device_id': None,
})
# ---
# name: test_registry[7E.333333333333-entry]
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'onewire',
'7E.333333333333',
),
}),
'labels': set({
}),
'manufacturer': 'Embedded Data Systems',
'model': None,
'model_id': 'EDS0065',
'name': '7E.333333333333',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '333333333333',
'sw_version': '3.2',
'via_device_id': None,
})
# ---
# name: test_registry[A6.111111111111-entry]
DeviceRegistryEntrySnapshot({
'area_id': None,

View File

@@ -2245,6 +2245,117 @@
'state': '13.9375',
})
# ---
# name: test_sensors[sensor.7e_333333333333_humidity-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.7e_333333333333_humidity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
'original_icon': None,
'original_name': 'Humidity',
'platform': 'onewire',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '/7E.333333333333/EDS0065/humidity',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[sensor.7e_333333333333_humidity-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'humidity',
'device_file': '/7E.333333333333/EDS0065/humidity',
'friendly_name': '7E.333333333333 Humidity',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.7e_333333333333_humidity',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '41.375',
})
# ---
# name: test_sensors[sensor.7e_333333333333_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.7e_333333333333_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature',
'platform': 'onewire',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '/7E.333333333333/EDS0065/temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[sensor.7e_333333333333_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'device_file': '/7E.333333333333/EDS0065/temperature',
'friendly_name': '7E.333333333333 Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.7e_333333333333_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '13.9375',
})
# ---
# name: test_sensors[sensor.a6_111111111111_hih3600_humidity-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -6,7 +6,7 @@ import dataclasses
from datetime import timedelta
import logging
import threading
from typing import Any, final
from typing import Any
from unittest.mock import MagicMock, PropertyMock, patch
from freezegun.api import FrozenDateTimeFactory
@@ -20,7 +20,6 @@ from homeassistant.config_entries import ConfigEntry, ConfigSubentryData
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
@@ -1879,7 +1878,6 @@ async def test_change_entity_id(
self.remove_calls = []
async def async_added_to_hass(self):
await super().async_added_to_hass()
self.added_calls.append(None)
self.async_on_remove(lambda: result.append(1))
@@ -2962,103 +2960,3 @@ async def test_platform_state_write_from_init_unique_id(
# The early attempt to write is interpreted as a unique ID collision
assert "Platform test_platform does not generate unique IDs." in caplog.text
assert "Entity id already exists - ignoring: test.test" not in caplog.text
async def test_included_entities(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test included entities are exposed via the entity_id attribute."""
entity_registry.async_get_or_create(
domain="hello",
platform="test",
unique_id="very_unique_oceans",
suggested_object_id="oceans",
)
entity_registry.async_get_or_create(
domain="hello",
platform="test",
unique_id="very_unique_continents",
suggested_object_id="continents",
)
entity_registry.async_get_or_create(
domain="hello",
platform="test",
unique_id="very_unique_moon",
suggested_object_id="moon",
)
class MockHelloBaseClass(entity.Entity):
"""Domain base entity platform domain Hello."""
@final
@property
def state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return {"extra": "beer"}
class MockHelloIncludedEntitiesClass(MockHelloBaseClass, entity.Entity):
"""Mock hello grouped entity class for a test integration."""
platform = MockEntityPlatform(hass, domain="hello", platform_name="test")
mock_entity = MockHelloIncludedEntitiesClass()
mock_entity.hass = hass
mock_entity.entity_id = "hello.universe"
mock_entity.unique_id = "very_unique_universe"
mock_entity._attr_included_unique_ids = [
"very_unique_continents",
"very_unique_oceans",
]
await platform.async_add_entities([mock_entity])
# Initiate mock grouped entity for hello domain
mock_entity.async_schedule_update_ha_state(True)
await hass.async_block_till_done()
state = hass.states.get(mock_entity.entity_id)
assert state.attributes.get(ATTR_ENTITY_ID) == ["hello.continents", "hello.oceans"]
# Add an entity to the group of included entities
mock_entity._attr_included_unique_ids = [
"very_unique_continents",
"very_unique_moon",
"very_unique_oceans",
]
mock_entity.async_write_ha_state()
await hass.async_block_till_done()
state = hass.states.get(mock_entity.entity_id)
assert state.attributes.get("extra") == "beer"
assert state.attributes.get(ATTR_ENTITY_ID) == [
"hello.continents",
"hello.moon",
"hello.oceans",
]
# Remove an entity from the group of included entities
mock_entity._attr_included_unique_ids = ["very_unique_moon", "very_unique_oceans"]
mock_entity.async_write_ha_state()
await hass.async_block_till_done()
state = hass.states.get(mock_entity.entity_id)
assert state.attributes.get(ATTR_ENTITY_ID) == ["hello.moon", "hello.oceans"]
# Rename an included entity via the registry entity
entity_registry.async_update_entity(
entity_id="hello.moon", new_entity_id="hello.moon_light"
)
await hass.async_block_till_done()
state = hass.states.get(mock_entity.entity_id)
assert state.attributes.get(ATTR_ENTITY_ID) == ["hello.moon_light", "hello.oceans"]
# Remove an included entity from the registry entity
entity_registry.async_remove(entity_id="hello.oceans")
await hass.async_block_till_done()
state = hass.states.get(mock_entity.entity_id)
assert state.attributes.get(ATTR_ENTITY_ID) == ["hello.moon_light"]

View File

@@ -661,11 +661,12 @@ async def test_discovery_requirements_dhcp(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("requirement", "is_built_in", "deprecation_info"),
("requirement", "is_built_in", "deprecation_prefix", "deprecation_info"),
[
(
"hello",
True,
"Detected that integration",
"which is deprecated for testing. This will stop working in Home Assistant"
" 2020.12, please create a bug report at https://github.com/home-assistant/"
"core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+test_component%22",
@@ -673,6 +674,7 @@ async def test_discovery_requirements_dhcp(hass: HomeAssistant) -> None:
(
"hello>=1.0.0",
False,
"Detected that custom integration",
"which is deprecated for testing. This will stop working in Home Assistant"
" 2020.12, please create a bug report at https://github.com/home-assistant/"
"core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+test_component%22",
@@ -680,6 +682,7 @@ async def test_discovery_requirements_dhcp(hass: HomeAssistant) -> None:
(
"pyserial-asyncio",
False,
"Detected that custom integration",
"which should be replaced by pyserial-asyncio-fast. This will stop"
" working in Home Assistant 2026.7, please create a bug report at "
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+"
@@ -688,6 +691,7 @@ async def test_discovery_requirements_dhcp(hass: HomeAssistant) -> None:
(
"pyserial-asyncio>=0.6",
True,
"Detected that integration",
"which should be replaced by pyserial-asyncio-fast. This will stop"
" working in Home Assistant 2026.7, please create a bug report at "
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+"
@@ -699,6 +703,7 @@ async def test_install_deprecated_package(
hass: HomeAssistant,
requirement: str,
is_built_in: bool,
deprecation_prefix: str,
deprecation_info: str,
caplog: pytest.LogCaptureFixture,
) -> None:
@@ -710,10 +715,16 @@ async def test_install_deprecated_package(
patch("homeassistant.util.package.install_package", return_value=True),
):
await async_process_requirements(
hass, "test_component", [requirement], is_built_in
hass,
"test_component",
[
requirement,
"git+https://github.com/user/project.git@1.2.3",
],
is_built_in,
)
assert (
f"Detected that {'' if is_built_in else 'custom '}integration "
f"'test_component' has requirement '{requirement}' {deprecation_info}"
f"{deprecation_prefix} 'test_component'"
f" has requirement '{requirement}' {deprecation_info}"
) in caplog.text