diff --git a/homeassistant/components/my_pv/__init__.py b/homeassistant/components/my_pv/__init__.py index c11db46da7bc..56826d0310e1 100644 --- a/homeassistant/components/my_pv/__init__.py +++ b/homeassistant/components/my_pv/__init__.py @@ -14,6 +14,7 @@ PLATFORMS: list[Platform] = [ Platform.BUTTON, Platform.NUMBER, Platform.SELECT, + Platform.SENSOR, Platform.SWITCH, Platform.WATER_HEATER, ] diff --git a/homeassistant/components/my_pv/icons.json b/homeassistant/components/my_pv/icons.json new file mode 100644 index 000000000000..4ddedfd85ccc --- /dev/null +++ b/homeassistant/components/my_pv/icons.json @@ -0,0 +1,21 @@ +{ + "entity": { + "sensor": { + "cur_eth_mode": { + "default": "mdi:network" + }, + "screen_mode_flag": { + "default": "mdi:check-circle" + }, + "wifi_signal": { + "state": { + "0": "mdi:wifi-strength-1", + "1": "mdi:wifi-strength-1", + "2": "mdi:wifi-strength-2", + "3": "mdi:wifi-strength-3", + "4": "mdi:wifi-strength-4" + } + } + } + } +} diff --git a/homeassistant/components/my_pv/quality_scale.yaml b/homeassistant/components/my_pv/quality_scale.yaml index 6975ed9648a4..87a42b5e1a2c 100644 --- a/homeassistant/components/my_pv/quality_scale.yaml +++ b/homeassistant/components/my_pv/quality_scale.yaml @@ -63,7 +63,9 @@ rules: status: exempt comment: The water heater entity does not provide a device class. entity-disabled-by-default: todo - entity-translations: done + entity-translations: + status: exempt + comment: The only entity uses the device name. exception-translations: done icon-translations: status: exempt diff --git a/homeassistant/components/my_pv/sensor.py b/homeassistant/components/my_pv/sensor.py new file mode 100644 index 000000000000..8c572e0ae7d9 --- /dev/null +++ b/homeassistant/components/my_pv/sensor.py @@ -0,0 +1,229 @@ +"""Creates Sensor entities for the my-PV Home Assistant integration.""" + +from datetime import date, datetime +from decimal import Decimal +from typing import Any, Final, override + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.typing import StateType + +from . import MyPVConfigEntry +from .entity import MyPVDataEntity + +SENSOR_DESCRIPTIONS: Final[dict[str, dict[str, Any]]] = { + "cur_eth_mode": { + "enabled": False, + "entity_category": EntityCategory.DIAGNOSTIC, + "translation_key": "cur_eth_mode", + }, + "curr_l1": { + "device_class": SensorDeviceClass.CURRENT, + "translation_key": "curr_l1", + }, + "curr_l2": { + "device_class": SensorDeviceClass.CURRENT, + "translation_key": "curr_l2", + }, + "curr_l3": { + "device_class": SensorDeviceClass.CURRENT, + "translation_key": "curr_l3", + }, + "curr_mains": { + "device_class": SensorDeviceClass.CURRENT, + "suggested_display_precision": 1, + "translation_key": "curr_l1", + }, + "freq": { + "device_class": SensorDeviceClass.FREQUENCY, + "enabled": False, + "entity_category": EntityCategory.DIAGNOSTIC, + "suggested_display_precision": 2, + "translation_key": "freq", + }, + "power": {"device_class": SensorDeviceClass.POWER}, + "power_ac9": {"device_class": SensorDeviceClass.POWER}, + "power_act": {"device_class": SensorDeviceClass.POWER}, + "power_elwa2": {"device_class": SensorDeviceClass.POWER}, + "power_grid": {"device_class": SensorDeviceClass.POWER}, + "power_solar": { + "device_class": SensorDeviceClass.POWER, + "translation_key": "power_solar", + }, + "screen_mode_flag": {"translation_key": "screen_mode_flag"}, + "temp1": { + "device_class": SensorDeviceClass.TEMPERATURE, + "suggested_display_precision": 1, + "translation_key": "temp1", + }, + "temp2": { + "device_class": SensorDeviceClass.TEMPERATURE, + "suggested_display_precision": 1, + "translation_key": "temp2", + }, + "temp3": { + "device_class": SensorDeviceClass.TEMPERATURE, + "suggested_display_precision": 1, + "translation_key": "temp3", + }, + "temp4": { + "device_class": SensorDeviceClass.TEMPERATURE, + "suggested_display_precision": 1, + "translation_key": "temp4", + }, + "temp_ps": { + "device_class": SensorDeviceClass.TEMPERATURE, + "enabled": False, + "entity_category": EntityCategory.DIAGNOSTIC, + "suggested_display_precision": 1, + "translation_key": "temp_ps", + }, + "uptime": { + "device_class": SensorDeviceClass.DURATION, + "enabled": False, + "entity_category": EntityCategory.DIAGNOSTIC, + "state_class": SensorStateClass.TOTAL_INCREASING, + "translation_key": "uptime", + }, + "volt_l1": { + "device_class": SensorDeviceClass.VOLTAGE, + "entity_category": EntityCategory.DIAGNOSTIC, + "translation_key": "volt_l1", + }, + "volt_l2": { + "device_class": SensorDeviceClass.VOLTAGE, + "entity_category": EntityCategory.DIAGNOSTIC, + "translation_key": "volt_l2", + }, + "volt_l3": { + "device_class": SensorDeviceClass.VOLTAGE, + "entity_category": EntityCategory.DIAGNOSTIC, + "translation_key": "volt_l3", + }, + "volt_mains": { + "device_class": SensorDeviceClass.VOLTAGE, + "entity_category": EntityCategory.DIAGNOSTIC, + "translation_key": "volt_l1", + }, + "volt_mains_l1": { + "device_class": SensorDeviceClass.VOLTAGE, + "entity_category": EntityCategory.DIAGNOSTIC, + "suggested_display_precision": 1, + "translation_key": "volt_l1", + }, + "volt_mains_l2": { + "device_class": SensorDeviceClass.VOLTAGE, + "entity_category": EntityCategory.DIAGNOSTIC, + "suggested_display_precision": 1, + "translation_key": "volt_l2", + }, + "volt_mains_l3": { + "device_class": SensorDeviceClass.VOLTAGE, + "entity_category": EntityCategory.DIAGNOSTIC, + "suggested_display_precision": 1, + "translation_key": "volt_l3", + }, + "volt_solar": { + "device_class": SensorDeviceClass.VOLTAGE, + "translation_key": "volt_solar", + }, + "wifi_signal": { + "device_class": SensorDeviceClass.SIGNAL_STRENGTH, + "enabled": False, + "entity_category": EntityCategory.DIAGNOSTIC, + "translation_key": "wifi_signal", + }, + "wifi_signal_strength": { + "device_class": SensorDeviceClass.SIGNAL_STRENGTH, + "enabled": False, + "entity_category": EntityCategory.DIAGNOSTIC, + "translation_key": "wifi_signal_strength", + }, +} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: MyPVConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the my-PV sensor.""" + coordinator = config_entry.runtime_data + entities = [] + + for key, config in coordinator.device.get_data_configurations().items(): + if config.get("type") != "boolean" and key in SENSOR_DESCRIPTIONS: + sensor_description: dict = SENSOR_DESCRIPTIONS[key] + + device_class = None + options = None + state_class = None + if config.get("type") == "enumeration": + device_class = SensorDeviceClass.ENUM + options = list(config["options"].keys()) + elif config.get("type") == "string": + device_class = sensor_description.get("device_class") + else: + device_class = sensor_description.get("device_class") + state_class = sensor_description.get( + "state_class", SensorStateClass.MEASUREMENT + ) + + translation_key: str | None = sensor_description.get("translation_key") + if ( + ( + key + in ( + "curr_mains", + "curr_l1", + ) + and not coordinator.device.supports_data("curr_l2") + ) + or ( + key in ("volt_mains", "volt_mains_l1", "volt_l1") + and not coordinator.device.supports_data("volt_l2") + and not coordinator.device.supports_data("volt_mains_l2") + ) + or (key == "temp1" and not coordinator.device.supports_data("temp2")) + ): + translation_key = None + + entity_description = SensorEntityDescription( + key=key, + device_class=device_class, + entity_category=sensor_description.get("entity_category"), + translation_key=translation_key, + native_unit_of_measurement=config.get("unit"), + options=options, + state_class=state_class, + suggested_display_precision=sensor_description.get( + "suggested_display_precision" + ), + entity_registry_enabled_default=sensor_description.get("enabled", True), + ) + entities.append( + MyPVSensor( + coordinator, + entity_description, + coordinator.device.serial_number, + ) + ) + + async_add_entities(entities) + + +class MyPVSensor(MyPVDataEntity, SensorEntity): + """Base my-PV Sensor.""" + + @property + @override + def native_value(self) -> StateType | date | datetime | Decimal: + """Return the value reported by the sensor.""" + return self.coordinator.device.get_data_value(self.entity_description.key) diff --git a/homeassistant/components/my_pv/strings.json b/homeassistant/components/my_pv/strings.json index 90be8105f028..34ad525949bc 100644 --- a/homeassistant/components/my_pv/strings.json +++ b/homeassistant/components/my_pv/strings.json @@ -81,6 +81,86 @@ } } }, + "sensor": { + "cur_eth_mode": { + "name": "Network mode", + "state": { + "0": "LAN", + "1": "WLAN", + "2": "AP" + } + }, + "curr_l1": { + "name": "Current phase L1" + }, + "curr_l2": { + "name": "Current phase L2" + }, + "curr_l3": { + "name": "Current phase L3" + }, + "freq": { + "name": "Mains frequency" + }, + "power_solar": { + "name": "PV power" + }, + "screen_mode_flag": { + "name": "Device status", + "state": { + "0": "[%key:common::state::standby%]", + "1": "Heat", + "2": "Boost", + "3": "Heating finished", + "4": "No connection / Disabled", + "5": "Failure", + "6": "Block active" + } + }, + "temp1": { + "name": "Temperature 1" + }, + "temp2": { + "name": "Temperature 2" + }, + "temp3": { + "name": "Temperature 3" + }, + "temp4": { + "name": "Temperature 4" + }, + "temp_ps": { + "name": "Device temperature" + }, + "uptime": { + "name": "Uptime" + }, + "volt_l1": { + "name": "Voltage phase L1" + }, + "volt_l2": { + "name": "Voltage phase L2" + }, + "volt_l3": { + "name": "Voltage phase L3" + }, + "volt_solar": { + "name": "PV voltage" + }, + "wifi_signal": { + "name": "Wi-Fi signal strength", + "state": { + "0": "Poor", + "1": "Low", + "2": "Average", + "3": "Good", + "4": "Strong" + } + }, + "wifi_signal_strength": { + "name": "Wi-Fi signal strength" + } + }, "switch": { "bstmode": { "name": "Boost Mode" diff --git a/tests/components/my_pv/conftest.py b/tests/components/my_pv/conftest.py index e748fd5a781b..6a90769efae0 100644 --- a/tests/components/my_pv/conftest.py +++ b/tests/components/my_pv/conftest.py @@ -35,6 +35,48 @@ SETUP_VALUE = { "ww_targ_h": 4.5, } +DATA_CONFIGURATION = { + "cur_eth_mode": { + "options": {"0": "LAN", "1": "WLAN", "2": "AP"}, + "type": "enumeration", + }, + "freq": {"type": "number", "unit": "Hz"}, + "power": {"type": "number", "unit": "W"}, + "screen_mode_flag": { + "options": { + "0": "Standby", + "1": "Heat", + "2": "Boost", + "3": "Heating finished", + "4": "No connection / Disabled", + "5": "Error", + "6": "Block active", + }, + "type": "enumeration", + }, + "temp1": {"type": "number", "unit": "°C"}, + "temp2": {"type": "number", "unit": "°C"}, + "temp3": {"type": "number", "unit": "°C"}, + "temp4": {"type": "number", "unit": "°C"}, + "temp_ps": {"type": "number", "unit": "°C"}, + "uptime": {"type": "number", "unit": "h"}, + "volt_mains": {"type": "number", "unit": "V"}, +} + +DATA_VALUE = { + "cur_eth_mode": "0", + "freq": 49.965, + "power": 3445, + "screen_mode_flag": "4", + "temp1": 12.3, + "temp2": 23.4, + "temp3": 34.5, + "temp4": 45.6, + "temp_ps": 56.7, + "uptime": 2, + "volt_mains": 238, +} + @pytest.fixture def setup_configuration() -> dict[str, Any]: @@ -48,6 +90,10 @@ def setup_values() -> dict[str, Any]: return SETUP_VALUE +def _data_value_lookup(key): + return DATA_VALUE.get(key) + + @pytest.fixture def mock_config_entry() -> MockConfigEntry: """Return the my-PV mocked config entry for local devices.""" @@ -100,6 +146,8 @@ def mock_my_pv_client( client.setup_uri = "http://127.0.0.1/" client.hardware_version = "v1.5A" client.firmware_version = "e0002200" + client.connected = True + client.is_on = True client.current_temperature = 54.3 client.target_temperature = 62.1 client.get_setup_configurations = Mock(return_value=setup_configuration) @@ -110,5 +158,8 @@ def mock_my_pv_client( client.connected = True client.is_on = True client.send_command = AsyncMock(return_value=True) + client.get_data_configurations = Mock(return_value=DATA_CONFIGURATION) + client.get_data_value = Mock(side_effect=_data_value_lookup) + client.supports_data = Mock(side_effect=DATA_CONFIGURATION.__contains__) yield client diff --git a/tests/components/my_pv/snapshots/test_sensor.ambr b/tests/components/my_pv/snapshots/test_sensor.ambr new file mode 100644 index 000000000000..d0a7500aa7b0 --- /dev/null +++ b/tests/components/my_pv/snapshots/test_sensor.ambr @@ -0,0 +1,655 @@ +# serializer version: 1 +# name: test_sensor[sensor.my_pv_ac_elwa_2_device_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.my_pv_ac_elwa_2_device_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Device status', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Device status', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'screen_mode_flag', + 'unique_id': '1601500000000000-screen_mode_flag', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_device_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'my-PV AC ELWA 2 Device status', + : list([ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + ]), + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_device_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '4', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_device_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_device_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Device temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Device temperature', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'temp_ps', + 'unique_id': '1601500000000000-temp_ps', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_device_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'my-PV AC ELWA 2 Device temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_device_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '56.7', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_mains_frequency-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_mains_frequency', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Mains frequency', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Mains frequency', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'freq', + 'unique_id': '1601500000000000-freq', + 'unit_of_measurement': 'Hz', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_mains_frequency-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'frequency', + : 'my-PV AC ELWA 2 Mains frequency', + : , + : 'Hz', + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_mains_frequency', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '49.965', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_network_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + '0', + '1', + '2', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_network_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Network mode', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Network mode', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cur_eth_mode', + 'unique_id': '1601500000000000-cur_eth_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_network_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'my-PV AC ELWA 2 Network mode', + : list([ + '0', + '1', + '2', + ]), + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_network_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.my_pv_ac_elwa_2_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Power', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '1601500000000000-power', + 'unit_of_measurement': 'W', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'my-PV AC ELWA 2 Power', + : , + : 'W', + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3445', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Temperature 1', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature 1', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'temp1', + 'unique_id': '1601500000000000-temp1', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'my-PV AC ELWA 2 Temperature 1', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '12.3', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Temperature 2', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature 2', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'temp2', + 'unique_id': '1601500000000000-temp2', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'my-PV AC ELWA 2 Temperature 2', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '23.4', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_3-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Temperature 3', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature 3', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'temp3', + 'unique_id': '1601500000000000-temp3', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'my-PV AC ELWA 2 Temperature 3', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '34.5', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_4-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_4', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Temperature 4', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature 4', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'temp4', + 'unique_id': '1601500000000000-temp4', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_temperature_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'my-PV AC ELWA 2 Temperature 4', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_temperature_4', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '45.6', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_uptime-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_uptime', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Uptime', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Uptime', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'uptime', + 'unique_id': '1601500000000000-uptime', + 'unit_of_measurement': 'h', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_uptime-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'duration', + : 'my-PV AC ELWA 2 Uptime', + : , + : 'h', + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_uptime', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_voltage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Voltage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Voltage', + 'platform': 'my_pv', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '1601500000000000-volt_mains', + 'unit_of_measurement': 'V', + }) +# --- +# name: test_sensor[sensor.my_pv_ac_elwa_2_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'voltage', + : 'my-PV AC ELWA 2 Voltage', + : , + : 'V', + }), + 'context': , + 'entity_id': 'sensor.my_pv_ac_elwa_2_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '238', + }) +# --- diff --git a/tests/components/my_pv/test_sensor.py b/tests/components/my_pv/test_sensor.py new file mode 100644 index 000000000000..b121024075f7 --- /dev/null +++ b/tests/components/my_pv/test_sensor.py @@ -0,0 +1,69 @@ +"""Test the my-PV sensor platform.""" + +from unittest.mock import AsyncMock, Mock, patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.mark.usefixtures("mock_my_pv_client") +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensor( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, +) -> None: + """Test successful setup of a sensor platform.""" + + with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SENSOR]): + mock_config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +async def test_sensor_unavailable_not_connected( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_my_pv_client: AsyncMock, +) -> None: + """Test if a sensor is unavailable when not connected.""" + + with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SENSOR]): + mock_config_entry.add_to_hass(hass) + + mock_my_pv_client.connected = False + + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("sensor.my_pv_ac_elwa_2_temperature_1") + assert state.state == STATE_UNAVAILABLE + + +async def test_sensor_unavailable_data_value_none( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_my_pv_client: AsyncMock, +) -> None: + """Test if a sensor is unavailable when data value is None.""" + + with patch("homeassistant.components.my_pv.PLATFORMS", [Platform.SENSOR]): + mock_config_entry.add_to_hass(hass) + + mock_my_pv_client.get_data_value = Mock(return_value=None) + + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("sensor.my_pv_ac_elwa_2_temperature_1") + assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/my_pv/test_water_heater.py b/tests/components/my_pv/test_water_heater.py index f3e0c5e2818d..c33f1477966e 100644 --- a/tests/components/my_pv/test_water_heater.py +++ b/tests/components/my_pv/test_water_heater.py @@ -1,6 +1,6 @@ """Test the my-PV water heater.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch from my_pv.exceptions import MyPVAuthenticationError, MyPVConnectionError import pytest @@ -74,7 +74,7 @@ async def test_water_heater_unavailable_data_value_none( mock_config_entry.add_to_hass(hass) - mock_my_pv_client.get_data_value.return_value = None + mock_my_pv_client.get_data_value = Mock(return_value=None) assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done()