From db76773727fc53fa15bb56c04c6ece9a3a9d3af6 Mon Sep 17 00:00:00 2001 From: shbatm Date: Sat, 25 Apr 2026 05:44:41 -0500 Subject: [PATCH] Standardize ISY994 sensor units and device classes (#169017) Co-authored-by: J. Nick Koston Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/isy994/const.py | 14 +- homeassistant/components/isy994/sensor.py | 117 +- tests/components/isy994/conftest.py | 97 ++ .../isy994/snapshots/test_sensor.ambr | 1348 +++++++++++++++++ tests/components/isy994/test_sensor.py | 139 ++ 5 files changed, 1706 insertions(+), 9 deletions(-) create mode 100644 tests/components/isy994/conftest.py create mode 100644 tests/components/isy994/snapshots/test_sensor.ambr create mode 100644 tests/components/isy994/test_sensor.py diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index b43385a0e5de7..9a0acf7360185 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -16,6 +16,7 @@ HVACMode, ) from homeassistant.components.lock import LockState +from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_MILLION, @@ -431,7 +432,7 @@ "127": UnitOfPressure.MMHG, "128": "J", "129": "BMI", # Body Mass Index - "130": f"{UnitOfVolume.LITERS}/{UnitOfTime.HOURS}", + "130": UnitOfVolumeFlowRate.LITERS_PER_HOUR, "131": SIGNAL_STRENGTH_DECIBELS_MILLIWATT, "132": "bpm", # Breaths per minute "133": UnitOfFrequency.KILOHERTZ, @@ -444,8 +445,8 @@ "140": f"{UnitOfMass.MILLIGRAMS}/{UnitOfVolume.LITERS}", "141": "N", # Netwon "142": f"{UnitOfVolume.GALLONS}/{UnitOfTime.SECONDS}", - "143": "gpm", # Gallon per Minute - "144": "gph", # Gallon per Hour + "143": UnitOfVolumeFlowRate.GALLONS_PER_MINUTE, + "144": UnitOfVolumeFlowRate.GALLONS_PER_HOUR, } UOM_TO_STATES = { @@ -653,6 +654,13 @@ HA_FAN_TO_ISY = {FAN_ON: "on", FAN_AUTO: "auto"} +TOTAL_INCREASING_DEVICE_CLASSES = { + SensorDeviceClass.ENERGY, + SensorDeviceClass.WATER, + SensorDeviceClass.GAS, + SensorDeviceClass.PRECIPITATION, +} + BINARY_SENSOR_DEVICE_TYPES_ISY = { BinarySensorDeviceClass.MOISTURE: ["16.8.", "16.13.", "16.14."], BinarySensorDeviceClass.OPENING: [ diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 6e0b5a8963795..6a0b19d3284f9 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -29,13 +29,19 @@ SensorEntity, SensorStateClass, ) -from homeassistant.const import EntityCategory, Platform, UnitOfTemperature +from homeassistant.const import ( + EntityCategory, + Platform, + UnitOfTemperature, + UnitOfVolumeFlowRate, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import ( _LOGGER, + TOTAL_INCREASING_DEVICE_CLASSES, UOM_DOUBLE_TEMP, UOM_FRIENDLY_NAME, UOM_INDEX, @@ -73,6 +79,7 @@ "DISTANC": SensorDeviceClass.DISTANCE, "ETO": SensorDeviceClass.PRECIPITATION_INTENSITY, # codespell:ignore eto "FATM": SensorDeviceClass.WEIGHT, + "FLOW": SensorDeviceClass.VOLUME_FLOW_RATE, "FREQ": SensorDeviceClass.FREQUENCY, "MUSCLEM": SensorDeviceClass.WEIGHT, "PF": SensorDeviceClass.POWER_FACTOR, @@ -95,9 +102,56 @@ "WEIGHT": SensorDeviceClass.WEIGHT, "WINDCH": SensorDeviceClass.TEMPERATURE, } -ISY_CONTROL_TO_STATE_CLASS = dict.fromkeys( - ISY_CONTROL_TO_DEVICE_CLASS, SensorStateClass.MEASUREMENT -) +UOM_TO_DEVICE_CLASS = { + "1": SensorDeviceClass.CURRENT, + "3": SensorDeviceClass.POWER, + "4": SensorDeviceClass.TEMPERATURE, + "7": SensorDeviceClass.VOLUME_FLOW_RATE, + "12": SensorDeviceClass.SOUND_PRESSURE, + "13": SensorDeviceClass.SOUND_PRESSURE, + "17": SensorDeviceClass.TEMPERATURE, + "23": SensorDeviceClass.ATMOSPHERIC_PRESSURE, + "24": SensorDeviceClass.PRECIPITATION_INTENSITY, + "26": SensorDeviceClass.TEMPERATURE, + "28": SensorDeviceClass.WEIGHT, + "29": SensorDeviceClass.VOLTAGE, + "30": SensorDeviceClass.POWER, + "31": SensorDeviceClass.PRESSURE, + "32": SensorDeviceClass.SPEED, + "33": SensorDeviceClass.ENERGY, + "35": SensorDeviceClass.WATER, + "39": SensorDeviceClass.VOLUME_FLOW_RATE, + "40": SensorDeviceClass.SPEED, + "41": SensorDeviceClass.CURRENT, + "43": SensorDeviceClass.VOLTAGE, + "46": SensorDeviceClass.PRECIPITATION_INTENSITY, + "48": SensorDeviceClass.SPEED, + "49": SensorDeviceClass.SPEED, + "52": SensorDeviceClass.WEIGHT, + "54": SensorDeviceClass.CO2, + "69": SensorDeviceClass.WATER, + "72": SensorDeviceClass.VOLTAGE, + "73": SensorDeviceClass.POWER, + "74": SensorDeviceClass.IRRADIANCE, + "82": SensorDeviceClass.DISTANCE, + "83": SensorDeviceClass.DISTANCE, + "90": SensorDeviceClass.FREQUENCY, + "105": SensorDeviceClass.DISTANCE, + "106": SensorDeviceClass.PRECIPITATION_INTENSITY, + "116": SensorDeviceClass.DISTANCE, + "117": SensorDeviceClass.PRESSURE, + "118": SensorDeviceClass.ATMOSPHERIC_PRESSURE, + "119": SensorDeviceClass.ENERGY, + "120": SensorDeviceClass.PRECIPITATION_INTENSITY, + "127": SensorDeviceClass.PRESSURE, + "130": SensorDeviceClass.VOLUME_FLOW_RATE, + "131": SensorDeviceClass.SIGNAL_STRENGTH, + "133": SensorDeviceClass.FREQUENCY, + "138": SensorDeviceClass.PRESSURE, + "142": SensorDeviceClass.VOLUME_FLOW_RATE, + "143": SensorDeviceClass.VOLUME_FLOW_RATE, + "144": SensorDeviceClass.VOLUME_FLOW_RATE, +} ISY_CONTROL_TO_ENTITY_CATEGORY = { PROP_RAMP_RATE: EntityCategory.DIAGNOSTIC, PROP_ON_LEVEL: EntityCategory.DIAGNOSTIC, @@ -105,6 +159,21 @@ } +def _check_volume_flow_rate_uom( + device_class: SensorDeviceClass | None, + uom: str | list[str] | None, +) -> SensorDeviceClass | None: + """Check if the volume flow rate unit is supported.""" + if device_class != SensorDeviceClass.VOLUME_FLOW_RATE: + return device_class + # Backwards compatibility for ISYv4 firmware which may return a list. + if isinstance(uom, list): + uom = uom[0] if uom else None + if uom is not None and UOM_FRIENDLY_NAME.get(uom) in UnitOfVolumeFlowRate: + return device_class + return None + + async def async_setup_entry( hass: HomeAssistant, entry: IsyConfigEntry, @@ -141,6 +210,26 @@ async def async_setup_entry( class ISYSensorEntity(ISYNodeEntity, SensorEntity): """Representation of an ISY sensor device.""" + def __init__(self, node: Node, device_info: DeviceInfo | None = None) -> None: + """Initialize the ISY sensor.""" + super().__init__(node, device_info=device_info) + uom = self._node.uom + if isinstance(uom, list): + uom = uom[0] + + # Determine device class + self._attr_device_class = _check_volume_flow_rate_uom( + UOM_TO_DEVICE_CLASS.get(uom), uom + ) + + # Determine state class + if self._attr_device_class in TOTAL_INCREASING_DEVICE_CLASSES: + self._attr_state_class = SensorStateClass.TOTAL_INCREASING + elif self._attr_device_class is not None: + self._attr_state_class = SensorStateClass.MEASUREMENT + else: + self._attr_state_class = None + @property def target(self) -> Node | NodeProperty | None: """Return target for the sensor.""" @@ -240,8 +329,24 @@ def __init__( self._control = control self._attr_entity_registry_enabled_default = enabled_default self._attr_entity_category = ISY_CONTROL_TO_ENTITY_CATEGORY.get(control) - self._attr_device_class = ISY_CONTROL_TO_DEVICE_CLASS.get(control) - self._attr_state_class = ISY_CONTROL_TO_STATE_CLASS.get(control) + + uom = None + if control in self._node.aux_properties: + uom = self._node.aux_properties[control].uom + + # Determine device class + self._attr_device_class = _check_volume_flow_rate_uom( + ISY_CONTROL_TO_DEVICE_CLASS.get(control), uom + ) + + # Determine state class + if self._attr_device_class in TOTAL_INCREASING_DEVICE_CLASSES: + self._attr_state_class = SensorStateClass.TOTAL_INCREASING + elif self._attr_device_class is not None: + self._attr_state_class = SensorStateClass.MEASUREMENT + else: + self._attr_state_class = None + self._attr_unique_id = unique_id self._change_handler: EventListener = None self._availability_handler: EventListener = None diff --git a/tests/components/isy994/conftest.py b/tests/components/isy994/conftest.py new file mode 100644 index 0000000000000..3cf24445914e4 --- /dev/null +++ b/tests/components/isy994/conftest.py @@ -0,0 +1,97 @@ +"""Fixtures for the ISY994 tests.""" + +from unittest.mock import AsyncMock, MagicMock, patch + +from pyisy.nodes import Node +import pytest + +from homeassistant.components.isy994.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME + +from tests.common import MockConfigEntry + +MOCK_UUID = "00:00:00:00:00:00" + + +@pytest.fixture +def mock_config_entry(): + """Return a mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "http://1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + unique_id=MOCK_UUID, + ) + + +@pytest.fixture +def mock_isy(): + """Return a mock ISY object.""" + mock = MagicMock() + mock.nodes = MagicMock() + mock.nodes.__iter__.return_value = [] + mock.nodes.status_events = MagicMock() + mock.programs = MagicMock() + mock.programs.get_by_name.return_value = None + mock.variables = MagicMock() + mock.variables.children = [] + mock.networking = MagicMock() + mock.networking.nobjs = [] + mock.clock = MagicMock() + mock.websocket = MagicMock() + mock.conf = { + "name": "Skynet ISY", + "model": "IoX", + "firmware": "6.0.4", + "Networking Module": True, + "Portal": True, + } + mock.uuid = MOCK_UUID + mock.conn.url = "http://1.1.1.1:80" + mock.initialize = AsyncMock() + return mock + + +@pytest.fixture +def mock_node(): + """Return a mock ISY node.""" + + def _mock_node(isy, address, name, node_def_id, node_type=None): + node = MagicMock(spec=Node) + node.isy = isy + node.address = address + node.name = name + node.node_def_id = node_def_id + node.type = node_type + node.status = 0 + node.uom = None + node.prec = 0 + node.protocol = "insteon" + node.folder = None + node.parent_node = None + node.primary_node = address + node.aux_properties = {} + node.status_events = MagicMock() + node.status_events.subscribe.return_value = MagicMock() + node.control_events = MagicMock() + node.control_events.subscribe.return_value = MagicMock() + node.is_backlight_supported = False + return node + + return _mock_node + + +@pytest.fixture(autouse=True) +def mock_isy_init(mock_isy): + """Mock pyisy.ISY initialization.""" + with ( + patch("homeassistant.components.isy994.ISY", return_value=mock_isy), + patch( + "homeassistant.components.isy994.config_flow.Connection.test_connection", + return_value="", + ), + ): + yield mock_isy diff --git a/tests/components/isy994/snapshots/test_sensor.ambr b/tests/components/isy994/snapshots/test_sensor.ambr new file mode 100644 index 0000000000000..3c8c7a5415bb5 --- /dev/null +++ b/tests/components/isy994/snapshots/test_sensor.ambr @@ -0,0 +1,1348 @@ +# serializer version: 1 +# name: test_sensor_snapshots[sensor.energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 5', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123456', + }) +# --- +# name: test_sensor_snapshots[sensor.energy_energy_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.energy_energy_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Energy Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Energy Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 5_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.energy_energy_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Energy Energy Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.energy_energy_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_aux_list-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.flow_aux_list', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 11', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.flow_aux_list-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Flow Aux List', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.flow_aux_list', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_aux_list_flow_aux_list_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.flow_aux_list_flow_aux_list_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow Aux List Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flow Aux List Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 11_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.flow_aux_list_flow_aux_list_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Aux List Flow Aux List Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.flow_aux_list_flow_aux_list_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_aux_list_flow_aux_list_flow-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.flow_aux_list_flow_aux_list_flow', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow Aux List Flow', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flow Aux List Flow', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 11_FLOW', + 'unit_of_measurement': 'gal/s', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_aux_list_flow_aux_list_flow-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Aux List Flow Aux List Flow', + 'unit_of_measurement': 'gal/s', + }), + 'context': , + 'entity_id': 'sensor.flow_aux_list_flow_aux_list_flow', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.1', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gph-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.flow_rate_gph', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 3', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gph-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'volume_flow_rate', + 'friendly_name': 'Flow Rate GPH', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gph', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '300', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gph_flow_rate_gph_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.flow_rate_gph_flow_rate_gph_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow Rate GPH Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flow Rate GPH Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 3_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gph_flow_rate_gph_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Rate GPH Flow Rate GPH Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gph_flow_rate_gph_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gpm-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.flow_rate_gpm', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 2', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gpm-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'volume_flow_rate', + 'friendly_name': 'Flow Rate GPM', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gpm', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '5.0', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gpm_flow_rate_gpm_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.flow_rate_gpm_flow_rate_gpm_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow Rate GPM Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flow Rate GPM Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 2_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gpm_flow_rate_gpm_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Rate GPM Flow Rate GPM Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gpm_flow_rate_gpm_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.flow_rate_gps', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 9', + 'unit_of_measurement': 'gal/s', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Rate GPS', + 'unit_of_measurement': 'gal/s', + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gps', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.1', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps_flow_rate_gps_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.flow_rate_gps_flow_rate_gps_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow Rate GPS Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flow Rate GPS Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 9_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps_flow_rate_gps_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Rate GPS Flow Rate GPS Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gps_flow_rate_gps_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps_list-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.flow_rate_gps_list', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 10', + 'unit_of_measurement': 'gal/s', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps_list-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Rate GPS List', + 'unit_of_measurement': 'gal/s', + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gps_list', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.2', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps_list_flow_rate_gps_list_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.flow_rate_gps_list_flow_rate_gps_list_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow Rate GPS List Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flow Rate GPS List Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 10_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_gps_list_flow_rate_gps_list_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Rate GPS List Flow Rate GPS List Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.flow_rate_gps_list_flow_rate_gps_list_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_lph-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.flow_rate_lph', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 1', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_lph-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'volume_flow_rate', + 'friendly_name': 'Flow Rate LPH', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.flow_rate_lph', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100.0', + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_lph_flow_rate_lph_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.flow_rate_lph_flow_rate_lph_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow Rate LPH Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flow Rate LPH Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 1_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.flow_rate_lph_flow_rate_lph_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Flow Rate LPH Flow Rate LPH Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.flow_rate_lph_flow_rate_lph_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.power_node-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.power_node', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 8', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.power_node-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Power Node', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.power_node', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensor_snapshots[sensor.power_node_power_node_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.power_node_power_node_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Power Node Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Power Node Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 8_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.power_node_power_node_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Power Node Power Node Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.power_node_power_node_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.power_node_power_node_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.power_node_power_node_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Power Node Power', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power Node Power', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 8_CPW', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.power_node_power_node_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Power Node Power Node Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.power_node_power_node_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '250', + }) +# --- +# name: test_sensor_snapshots[sensor.power_node_power_node_total_energy_used-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.power_node_power_node_total_energy_used', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Power Node Total Energy Used', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power Node Total Energy Used', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 8_TPW', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.power_node_power_node_total_energy_used-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Power Node Power Node Total Energy Used', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.power_node_power_node_total_energy_used', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '50000', + }) +# --- +# name: test_sensor_snapshots[sensor.rain_rate-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rain_rate', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 6', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.rain_rate-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'precipitation_intensity', + 'friendly_name': 'Rain Rate', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.rain_rate', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '30.48', + }) +# --- +# name: test_sensor_snapshots[sensor.rain_rate_rain_rate_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.rain_rate_rain_rate_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Rain Rate Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Rain Rate Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 6_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.rain_rate_rain_rate_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Rain Rate Rain Rate Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.rain_rate_rain_rate_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 4', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '21.5', + }) +# --- +# name: test_sensor_snapshots[sensor.temperature_temperature_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.temperature_temperature_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Temperature Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Temperature Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 4_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.temperature_temperature_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Temperature Temperature Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.temperature_temperature_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_snapshots[sensor.water_meter-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.water_meter', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 7', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_snapshots[sensor.water_meter-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': 'Water Meter', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.water_meter', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '37384.726778784', + }) +# --- +# name: test_sensor_snapshots[sensor.water_meter_water_meter_device_communication_errors-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.water_meter_water_meter_device_communication_errors', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Water Meter Device Communication Errors', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Water Meter Device Communication Errors', + 'platform': 'isy994', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00:00:00:00:00:00_22 22 22 7_ERR', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_snapshots[sensor.water_meter_water_meter_device_communication_errors-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Water Meter Water Meter Device Communication Errors', + }), + 'context': , + 'entity_id': 'sensor.water_meter_water_meter_device_communication_errors', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/isy994/test_sensor.py b/tests/components/isy994/test_sensor.py new file mode 100644 index 0000000000000..538fd8da129c6 --- /dev/null +++ b/tests/components/isy994/test_sensor.py @@ -0,0 +1,139 @@ +"""Test the ISY994 sensor platform.""" + +from collections.abc import Callable +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.fixture(autouse=True) +def mock_sensor_platform(): + """Mock the platforms to only include sensor.""" + with patch("homeassistant.components.isy994.PLATFORMS", [Platform.SENSOR]): + yield + + +async def test_sensor_snapshots( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + mock_isy: MagicMock, + mock_node: Callable[..., Any], +) -> None: + """Test sensors with snapshots.""" + mock_config_entry.add_to_hass(hass) + + # Mock nodes covering various UOMs and device classes + nodes = [] + + # Standardized UOMs + # Node 1: Liters per Hour + node1 = mock_node(mock_isy, "22 22 22 1", "Flow Rate LPH", "GenericSensor") + node1.status = 1000 + node1.uom = "130" + node1.prec = "1" + nodes.append(("Sensors/Flow Rate LPH", node1)) + + # Node 2: Gallons per Minute + node2 = mock_node(mock_isy, "22 22 22 2", "Flow Rate GPM", "GenericSensor") + node2.status = 50 + node2.uom = "143" + node2.prec = "1" + nodes.append(("Sensors/Flow Rate GPM", node2)) + + # Node 3: Gallons per Hour + node3 = mock_node(mock_isy, "22 22 22 3", "Flow Rate GPH", "GenericSensor") + node3.status = 300 + node3.uom = "144" + node3.prec = "0" + nodes.append(("Sensors/Flow Rate GPH", node3)) + + # Node 9: Gallons per Second (142) - Should have NO device_class due to guard + node9 = mock_node(mock_isy, "22 22 22 9", "Flow Rate GPS", "GenericSensor") + node9.status = 1 + node9.uom = "142" + node9.prec = "1" + nodes.append(("Sensors/Flow Rate GPS", node9)) + + # Node 10: Gallons per Second (142) in ISYv4 list form - guard must still apply + node10 = mock_node(mock_isy, "22 22 22 10", "Flow Rate GPS List", "GenericSensor") + node10.status = 2 + node10.uom = ["142"] + node10.prec = "1" + nodes.append(("Sensors/Flow Rate GPS List", node10)) + + # Other UOMs from test_mappings + # Temperature (4) + node4 = mock_node(mock_isy, "22 22 22 4", "Temperature", "GenericSensor") + node4.status = 215 + node4.uom = "4" + node4.prec = "1" + nodes.append(("Sensors/Temperature", node4)) + + # Energy (33) - TOTAL_INCREASING + node5 = mock_node(mock_isy, "22 22 22 5", "Energy", "GenericSensor") + node5.status = 123456 + node5.uom = "33" + node5.prec = "0" + nodes.append(("Sensors/Energy", node5)) + + # Precipitation Intensity (24) + node6 = mock_node(mock_isy, "22 22 22 6", "Rain Rate", "GenericSensor") + node6.status = 12 + node6.uom = "24" + node6.prec = "1" + nodes.append(("Sensors/Rain Rate", node6)) + + # Water (69) + node7 = mock_node(mock_isy, "22 22 22 7", "Water Meter", "GenericSensor") + node7.status = 9876 + node7.uom = "69" + node7.prec = "0" + nodes.append(("Sensors/Water Meter", node7)) + + # Aux Properties (TPW, CPW) + node8 = mock_node(mock_isy, "22 22 22 8", "Power Node", "GenericSensor") + node8.status = 0 + node8.uom = "73" # Watts + node8.aux_properties = { + "TPW": MagicMock(value=50000, uom="33", prec="0"), # Total Power (Energy) + "CPW": MagicMock(value=250, uom="73", prec="0"), # Current Power + } + nodes.append(("Sensors/Power Node", node8)) + + # Aux FLOW with ISYv4 list-form UOM 142 (gal/s) - guard must clear + # device_class without raising TypeError on the unhashable list. + node11 = mock_node(mock_isy, "22 22 22 11", "Flow Aux List", "GenericSensor") + node11.status = 0 + node11.uom = "73" + node11.aux_properties = { + "FLOW": MagicMock(value=1, uom=["142"], prec="1"), + } + nodes.append(("Sensors/Flow Aux List", node11)) + + mock_isy.nodes.__iter__.return_value = nodes + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + # Enable disabled entities (like aux sensors) + entity_entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + for entry in entity_entries: + if entry.disabled_by: + entity_registry.async_update_entity(entry.entity_id, disabled_by=None) + + await hass.config_entries.async_reload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)