From f803cedfad165bdd1e52eb2c2a04982ba138a52e Mon Sep 17 00:00:00 2001 From: Shay Redmond <7673052+shayredmond@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:38:53 +0100 Subject: [PATCH 01/22] Add clarity to the STT Labs title (#180927) --- homeassistant/components/cloud/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/cloud/strings.json b/homeassistant/components/cloud/strings.json index 515b8625c3bc8..068c2f6266af1 100644 --- a/homeassistant/components/cloud/strings.json +++ b/homeassistant/components/cloud/strings.json @@ -89,7 +89,7 @@ "description": "We're testing a new speech-to-text engine for Home Assistant Cloud, the part of voice control that turns what you say into words. In our testing it copes better with accents, background noise, and languages other than English.\n\nWhile this is enabled, any audio from your voice commands is processed by [Soniox](https://soniox.com/) rather than our current provider. This is relevant only to Assistants configured to use Home Assistant Cloud as an STT engine. It travels through a Nabu Casa proxy, which handles authentication and forwards it to the processing region closest to you. Nabu Casa doesn't process, store or log your audio at any point. Everything else about your voice pipeline stays the same, and you can turn this off again at any time.", "disable_confirmation": "Any Assistant currently using the new engine will go back to being transcribed by our current speech-to-text provider.", "enable_confirmation": "This feature is still in development and may change. While it's enabled, audio from your voice commands in supported languages is transcribed by Soniox instead of our current provider.", - "name": "Speech-to-text" + "name": "New speech-to-text engine" } }, "services": { From 16040b561a67a459cdad34ea016b474119a88f7d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Aug 2026 17:41:49 +0200 Subject: [PATCH 02/22] Use registry fixtures in tests (6/8) (#180928) --- tests/components/onedrive/test_init.py | 2 +- tests/components/onvif/test_init.py | 6 +++--- tests/components/openrgb/test_init.py | 5 ++--- tests/components/opower/test_coordinator.py | 4 ++-- tests/components/opower/test_sensor.py | 3 +-- tests/components/proxmoxve/test_init.py | 3 --- tests/components/roborock/test_init.py | 6 ++---- tests/components/roborock/test_vacuum.py | 6 ++++-- tests/components/sensor/test_recorder.py | 5 +++-- 9 files changed, 18 insertions(+), 22 deletions(-) diff --git a/tests/components/onedrive/test_init.py b/tests/components/onedrive/test_init.py index a48c1b4bb705b..f56690506800c 100644 --- a/tests/components/onedrive/test_init.py +++ b/tests/components/onedrive/test_init.py @@ -261,6 +261,7 @@ async def test_device( ) async def test_data_cap_issues( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_config_entry: MockConfigEntry, mock_onedrive_client: MagicMock, mock_drive: Drive, @@ -274,7 +275,6 @@ async def test_data_cap_issues( await setup_integration(hass, mock_config_entry) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue(DOMAIN, issue_key) assert (issue is not None) == issue_exists diff --git a/tests/components/onvif/test_init.py b/tests/components/onvif/test_init.py index 954d522d06196..8f9b11d90f9bb 100644 --- a/tests/components/onvif/test_init.py +++ b/tests/components/onvif/test_init.py @@ -28,13 +28,13 @@ from tests.common import MockConfigEntry -async def test_migrate_camera_entities_unique_ids(hass: HomeAssistant) -> None: +async def test_migrate_camera_entities_unique_ids( + hass: HomeAssistant, entity_registry: er.EntityRegistry +) -> None: """Test that camera entities unique ids get migrated properly.""" config_entry = MockConfigEntry(domain=DOMAIN, unique_id=MAC) config_entry.add_to_hass(hass) - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - entity_with_only_mac = entity_registry.async_get_or_create( domain="camera", platform="onvif", diff --git a/tests/components/openrgb/test_init.py b/tests/components/openrgb/test_init.py index fa1bac7a75b1d..3d75827dbb46d 100644 --- a/tests/components/openrgb/test_init.py +++ b/tests/components/openrgb/test_init.py @@ -40,6 +40,7 @@ async def test_entry_setup_unload( @pytest.mark.usefixtures("mock_openrgb_client") async def test_remove_config_entry_device_server( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Test that server device cannot be removed.""" @@ -48,7 +49,6 @@ async def test_remove_config_entry_device_server( await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures server_device = device_registry.async_get_device_by_identifier( (DOMAIN, mock_config_entry.entry_id), mock_config_entry.entry_id ) @@ -66,6 +66,7 @@ async def test_remove_config_entry_device_server( @pytest.mark.usefixtures("mock_openrgb_client") async def test_remove_config_entry_device_still_connected( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Test that connected devices cannot be removed.""" @@ -74,8 +75,6 @@ async def test_remove_config_entry_device_still_connected( await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - # Get a device that's in coordinator.data (still connected) devices = dr.async_entries_for_config_entry( device_registry, mock_config_entry.entry_id diff --git a/tests/components/opower/test_coordinator.py b/tests/components/opower/test_coordinator.py index 0aef37a8493ca..03abc408850e6 100644 --- a/tests/components/opower/test_coordinator.py +++ b/tests/components/opower/test_coordinator.py @@ -182,6 +182,7 @@ async def test_coordinator_subsequent_run_no_energy_data( async def test_coordinator_migration( recorder_mock: Recorder, hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_config_entry: MockConfigEntry, mock_opower_api: AsyncMock, snapshot: SnapshotAssertion, @@ -238,7 +239,6 @@ async def test_coordinator_migration( assert stats == snapshot # Check that an issue was created - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue(DOMAIN, "return_to_grid_migration_111111") assert issue is not None assert issue.severity == ir.IssueSeverity.WARNING @@ -370,6 +370,7 @@ def mock_get_cost_reads(acc, aggregate_type, start, end): async def test_coordinator_migration_empty_source_stats( recorder_mock: Recorder, hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_config_entry: MockConfigEntry, mock_opower_api: AsyncMock, ) -> None: @@ -412,7 +413,6 @@ async def test_coordinator_migration_empty_source_stats( # no individual stats were found assert migrated is False - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue(DOMAIN, "return_to_grid_migration_111111") assert issue is None diff --git a/tests/components/opower/test_sensor.py b/tests/components/opower/test_sensor.py index 50b38d092508e..aa52d3a542f6c 100644 --- a/tests/components/opower/test_sensor.py +++ b/tests/components/opower/test_sensor.py @@ -19,6 +19,7 @@ async def test_sensors( recorder_mock: Recorder, hass: HomeAssistant, + entity_registry: er.EntityRegistry, mock_config_entry: MockConfigEntry, mock_opower_api: AsyncMock, ) -> None: @@ -39,8 +40,6 @@ async def test_sensors( await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - # Check electric sensors entry = entity_registry.async_get( "sensor.elec_account_111111_current_bill_electric_usage_to_date" diff --git a/tests/components/proxmoxve/test_init.py b/tests/components/proxmoxve/test_init.py index 6655b7251fe3c..92d6c492f9e94 100644 --- a/tests/components/proxmoxve/test_init.py +++ b/tests/components/proxmoxve/test_init.py @@ -181,9 +181,6 @@ async def test_migration_v1_to_v3( entry.add_to_hass(hass) assert entry.version == 1 - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - vm_device = device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, f"{entry.entry_id}_vm_100")}, diff --git a/tests/components/roborock/test_init.py b/tests/components/roborock/test_init.py index 0fa7913f032f1..0bba878be785e 100644 --- a/tests/components/roborock/test_init.py +++ b/tests/components/roborock/test_init.py @@ -468,6 +468,7 @@ async def test_update_unavailability_threshold( async def test_cloud_api_repair( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_roborock_entry: MockConfigEntry, fake_vacuum: FakeDevice, ) -> None: @@ -480,7 +481,6 @@ async def test_cloud_api_repair( await hass.config_entries.async_setup(mock_roborock_entry.entry_id) await hass.async_block_till_done() - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert len(issue_registry.issues) == 1 # Check that both expected device names are present, regardless of order assert all( @@ -507,6 +507,7 @@ async def test_cloud_api_repair( @pytest.mark.parametrize("platforms", [[Platform.SENSOR]]) async def test_cloud_api_repair_cleared_on_update( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_roborock_entry: MockConfigEntry, fake_vacuum: FakeDevice, freezer: FrozenDateTimeFactory, @@ -523,7 +524,6 @@ async def test_cloud_api_repair_cleared_on_update( await hass.async_block_till_done() assert mock_roborock_entry.state is ConfigEntryState.LOADED - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert len(issue_registry.issues) == 1 # Fake that the device is reachable locally again. @@ -541,7 +541,6 @@ async def test_cloud_api_repair_cleared_on_update( await hass.async_block_till_done() # Verify that the repair issue is cleared - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert len(issue_registry.issues) == 0 # Fake the device is cloud only again. Refreshing the coordinator @@ -557,7 +556,6 @@ async def test_cloud_api_repair_cleared_on_update( await hass.async_block_till_done() # Verify that the repair issue still does not exist - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert len(issue_registry.issues) == 0 diff --git a/tests/components/roborock/test_vacuum.py b/tests/components/roborock/test_vacuum.py index b0afaf43b1a3a..68069d850196c 100644 --- a/tests/components/roborock/test_vacuum.py +++ b/tests/components/roborock/test_vacuum.py @@ -625,6 +625,7 @@ async def test_segments_changed_issue( hass: HomeAssistant, setup_entry: MockConfigEntry, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, fake_vacuum: FakeDevice, ) -> None: """Test repair issue created when segments change after mapping.""" @@ -647,7 +648,7 @@ async def test_segments_changed_issue( await hass.async_block_till_done() issue_id = f"segments_changed_{entity_entry.id}" - issue = ir.async_get(hass).async_get_issue(VACUUM_DOMAIN, issue_id) # pylint: disable=home-assistant-tests-registry-fixtures + issue = issue_registry.async_get_issue(VACUUM_DOMAIN, issue_id) assert issue is not None assert issue.severity == ir.IssueSeverity.WARNING assert issue.translation_key == "segments_changed" @@ -657,6 +658,7 @@ async def test_segments_changed_issue_no_map_info( hass: HomeAssistant, setup_entry: MockConfigEntry, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, fake_vacuum: FakeDevice, ) -> None: """Test no repair issue is created when map info is not loaded/empty.""" @@ -680,7 +682,7 @@ async def test_segments_changed_issue_no_map_info( await hass.async_block_till_done() issue_id = f"segments_changed_{entity_entry.id}" - issue = ir.async_get(hass).async_get_issue(VACUUM_DOMAIN, issue_id) # pylint: disable=home-assistant-tests-registry-fixtures + issue = issue_registry.async_get_issue(VACUUM_DOMAIN, issue_id) assert issue is None diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index b73cb177498de..b92411ee1225f 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -6754,11 +6754,12 @@ def _fetch_states() -> list[State]: ], ) async def test_clean_up_repairs( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + hass_ws_client: WebSocketGenerator, ) -> None: """Test cleaning up repairs.""" await async_setup_component(hass, DOMAIN, {}) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures client = await hass_ws_client() # Create some issues From 49fd8f4dda9e0a836d773bb62eaa48d6ea86122d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Strandberg?= Date: Mon, 31 Aug 2026 18:08:27 +0200 Subject: [PATCH 03/22] Add miele washing machine program for towels (#180916) --- homeassistant/components/miele/const.py | 1 + homeassistant/components/miele/strings.json | 1 + .../miele/snapshots/test_sensor.ambr | 5244 +++++++++-------- 3 files changed, 2627 insertions(+), 2619 deletions(-) diff --git a/homeassistant/components/miele/const.py b/homeassistant/components/miele/const.py index 93d475b100fb7..53a512040e56d 100644 --- a/homeassistant/components/miele/const.py +++ b/homeassistant/components/miele/const.py @@ -466,6 +466,7 @@ class WashingMachineProgramId(MieleEnum, missing_to_none=True): table_linen = 33 outerwear = 37 pillows = 39 + towels = 44 cool_air = 45 # washer-dryer warm_air = 46 # washer-dryer rinse_out_lint = 48 # washer-dryer diff --git a/homeassistant/components/miele/strings.json b/homeassistant/components/miele/strings.json index 5d24257e8f043..33235a7b7979e 100644 --- a/homeassistant/components/miele/strings.json +++ b/homeassistant/components/miele/strings.json @@ -891,6 +891,7 @@ "toffee_date_dessert_several_small": "Toffee-date dessert (several small)", "top_heat": "Top heat", "tortellini_fresh": "Tortellini (fresh)", + "towels": "Towels", "trainers": "Trainers", "trainers_refresh": "Trainers refresh", "treacle_sponge_pudding_one_large": "Treacle sponge pudding (one large)", diff --git a/tests/components/miele/snapshots/test_sensor.ambr b/tests/components/miele/snapshots/test_sensor.ambr index a80757fa90e6e..bf685098aa28d 100644 --- a/tests/components/miele/snapshots/test_sensor.ambr +++ b/tests/components/miele/snapshots/test_sensor.ambr @@ -877,112 +877,6 @@ 'state': '20', }) # --- -# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_degreasing_cycles-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.miele_test_degreasing_cycles', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Degreasing cycles', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Degreasing cycles', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'degreasing_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', - 'unit_of_measurement': None, - }) -# --- -# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_degreasing_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Degreasing cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_descaling_cycles-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.miele_test_descaling_cycles', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Descaling cycles', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Descaling cycles', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'descaling_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', - 'unit_of_measurement': None, - }) -# --- -# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_descaling_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Descaling cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_descaling_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- # name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.hob_with_extraction-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -2168,6 +2062,112 @@ 'state': 'off', }) # --- +# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_degreasing_cycles-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.miele_test_degreasing_cycles', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Degreasing cycles', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Degreasing cycles', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'degreasing_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'unit_of_measurement': None, + }) +# --- +# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_degreasing_cycles-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Degreasing cycles', + : , + }), + 'context': , + 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_descaling_cycles-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.miele_test_descaling_cycles', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Descaling cycles', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Descaling cycles', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'descaling_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', + 'unit_of_measurement': None, + }) +# --- +# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_descaling_cycles-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Descaling cycles', + : , + }), + 'context': , + 'entity_id': 'sensor.miele_test_descaling_cycles', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- # name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.miele_test_milk_pipework_cleaning_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -2578,14 +2578,34 @@ 'state': '20', }) # --- -# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_degreasing_cycles-entry] +# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.fridge_freezer-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : , + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -2593,8 +2613,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'entity_category': None, + 'entity_id': 'sensor.fridge_freezer', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2602,148 +2622,22 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Degreasing cycles', + 'object_id_base': None, 'options': dict({ }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Degreasing cycles', + 'original_device_class': , + 'original_icon': 'mdi:fridge-outline', + 'original_name': None, 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'degreasing_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'translation_key': 'status', + 'unique_id': 'DummyAppliance_Fridge_Freezer-state_status', 'unit_of_measurement': None, }) # --- -# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_degreasing_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Degreasing cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_descaling_cycles-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.miele_test_descaling_cycles', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Descaling cycles', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Descaling cycles', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'descaling_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', - 'unit_of_measurement': None, - }) -# --- -# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_descaling_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Descaling cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_descaling_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.fridge_freezer-entry] - EntityRegistryEntrySnapshot({ - 'aliases': list([ - None, - ]), - 'area_id': None, - 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.fridge_freezer', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': 'mdi:fridge-outline', - 'original_name': None, - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'DummyAppliance_Fridge_Freezer-state_status', - 'unit_of_measurement': None, - }) -# --- -# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.fridge_freezer-state] +# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.fridge_freezer-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : 'enum', @@ -3048,6 +2942,112 @@ 'state': '-18.0', }) # --- +# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_degreasing_cycles-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.miele_test_degreasing_cycles', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Degreasing cycles', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Degreasing cycles', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'degreasing_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'unit_of_measurement': None, + }) +# --- +# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_degreasing_cycles-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Degreasing cycles', + : , + }), + 'context': , + 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_descaling_cycles-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.miele_test_descaling_cycles', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Descaling cycles', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Descaling cycles', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'descaling_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', + 'unit_of_measurement': None, + }) +# --- +# name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_descaling_cycles-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Descaling cycles', + : , + }), + 'context': , + 'entity_id': 'sensor.miele_test_descaling_cycles', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- # name: test_fridge_freezer_sensor_states[platforms0-fridge_freezer.json][sensor.miele_test_milk_pipework_cleaning_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -3458,140 +3458,34 @@ 'state': '20', }) # --- -# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_degreasing_cycles-entry] +# name: test_hob_sensor_states[platforms0-hob.json][sensor.kdma7774_app2_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': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Degreasing cycles', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Degreasing cycles', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'degreasing_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', - 'unit_of_measurement': None, - }) -# --- -# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_degreasing_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Degreasing cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_descaling_cycles-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.miele_test_descaling_cycles', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Descaling cycles', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Descaling cycles', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'descaling_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', - 'unit_of_measurement': None, - }) -# --- -# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_descaling_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Descaling cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_descaling_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_hob_sensor_states[platforms0-hob.json][sensor.kdma7774_app2_2-entry] - EntityRegistryEntrySnapshot({ - 'aliases': list([ - None, - ]), - 'area_id': None, - 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -4159,6 +4053,112 @@ 'state': 'plate_step_boost', }) # --- +# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_degreasing_cycles-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.miele_test_degreasing_cycles', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Degreasing cycles', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Degreasing cycles', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'degreasing_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'unit_of_measurement': None, + }) +# --- +# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_degreasing_cycles-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Degreasing cycles', + : , + }), + 'context': , + 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_descaling_cycles-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.miele_test_descaling_cycles', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Descaling cycles', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Descaling cycles', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'descaling_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', + 'unit_of_measurement': None, + }) +# --- +# name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_descaling_cycles-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Descaling cycles', + : , + }), + 'context': , + 'entity_id': 'sensor.miele_test_descaling_cycles', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- # name: test_hob_sensor_states[platforms0-hob.json][sensor.miele_test_milk_pipework_cleaning_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -4569,140 +4569,34 @@ 'state': '20', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_degreasing_cycles-entry] +# name: test_sensor_states[platforms0][sensor.freezer-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.miele_test_degreasing_cycles', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Degreasing cycles', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Degreasing cycles', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'degreasing_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensor_states[platforms0][sensor.miele_test_degreasing_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Degreasing cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_sensor_states[platforms0][sensor.miele_test_descaling_cycles-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.miele_test_descaling_cycles', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Descaling cycles', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Descaling cycles', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'descaling_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensor_states[platforms0][sensor.miele_test_descaling_cycles-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Descaling cycles', - : , - }), - 'context': , - 'entity_id': 'sensor.miele_test_descaling_cycles', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '1', - }) -# --- -# name: test_sensor_states[platforms0][sensor.freezer-entry] - EntityRegistryEntrySnapshot({ - 'aliases': list([ - None, - ]), - 'area_id': None, - 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -4923,7 +4817,7 @@ 'state': 'off', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-entry] +# name: test_sensor_states[platforms0][sensor.miele_test_degreasing_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -4939,7 +4833,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', + 'entity_id': 'sensor.miele_test_degreasing_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4947,63 +4841,43 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Milk pipework cleaning cycles', + 'object_id_base': 'Degreasing cycles', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Milk pipework cleaning cycles', + 'original_name': 'Degreasing cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'milk_cleaning_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-milk_cleaning_counter', + 'translation_key': 'degreasing_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-state] +# name: test_sensor_states[platforms0][sensor.miele_test_degreasing_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Milk pipework cleaning cycles', + : 'Miele test Degreasing cycles', : , }), 'context': , - 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', + 'entity_id': 'sensor.miele_test_degreasing_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '3', + 'state': '2', }) # --- -# name: test_sensor_states[platforms0][sensor.oven-entry] +# name: test_sensor_states[platforms0][sensor.miele_test_descaling_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + : , }), 'config_entry_id': , 'config_subentry_id': , @@ -5011,8 +4885,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.oven', + 'entity_category': , + 'entity_id': 'sensor.miele_test_descaling_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5020,65 +4894,43 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'Descaling cycles', 'options': dict({ }), - 'original_device_class': , - 'original_icon': 'mdi:chef-hat', - 'original_name': None, + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Descaling cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'DummyAppliance_12-state_status', + 'translation_key': 'descaling_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.oven-state] +# name: test_sensor_states[platforms0][sensor.miele_test_descaling_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Oven', - : 'mdi:chef-hat', - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + : 'Miele test Descaling cycles', + : , }), 'context': , - 'entity_id': 'sensor.oven', + 'entity_id': 'sensor.miele_test_descaling_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'in_use', + 'state': '1', }) # --- -# name: test_sensor_states[platforms0][sensor.oven_core_temperature-entry] +# name: test_sensor_states[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : , + : , }), 'config_entry_id': , 'config_subentry_id': , @@ -5086,8 +4938,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.oven_core_temperature', + 'entity_category': , + 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5095,41 +4947,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Core temperature', + 'object_id_base': 'Milk pipework cleaning cycles', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Core temperature', + 'original_name': 'Milk pipework cleaning cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'core_temperature', - 'unique_id': 'DummyAppliance_12-state_core_temperature', - 'unit_of_measurement': , + 'translation_key': 'milk_cleaning_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-milk_cleaning_counter', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.oven_core_temperature-state] +# name: test_sensor_states[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'temperature', - : 'Oven Core temperature', - : , - : , + : 'Miele test Milk pipework cleaning cycles', + : , }), 'context': , - 'entity_id': 'sensor.oven_core_temperature', + 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '22.0', + 'state': '3', }) # --- -# name: test_sensor_states[platforms0][sensor.oven_elapsed_time-entry] +# name: test_sensor_states[platforms0][sensor.miele_test_powerdisk_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -5143,7 +4990,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.oven_elapsed_time', + 'entity_id': 'sensor.miele_test_powerdisk_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5151,7 +4998,415 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Elapsed time', + 'object_id_base': 'PowerDisk level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'PowerDisk level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'power_disk_level', + 'unique_id': 'Dummy_Appliance_5-power_disk_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_powerdisk_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test PowerDisk level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_powerdisk_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '80.5', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_rinse_aid_level-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.miele_test_rinse_aid_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Rinse aid level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Rinse aid level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'rinse_aid_level', + 'unique_id': 'Dummy_Appliance_5-rinse_aid_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_rinse_aid_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Rinse aid level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_rinse_aid_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '25', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_salt_level-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.miele_test_salt_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Salt level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Salt level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'salt_level', + 'unique_id': 'Dummy_Appliance_5-salt_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_salt_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Salt level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_salt_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '75', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_twindos_1_level-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.miele_test_twindos_1_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'TwinDos 1 level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 1 level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'twin_dos_1_level', + 'unique_id': 'DummyWasher-twin_dos_1_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_twindos_1_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test TwinDos 1 level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_twindos_1_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '63', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_twindos_2_level-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.miele_test_twindos_2_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'TwinDos 2 level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 2 level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'twin_dos_2_level', + 'unique_id': 'DummyWasher-twin_dos_2_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor_states[platforms0][sensor.miele_test_twindos_2_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test TwinDos 2 level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_twindos_2_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '20', + }) +# --- +# name: test_sensor_states[platforms0][sensor.oven-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.oven', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:chef-hat', + 'original_name': None, + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'DummyAppliance_12-state_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_states[platforms0][sensor.oven-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Oven', + : 'mdi:chef-hat', + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), + 'context': , + 'entity_id': 'sensor.oven', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'in_use', + }) +# --- +# name: test_sensor_states[platforms0][sensor.oven_core_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': None, + 'entity_id': 'sensor.oven_core_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Core temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Core temperature', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'core_temperature', + 'unique_id': 'DummyAppliance_12-state_core_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_states[platforms0][sensor.oven_core_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Oven Core temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.oven_core_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '22.0', + }) +# --- +# name: test_sensor_states[platforms0][sensor.oven_elapsed_time-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.oven_elapsed_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Elapsed time', 'options': dict({ 'sensor': dict({ 'suggested_display_precision': 2, @@ -6863,57 +7118,6 @@ 'state': '19.54', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_powerdisk_level-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.miele_test_powerdisk_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'PowerDisk level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'PowerDisk level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'power_disk_level', - 'unique_id': 'Dummy_Appliance_5-power_disk_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensor_states[platforms0][sensor.miele_test_powerdisk_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test PowerDisk level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_powerdisk_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '80.5', - }) -# --- # name: test_sensor_states[platforms0][sensor.refrigerator-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -7067,7 +7271,102 @@ 'state': '4.0', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_rinse_aid_level-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.washing_machine', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:washing-machine', + 'original_name': None, + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'Dummy_Appliance_3-state_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_states[platforms0][sensor.washing_machine-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Washing machine', + : 'mdi:washing-machine', + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), + 'context': , + 'entity_id': 'sensor.washing_machine', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_sensor_states[platforms0][sensor.washing_machine_elapsed_time-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7081,7 +7380,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_rinse_aid_level', + 'entity_id': 'sensor.washing_machine_elapsed_time', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7089,42 +7388,48 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Rinse aid level', + 'object_id_base': 'Elapsed time', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Rinse aid level', + 'original_name': 'Elapsed time', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'rinse_aid_level', - 'unique_id': 'Dummy_Appliance_5-rinse_aid_level', - 'unit_of_measurement': '%', + 'translation_key': 'elapsed_time', + 'unique_id': 'Dummy_Appliance_3-state_elapsed_time', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_rinse_aid_level-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_elapsed_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Rinse aid level', - : '%', + : 'duration', + : 'Washing machine Elapsed time', + : , }), 'context': , - 'entity_id': 'sensor.miele_test_rinse_aid_level', + 'entity_id': 'sensor.washing_machine_elapsed_time', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '25', + 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_salt_level-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_energy_consumption-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -7132,7 +7437,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_salt_level', + 'entity_id': 'sensor.washing_machine_energy_consumption', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7140,36 +7445,41 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Salt level', + 'object_id_base': 'Energy consumption', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Salt level', + 'original_name': 'Energy consumption', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'salt_level', - 'unique_id': 'Dummy_Appliance_5-salt_level', - 'unit_of_measurement': '%', + 'translation_key': 'energy_consumption', + 'unique_id': 'Dummy_Appliance_3-current_energy_consumption', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_salt_level-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_energy_consumption-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Salt level', - : '%', + : 'energy', + : 'Washing machine Energy consumption', + : , + : , }), 'context': , - 'entity_id': 'sensor.miele_test_salt_level', + 'entity_id': 'sensor.washing_machine_energy_consumption', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '75', + 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_twindos_1_level-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_energy_forecast-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7183,7 +7493,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_twindos_1_level', + 'entity_id': 'sensor.washing_machine_energy_forecast', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7191,36 +7501,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 1 level', + 'object_id_base': 'Energy forecast', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'TwinDos 1 level', + 'original_name': 'Energy forecast', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_1_level', - 'unique_id': 'DummyWasher-twin_dos_1_level', + 'translation_key': 'energy_forecast', + 'unique_id': 'Dummy_Appliance_3-energy_forecast', 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_twindos_1_level-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_energy_forecast-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 1 level', + : 'Washing machine Energy forecast', : '%', }), 'context': , - 'entity_id': 'sensor.miele_test_twindos_1_level', + 'entity_id': 'sensor.washing_machine_energy_forecast', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '63', + 'state': '10.0', }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_twindos_2_level-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_finish-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7234,7 +7544,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_twindos_2_level', + 'entity_id': 'sensor.washing_machine_finish', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7242,36 +7552,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 2 level', + 'object_id_base': 'Finish', 'options': dict({ }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'TwinDos 2 level', + 'original_name': 'Finish', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_2_level', - 'unique_id': 'DummyWasher-twin_dos_2_level', - 'unit_of_measurement': '%', + 'translation_key': 'finish', + 'unique_id': 'Dummy_Appliance_3-state_finish_timestamp', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.miele_test_twindos_2_level-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_finish-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 2 level', - : '%', + : 'timestamp', + : 'Washing machine Finish', }), 'context': , - 'entity_id': 'sensor.miele_test_twindos_2_level', + 'entity_id': 'sensor.washing_machine_finish', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '20', + 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_program-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7279,25 +7589,55 @@ 'area_id': None, 'capabilities': dict({ : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', + 'automatic_plus', + 'bed_linen', + 'clean_machine', + 'cool_air', + 'cottonrepair', + 'cottons', + 'cottons_eco', + 'cottons_hygiene', + 'curtains', + 'dark_garments', + 'dark_jeans', + 'delicates', + 'denim', + 'down_duvets', + 'down_filled_items', + 'drain_spin', + 'easy_care', + 'eco_40_60', + 'express_20', + 'first_wash', + 'freshen_up', + 'game_pieces', + 'minimum_iron', + 'no_program', + 'normal', + 'outdoor_garments', + 'outerwear', + 'pillows', + 'powerfresh', + 'pre_ironing', + 'proofing', + 'quick_intense', + 'quick_power_wash', + 'rinse', + 'rinse_out_lint', + 'separate_rinse_starch', + 'shirts', + 'silks', + 'smartmatic', + 'sportswear', + 'starch', + 'steam_care', + 'stuffed_toys', + 'table_linen', + 'towels', + 'trainers', + 'trainers_refresh', + 'warm_air', + 'woollens', ]), }), 'config_entry_id': , @@ -7307,7 +7647,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.washing_machine', + 'entity_id': 'sensor.washing_machine_program', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7315,72 +7655,127 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'Program', 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:washing-machine', - 'original_name': None, + 'original_icon': None, + 'original_name': 'Program', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'Dummy_Appliance_3-state_status', + 'translation_key': 'program_id', + 'unique_id': 'Dummy_Appliance_3-state_program_id', 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_program-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : 'enum', - : 'Washing machine', - : 'mdi:washing-machine', + : 'Washing machine Program', : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', + 'automatic_plus', + 'bed_linen', + 'clean_machine', + 'cool_air', + 'cottonrepair', + 'cottons', + 'cottons_eco', + 'cottons_hygiene', + 'curtains', + 'dark_garments', + 'dark_jeans', + 'delicates', + 'denim', + 'down_duvets', + 'down_filled_items', + 'drain_spin', + 'easy_care', + 'eco_40_60', + 'express_20', + 'first_wash', + 'freshen_up', + 'game_pieces', + 'minimum_iron', + 'no_program', + 'normal', + 'outdoor_garments', + 'outerwear', + 'pillows', + 'powerfresh', + 'pre_ironing', + 'proofing', + 'quick_intense', + 'quick_power_wash', + 'rinse', + 'rinse_out_lint', + 'separate_rinse_starch', + 'shirts', + 'silks', + 'smartmatic', + 'sportswear', + 'starch', + 'steam_care', + 'stuffed_toys', + 'table_linen', + 'towels', + 'trainers', + 'trainers_refresh', + 'warm_air', + 'woollens', ]), }), 'context': , - 'entity_id': 'sensor.washing_machine', + 'entity_id': 'sensor.washing_machine_program', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'off', + 'state': 'no_program', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_elapsed_time-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_program_phase-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : list([ + 'anti_crease', + 'automatic_start', + 'cancelled', + 'cleaning', + 'cooling_down', + 'disinfecting', + 'drain', + 'drying', + 'finished', + 'flex_load_active', + 'freshen_up_and_moisten', + 'hygiene', + 'main_wash', + 'not_running', + 'paused', + 'pre_wash', + 'rinse', + 'rinse_hold', + 'soak', + 'spin', + 'starch_stop', + 'steam_smoothing', + 'venting', + ]), + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_elapsed_time', + 'entity_category': None, + 'entity_id': 'sensor.washing_machine_program_phase', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7388,47 +7783,73 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Elapsed time', + 'object_id_base': 'Program phase', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Elapsed time', + 'original_name': 'Program phase', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'elapsed_time', - 'unique_id': 'Dummy_Appliance_3-state_elapsed_time', - 'unit_of_measurement': , + 'translation_key': 'program_phase', + 'unique_id': 'Dummy_Appliance_3-state_program_phase', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_elapsed_time-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_program_phase-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'duration', - : 'Washing machine Elapsed time', - : , + : 'enum', + : 'Washing machine Program phase', + : list([ + 'anti_crease', + 'automatic_start', + 'cancelled', + 'cleaning', + 'cooling_down', + 'disinfecting', + 'drain', + 'drying', + 'finished', + 'flex_load_active', + 'freshen_up_and_moisten', + 'hygiene', + 'main_wash', + 'not_running', + 'paused', + 'pre_wash', + 'rinse', + 'rinse_hold', + 'soak', + 'spin', + 'starch_stop', + 'steam_smoothing', + 'venting', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_elapsed_time', + 'entity_id': 'sensor.washing_machine_program_phase', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'not_running', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_energy_consumption-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_program_type-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : , + : list([ + 'automatic_program', + 'maintenance_program', + 'normal_operation_mode', + 'own_program', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -7437,7 +7858,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_energy_consumption', + 'entity_id': 'sensor.washing_machine_program_type', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7445,41 +7866,42 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Energy consumption', + 'object_id_base': 'Program type', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy consumption', + 'original_name': 'Program type', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'energy_consumption', - 'unique_id': 'Dummy_Appliance_3-current_energy_consumption', - 'unit_of_measurement': , + 'translation_key': 'program_type', + 'unique_id': 'Dummy_Appliance_3-state_program_type', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_energy_consumption-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_program_type-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'energy', - : 'Washing machine Energy consumption', - : , - : , + : 'enum', + : 'Washing machine Program type', + : list([ + 'automatic_program', + 'maintenance_program', + 'normal_operation_mode', + 'own_program', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_energy_consumption', + 'entity_id': 'sensor.washing_machine_program_type', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'normal_operation_mode', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_energy_forecast-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_remaining_time-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7493,7 +7915,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_energy_forecast', + 'entity_id': 'sensor.washing_machine_remaining_time', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7501,36 +7923,40 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Energy forecast', + 'object_id_base': 'Remaining time', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy forecast', + 'original_name': 'Remaining time', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'energy_forecast', - 'unique_id': 'Dummy_Appliance_3-energy_forecast', - 'unit_of_measurement': '%', + 'translation_key': 'remaining_time', + 'unique_id': 'Dummy_Appliance_3-state_remaining_time', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_energy_forecast-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_remaining_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine Energy forecast', - : '%', + : 'duration', + : 'Washing machine Remaining time', + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_energy_forecast', + 'entity_id': 'sensor.washing_machine_remaining_time', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '10.0', + 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_finish-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_spin_speed-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7544,7 +7970,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_finish', + 'entity_id': 'sensor.washing_machine_spin_speed', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7552,228 +7978,101 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Finish', + 'object_id_base': 'Spin speed', 'options': dict({ }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Finish', + 'original_name': 'Spin speed', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'finish', - 'unique_id': 'Dummy_Appliance_3-state_finish_timestamp', - 'unit_of_measurement': None, + 'translation_key': 'spin_speed', + 'unique_id': 'Dummy_Appliance_3-state_spinning_speed', + 'unit_of_measurement': 'rpm', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_finish-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_spin_speed-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'timestamp', - : 'Washing machine Finish', + : 'Washing machine Spin speed', + : 'rpm', }), 'context': , - 'entity_id': 'sensor.washing_machine_finish', + 'entity_id': 'sensor.washing_machine_spin_speed', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_program-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_start-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'automatic_plus', - 'bed_linen', - 'clean_machine', - 'cool_air', - 'cottonrepair', - 'cottons', - 'cottons_eco', - 'cottons_hygiene', - 'curtains', - 'dark_garments', - 'dark_jeans', - 'delicates', - 'denim', - 'down_duvets', - 'down_filled_items', - 'drain_spin', - 'easy_care', - 'eco_40_60', - 'express_20', - 'first_wash', - 'freshen_up', - 'game_pieces', - 'minimum_iron', - 'no_program', - 'normal', - 'outdoor_garments', - 'outerwear', - 'pillows', - 'powerfresh', - 'pre_ironing', - 'proofing', - 'quick_intense', - 'quick_power_wash', - 'rinse', - 'rinse_out_lint', - 'separate_rinse_starch', - 'shirts', - 'silks', - 'smartmatic', - 'sportswear', - 'starch', - 'steam_care', - 'stuffed_toys', - 'table_linen', - 'trainers', - 'trainers_refresh', - 'warm_air', - 'woollens', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.washing_machine_program', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Program', - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Program', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'program_id', - 'unique_id': 'Dummy_Appliance_3-state_program_id', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensor_states[platforms0][sensor.washing_machine_program-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'enum', - : 'Washing machine Program', - : list([ - 'automatic_plus', - 'bed_linen', - 'clean_machine', - 'cool_air', - 'cottonrepair', - 'cottons', - 'cottons_eco', - 'cottons_hygiene', - 'curtains', - 'dark_garments', - 'dark_jeans', - 'delicates', - 'denim', - 'down_duvets', - 'down_filled_items', - 'drain_spin', - 'easy_care', - 'eco_40_60', - 'express_20', - 'first_wash', - 'freshen_up', - 'game_pieces', - 'minimum_iron', - 'no_program', - 'normal', - 'outdoor_garments', - 'outerwear', - 'pillows', - 'powerfresh', - 'pre_ironing', - 'proofing', - 'quick_intense', - 'quick_power_wash', - 'rinse', - 'rinse_out_lint', - 'separate_rinse_starch', - 'shirts', - 'silks', - 'smartmatic', - 'sportswear', - 'starch', - 'steam_care', - 'stuffed_toys', - 'table_linen', - 'trainers', - 'trainers_refresh', - 'warm_air', - 'woollens', - ]), + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.washing_machine_start', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Start', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Start', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'start', + 'unique_id': 'Dummy_Appliance_3-state_start_timestamp', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_states[platforms0][sensor.washing_machine_start-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Washing machine Start', }), 'context': , - 'entity_id': 'sensor.washing_machine_program', + 'entity_id': 'sensor.washing_machine_start', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'no_program', + 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_program_phase-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_start_in-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'anti_crease', - 'automatic_start', - 'cancelled', - 'cleaning', - 'cooling_down', - 'disinfecting', - 'drain', - 'drying', - 'finished', - 'flex_load_active', - 'freshen_up_and_moisten', - 'hygiene', - 'main_wash', - 'not_running', - 'paused', - 'pre_wash', - 'rinse', - 'rinse_hold', - 'soak', - 'spin', - 'starch_stop', - 'steam_smoothing', - 'venting', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.washing_machine_program_phase', + 'entity_category': , + 'entity_id': 'sensor.washing_machine_start_in', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7781,73 +8080,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Program phase', + 'object_id_base': 'Start in', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Program phase', + 'original_name': 'Start in', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'program_phase', - 'unique_id': 'Dummy_Appliance_3-state_program_phase', - 'unit_of_measurement': None, + 'translation_key': 'start_time', + 'unique_id': 'Dummy_Appliance_3-state_start_time', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_program_phase-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_start_in-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Washing machine Program phase', - : list([ - 'anti_crease', - 'automatic_start', - 'cancelled', - 'cleaning', - 'cooling_down', - 'disinfecting', - 'drain', - 'drying', - 'finished', - 'flex_load_active', - 'freshen_up_and_moisten', - 'hygiene', - 'main_wash', - 'not_running', - 'paused', - 'pre_wash', - 'rinse', - 'rinse_hold', - 'soak', - 'spin', - 'starch_stop', - 'steam_smoothing', - 'venting', - ]), + : 'duration', + : 'Washing machine Start in', + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_program_phase', + 'entity_id': 'sensor.washing_machine_start_in', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'not_running', + 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_program_type-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_target_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : list([ - 'automatic_program', - 'maintenance_program', - 'normal_operation_mode', - 'own_program', - ]), + : , }), 'config_entry_id': , 'config_subentry_id': , @@ -7855,8 +8131,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_program_type', + 'entity_category': None, + 'entity_id': 'sensor.washing_machine_target_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7864,42 +8140,41 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Program type', + 'object_id_base': 'Target temperature', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Program type', + 'original_name': 'Target temperature', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'program_type', - 'unique_id': 'Dummy_Appliance_3-state_program_type', - 'unit_of_measurement': None, + 'translation_key': 'target_temperature', + 'unique_id': 'Dummy_Appliance_3-state_target_temperature', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_program_type-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_target_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Washing machine Program type', - : list([ - 'automatic_program', - 'maintenance_program', - 'normal_operation_mode', - 'own_program', - ]), + : 'temperature', + : 'Washing machine Target temperature', + : , + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_program_type', + 'entity_id': 'sensor.washing_machine_target_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'normal_operation_mode', + 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_remaining_time-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_1_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7913,7 +8188,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_remaining_time', + 'entity_id': 'sensor.washing_machine_twindos_1_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7921,40 +8196,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Remaining time', + 'object_id_base': 'TwinDos 1 level', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Remaining time', + 'original_name': 'TwinDos 1 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'remaining_time', - 'unique_id': 'Dummy_Appliance_3-state_remaining_time', - 'unit_of_measurement': , + 'translation_key': 'twin_dos_1_level', + 'unique_id': 'Dummy_Appliance_3-twin_dos_1_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_remaining_time-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_1_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'duration', - : 'Washing machine Remaining time', - : , + : 'Washing machine TwinDos 1 level', + : '%', }), 'context': , - 'entity_id': 'sensor.washing_machine_remaining_time', + 'entity_id': 'sensor.washing_machine_twindos_1_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '63', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_spin_speed-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_2_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -7968,7 +8239,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_spin_speed', + 'entity_id': 'sensor.washing_machine_twindos_2_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7976,42 +8247,44 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Spin speed', + 'object_id_base': 'TwinDos 2 level', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Spin speed', + 'original_name': 'TwinDos 2 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'spin_speed', - 'unique_id': 'Dummy_Appliance_3-state_spinning_speed', - 'unit_of_measurement': 'rpm', + 'translation_key': 'twin_dos_2_level', + 'unique_id': 'Dummy_Appliance_3-twin_dos_2_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_spin_speed-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_2_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine Spin speed', - : 'rpm', + : 'Washing machine TwinDos 2 level', + : '%', }), 'context': , - 'entity_id': 'sensor.washing_machine_spin_speed', + 'entity_id': 'sensor.washing_machine_twindos_2_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '20', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_start-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_water_consumption-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -8019,7 +8292,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_start', + 'entity_id': 'sensor.washing_machine_water_consumption', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8027,36 +8300,41 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Start', + 'object_id_base': 'Water consumption', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Start', + 'original_name': 'Water consumption', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'start', - 'unique_id': 'Dummy_Appliance_3-state_start_timestamp', - 'unit_of_measurement': None, + 'translation_key': 'water_consumption', + 'unique_id': 'Dummy_Appliance_3-current_water_consumption', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_start-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_water_consumption-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'timestamp', - : 'Washing machine Start', + : 'water', + : 'Washing machine Water consumption', + : , + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_start', + 'entity_id': 'sensor.washing_machine_water_consumption', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'unknown', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_start_in-entry] +# name: test_sensor_states[platforms0][sensor.washing_machine_water_forecast-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -8070,7 +8348,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_start_in', + 'entity_id': 'sensor.washing_machine_water_forecast', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8078,50 +8356,63 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Start in', + 'object_id_base': 'Water forecast', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Start in', + 'original_name': 'Water forecast', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'start_time', - 'unique_id': 'Dummy_Appliance_3-state_start_time', - 'unit_of_measurement': , + 'translation_key': 'water_forecast', + 'unique_id': 'Dummy_Appliance_3-water_forecast', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_start_in-state] +# name: test_sensor_states[platforms0][sensor.washing_machine_water_forecast-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'duration', - : 'Washing machine Start in', - : , + : 'Washing machine Water forecast', + : '%', }), 'context': , - 'entity_id': 'sensor.washing_machine_start_in', + 'entity_id': 'sensor.washing_machine_water_forecast', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '0.0', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_target_temperature-entry] +# name: test_sensor_states_api_push[platforms0][sensor.freezer-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : , + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -8130,7 +8421,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.washing_machine_target_temperature', + 'entity_id': 'sensor.freezer', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8138,55 +8429,74 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Target temperature', + 'object_id_base': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Target temperature', + 'original_device_class': , + 'original_icon': 'mdi:fridge-industrial-outline', + 'original_name': None, 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'target_temperature', - 'unique_id': 'Dummy_Appliance_3-state_target_temperature', - 'unit_of_measurement': , + 'translation_key': 'status', + 'unique_id': 'Dummy_Appliance_1-state_status', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_target_temperature-state] +# name: test_sensor_states_api_push[platforms0][sensor.freezer-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'temperature', - : 'Washing machine Target temperature', - : , - : , + : 'enum', + : 'Freezer', + : 'mdi:fridge-industrial-outline', + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_target_temperature', + 'entity_id': 'sensor.freezer', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'in_use', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_1_level-entry] +# name: test_sensor_states_api_push[platforms0][sensor.freezer_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_twindos_1_level', + 'entity_category': None, + 'entity_id': 'sensor.freezer_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8194,50 +8504,77 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 1 level', + 'object_id_base': 'Temperature', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'TwinDos 1 level', + 'original_name': 'Temperature', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_1_level', - 'unique_id': 'Dummy_Appliance_3-twin_dos_1_level', - 'unit_of_measurement': '%', + 'translation_key': None, + 'unique_id': 'Dummy_Appliance_1-state_temperature_1', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_1_level-state] +# name: test_sensor_states_api_push[platforms0][sensor.freezer_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine TwinDos 1 level', - : '%', + : 'temperature', + : 'Freezer Temperature', + : , + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_twindos_1_level', + 'entity_id': 'sensor.freezer_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '63', + 'state': '-18.0', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_2_level-entry] +# name: test_sensor_states_api_push[platforms0][sensor.hood-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_twindos_2_level', + 'entity_category': None, + 'entity_id': 'sensor.hood', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8245,36 +8582,58 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 2 level', + 'object_id_base': None, 'options': dict({ }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 2 level', + 'original_device_class': , + 'original_icon': 'mdi:turbine', + 'original_name': None, 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_2_level', - 'unique_id': 'Dummy_Appliance_3-twin_dos_2_level', - 'unit_of_measurement': '%', + 'translation_key': 'status', + 'unique_id': 'DummyAppliance_18-state_status', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_twindos_2_level-state] +# name: test_sensor_states_api_push[platforms0][sensor.hood-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine TwinDos 2 level', - : '%', + : 'enum', + : 'Hood', + : 'mdi:turbine', + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_twindos_2_level', + 'entity_id': 'sensor.hood', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '20', + 'state': 'off', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_water_consumption-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_degreasing_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -8290,7 +8649,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_water_consumption', + 'entity_id': 'sensor.miele_test_degreasing_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8298,47 +8657,44 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Water consumption', + 'object_id_base': 'Degreasing cycles', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Water consumption', + 'original_name': 'Degreasing cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'water_consumption', - 'unique_id': 'Dummy_Appliance_3-current_water_consumption', - 'unit_of_measurement': , + 'translation_key': 'degreasing_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_water_consumption-state] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_degreasing_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'water', - : 'Washing machine Water consumption', + : 'Miele test Degreasing cycles', : , - : , }), 'context': , - 'entity_id': 'sensor.washing_machine_water_consumption', + 'entity_id': 'sensor.miele_test_degreasing_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '2', }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_water_forecast-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_descaling_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -8346,7 +8702,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_water_forecast', + 'entity_id': 'sensor.miele_test_descaling_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8354,36 +8710,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Water forecast', + 'object_id_base': 'Descaling cycles', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Water forecast', + 'original_name': 'Descaling cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'water_forecast', - 'unique_id': 'Dummy_Appliance_3-water_forecast', - 'unit_of_measurement': '%', + 'translation_key': 'descaling_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states[platforms0][sensor.washing_machine_water_forecast-state] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_descaling_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine Water forecast', - : '%', + : 'Miele test Descaling cycles', + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_water_forecast', + 'entity_id': 'sensor.miele_test_descaling_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '1', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_degreasing_cycles-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -8399,7 +8755,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8407,44 +8763,42 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Degreasing cycles', + 'object_id_base': 'Milk pipework cleaning cycles', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Degreasing cycles', + 'original_name': 'Milk pipework cleaning cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'degreasing_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'translation_key': 'milk_cleaning_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-milk_cleaning_counter', 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_degreasing_cycles-state] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Degreasing cycles', + : 'Miele test Milk pipework cleaning cycles', : , }), 'context': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2', + 'state': '3', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_descaling_cycles-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_powerdisk_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -8452,7 +8806,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_descaling_cycles', + 'entity_id': 'sensor.miele_test_powerdisk_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8460,72 +8814,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Descaling cycles', + 'object_id_base': 'PowerDisk level', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Descaling cycles', + 'original_name': 'PowerDisk level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'descaling_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', - 'unit_of_measurement': None, + 'translation_key': 'power_disk_level', + 'unique_id': 'Dummy_Appliance_5-power_disk_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_descaling_cycles-state] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_powerdisk_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Descaling cycles', - : , + : 'Miele test PowerDisk level', + : '%', }), 'context': , - 'entity_id': 'sensor.miele_test_descaling_cycles', + 'entity_id': 'sensor.miele_test_powerdisk_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1', + 'state': '80.5', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.freezer-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_rinse_aid_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.freezer', + 'entity_category': , + 'entity_id': 'sensor.miele_test_rinse_aid_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8533,74 +8865,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'Rinse aid level', 'options': dict({ }), - 'original_device_class': , - 'original_icon': 'mdi:fridge-industrial-outline', - 'original_name': None, + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Rinse aid level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'Dummy_Appliance_1-state_status', - 'unit_of_measurement': None, + 'translation_key': 'rinse_aid_level', + 'unique_id': 'Dummy_Appliance_5-rinse_aid_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.freezer-state] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_rinse_aid_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Freezer', - : 'mdi:fridge-industrial-outline', - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + : 'Miele test Rinse aid level', + : '%', }), 'context': , - 'entity_id': 'sensor.freezer', + 'entity_id': 'sensor.miele_test_rinse_aid_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'in_use', + 'state': '25', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.freezer_temperature-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_salt_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.freezer_temperature', + 'entity_category': , + 'entity_id': 'sensor.miele_test_salt_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8608,77 +8916,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Temperature', + 'object_id_base': 'Salt level', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Salt level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'Dummy_Appliance_1-state_temperature_1', - 'unit_of_measurement': , + 'translation_key': 'salt_level', + 'unique_id': 'Dummy_Appliance_5-salt_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.freezer_temperature-state] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_salt_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'temperature', - : 'Freezer Temperature', - : , - : , + : 'Miele test Salt level', + : '%', }), 'context': , - 'entity_id': 'sensor.freezer_temperature', + 'entity_id': 'sensor.miele_test_salt_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-18.0', + 'state': '75', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.hood-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_1_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.hood', + 'entity_category': , + 'entity_id': 'sensor.miele_test_twindos_1_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8686,66 +8967,42 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'TwinDos 1 level', 'options': dict({ }), - 'original_device_class': , - 'original_icon': 'mdi:turbine', - 'original_name': None, + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 1 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'DummyAppliance_18-state_status', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensor_states_api_push[platforms0][sensor.hood-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'enum', - : 'Hood', - : 'mdi:turbine', - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + 'supported_features': 0, + 'translation_key': 'twin_dos_1_level', + 'unique_id': 'DummyWasher-twin_dos_1_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_1_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test TwinDos 1 level', + : '%', }), 'context': , - 'entity_id': 'sensor.hood', + 'entity_id': 'sensor.miele_test_twindos_1_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'off', + 'state': '63', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-entry] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_2_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -8753,7 +9010,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', + 'entity_id': 'sensor.miele_test_twindos_2_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8761,33 +9018,33 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Milk pipework cleaning cycles', + 'object_id_base': 'TwinDos 2 level', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Milk pipework cleaning cycles', + 'original_name': 'TwinDos 2 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'milk_cleaning_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-milk_cleaning_counter', - 'unit_of_measurement': None, + 'translation_key': 'twin_dos_2_level', + 'unique_id': 'DummyWasher-twin_dos_2_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-state] +# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_2_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Milk pipework cleaning cycles', - : , + : 'Miele test TwinDos 2 level', + : '%', }), 'context': , - 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', + 'entity_id': 'sensor.miele_test_twindos_2_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '3', + 'state': '20', }) # --- # name: test_sensor_states_api_push[platforms0][sensor.oven-entry] @@ -10677,57 +10934,6 @@ 'state': '19.54', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_powerdisk_level-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.miele_test_powerdisk_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'PowerDisk level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'PowerDisk level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'power_disk_level', - 'unique_id': 'Dummy_Appliance_5-power_disk_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_powerdisk_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test PowerDisk level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_powerdisk_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '80.5', - }) -# --- # name: test_sensor_states_api_push[platforms0][sensor.refrigerator-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -10881,7 +11087,102 @@ 'state': '4.0', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_rinse_aid_level-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.washing_machine', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:washing-machine', + 'original_name': None, + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'Dummy_Appliance_3-state_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Washing machine', + : 'mdi:washing-machine', + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), + 'context': , + 'entity_id': 'sensor.washing_machine', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_elapsed_time-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -10895,7 +11196,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_rinse_aid_level', + 'entity_id': 'sensor.washing_machine_elapsed_time', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -10903,42 +11204,48 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Rinse aid level', + 'object_id_base': 'Elapsed time', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Rinse aid level', + 'original_name': 'Elapsed time', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'rinse_aid_level', - 'unique_id': 'Dummy_Appliance_5-rinse_aid_level', - 'unit_of_measurement': '%', + 'translation_key': 'elapsed_time', + 'unique_id': 'Dummy_Appliance_3-state_elapsed_time', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_rinse_aid_level-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_elapsed_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Rinse aid level', - : '%', + : 'duration', + : 'Washing machine Elapsed time', + : , }), 'context': , - 'entity_id': 'sensor.miele_test_rinse_aid_level', + 'entity_id': 'sensor.washing_machine_elapsed_time', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '25', + 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_salt_level-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_consumption-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -10946,7 +11253,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_salt_level', + 'entity_id': 'sensor.washing_machine_energy_consumption', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -10954,36 +11261,41 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Salt level', + 'object_id_base': 'Energy consumption', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Salt level', + 'original_name': 'Energy consumption', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'salt_level', - 'unique_id': 'Dummy_Appliance_5-salt_level', - 'unit_of_measurement': '%', + 'translation_key': 'energy_consumption', + 'unique_id': 'Dummy_Appliance_3-current_energy_consumption', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_salt_level-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_consumption-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Salt level', - : '%', + : 'energy', + : 'Washing machine Energy consumption', + : , + : , }), 'context': , - 'entity_id': 'sensor.miele_test_salt_level', + 'entity_id': 'sensor.washing_machine_energy_consumption', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '75', + 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_1_level-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_forecast-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -10997,7 +11309,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_twindos_1_level', + 'entity_id': 'sensor.washing_machine_energy_forecast', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11005,36 +11317,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 1 level', + 'object_id_base': 'Energy forecast', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'TwinDos 1 level', + 'original_name': 'Energy forecast', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_1_level', - 'unique_id': 'DummyWasher-twin_dos_1_level', + 'translation_key': 'energy_forecast', + 'unique_id': 'Dummy_Appliance_3-energy_forecast', 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_1_level-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_forecast-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 1 level', + : 'Washing machine Energy forecast', : '%', }), 'context': , - 'entity_id': 'sensor.miele_test_twindos_1_level', + 'entity_id': 'sensor.washing_machine_energy_forecast', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '63', + 'state': '10.0', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_2_level-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_finish-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -11048,7 +11360,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_twindos_2_level', + 'entity_id': 'sensor.washing_machine_finish', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11056,36 +11368,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 2 level', + 'object_id_base': 'Finish', 'options': dict({ }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'TwinDos 2 level', + 'original_name': 'Finish', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_2_level', - 'unique_id': 'DummyWasher-twin_dos_2_level', - 'unit_of_measurement': '%', + 'translation_key': 'finish', + 'unique_id': 'Dummy_Appliance_3-state_finish_timestamp', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.miele_test_twindos_2_level-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_finish-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 2 level', - : '%', + : 'timestamp', + : 'Washing machine Finish', }), 'context': , - 'entity_id': 'sensor.miele_test_twindos_2_level', + 'entity_id': 'sensor.washing_machine_finish', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '20', + 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -11093,25 +11405,55 @@ 'area_id': None, 'capabilities': dict({ : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', + 'automatic_plus', + 'bed_linen', + 'clean_machine', + 'cool_air', + 'cottonrepair', + 'cottons', + 'cottons_eco', + 'cottons_hygiene', + 'curtains', + 'dark_garments', + 'dark_jeans', + 'delicates', + 'denim', + 'down_duvets', + 'down_filled_items', + 'drain_spin', + 'easy_care', + 'eco_40_60', + 'express_20', + 'first_wash', + 'freshen_up', + 'game_pieces', + 'minimum_iron', + 'no_program', + 'normal', + 'outdoor_garments', + 'outerwear', + 'pillows', + 'powerfresh', + 'pre_ironing', + 'proofing', + 'quick_intense', + 'quick_power_wash', + 'rinse', + 'rinse_out_lint', + 'separate_rinse_starch', + 'shirts', + 'silks', + 'smartmatic', + 'sportswear', + 'starch', + 'steam_care', + 'stuffed_toys', + 'table_linen', + 'towels', + 'trainers', + 'trainers_refresh', + 'warm_air', + 'woollens', ]), }), 'config_entry_id': , @@ -11121,7 +11463,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.washing_machine', + 'entity_id': 'sensor.washing_machine_program', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11129,72 +11471,127 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'Program', 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:washing-machine', - 'original_name': None, + 'original_icon': None, + 'original_name': 'Program', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'Dummy_Appliance_3-state_status', + 'translation_key': 'program_id', + 'unique_id': 'Dummy_Appliance_3-state_program_id', 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program-state] StateSnapshot({ 'attributes': ReadOnlyDict({ : 'enum', - : 'Washing machine', - : 'mdi:washing-machine', + : 'Washing machine Program', : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', + 'automatic_plus', + 'bed_linen', + 'clean_machine', + 'cool_air', + 'cottonrepair', + 'cottons', + 'cottons_eco', + 'cottons_hygiene', + 'curtains', + 'dark_garments', + 'dark_jeans', + 'delicates', + 'denim', + 'down_duvets', + 'down_filled_items', + 'drain_spin', + 'easy_care', + 'eco_40_60', + 'express_20', + 'first_wash', + 'freshen_up', + 'game_pieces', + 'minimum_iron', + 'no_program', + 'normal', + 'outdoor_garments', + 'outerwear', + 'pillows', + 'powerfresh', + 'pre_ironing', + 'proofing', + 'quick_intense', + 'quick_power_wash', + 'rinse', + 'rinse_out_lint', + 'separate_rinse_starch', + 'shirts', + 'silks', + 'smartmatic', + 'sportswear', + 'starch', + 'steam_care', + 'stuffed_toys', + 'table_linen', + 'towels', + 'trainers', + 'trainers_refresh', + 'warm_air', + 'woollens', ]), }), 'context': , - 'entity_id': 'sensor.washing_machine', + 'entity_id': 'sensor.washing_machine_program', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'off', + 'state': 'no_program', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_elapsed_time-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_phase-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : list([ + 'anti_crease', + 'automatic_start', + 'cancelled', + 'cleaning', + 'cooling_down', + 'disinfecting', + 'drain', + 'drying', + 'finished', + 'flex_load_active', + 'freshen_up_and_moisten', + 'hygiene', + 'main_wash', + 'not_running', + 'paused', + 'pre_wash', + 'rinse', + 'rinse_hold', + 'soak', + 'spin', + 'starch_stop', + 'steam_smoothing', + 'venting', + ]), + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_elapsed_time', + 'entity_category': None, + 'entity_id': 'sensor.washing_machine_program_phase', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11202,47 +11599,73 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Elapsed time', + 'object_id_base': 'Program phase', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Elapsed time', + 'original_name': 'Program phase', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'elapsed_time', - 'unique_id': 'Dummy_Appliance_3-state_elapsed_time', - 'unit_of_measurement': , + 'translation_key': 'program_phase', + 'unique_id': 'Dummy_Appliance_3-state_program_phase', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_elapsed_time-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_phase-state] StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'duration', - : 'Washing machine Elapsed time', - : , + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Washing machine Program phase', + : list([ + 'anti_crease', + 'automatic_start', + 'cancelled', + 'cleaning', + 'cooling_down', + 'disinfecting', + 'drain', + 'drying', + 'finished', + 'flex_load_active', + 'freshen_up_and_moisten', + 'hygiene', + 'main_wash', + 'not_running', + 'paused', + 'pre_wash', + 'rinse', + 'rinse_hold', + 'soak', + 'spin', + 'starch_stop', + 'steam_smoothing', + 'venting', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_elapsed_time', + 'entity_id': 'sensor.washing_machine_program_phase', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'not_running', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_consumption-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_type-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : , + : list([ + 'automatic_program', + 'maintenance_program', + 'normal_operation_mode', + 'own_program', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -11251,7 +11674,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_energy_consumption', + 'entity_id': 'sensor.washing_machine_program_type', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11259,41 +11682,42 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Energy consumption', + 'object_id_base': 'Program type', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy consumption', + 'original_name': 'Program type', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'energy_consumption', - 'unique_id': 'Dummy_Appliance_3-current_energy_consumption', - 'unit_of_measurement': , + 'translation_key': 'program_type', + 'unique_id': 'Dummy_Appliance_3-state_program_type', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_consumption-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_type-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'energy', - : 'Washing machine Energy consumption', - : , - : , + : 'enum', + : 'Washing machine Program type', + : list([ + 'automatic_program', + 'maintenance_program', + 'normal_operation_mode', + 'own_program', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_energy_consumption', + 'entity_id': 'sensor.washing_machine_program_type', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'normal_operation_mode', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_forecast-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_remaining_time-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -11307,7 +11731,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_energy_forecast', + 'entity_id': 'sensor.washing_machine_remaining_time', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11315,36 +11739,40 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Energy forecast', + 'object_id_base': 'Remaining time', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy forecast', + 'original_name': 'Remaining time', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'energy_forecast', - 'unique_id': 'Dummy_Appliance_3-energy_forecast', - 'unit_of_measurement': '%', + 'translation_key': 'remaining_time', + 'unique_id': 'Dummy_Appliance_3-state_remaining_time', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_forecast-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_remaining_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine Energy forecast', - : '%', + : 'duration', + : 'Washing machine Remaining time', + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_energy_forecast', + 'entity_id': 'sensor.washing_machine_remaining_time', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '10.0', + 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_finish-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_spin_speed-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -11358,7 +11786,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_finish', + 'entity_id': 'sensor.washing_machine_spin_speed', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11366,101 +11794,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Finish', + 'object_id_base': 'Spin speed', 'options': dict({ }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Finish', + 'original_name': 'Spin speed', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'finish', - 'unique_id': 'Dummy_Appliance_3-state_finish_timestamp', - 'unit_of_measurement': None, + 'translation_key': 'spin_speed', + 'unique_id': 'Dummy_Appliance_3-state_spinning_speed', + 'unit_of_measurement': 'rpm', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_finish-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_spin_speed-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'timestamp', - : 'Washing machine Finish', + : 'Washing machine Spin speed', + : 'rpm', }), 'context': , - 'entity_id': 'sensor.washing_machine_finish', + 'entity_id': 'sensor.washing_machine_spin_speed', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'automatic_plus', - 'bed_linen', - 'clean_machine', - 'cool_air', - 'cottonrepair', - 'cottons', - 'cottons_eco', - 'cottons_hygiene', - 'curtains', - 'dark_garments', - 'dark_jeans', - 'delicates', - 'denim', - 'down_duvets', - 'down_filled_items', - 'drain_spin', - 'easy_care', - 'eco_40_60', - 'express_20', - 'first_wash', - 'freshen_up', - 'game_pieces', - 'minimum_iron', - 'no_program', - 'normal', - 'outdoor_garments', - 'outerwear', - 'pillows', - 'powerfresh', - 'pre_ironing', - 'proofing', - 'quick_intense', - 'quick_power_wash', - 'rinse', - 'rinse_out_lint', - 'separate_rinse_starch', - 'shirts', - 'silks', - 'smartmatic', - 'sportswear', - 'starch', - 'steam_care', - 'stuffed_toys', - 'table_linen', - 'trainers', - 'trainers_refresh', - 'warm_air', - 'woollens', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.washing_machine_program', + 'entity_category': , + 'entity_id': 'sensor.washing_machine_start', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11468,126 +11845,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Program', + 'object_id_base': 'Start', 'options': dict({ }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Program', + 'original_name': 'Start', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'program_id', - 'unique_id': 'Dummy_Appliance_3-state_program_id', + 'translation_key': 'start', + 'unique_id': 'Dummy_Appliance_3-state_start_timestamp', 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Washing machine Program', - : list([ - 'automatic_plus', - 'bed_linen', - 'clean_machine', - 'cool_air', - 'cottonrepair', - 'cottons', - 'cottons_eco', - 'cottons_hygiene', - 'curtains', - 'dark_garments', - 'dark_jeans', - 'delicates', - 'denim', - 'down_duvets', - 'down_filled_items', - 'drain_spin', - 'easy_care', - 'eco_40_60', - 'express_20', - 'first_wash', - 'freshen_up', - 'game_pieces', - 'minimum_iron', - 'no_program', - 'normal', - 'outdoor_garments', - 'outerwear', - 'pillows', - 'powerfresh', - 'pre_ironing', - 'proofing', - 'quick_intense', - 'quick_power_wash', - 'rinse', - 'rinse_out_lint', - 'separate_rinse_starch', - 'shirts', - 'silks', - 'smartmatic', - 'sportswear', - 'starch', - 'steam_care', - 'stuffed_toys', - 'table_linen', - 'trainers', - 'trainers_refresh', - 'warm_air', - 'woollens', - ]), + : 'timestamp', + : 'Washing machine Start', }), 'context': , - 'entity_id': 'sensor.washing_machine_program', + 'entity_id': 'sensor.washing_machine_start', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'no_program', + 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_phase-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start_in-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'anti_crease', - 'automatic_start', - 'cancelled', - 'cleaning', - 'cooling_down', - 'disinfecting', - 'drain', - 'drying', - 'finished', - 'flex_load_active', - 'freshen_up_and_moisten', - 'hygiene', - 'main_wash', - 'not_running', - 'paused', - 'pre_wash', - 'rinse', - 'rinse_hold', - 'soak', - 'spin', - 'starch_stop', - 'steam_smoothing', - 'venting', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.washing_machine_program_phase', + 'entity_category': , + 'entity_id': 'sensor.washing_machine_start_in', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11595,73 +11896,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Program phase', + 'object_id_base': 'Start in', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Program phase', + 'original_name': 'Start in', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'program_phase', - 'unique_id': 'Dummy_Appliance_3-state_program_phase', - 'unit_of_measurement': None, + 'translation_key': 'start_time', + 'unique_id': 'Dummy_Appliance_3-state_start_time', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_phase-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start_in-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Washing machine Program phase', - : list([ - 'anti_crease', - 'automatic_start', - 'cancelled', - 'cleaning', - 'cooling_down', - 'disinfecting', - 'drain', - 'drying', - 'finished', - 'flex_load_active', - 'freshen_up_and_moisten', - 'hygiene', - 'main_wash', - 'not_running', - 'paused', - 'pre_wash', - 'rinse', - 'rinse_hold', - 'soak', - 'spin', - 'starch_stop', - 'steam_smoothing', - 'venting', - ]), + : 'duration', + : 'Washing machine Start in', + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_program_phase', + 'entity_id': 'sensor.washing_machine_start_in', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'not_running', + 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_type-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_target_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : list([ - 'automatic_program', - 'maintenance_program', - 'normal_operation_mode', - 'own_program', - ]), + : , }), 'config_entry_id': , 'config_subentry_id': , @@ -11669,8 +11947,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_program_type', + 'entity_category': None, + 'entity_id': 'sensor.washing_machine_target_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11678,42 +11956,41 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Program type', + 'object_id_base': 'Target temperature', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Program type', + 'original_name': 'Target temperature', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'program_type', - 'unique_id': 'Dummy_Appliance_3-state_program_type', - 'unit_of_measurement': None, + 'translation_key': 'target_temperature', + 'unique_id': 'Dummy_Appliance_3-state_target_temperature', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_program_type-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_target_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Washing machine Program type', - : list([ - 'automatic_program', - 'maintenance_program', - 'normal_operation_mode', - 'own_program', - ]), + : 'temperature', + : 'Washing machine Target temperature', + : , + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_program_type', + 'entity_id': 'sensor.washing_machine_target_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'normal_operation_mode', + 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_remaining_time-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_1_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -11727,7 +12004,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_remaining_time', + 'entity_id': 'sensor.washing_machine_twindos_1_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11735,40 +12012,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Remaining time', + 'object_id_base': 'TwinDos 1 level', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Remaining time', + 'original_name': 'TwinDos 1 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'remaining_time', - 'unique_id': 'Dummy_Appliance_3-state_remaining_time', - 'unit_of_measurement': , + 'translation_key': 'twin_dos_1_level', + 'unique_id': 'Dummy_Appliance_3-twin_dos_1_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_remaining_time-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_1_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'duration', - : 'Washing machine Remaining time', - : , + : 'Washing machine TwinDos 1 level', + : '%', }), 'context': , - 'entity_id': 'sensor.washing_machine_remaining_time', + 'entity_id': 'sensor.washing_machine_twindos_1_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '63', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_spin_speed-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_2_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -11782,7 +12055,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_spin_speed', + 'entity_id': 'sensor.washing_machine_twindos_2_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11790,42 +12063,44 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Spin speed', + 'object_id_base': 'TwinDos 2 level', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Spin speed', + 'original_name': 'TwinDos 2 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'spin_speed', - 'unique_id': 'Dummy_Appliance_3-state_spinning_speed', - 'unit_of_measurement': 'rpm', + 'translation_key': 'twin_dos_2_level', + 'unique_id': 'Dummy_Appliance_3-twin_dos_2_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_spin_speed-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_2_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine Spin speed', - : 'rpm', + : 'Washing machine TwinDos 2 level', + : '%', }), 'context': , - 'entity_id': 'sensor.washing_machine_spin_speed', + 'entity_id': 'sensor.washing_machine_twindos_2_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '20', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_consumption-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -11833,7 +12108,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_start', + 'entity_id': 'sensor.washing_machine_water_consumption', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11841,36 +12116,41 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Start', + 'object_id_base': 'Water consumption', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Start', + 'original_name': 'Water consumption', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'start', - 'unique_id': 'Dummy_Appliance_3-state_start_timestamp', - 'unit_of_measurement': None, + 'translation_key': 'water_consumption', + 'unique_id': 'Dummy_Appliance_3-current_water_consumption', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_consumption-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'timestamp', - : 'Washing machine Start', + : 'water', + : 'Washing machine Water consumption', + : , + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_start', + 'entity_id': 'sensor.washing_machine_water_consumption', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'unknown', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start_in-entry] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_forecast-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -11884,7 +12164,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_start_in', + 'entity_id': 'sensor.washing_machine_water_forecast', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11892,50 +12172,63 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Start in', + 'object_id_base': 'Water forecast', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Start in', + 'original_name': 'Water forecast', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'start_time', - 'unique_id': 'Dummy_Appliance_3-state_start_time', - 'unit_of_measurement': , + 'translation_key': 'water_forecast', + 'unique_id': 'Dummy_Appliance_3-water_forecast', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_start_in-state] +# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_forecast-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'duration', - : 'Washing machine Start in', - : , + : 'Washing machine Water forecast', + : '%', }), 'context': , - 'entity_id': 'sensor.washing_machine_start_in', + 'entity_id': 'sensor.washing_machine_water_forecast', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '0.0', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_target_temperature-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, 'capabilities': dict({ - : , + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -11944,7 +12237,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.washing_machine_target_temperature', + 'entity_id': 'sensor.freezer', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11952,55 +12245,74 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Target temperature', + 'object_id_base': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Target temperature', + 'original_device_class': , + 'original_icon': 'mdi:fridge-industrial-outline', + 'original_name': None, 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'target_temperature', - 'unique_id': 'Dummy_Appliance_3-state_target_temperature', - 'unit_of_measurement': , + 'translation_key': 'status', + 'unique_id': 'Dummy_Appliance_1-state_status', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_target_temperature-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'temperature', - : 'Washing machine Target temperature', - : , - : , + : 'enum', + : 'Freezer', + : 'mdi:fridge-industrial-outline', + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_target_temperature', + 'entity_id': 'sensor.freezer', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'in_use', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_1_level-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_twindos_1_level', + 'entity_category': None, + 'entity_id': 'sensor.freezer_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12008,50 +12320,77 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 1 level', + 'object_id_base': 'Temperature', 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'TwinDos 1 level', + 'original_name': 'Temperature', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_1_level', - 'unique_id': 'Dummy_Appliance_3-twin_dos_1_level', - 'unit_of_measurement': '%', + 'translation_key': None, + 'unique_id': 'Dummy_Appliance_1-state_temperature_1', + 'unit_of_measurement': , }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_1_level-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine TwinDos 1 level', - : '%', + : 'temperature', + : 'Freezer Temperature', + : , + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_twindos_1_level', + 'entity_id': 'sensor.freezer_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '63', + 'state': '-19.0', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_2_level-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.hood-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.washing_machine_twindos_2_level', + 'entity_category': None, + 'entity_id': 'sensor.hood', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12059,36 +12398,58 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'TwinDos 2 level', + 'object_id_base': None, 'options': dict({ }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 2 level', + 'original_device_class': , + 'original_icon': 'mdi:turbine', + 'original_name': None, 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'twin_dos_2_level', - 'unique_id': 'Dummy_Appliance_3-twin_dos_2_level', - 'unit_of_measurement': '%', + 'translation_key': 'status', + 'unique_id': 'DummyAppliance_18-state_status', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_twindos_2_level-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.hood-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine TwinDos 2 level', - : '%', + : 'enum', + : 'Hood', + : 'mdi:turbine', + : list([ + 'autocleaning', + 'failure', + 'idle', + 'in_use', + 'not_connected', + 'off', + 'on', + 'pause', + 'program_ended', + 'program_interrupted', + 'programmed', + 'reserved', + 'rinse_hold', + 'service', + 'supercooling', + 'supercooling_superfreezing', + 'superfreezing', + 'superheating', + 'waiting_to_start', + ]), }), 'context': , - 'entity_id': 'sensor.washing_machine_twindos_2_level', + 'entity_id': 'sensor.hood', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '20', + 'state': 'off', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_consumption-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_degreasing_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -12104,7 +12465,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_water_consumption', + 'entity_id': 'sensor.miele_test_degreasing_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12112,47 +12473,44 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Water consumption', + 'object_id_base': 'Degreasing cycles', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Water consumption', + 'original_name': 'Degreasing cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'water_consumption', - 'unique_id': 'Dummy_Appliance_3-current_water_consumption', - 'unit_of_measurement': , + 'translation_key': 'degreasing_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_consumption-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_degreasing_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'water', - : 'Washing machine Water consumption', + : 'Miele test Degreasing cycles', : , - : , }), 'context': , - 'entity_id': 'sensor.washing_machine_water_consumption', + 'entity_id': 'sensor.miele_test_degreasing_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '2', }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_forecast-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_descaling_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + : , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -12160,7 +12518,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.washing_machine_water_forecast', + 'entity_id': 'sensor.miele_test_descaling_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12168,36 +12526,36 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Water forecast', + 'object_id_base': 'Descaling cycles', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Water forecast', + 'original_name': 'Descaling cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'water_forecast', - 'unique_id': 'Dummy_Appliance_3-water_forecast', - 'unit_of_measurement': '%', + 'translation_key': 'descaling_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', + 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_water_forecast-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_descaling_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Washing machine Water forecast', - : '%', + : 'Miele test Descaling cycles', + : , }), 'context': , - 'entity_id': 'sensor.washing_machine_water_forecast', + 'entity_id': 'sensor.miele_test_descaling_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '1', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_degreasing_cycles-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, @@ -12213,7 +12571,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12221,44 +12579,42 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Degreasing cycles', + 'object_id_base': 'Milk pipework cleaning cycles', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Degreasing cycles', + 'original_name': 'Milk pipework cleaning cycles', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'degreasing_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-degreasing_counter', + 'translation_key': 'milk_cleaning_counter', + 'unique_id': 'DummyAppliance_CoffeeSystem-milk_cleaning_counter', 'unit_of_measurement': None, }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_degreasing_cycles-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Degreasing cycles', + : 'Miele test Milk pipework cleaning cycles', : , }), 'context': , - 'entity_id': 'sensor.miele_test_degreasing_cycles', + 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2', + 'state': '3', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_descaling_cycles-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_powerdisk_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -12266,7 +12622,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_descaling_cycles', + 'entity_id': 'sensor.miele_test_powerdisk_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12274,72 +12630,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Descaling cycles', + 'object_id_base': 'PowerDisk level', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Descaling cycles', + 'original_name': 'PowerDisk level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'descaling_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-descaling_counter', - 'unit_of_measurement': None, + 'translation_key': 'power_disk_level', + 'unique_id': 'Dummy_Appliance_5-power_disk_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_descaling_cycles-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_powerdisk_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Descaling cycles', - : , + : 'Miele test PowerDisk level', + : '%', }), 'context': , - 'entity_id': 'sensor.miele_test_descaling_cycles', + 'entity_id': 'sensor.miele_test_powerdisk_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1', + 'state': '80.5', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_rinse_aid_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.freezer', + 'entity_category': , + 'entity_id': 'sensor.miele_test_rinse_aid_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12347,74 +12681,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'Rinse aid level', 'options': dict({ }), - 'original_device_class': , - 'original_icon': 'mdi:fridge-industrial-outline', - 'original_name': None, + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Rinse aid level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'Dummy_Appliance_1-state_status', - 'unit_of_measurement': None, + 'translation_key': 'rinse_aid_level', + 'unique_id': 'Dummy_Appliance_5-rinse_aid_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_rinse_aid_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Freezer', - : 'mdi:fridge-industrial-outline', - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + : 'Miele test Rinse aid level', + : '%', }), 'context': , - 'entity_id': 'sensor.freezer', + 'entity_id': 'sensor.miele_test_rinse_aid_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'in_use', + 'state': '25', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer_temperature-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_salt_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.freezer_temperature', + 'entity_category': , + 'entity_id': 'sensor.miele_test_salt_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12422,77 +12732,50 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Temperature', + 'object_id_base': 'Salt level', 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Salt level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'Dummy_Appliance_1-state_temperature_1', - 'unit_of_measurement': , + 'translation_key': 'salt_level', + 'unique_id': 'Dummy_Appliance_5-salt_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.freezer_temperature-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_salt_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'temperature', - : 'Freezer Temperature', - : , - : , + : 'Miele test Salt level', + : '%', }), 'context': , - 'entity_id': 'sensor.freezer_temperature', + 'entity_id': 'sensor.miele_test_salt_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-19.0', + 'state': '75', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.hood-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_1_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.hood', + 'entity_category': , + 'entity_id': 'sensor.miele_test_twindos_1_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12500,66 +12783,42 @@ 'labels': set({ }), 'name': None, - 'object_id_base': None, + 'object_id_base': 'TwinDos 1 level', 'options': dict({ }), - 'original_device_class': , - 'original_icon': 'mdi:turbine', - 'original_name': None, + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 1 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'status', - 'unique_id': 'DummyAppliance_18-state_status', - 'unit_of_measurement': None, + 'translation_key': 'twin_dos_1_level', + 'unique_id': 'DummyWasher-twin_dos_1_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.hood-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_1_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'enum', - : 'Hood', - : 'mdi:turbine', - : list([ - 'autocleaning', - 'failure', - 'idle', - 'in_use', - 'not_connected', - 'off', - 'on', - 'pause', - 'program_ended', - 'program_interrupted', - 'programmed', - 'reserved', - 'rinse_hold', - 'service', - 'supercooling', - 'supercooling_superfreezing', - 'superfreezing', - 'superheating', - 'waiting_to_start', - ]), + : 'Miele test TwinDos 1 level', + : '%', }), 'context': , - 'entity_id': 'sensor.hood', + 'entity_id': 'sensor.miele_test_twindos_1_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'off', + 'state': '63', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-entry] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_2_level-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ None, ]), 'area_id': None, - 'capabilities': dict({ - : , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -12567,7 +12826,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', + 'entity_id': 'sensor.miele_test_twindos_2_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12575,33 +12834,33 @@ 'labels': set({ }), 'name': None, - 'object_id_base': 'Milk pipework cleaning cycles', + 'object_id_base': 'TwinDos 2 level', 'options': dict({ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Milk pipework cleaning cycles', + 'original_name': 'TwinDos 2 level', 'platform': 'miele', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'milk_cleaning_counter', - 'unique_id': 'DummyAppliance_CoffeeSystem-milk_cleaning_counter', - 'unit_of_measurement': None, + 'translation_key': 'twin_dos_2_level', + 'unique_id': 'DummyWasher-twin_dos_2_level', + 'unit_of_measurement': '%', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_milk_pipework_cleaning_cycles-state] +# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_2_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - : 'Miele test Milk pipework cleaning cycles', - : , + : 'Miele test TwinDos 2 level', + : '%', }), 'context': , - 'entity_id': 'sensor.miele_test_milk_pipework_cleaning_cycles', + 'entity_id': 'sensor.miele_test_twindos_2_level', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '3', + 'state': '20', }) # --- # name: test_sensor_states_api_push_one_device[platforms0][sensor.oven-entry] @@ -14491,57 +14750,6 @@ 'state': '19.54', }) # --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_powerdisk_level-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.miele_test_powerdisk_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'PowerDisk level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'PowerDisk level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'power_disk_level', - 'unique_id': 'Dummy_Appliance_5-power_disk_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_powerdisk_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test PowerDisk level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_powerdisk_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '80.5', - }) -# --- # name: test_sensor_states_api_push_one_device[platforms0][sensor.refrigerator-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -14692,211 +14900,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '4.0', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_rinse_aid_level-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.miele_test_rinse_aid_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Rinse aid level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Rinse aid level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'rinse_aid_level', - 'unique_id': 'Dummy_Appliance_5-rinse_aid_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_rinse_aid_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Rinse aid level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_rinse_aid_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '25', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_salt_level-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.miele_test_salt_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Salt level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Salt level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'salt_level', - 'unique_id': 'Dummy_Appliance_5-salt_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_salt_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Salt level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_salt_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '75', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_1_level-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.miele_test_twindos_1_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'TwinDos 1 level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 1 level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'twin_dos_1_level', - 'unique_id': 'DummyWasher-twin_dos_1_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_1_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 1 level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_twindos_1_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '63', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_2_level-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.miele_test_twindos_2_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'TwinDos 2 level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 2 level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'twin_dos_2_level', - 'unique_id': 'DummyWasher-twin_dos_2_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensor_states_api_push_one_device[platforms0][sensor.miele_test_twindos_2_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 2 level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_twindos_2_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '20', + 'state': '4.0', }) # --- # name: test_sensor_states_api_push_one_device[platforms0][sensor.washing_machine-entry] @@ -15261,6 +15265,7 @@ 'steam_care', 'stuffed_toys', 'table_linen', + 'towels', 'trainers', 'trainers_refresh', 'warm_air', @@ -15347,6 +15352,7 @@ 'steam_care', 'stuffed_toys', 'table_linen', + 'towels', 'trainers', 'trainers_refresh', 'warm_air', @@ -16272,6 +16278,261 @@ 'state': '25', }) # --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_salt_level-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.miele_test_salt_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Salt level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Salt level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'salt_level', + 'unique_id': 'Dummy_Appliance_5-salt_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_salt_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test Salt level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_salt_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '75', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level-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.miele_test_twindos_1_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'TwinDos 1 level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 1 level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'twin_dos_1_level', + 'unique_id': 'Dummy_Appliance_3-twin_dos_1_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test TwinDos 1 level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_twindos_1_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '63', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level_2-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.miele_test_twindos_1_level_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'TwinDos 1 level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 1 level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'twin_dos_1_level', + 'unique_id': 'DummyWasher-twin_dos_1_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test TwinDos 1 level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_twindos_1_level_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '63', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level-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.miele_test_twindos_2_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'TwinDos 2 level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 2 level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'twin_dos_2_level', + 'unique_id': 'Dummy_Appliance_3-twin_dos_2_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test TwinDos 2 level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_twindos_2_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '20', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level_2-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.miele_test_twindos_2_level_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'TwinDos 2 level', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'TwinDos 2 level', + 'platform': 'miele', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'twin_dos_2_level', + 'unique_id': 'DummyWasher-twin_dos_2_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Miele test TwinDos 2 level', + : '%', + }), + 'context': , + 'entity_id': 'sensor.miele_test_twindos_2_level_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '20', + }) +# --- # name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.robot_vacuum_cleaner-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -16761,258 +17022,3 @@ 'state': 'unknown', }) # --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_salt_level-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.miele_test_salt_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Salt level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Salt level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'salt_level', - 'unique_id': 'Dummy_Appliance_5-salt_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_salt_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test Salt level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_salt_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '75', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level-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.miele_test_twindos_1_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'TwinDos 1 level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 1 level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'twin_dos_1_level', - 'unique_id': 'Dummy_Appliance_3-twin_dos_1_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 1 level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_twindos_1_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '63', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level_2-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.miele_test_twindos_1_level_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'TwinDos 1 level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 1 level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'twin_dos_1_level', - 'unique_id': 'DummyWasher-twin_dos_1_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_1_level_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 1 level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_twindos_1_level_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '63', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level-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.miele_test_twindos_2_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'TwinDos 2 level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 2 level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'twin_dos_2_level', - 'unique_id': 'Dummy_Appliance_3-twin_dos_2_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 2 level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_twindos_2_level', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '20', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level_2-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.miele_test_twindos_2_level_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'TwinDos 2 level', - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'TwinDos 2 level', - 'platform': 'miele', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'twin_dos_2_level', - 'unique_id': 'DummyWasher-twin_dos_2_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.miele_test_twindos_2_level_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - : 'Miele test TwinDos 2 level', - : '%', - }), - 'context': , - 'entity_id': 'sensor.miele_test_twindos_2_level_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '20', - }) -# --- From 8c5493a8c28b86f6b5be05e22b0c4a48f18545be Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Aug 2026 18:10:52 +0200 Subject: [PATCH 04/22] Use registry fixtures in tests (8/8) (#180931) --- tests/components/timer/test_trigger.py | 8 +++---- .../components/traccar/test_device_tracker.py | 6 ++++-- tests/components/tuya/test_services.py | 4 ++-- .../unifiprotect/test_binary_sensor.py | 2 +- tests/components/unifiprotect/test_camera.py | 8 ++++--- .../usage_prediction/test_common_control.py | 9 ++++---- tests/components/vacuum/test_init.py | 8 ++++--- tests/components/vacuum/test_intent.py | 21 ++++++++++--------- tests/components/vicare/test_init.py | 6 ++++-- tests/components/voip/test_voip.py | 4 ++-- tests/components/zha/test_dynamic_entities.py | 17 ++++++++------- 11 files changed, 52 insertions(+), 41 deletions(-) diff --git a/tests/components/timer/test_trigger.py b/tests/components/timer/test_trigger.py index e2aa927962cb6..d2d587a05ab63 100644 --- a/tests/components/timer/test_trigger.py +++ b/tests/components/timer/test_trigger.py @@ -634,13 +634,13 @@ async def test_time_remaining_trigger_entity_removed_from_target( hass: HomeAssistant, freezer: FrozenDateTimeFactory, entity_registry: er.EntityRegistry, + label_registry: lr.LabelRegistry, ) -> None: """Test trigger cancels scheduled fire when entity is removed from the target.""" now = dt_util.utcnow() calls: list[dict[str, Any]] = [] - label_reg = lr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - label = label_reg.async_create("Test Time Remaining") + label = label_registry.async_create("Test Time Remaining") entry = entity_registry.async_get_or_create( domain=DOMAIN, platform="test", unique_id="time_remaining_remove" @@ -683,13 +683,13 @@ async def test_time_remaining_trigger_entity_added_to_target( hass: HomeAssistant, freezer: FrozenDateTimeFactory, entity_registry: er.EntityRegistry, + label_registry: lr.LabelRegistry, ) -> None: """Test trigger schedules a fire for an active timer added to the target later.""" now = dt_util.utcnow() calls: list[dict[str, Any]] = [] - label_reg = lr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - label = label_reg.async_create("Test Time Remaining Add") + label = label_registry.async_create("Test Time Remaining Add") entry = entity_registry.async_get_or_create( domain=DOMAIN, platform="test", unique_id="time_remaining_add" diff --git a/tests/components/traccar/test_device_tracker.py b/tests/components/traccar/test_device_tracker.py index 850713d745eef..0bab03b969233 100644 --- a/tests/components/traccar/test_device_tracker.py +++ b/tests/components/traccar/test_device_tracker.py @@ -29,13 +29,15 @@ def mock_dev_track(mock_device_tracker_conf: list[Device]) -> None: """Mock device tracker config loading.""" -async def test_restore_state(hass: HomeAssistant) -> None: +async def test_restore_state( + hass: HomeAssistant, device_registry: dr.DeviceRegistry +) -> None: """Test that the previous location is restored for a known device.""" assert await async_setup_component(hass, DEVICE_TRACKER_DOMAIN, {}) entry = MockConfigEntry(domain=DOMAIN, data={CONF_WEBHOOK_ID: "webhook_id"}) entry.add_to_hass(hass) - dr.async_get(hass).async_get_or_create( # pylint: disable=home-assistant-tests-registry-fixtures + device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, DEVICE_ID)}, ) diff --git a/tests/components/tuya/test_services.py b/tests/components/tuya/test_services.py index 467e8cd8ebbd5..1d3a3a3d071ea 100644 --- a/tests/components/tuya/test_services.py +++ b/tests/components/tuya/test_services.py @@ -195,6 +195,7 @@ async def test_get_tuya_device_error_device_not_found( @pytest.mark.parametrize("mock_device_code", ["cwwsq_wfkzyy0evslzsmoi"]) async def test_get_tuya_device_error_non_tuya_device( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, @@ -202,7 +203,6 @@ async def test_get_tuya_device_error_non_tuya_device( """Test service error when target device is not a Tuya device.""" await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures non_tuya_device = device_registry.async_get_or_create( config_entry_id=mock_config_entry.entry_id, identifiers={("other_domain", "some_id")}, @@ -224,6 +224,7 @@ async def test_get_tuya_device_error_non_tuya_device( @pytest.mark.parametrize("mock_device_code", ["cwwsq_wfkzyy0evslzsmoi"]) async def test_get_tuya_device_error_unknown_tuya_device( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, @@ -231,7 +232,6 @@ async def test_get_tuya_device_error_unknown_tuya_device( """Test service error when Tuya identifier is not present in manager map.""" await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures tuya_device = device_registry.async_get_or_create( config_entry_id=mock_config_entry.entry_id, identifiers={(DOMAIN, "unknown_tuya_id")}, diff --git a/tests/components/unifiprotect/test_binary_sensor.py b/tests/components/unifiprotect/test_binary_sensor.py index b2b773a3daa7d..a1ff0af14aa93 100644 --- a/tests/components/unifiprotect/test_binary_sensor.py +++ b/tests/components/unifiprotect/test_binary_sensor.py @@ -981,6 +981,7 @@ async def test_binary_sensor_doorbell_ring( async def test_aiport_no_binary_sensor_entities( hass: HomeAssistant, + entity_registry: er.EntityRegistry, ufp: MockUFPFixture, aiport: AiPort, ) -> None: @@ -990,7 +991,6 @@ async def test_aiport_no_binary_sensor_entities( # AI Port should not create any camera-specific binary sensors # (motion, smart detection, etc.) # NVR HDD sensors will still be created, but no AI Port-specific entities - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures entities = er.async_entries_for_config_entry(entity_registry, ufp.entry.entry_id) for entity in entities: diff --git a/tests/components/unifiprotect/test_camera.py b/tests/components/unifiprotect/test_camera.py index 76c12ab5ae9f6..53c0806651267 100644 --- a/tests/components/unifiprotect/test_camera.py +++ b/tests/components/unifiprotect/test_camera.py @@ -132,6 +132,7 @@ async def test_doorbell_setup( async def test_first_active_quality_is_default( hass: HomeAssistant, + entity_registry: er.EntityRegistry, ufp: MockUFPFixture, camera_all: ProtectCamera, issue_registry: ir.IssueRegistry, @@ -152,7 +153,6 @@ async def test_first_active_quality_is_default( == camera_all.channels[1].rtsps_no_srtp_url ) - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert entity_registry.async_get(_channel_entity_id(camera_all, 0)) is None assert entity_registry.async_get(_channel_entity_id(camera_all, 2)) is None assert ( @@ -408,7 +408,10 @@ async def test_aiport_no_camera_entities( async def test_public_only_camera( - hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + ufp: MockUFPFixture, + camera: ProtectCamera, ) -> None: """A public-only camera builds a working entity with degraded diagnostics.""" # This camera is intentionally kept out of the private bootstrap; wire the @@ -439,7 +442,6 @@ async def _prime_public_only() -> Any: # device identity degrades to name-only, but the NVR link still resolves # from the public bootstrap - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures device = device_registry.async_get_device_by_connection( (dr.CONNECTION_NETWORK_MAC, public.mac), ufp.entry.entry_id ) diff --git a/tests/components/usage_prediction/test_common_control.py b/tests/components/usage_prediction/test_common_control.py index b9217fca004f2..ef65cd831c0f9 100644 --- a/tests/components/usage_prediction/test_common_control.py +++ b/tests/components/usage_prediction/test_common_control.py @@ -136,19 +136,20 @@ async def test_with_service_calls(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("recorder_mock") -async def test_multiple_entities_in_one_call(hass: HomeAssistant) -> None: +async def test_multiple_entities_in_one_call( + hass: HomeAssistant, entity_registry: er.EntityRegistry +) -> None: """Test handling of service calls with multiple entity IDs.""" user_id = str(uuid.uuid4()) - ent_reg = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - ent_reg.async_get_or_create( + entity_registry.async_get_or_create( "light", "test", "living_room", suggested_object_id="living_room", hidden_by=er.RegistryEntryHider.USER, ) - ent_reg.async_get_or_create( + entity_registry.async_get_or_create( "light", "test", "kitchen", diff --git a/tests/components/vacuum/test_init.py b/tests/components/vacuum/test_init.py index a415ac6b288e1..5f15536053fc5 100644 --- a/tests/components/vacuum/test_init.py +++ b/tests/components/vacuum/test_init.py @@ -496,7 +496,9 @@ async def test_last_seen_segments( @pytest.mark.usefixtures("config_flow_fixture") async def test_segments_changed_issue( - hass: HomeAssistant, entity_registry: er.EntityRegistry + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, ) -> None: """Test segments changed issue.""" mock_vacuum = MockVacuumWithCleanArea(name="Testing", entity_id="vacuum.testing") @@ -531,7 +533,7 @@ async def test_segments_changed_issue( mock_vacuum.async_create_segments_issue() issue_id = f"segments_changed_{entity_entry.id}" - issue = ir.async_get(hass).async_get_issue(DOMAIN, issue_id) # pylint: disable=home-assistant-tests-registry-fixtures + issue = issue_registry.async_get_issue(DOMAIN, issue_id) assert issue is not None assert issue.severity == ir.IssueSeverity.WARNING assert issue.translation_key == "segments_changed" @@ -549,4 +551,4 @@ async def test_segments_changed_issue( ) await hass.async_block_till_done() - assert ir.async_get(hass).async_get_issue(DOMAIN, issue_id) is None # pylint: disable=home-assistant-tests-registry-fixtures + assert issue_registry.async_get_issue(DOMAIN, issue_id) is None diff --git a/tests/components/vacuum/test_intent.py b/tests/components/vacuum/test_intent.py index c58f1b4ff7197..9ddec2d4608ab 100644 --- a/tests/components/vacuum/test_intent.py +++ b/tests/components/vacuum/test_intent.py @@ -121,12 +121,11 @@ async def test_return_to_base_without_name(hass: HomeAssistant) -> None: assert call.data == {"entity_id": entity_id} -async def test_clean_area(hass: HomeAssistant) -> None: +async def test_clean_area(hass: HomeAssistant, area_registry: ar.AreaRegistry) -> None: """Test HassVacuumCleanArea intent.""" await vacuum_intent.async_setup_intents(hass) - area_reg = ar.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - kitchen = area_reg.async_create("Kitchen") + kitchen = area_registry.async_create("Kitchen") vacuum_1 = f"{DOMAIN}.vacuum_1" vacuum_2 = f"{DOMAIN}.vacuum_2" @@ -179,12 +178,13 @@ async def test_clean_area(hass: HomeAssistant) -> None: } -async def test_clean_area_no_matching_vacuum(hass: HomeAssistant) -> None: +async def test_clean_area_no_matching_vacuum( + hass: HomeAssistant, area_registry: ar.AreaRegistry +) -> None: """Test HassVacuumCleanArea intent with no matching vacuum.""" await vacuum_intent.async_setup_intents(hass) - area_reg = ar.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - area_reg.async_create("Kitchen") + area_registry.async_create("Kitchen") # No vacuums at all with pytest.raises(intent.MatchFailedError) as err: @@ -234,12 +234,13 @@ async def test_clean_area_invalid_area(hass: HomeAssistant) -> None: assert err.value.result.no_match_name == "Nonexistent room" -async def test_clean_area_service_failure(hass: HomeAssistant) -> None: +async def test_clean_area_service_failure( + hass: HomeAssistant, area_registry: ar.AreaRegistry +) -> None: """Test HassVacuumCleanArea intent when the service call fails.""" await vacuum_intent.async_setup_intents(hass) - area_reg = ar.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - area_reg.async_create("Kitchen") + area_registry.async_create("Kitchen") entity_id = f"{DOMAIN}.test_vacuum" hass.states.async_set( @@ -248,7 +249,7 @@ async def test_clean_area_service_failure(hass: HomeAssistant) -> None: {ATTR_SUPPORTED_FEATURES: VacuumEntityFeature.CLEAN_AREA}, ) - kitchen = area_reg.async_get_area_by_name("Kitchen") + kitchen = area_registry.async_get_area_by_name("Kitchen") assert kitchen is not None with ( diff --git a/tests/components/vicare/test_init.py b/tests/components/vicare/test_init.py index 7a1f4a7d9d154..ad28adaaee6ad 100644 --- a/tests/components/vicare/test_init.py +++ b/tests/components/vicare/test_init.py @@ -154,7 +154,9 @@ async def test_migrate_entry_token_failure(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_setup_entry") -async def test_migrate_entry_creates_repair_issue(hass: HomeAssistant) -> None: +async def test_migrate_entry_creates_repair_issue( + hass: HomeAssistant, issue_registry: ir.IssueRegistry +) -> None: """Test migration creates a repair issue for redirect URI update.""" config_entry = MockConfigEntry( domain=DOMAIN, @@ -181,7 +183,7 @@ async def test_migrate_entry_creates_repair_issue(hass: HomeAssistant) -> None: await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - issue = ir.async_get(hass).async_get_issue(DOMAIN, "update_redirect_uri") # pylint: disable=home-assistant-tests-registry-fixtures + issue = issue_registry.async_get_issue(DOMAIN, "update_redirect_uri") assert issue is not None assert issue.severity == ir.IssueSeverity.WARNING diff --git a/tests/components/voip/test_voip.py b/tests/components/voip/test_voip.py index 270ae5fc4b533..c91d74e971100 100644 --- a/tests/components/voip/test_voip.py +++ b/tests/components/voip/test_voip.py @@ -51,6 +51,7 @@ def _empty_wav(framerate=16000) -> bytes: async def test_is_valid_call( hass: HomeAssistant, + entity_registry: er.EntityRegistry, voip_devices: VoIPDevices, voip_device: VoIPDevice, call_info: CallInfo, @@ -60,8 +61,7 @@ async def test_is_valid_call( protocol = HassVoipDatagramProtocol(hass, voip_devices) assert not protocol.is_valid_call(call_info) - ent_reg = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - allowed_call_entity_id = ent_reg.async_get_entity_id( + allowed_call_entity_id = entity_registry.async_get_entity_id( "switch", voip.DOMAIN, f"{voip_device.voip_id}-allow_call" ) assert allowed_call_entity_id is not None diff --git a/tests/components/zha/test_dynamic_entities.py b/tests/components/zha/test_dynamic_entities.py index 8b9f1ca1e20fd..a25fabbd84742 100644 --- a/tests/components/zha/test_dynamic_entities.py +++ b/tests/components/zha/test_dynamic_entities.py @@ -92,6 +92,7 @@ def _get_platform_entity( async def test_dynamic_entity_lifecycle( hass: HomeAssistant, + entity_registry: er.EntityRegistry, setup_zha: Callable[..., Coroutine[None]], zigpy_device_mock: Callable[..., Device], ) -> None: @@ -132,11 +133,10 @@ async def test_dynamic_entity_lifecycle( ), ) await hass.async_block_till_done() - registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - assert registry.async_get(entity_id) is None + assert entity_registry.async_get(entity_id) is None assert hass.states.get(entity_id) is None # The binary_sensor with the same unique_id is untouched. - assert registry.async_get(binary_sensor_id) is not None + assert entity_registry.async_get(binary_sensor_id) is not None assert hass.states.get(binary_sensor_id) == binary_sensor_state_before ha_zha_data = get_zha_data(hass) @@ -170,7 +170,7 @@ async def test_dynamic_entity_lifecycle( assert len(matching_refs) == 1 # Soft remove: registry entry is kept, state goes unavailable. - entry = registry.async_get(entity_id) + entry = entity_registry.async_get(entity_id) assert entry is not None zha_device_proxy.device.emit( DeviceEntityRemovedEvent.event_type, @@ -181,7 +181,7 @@ async def test_dynamic_entity_lifecycle( ), ) await hass.async_block_till_done() - assert registry.async_get(entity_id) is not None + assert entity_registry.async_get(entity_id) is not None state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_UNAVAILABLE @@ -191,6 +191,7 @@ async def test_dynamic_entity_lifecycle( async def test_unknown_unique_id_is_noop( hass: HomeAssistant, + entity_registry: er.EntityRegistry, setup_zha: Callable[..., Coroutine[None]], zigpy_device_mock: Callable[..., Device], ) -> None: @@ -201,7 +202,6 @@ async def test_unknown_unique_id_is_noop( assert entity_id is not None ha_zha_data = get_zha_data(hass) - registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # Added: nothing queued, no dispatcher signal fired. with patch( @@ -231,12 +231,13 @@ async def test_unknown_unique_id_is_noop( ) await hass.async_block_till_done() - assert registry.async_get(entity_id) is not None + assert entity_registry.async_get(entity_id) is not None assert hass.states.get(entity_id) is not None async def test_remove_entity_reference_when_ieee_already_cleared( hass: HomeAssistant, + entity_registry: er.EntityRegistry, setup_zha: Callable[..., Coroutine[None]], zigpy_device_mock: Callable[..., Device], ) -> None: @@ -255,7 +256,7 @@ async def test_remove_entity_reference_when_ieee_already_cleared( ieee = zha_device_proxy.device.ieee gateway_proxy._ha_entity_refs.pop(ieee, None) - er.async_get(hass).async_remove(entity_id) # pylint: disable=home-assistant-tests-registry-fixtures + entity_registry.async_remove(entity_id) await hass.async_block_till_done() assert ieee not in gateway_proxy._ha_entity_refs From b7ad53e134b33b484dd24da8798fbecb3181c4da Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 31 Aug 2026 09:31:34 -0700 Subject: [PATCH 05/22] Suggest hours as unit of measurement for Google Health sleep duration sensors (#180929) Co-authored-by: Home Assistant Developer --- .../components/google_health/sensor.py | 6 +++++ .../google_health/snapshots/test_sensor.ambr | 27 ++++++++++++------- tests/components/google_health/test_sensor.py | 6 ++--- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/google_health/sensor.py b/homeassistant/components/google_health/sensor.py index e9c23357bfb1f..e0a7c37ebced9 100644 --- a/homeassistant/components/google_health/sensor.py +++ b/homeassistant/components/google_health/sensor.py @@ -158,6 +158,8 @@ class GoogleHealthSensorEntityDescription[ key="sleep_asleep", translation_key="sleep_asleep", native_unit_of_measurement=UnitOfTime.MINUTES, + suggested_unit_of_measurement=UnitOfTime.HOURS, + suggested_display_precision=2, device_class=SensorDeviceClass.DURATION, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: ( @@ -170,6 +172,8 @@ class GoogleHealthSensorEntityDescription[ key="sleep_awake", translation_key="sleep_awake", native_unit_of_measurement=UnitOfTime.MINUTES, + suggested_unit_of_measurement=UnitOfTime.HOURS, + suggested_display_precision=2, device_class=SensorDeviceClass.DURATION, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: ( @@ -182,6 +186,8 @@ class GoogleHealthSensorEntityDescription[ key="sleep_in_bed", translation_key="sleep_in_bed", native_unit_of_measurement=UnitOfTime.MINUTES, + suggested_unit_of_measurement=UnitOfTime.HOURS, + suggested_display_precision=2, device_class=SensorDeviceClass.DURATION, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: ( diff --git a/tests/components/google_health/snapshots/test_sensor.ambr b/tests/components/google_health/snapshots/test_sensor.ambr index 5f084d060e537..2785c01fa2deb 100644 --- a/tests/components/google_health/snapshots/test_sensor.ambr +++ b/tests/components/google_health/snapshots/test_sensor.ambr @@ -690,6 +690,9 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), 'original_device_class': , 'original_icon': None, @@ -700,7 +703,7 @@ 'supported_features': 0, 'translation_key': 'sleep_asleep', 'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_sleep_asleep', - 'unit_of_measurement': , + 'unit_of_measurement': , }) # --- # name: test_all_entities[sensor.google_health_time_asleep-state] @@ -709,14 +712,14 @@ : 'duration', : 'Google Health Time asleep', : , - : , + : , }), 'context': , 'entity_id': 'sensor.google_health_time_asleep', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '420', + 'state': '7.0', }) # --- # name: test_all_entities[sensor.google_health_time_awake-entry] @@ -748,6 +751,9 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), 'original_device_class': , 'original_icon': None, @@ -758,7 +764,7 @@ 'supported_features': 0, 'translation_key': 'sleep_awake', 'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_sleep_awake', - 'unit_of_measurement': , + 'unit_of_measurement': , }) # --- # name: test_all_entities[sensor.google_health_time_awake-state] @@ -767,14 +773,14 @@ : 'duration', : 'Google Health Time awake', : , - : , + : , }), 'context': , 'entity_id': 'sensor.google_health_time_awake', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '60', + 'state': '1.0', }) # --- # name: test_all_entities[sensor.google_health_time_in_bed-entry] @@ -806,6 +812,9 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), 'original_device_class': , 'original_icon': None, @@ -816,7 +825,7 @@ 'supported_features': 0, 'translation_key': 'sleep_in_bed', 'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_sleep_in_bed', - 'unit_of_measurement': , + 'unit_of_measurement': , }) # --- # name: test_all_entities[sensor.google_health_time_in_bed-state] @@ -825,14 +834,14 @@ : 'duration', : 'Google Health Time in bed', : , - : , + : , }), 'context': , 'entity_id': 'sensor.google_health_time_in_bed', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '480', + 'state': '8.0', }) # --- # name: test_all_entities[sensor.google_health_time_to_fall_asleep-entry] diff --git a/tests/components/google_health/test_sensor.py b/tests/components/google_health/test_sensor.py index 91a0b652f7e00..9b1b890146df3 100644 --- a/tests/components/google_health/test_sensor.py +++ b/tests/components/google_health/test_sensor.py @@ -119,15 +119,15 @@ async def test_sensor_in_progress_sleep( time_asleep_state = hass.states.get("sensor.google_health_time_asleep") assert time_asleep_state is not None - assert time_asleep_state.state == "420" + assert time_asleep_state.state == "7.0" time_awake_state = hass.states.get("sensor.google_health_time_awake") assert time_awake_state is not None - assert time_awake_state.state == "60" + assert time_awake_state.state == "1.0" time_in_bed_state = hass.states.get("sensor.google_health_time_in_bed") assert time_in_bed_state is not None - assert time_in_bed_state.state == "480" + assert time_in_bed_state.state == "8.0" time_to_fall_asleep_state = hass.states.get( "sensor.google_health_time_to_fall_asleep" From 3b84971576ad23f788bb58787238c6c1c9725947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Diego=20Rodr=C3=ADguez=20Royo?= Date: Mon, 31 Aug 2026 18:44:04 +0200 Subject: [PATCH 06/22] Revert images functionality in Home Connect (#180933) --- .../components/home_connect/__init__.py | 1 - .../components/home_connect/common.py | 16 +- .../components/home_connect/config_flow.py | 41 +--- .../components/home_connect/coordinator.py | 74 ------ .../components/home_connect/image.py | 129 ---------- .../components/home_connect/strings.json | 21 -- tests/components/home_connect/conftest.py | 20 -- .../home_connect/test_config_flow.py | 88 ++----- .../home_connect/test_coordinator.py | 25 -- tests/components/home_connect/test_image.py | 227 ------------------ 10 files changed, 29 insertions(+), 613 deletions(-) delete mode 100644 homeassistant/components/home_connect/image.py delete mode 100644 tests/components/home_connect/test_image.py diff --git a/homeassistant/components/home_connect/__init__.py b/homeassistant/components/home_connect/__init__.py index b1c8adc9f49be..c65e96298e019 100644 --- a/homeassistant/components/home_connect/__init__.py +++ b/homeassistant/components/home_connect/__init__.py @@ -43,7 +43,6 @@ Platform.BUTTON, Platform.CLIMATE, Platform.FAN, - Platform.IMAGE, Platform.LIGHT, Platform.NUMBER, Platform.SELECT, diff --git a/homeassistant/components/home_connect/common.py b/homeassistant/components/home_connect/common.py index 4cd0f6734240a..e0f26a54a0ec4 100644 --- a/homeassistant/components/home_connect/common.py +++ b/homeassistant/components/home_connect/common.py @@ -1,7 +1,7 @@ """Common callbacks for all Home Connect platforms.""" from collections import defaultdict -from collections.abc import Callable, Sequence +from collections.abc import Callable from functools import partial from typing import cast @@ -10,7 +10,7 @@ from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.entity import Entity, EntityDescription +from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import DOMAIN @@ -48,7 +48,7 @@ def _create_option_entities( known_entity_unique_ids: dict[str, str], get_option_entities_for_appliance: Callable[ [HomeConnectApplianceCoordinator, er.EntityRegistry], - Sequence[HomeConnectEntity], + list[HomeConnectEntity], ], async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: @@ -74,11 +74,11 @@ def _handle_paired_or_connected_appliance( entry: HomeConnectConfigEntry, known_entity_unique_ids: dict[str, str], get_entities_for_appliance: Callable[ - [HomeConnectApplianceCoordinator], Sequence[Entity] + [HomeConnectApplianceCoordinator], list[HomeConnectEntity] ], get_option_entities_for_appliance: Callable[ [HomeConnectApplianceCoordinator, er.EntityRegistry], - Sequence[HomeConnectEntity], + list[HomeConnectEntity], ] | None, changed_options_listener_remove_callbacks: dict[str, list[Callable[[], None]]], @@ -91,7 +91,7 @@ def _handle_paired_or_connected_appliance( when they are turned off, so we need to check if the entities have been added already or it is the first time we see them when the appliance is connected. """ - entities: list[Entity] = [] + entities: list[HomeConnectEntity] = [] entity_registry = er.async_get(hass) for appliance_coordinator in entry.runtime_data.appliance_coordinators.values(): appliance_ha_id = appliance_coordinator.data.info.ha_id @@ -161,12 +161,12 @@ def setup_home_connect_entry( hass: HomeAssistant, entry: HomeConnectConfigEntry, get_entities_for_appliance: Callable[ - [HomeConnectApplianceCoordinator], Sequence[Entity] + [HomeConnectApplianceCoordinator], list[HomeConnectEntity] ], async_add_entities: AddConfigEntryEntitiesCallback, get_option_entities_for_appliance: Callable[ [HomeConnectApplianceCoordinator, er.EntityRegistry], - Sequence[HomeConnectEntity], + list[HomeConnectEntity], ] | None = None, ) -> None: diff --git a/homeassistant/components/home_connect/config_flow.py b/homeassistant/components/home_connect/config_flow.py index 4c852654ebf2c..897416b156b6a 100644 --- a/homeassistant/components/home_connect/config_flow.py +++ b/homeassistant/components/home_connect/config_flow.py @@ -2,7 +2,7 @@ from collections.abc import Mapping import logging -from typing import Any, Final, override +from typing import Any, override import jwt import voluptuous as vol @@ -13,8 +13,6 @@ from .const import DOMAIN -INPUT_IMAGES_SCOPE: Final = "images_scope" - class OAuth2FlowHandler( config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN @@ -25,49 +23,12 @@ class OAuth2FlowHandler( MINOR_VERSION = 3 - images_scope: bool | None = None - @property @override def logger(self) -> logging.Logger: """Return logger.""" return logging.getLogger(__name__) - @property - @override - def extra_authorize_data(self) -> dict[str, str]: - return { - "scope": ( - "Control Monitor Settings" - f" IdentifyAppliance{' Images' if self.images_scope else ''}" - ), - } - - @override - async def async_step_user( - self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: - """Handle a flow start.""" - return await self.async_step_scopes(user_input) - - async def async_step_scopes( - self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: - """Ask for the scopes to use.""" - if user_input is not None: - self.images_scope = user_input[INPUT_IMAGES_SCOPE] - if self.images_scope is not None: - return await self.async_step_pick_implementation(None) - - return self.async_show_form( - step_id="scopes", - data_schema=vol.Schema( - { - vol.Required(INPUT_IMAGES_SCOPE): bool, - } - ), - ) - async def async_step_reauth( self, entry_data: Mapping[str, Any] ) -> ConfigFlowResult: diff --git a/homeassistant/components/home_connect/coordinator.py b/homeassistant/components/home_connect/coordinator.py index 496631ba91cd3..7e85e2957db77 100644 --- a/homeassistant/components/home_connect/coordinator.py +++ b/homeassistant/components/home_connect/coordinator.py @@ -29,7 +29,6 @@ TooManyRequestsError, UnauthorizedError, ) -from aiohomeconnect.model.image import Image from aiohomeconnect.model.program import EnumerateProgram, ProgramDefinitionOption from homeassistant.config_entries import ConfigEntry @@ -66,7 +65,6 @@ class HomeConnectApplianceData: programs: list[EnumerateProgram] settings: dict[SettingKey, GetSetting] status: dict[StatusKey, Status] - images: dict[str, Image] def update(self, other: HomeConnectApplianceData) -> None: """Update data with data from other instance.""" @@ -80,7 +78,6 @@ def update(self, other: HomeConnectApplianceData) -> None: self.programs.extend(other.programs) self.settings.update(other.settings) self.status.update(other.status) - self.images.update(other.images) @classmethod def empty(cls, appliance: HomeAppliance) -> HomeConnectApplianceData: @@ -93,7 +90,6 @@ def empty(cls, appliance: HomeAppliance) -> HomeConnectApplianceData: programs=[], settings={}, status={}, - images={}, ) @@ -272,10 +268,6 @@ def __init__( self.global_listeners = global_listeners self.data = HomeConnectApplianceData.empty(appliance) self._execution_tracker: list[float] = [] - self.should_fetch_images = "Images" in self._config_entry.data["token"].get( - "scope", "" - ) - self._image_listeners: dict[str, list[CALLBACK_TYPE]] = {} def _get_listeners_for_event_key(self, event_key: EventKey) -> list[CALLBACK_TYPE]: return [ @@ -575,18 +567,6 @@ async def get_appliance_data(self) -> None: except HomeConnectError: commands = set() - try: - images = await self.get_latest_images() if self.should_fetch_images else {} - except TooManyRequestsError: - raise - except HomeConnectError as error: - _LOGGER.debug( - "Error fetching images for %s: %s", - appliance.ha_id, - error, - ) - images = {} - self.data.update( HomeConnectApplianceData( commands=commands, @@ -596,7 +576,6 @@ async def get_appliance_data(self) -> None: programs=programs, settings=settings, status=status, - images=images, ) ) @@ -667,59 +646,6 @@ async def update_options(self, program_key: ProgramKey) -> None: for listener in self._get_listeners_for_event_key(EventKey(option_key)): listener() - async def get_latest_images(self) -> dict[str, Image]: - """Get the latest images for the appliance.""" - try: - new_images = await self.client.get_images(self.data.info.ha_id) - except TooManyRequestsError: - raise - except HomeConnectError as error: - _LOGGER.debug( - "Error fetching images for %s: %s", - self.data.info.ha_id, - error, - ) - return {} - - latest_images: dict[str, Image] = {} - for image in new_images.images: - if ( - existing_image := latest_images.get(image.key) - ) is None or image.timestamp > existing_image.timestamp: - latest_images[image.key] = image - - return latest_images - - async def update_images(self) -> None: - """Update images for appliance.""" - old_images = self.data.images.copy() - self.data.images.update(await self.get_latest_images()) - for image_key, image in self.data.images.items(): - if image.image_key != old_images[image_key].image_key: - for listener in self._image_listeners.get(image_key, []): - listener() - - def add_image_listener( - self, image_key: str, update_callback: CALLBACK_TYPE - ) -> Callable[[], None]: - """Listen for image updates. - - These listeners will not be called on refresh. - """ - - @callback - def remove_listener() -> None: - """Remove update listener.""" - self._image_listeners[image_key].remove(update_callback) - if not self._image_listeners[image_key]: - del self._image_listeners[image_key] - - if image_key not in self._image_listeners: - self._image_listeners[image_key] = [] - self._image_listeners[image_key].append(update_callback) - - return remove_listener - def refreshed_too_often_recently(self) -> bool: """Check if the appliance data hasn't been refreshed too often recently.""" diff --git a/homeassistant/components/home_connect/image.py b/homeassistant/components/home_connect/image.py deleted file mode 100644 index 078c681ac1586..0000000000000 --- a/homeassistant/components/home_connect/image.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Image entity for Home Connect.""" - -from typing import override - -from aiohomeconnect.model.error import HomeConnectError - -from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription -from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from homeassistant.util import dt as dt_util - -from .common import setup_home_connect_entry -from .const import DOMAIN -from .coordinator import HomeConnectApplianceCoordinator, HomeConnectConfigEntry -from .utils import get_dict_from_home_connect_error - -PARALLEL_UPDATES = 1 - -IMAGES = ( - ImageEntityDescription( - key="Refrigeration.Common.EnumType.Compartment.Type.InteriorRightRC", - translation_key="interior_right_camera", - ), - ImageEntityDescription( - key="Refrigeration.Common.EnumType.Compartment.Type.DoorRightRC", - translation_key="door_right_camera", - ), -) - - -def _get_entities_for_appliance( - appliance_coordinator: HomeConnectApplianceCoordinator, -) -> list[HomeConnectImageEntity]: - """Get all entities for an appliance.""" - return [ - HomeConnectImageEntity(appliance_coordinator, desc) - for desc in IMAGES - if desc.key in appliance_coordinator.data.images - ] - - -async def async_setup_entry( - hass: HomeAssistant, - entry: HomeConnectConfigEntry, - async_add_entities: AddConfigEntryEntitiesCallback, -) -> None: - """Set up the Home Connect sensor.""" - setup_home_connect_entry( - hass, - entry, - _get_entities_for_appliance, - async_add_entities, - ) - - -class HomeConnectImageEntity(ImageEntity): - """Image class for Home Connect.""" - - _attr_has_entity_name = True - _last_image_key_fetched: str | None = None - - def __init__( - self, - appliance_coordinator: HomeConnectApplianceCoordinator, - desc: ImageEntityDescription, - ) -> None: - """Initialize the entity.""" - appliance_ha_id = appliance_coordinator.data.info.ha_id - super().__init__(appliance_coordinator.hass) - self.appliance = appliance_coordinator.data - self.entity_description = desc - self.appliance_coordinator = appliance_coordinator - self._attr_unique_id = f"{appliance_ha_id}-{desc.key}" - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, appliance_ha_id)}, - ) - - @override - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - await super().async_added_to_hass() - await self.async_fetch_image() - self.async_on_remove( - self.appliance_coordinator.add_image_listener( - self.entity_description.key, self._handle_coordinator_update - ) - ) - - async def async_update(self) -> None: - """Set the value of the image based on the given value.""" - await self.appliance_coordinator.update_images() - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - self.hass.async_create_task(self.async_fetch_image()) - - async def async_fetch_image(self) -> None: - """Fetch the image from the Home Connect API if it has changed since the last fetch.""" - image_info = self.appliance_coordinator.data.images[self.entity_description.key] - if image_info.image_key == self._last_image_key_fetched: - return - self._last_image_key_fetched = image_info.image_key - self._attr_image_last_updated = dt_util.utc_from_timestamp( - # It is not specified whether the timestamp is in seconds or milliseconds, - # so we check if it is larger than 10^10 (which would indicate milliseconds) - # and convert it to seconds if necessary. - image_info.timestamp / 1000 - if image_info.timestamp > 10_000_000_000 - else image_info.timestamp - ) - try: - image_data = await self.appliance_coordinator.client.get_image( - self.appliance.info.ha_id, image_key=image_info.image_key - ) - except HomeConnectError as err: - raise HomeAssistantError( - translation_domain=DOMAIN, - translation_key="fetch_image_error", - translation_placeholders=get_dict_from_home_connect_error(err), - ) from err - - self._cached_image = Image( - content_type="image/jpeg", - content=image_data, - ) - self.async_write_ha_state() diff --git a/homeassistant/components/home_connect/strings.json b/homeassistant/components/home_connect/strings.json index ff70c5c8afc70..a6d266a9a1ecc 100644 --- a/homeassistant/components/home_connect/strings.json +++ b/homeassistant/components/home_connect/strings.json @@ -32,16 +32,6 @@ "reauth_confirm": { "description": "The Home Connect integration needs to re-authenticate your account", "title": "[%key:common::config_flow::title::reauth%]" - }, - "scopes": { - "data": { - "images_scope": "Images scope" - }, - "data_description": { - "images_scope": "Allows Home Assistant to access images from your Home Connect devices." - }, - "description": "Select the optional scopes you want to enable for Home Connect authentication.", - "title": "Scopes" } } }, @@ -152,14 +142,6 @@ } } }, - "image": { - "door_right_camera": { - "name": "Door right camera" - }, - "interior_right_camera": { - "name": "Interior right camera" - } - }, "light": { "ambient_light": { "name": "Ambient light" @@ -1601,9 +1583,6 @@ "fetch_api_error": { "message": "Error obtaining data from the API: {error}" }, - "fetch_image_error": { - "message": "Error obtaining image from the API: {error}" - }, "fetch_program_error": { "message": "Error obtaining the selected or active program: {error}" }, diff --git a/tests/components/home_connect/conftest.py b/tests/components/home_connect/conftest.py index bcbc607fca740..97b71d205adb8 100644 --- a/tests/components/home_connect/conftest.py +++ b/tests/components/home_connect/conftest.py @@ -12,7 +12,6 @@ ArrayOfCommands, ArrayOfEvents, ArrayOfHomeAppliances, - ArrayOfImages, ArrayOfOptions, ArrayOfPrograms, ArrayOfSettings, @@ -79,7 +78,6 @@ def mock_token_entry(token_expiration_time: float) -> dict[str, Any]: "access_token": FAKE_ACCESS_TOKEN, "type": "Bearer", "expires_at": token_expiration_time, - "scope": "Control Monitor Images Settings IdentifyAppliance", } @@ -123,22 +121,6 @@ def mock_config_entry_v1_2(token_entry: dict[str, Any]) -> MockConfigEntry: ) -@pytest.fixture(name="config_entry_no_images_scope") -def mock_config_entry_no_image_scope(token_entry: dict[str, Any]) -> MockConfigEntry: - """Fixture for a config entry.""" - _token_entry = token_entry.copy() - _token_entry["scope"] = "Control Monitor Settings IdentifyAppliance" - return MockConfigEntry( - domain=DOMAIN, - data={ - "auth_implementation": FAKE_AUTH_IMPL, - "token": _token_entry, - }, - minor_version=3, - unique_id="1234567890", - ) - - @pytest.fixture(autouse=True) async def setup_credentials(hass: HomeAssistant) -> None: """Fixture to setup credentials.""" @@ -442,7 +424,6 @@ async def _get_available_commands_side_effect(ha_id: str) -> ArrayOfCommands: mock.get_settings = AsyncMock(side_effect=_get_settings_side_effect) mock.get_setting = AsyncMock(side_effect=_get_setting_side_effect) mock.get_status = AsyncMock(return_value=copy.deepcopy(MOCK_STATUS)) - mock.get_images = AsyncMock(return_value=ArrayOfImages([])) mock.get_all_programs = AsyncMock(side_effect=_get_all_programs_side_effect) mock.get_available_commands = AsyncMock( side_effect=_get_available_commands_side_effect @@ -506,7 +487,6 @@ async def stream_all_events() -> AsyncGenerator[EventMessage]: mock.get_settings = AsyncMock(side_effect=exception) mock.get_setting = AsyncMock(side_effect=exception) mock.get_status = AsyncMock(side_effect=exception) - mock.get_images = AsyncMock(side_effect=exception) mock.get_all_programs = AsyncMock(side_effect=exception) mock.get_available_commands = AsyncMock(side_effect=exception) mock.put_command = AsyncMock(side_effect=exception) diff --git a/tests/components/home_connect/test_config_flow.py b/tests/components/home_connect/test_config_flow.py index 1464be1b580c4..29afa27bea06c 100644 --- a/tests/components/home_connect/test_config_flow.py +++ b/tests/components/home_connect/test_config_flow.py @@ -3,7 +3,6 @@ from collections.abc import Awaitable, Callable from http import HTTPStatus from unittest.mock import MagicMock, patch -from urllib.parse import parse_qsl, urlsplit from aiohomeconnect.const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN from aiohomeconnect.model import HomeAppliance @@ -26,23 +25,6 @@ CLIENT_ID = "1234" CLIENT_SECRET = "5678" - -def assert_authorize_url(url: str, state: str, images_scope: bool | None) -> None: - """Assert the generated OAuth authorize URL.""" - split_url = urlsplit(url) - - assert ( - f"{split_url.scheme}://{split_url.netloc}{split_url.path}" == OAUTH2_AUTHORIZE - ) - assert dict(parse_qsl(split_url.query)) == { - "response_type": "code", - "client_id": CLIENT_ID, - "redirect_uri": "https://example.com/auth/external/callback", - "state": state, - "scope": f"Control Monitor Settings IdentifyAppliance{' Images' if images_scope else ''}", - } - - DHCP_DISCOVERY = ( DhcpServiceInfo( ip="1.1.1.1", @@ -113,14 +95,10 @@ def assert_authorize_url(url: str, state: str, images_scope: bool | None) -> Non @pytest.mark.usefixtures("current_request_with_host") -@pytest.mark.parametrize( - "images_scope", [True, False], ids=["images_scope", "no_images_scope"] -) async def test_full_flow( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, aioclient_mock: AiohttpClientMocker, - images_scope: bool, ) -> None: """Check full flow.""" assert await setup.async_setup_component(hass, "home_connect", {}) @@ -128,13 +106,6 @@ async def test_full_flow( result = await hass.config_entries.flow.async_init( DOMAIN, context=ConfigFlowContext(source=config_entries.SOURCE_USER) ) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "scopes" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={"images_scope": images_scope} - ) state = config_entry_oauth2_flow._encode_jwt( hass, { @@ -144,7 +115,11 @@ async def test_full_flow( ) assert result["type"] is FlowResultType.EXTERNAL_STEP - assert_authorize_url(result["url"], state, images_scope) + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) client = await hass_client_no_auth() resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") @@ -186,13 +161,6 @@ async def test_prevent_reconfiguring_same_account( result = await hass.config_entries.flow.async_init( DOMAIN, context=ConfigFlowContext(source=config_entries.SOURCE_USER) ) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "scopes" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={"images_scope": True} - ) state = config_entry_oauth2_flow._encode_jwt( hass, { @@ -202,7 +170,11 @@ async def test_prevent_reconfiguring_same_account( ) assert result["type"] is FlowResultType.EXTERNAL_STEP - assert_authorize_url(result["url"], state, True) + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) client = await hass_client_no_auth() resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") @@ -242,13 +214,6 @@ async def test_reauth_flow( assert result["step_id"] == "reauth_confirm" result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "scopes" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={"images_scope": False} - ) state = config_entry_oauth2_flow._encode_jwt( hass, { @@ -303,13 +268,6 @@ async def test_reauth_flow_with_different_account( assert result["step_id"] == "reauth_confirm" result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "scopes" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={"images_scope": True} - ) state = config_entry_oauth2_flow._encode_jwt( hass, { @@ -365,13 +323,6 @@ async def test_zeroconf_flow( result["flow_id"], {}, ) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "scopes" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={"images_scope": True} - ) state = config_entry_oauth2_flow._encode_jwt( hass, { @@ -381,7 +332,11 @@ async def test_zeroconf_flow( ) assert result["type"] is FlowResultType.EXTERNAL_STEP - assert_authorize_url(result["url"], state, True) + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) client = await hass_client_no_auth() resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") @@ -451,13 +406,6 @@ async def test_dhcp_flow( result["flow_id"], {}, ) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "scopes" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={"images_scope": True} - ) state = config_entry_oauth2_flow._encode_jwt( hass, { @@ -466,7 +414,11 @@ async def test_dhcp_flow( }, ) assert result["type"] is FlowResultType.EXTERNAL_STEP - assert_authorize_url(result["url"], state, True) + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) client = await hass_client_no_auth() resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") diff --git a/tests/components/home_connect/test_coordinator.py b/tests/components/home_connect/test_coordinator.py index 1ec6c03f0170d..e57ab22392906 100644 --- a/tests/components/home_connect/test_coordinator.py +++ b/tests/components/home_connect/test_coordinator.py @@ -77,7 +77,6 @@ "get_all_programs", "get_available_commands", "get_available_program", - "get_images", ] @@ -1100,27 +1099,3 @@ async def test_option_values_kept_after_changing_program( await hass.async_block_till_done() assert hass.states.is_state(entity_id, "on") - - -async def test_images_not_fetched_if_no_images_scope( - hass: HomeAssistant, - client: MagicMock, - config_entry_no_images_scope: MockConfigEntry, - platforms: list[str], -) -> None: - """Test that images are not fetched if the images scope is not granted.""" - config_entry_no_images_scope.add_to_hass(hass) - assert config_entry_no_images_scope.state is ConfigEntryState.NOT_LOADED - with ( - patch("homeassistant.components.home_connect.PLATFORMS", platforms), - patch("homeassistant.components.home_connect.HomeConnectClient") as client_mock, - ): - client_mock.return_value = client - assert await hass.config_entries.async_setup( - config_entry_no_images_scope.entry_id - ) - await hass.async_block_till_done() - - assert config_entry_no_images_scope.state is ConfigEntryState.LOADED - - client.get_images.assert_not_awaited() diff --git a/tests/components/home_connect/test_image.py b/tests/components/home_connect/test_image.py deleted file mode 100644 index 156a9002af662..0000000000000 --- a/tests/components/home_connect/test_image.py +++ /dev/null @@ -1,227 +0,0 @@ -"""Tests for home_connect image entities.""" - -from collections.abc import Awaitable, Callable -from unittest.mock import AsyncMock, MagicMock - -from aiohomeconnect.model import ArrayOfEvents, EventMessage, EventType, HomeAppliance -from aiohomeconnect.model.image import ArrayOfImages, Image -import pytest - -from homeassistant.components.home_connect.const import DOMAIN -from homeassistant.components.homeassistant import ( - DOMAIN as HA_DOMAIN, - SERVICE_UPDATE_ENTITY, -) -from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ATTR_ENTITY_ID, EntityStateAttribute, Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr, entity_registry as er -from homeassistant.setup import async_setup_component - -from tests.common import MockConfigEntry -from tests.typing import ClientSessionGenerator - - -@pytest.fixture -def platforms() -> list[Platform]: - """Fixture to specify platforms to test.""" - return [Platform.IMAGE] - - -@pytest.fixture(autouse=True) -def images(client: MagicMock) -> list[Image]: - """Fixture to inject and return the default image entities.""" - images = [ - Image( - key="Refrigeration.Common.EnumType.Compartment.Type.InteriorRightRC", - image_key="image_key_1", - preview_image_key="preview_image_key_1", - timestamp=1785974400000, - quality="good", - ), - Image( - key="Refrigeration.Common.EnumType.Compartment.Type.DoorRightRC", - image_key="image_key_2", - preview_image_key="preview_image_key_2", - timestamp=1785974400000, - quality="good", - ), - ] - client.get_images = AsyncMock(return_value=ArrayOfImages(images)) - return images - - -@pytest.mark.parametrize("appliance", ["FridgeFreezer"], indirect=True) -async def test_paired_depaired_devices_flow( - hass: HomeAssistant, - device_registry: dr.DeviceRegistry, - entity_registry: er.EntityRegistry, - client: MagicMock, - config_entry: MockConfigEntry, - integration_setup: Callable[[MagicMock], Awaitable[bool]], - appliance: HomeAppliance, -) -> None: - """Test device removal and re-addition on API events.""" - assert await integration_setup(client) - assert config_entry.state is ConfigEntryState.LOADED - - device = device_registry.async_get_device_by_identifier( - (DOMAIN, appliance.ha_id), config_entry.entry_id - ) - assert device - entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) - assert entity_entries - - await client.add_events( - [ - EventMessage( - appliance.ha_id, - EventType.DEPAIRED, - data=ArrayOfEvents([]), - ) - ] - ) - await hass.async_block_till_done() - - device = device_registry.async_get_device_by_identifier( - (DOMAIN, appliance.ha_id), config_entry.entry_id - ) - assert not device - for entity_entry in entity_entries: - assert not entity_registry.async_get(entity_entry.entity_id) - - # Now that all everything related to the device is removed, pair it again - await client.add_events( - [ - EventMessage( - appliance.ha_id, - EventType.PAIRED, - data=ArrayOfEvents([]), - ) - ] - ) - await hass.async_block_till_done() - - assert device_registry.async_get_device_by_identifier( - (DOMAIN, appliance.ha_id), config_entry.entry_id - ) - for entity_entry in entity_entries: - assert entity_registry.async_get(entity_entry.entity_id) - - -@pytest.mark.parametrize("appliance", ["FridgeFreezer"], indirect=True) -async def test_image_functionality( - hass: HomeAssistant, - client: MagicMock, - hass_client: ClientSessionGenerator, - config_entry: MockConfigEntry, - integration_setup: Callable[[MagicMock], Awaitable[bool]], - appliance: HomeAppliance, -) -> None: - """Test that the image entities use the correct image data.""" - image_data_dict = { - "image_key_1": b"image_data_1", - "image_key_2": b"image_data_2", - } - - async def mock_get_image(_: str, *, image_key: str) -> bytes: - return image_data_dict[image_key] - - client.get_image = AsyncMock(wraps=mock_get_image) - - assert await integration_setup(client) - assert config_entry.state is ConfigEntryState.LOADED - - client.get_images.assert_awaited_once_with(appliance.ha_id) - assert client.get_image.await_count == 2 - for image_key in image_data_dict: - client.get_image.assert_any_call(appliance.ha_id, image_key=image_key) - - _client = await hass_client() - for entity_id, expected_image_data in ( - ("image.fridgefreezer_interior_right_camera", b"image_data_1"), - ("image.fridgefreezer_door_right_camera", b"image_data_2"), - ): - state = hass.states.get(entity_id) - assert state - assert state.state == "2026-08-06T00:00:00+00:00" - - resp = await _client.get(state.attributes[EntityStateAttribute.ENTITY_PICTURE]) - assert resp.status == 200 - assert await resp.read() == expected_image_data - - -@pytest.mark.parametrize("appliance", ["FridgeFreezer"], indirect=True) -async def test_update_image_entity_functionality( - hass: HomeAssistant, - client: MagicMock, - hass_client: ClientSessionGenerator, - config_entry: MockConfigEntry, - integration_setup: Callable[[MagicMock], Awaitable[bool]], - appliance: HomeAppliance, -) -> None: - """Test that the image update correctly. - - Whenever an entity does update, the integration update any other from the same - appliance y there's an update available. - """ - image_data_dict = { - "image_key_1": b"image_data_1", - "image_key_2": b"image_data_2", - "image_key_3": b"image_data_3", - "image_key_4": b"image_data_4", - } - entity_id = "image.fridgefreezer_interior_right_camera" - - async def mock_get_image(_: str, *, image_key: str) -> bytes: - return image_data_dict[image_key] - - client.get_image = AsyncMock(wraps=mock_get_image) - - await async_setup_component(hass, HA_DOMAIN, {}) - assert await integration_setup(client) - assert config_entry.state is ConfigEntryState.LOADED - - client.get_images.assert_awaited_once_with(appliance.ha_id) - client.get_images.reset_mock() - client.get_image.reset_mock() - - client.get_images.return_value.images.extend( - [ - Image( - key="Refrigeration.Common.EnumType.Compartment.Type.InteriorRightRC", - image_key="image_key_3", - preview_image_key="preview_image_key_3", - timestamp=1785978000000, - quality="good", - ), - Image( - key="Refrigeration.Common.EnumType.Compartment.Type.DoorRightRC", - image_key="image_key_4", - preview_image_key="preview_image_key_4", - timestamp=1785978000000, - quality="good", - ), - ] - ) - await hass.services.async_call( - HA_DOMAIN, SERVICE_UPDATE_ENTITY, {ATTR_ENTITY_ID: entity_id}, blocking=True - ) - - client.get_images.assert_awaited_once_with(appliance.ha_id) - assert client.get_image.await_count == 2 - client.get_image.assert_any_await(appliance.ha_id, image_key="image_key_3") - client.get_image.assert_any_await(appliance.ha_id, image_key="image_key_4") - - for entity_id, expected_image_data in ( - ("image.fridgefreezer_interior_right_camera", b"image_data_3"), - ("image.fridgefreezer_door_right_camera", b"image_data_4"), - ): - state = hass.states.get(entity_id) - assert state - assert state.state == "2026-08-06T01:00:00+00:00" - - _client = await hass_client() - resp = await _client.get(state.attributes[EntityStateAttribute.ENTITY_PICTURE]) - assert resp.status == 200 - assert await resp.read() == expected_image_data From acf7a9e1c2484a3c9bbe7c7314f0a6acb6ac085b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Aug 2026 18:52:07 +0200 Subject: [PATCH 07/22] Use registry fixtures in tests (3/8) (#180923) --- tests/components/hassio/test_backup.py | 2 +- tests/components/hassio/test_init.py | 5 +++-- tests/components/heos/test_diagnostics.py | 2 +- .../components/homeassistant_hardware/test_switch.py | 2 +- tests/components/homekit_controller/test_init.py | 8 +++++--- tests/components/homematicip_cloud/test_sensor.py | 5 +++-- tests/components/homewizard/test_init.py | 12 ++++++------ tests/components/hypontech/test_sensor.py | 2 +- tests/components/imou/test_button.py | 6 ++++-- 9 files changed, 25 insertions(+), 19 deletions(-) diff --git a/tests/components/hassio/test_backup.py b/tests/components/hassio/test_backup.py index aabc926425633..f58f9ac4758d9 100644 --- a/tests/components/hassio/test_backup.py +++ b/tests/components/hassio/test_backup.py @@ -1122,6 +1122,7 @@ async def test_reader_writer_create( ) async def test_reader_writer_create_addon_folder_error( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, hass_supervisor_ws_client: WebSocketGenerator, freezer: FrozenDateTimeFactory, supervisor_client: AsyncMock, @@ -1146,7 +1147,6 @@ async def test_reader_writer_create_addon_folder_error( ), ] - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert not issue_registry.issues await client.send_json_auto_id({"type": "backup/subscribe_events"}) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 220f104f64e6e..e2adad92dcc96 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -970,7 +970,9 @@ async def test_invalid_service_calls_folder_duplicates(hass: HomeAssistant) -> N @pytest.mark.usefixtures("hassio_env") async def test_partial_backup_legacy_homeassistant_folder( - hass: HomeAssistant, supervisor_client: AsyncMock + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + supervisor_client: AsyncMock, ) -> None: """Test legacy "homeassistant" folder is translated to homeassistant=True.""" assert await async_setup_component(hass, DOMAIN, {}) @@ -991,7 +993,6 @@ async def test_partial_backup_legacy_homeassistant_folder( folders={Folder.SSL}, ) ) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert ( issue_registry.async_get_issue("hassio", "legacy_homeassistant_folder") is not None diff --git a/tests/components/heos/test_diagnostics.py b/tests/components/heos/test_diagnostics.py index 97eadff962a9c..ec17b0dfee874 100644 --- a/tests/components/heos/test_diagnostics.py +++ b/tests/components/heos/test_diagnostics.py @@ -64,6 +64,7 @@ async def test_config_entry_diagnostics_error_getting_system( async def test_device_diagnostics( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, hass_client: ClientSessionGenerator, config_entry: MockConfigEntry, snapshot: SnapshotAssertion, @@ -71,7 +72,6 @@ async def test_device_diagnostics( """Test generating diagnostics for a config entry.""" config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures device = device_registry.async_get_device_by_identifier( (DOMAIN, "1"), config_entry.entry_id ) diff --git a/tests/components/homeassistant_hardware/test_switch.py b/tests/components/homeassistant_hardware/test_switch.py index c36227b777485..bc65b853e54b8 100644 --- a/tests/components/homeassistant_hardware/test_switch.py +++ b/tests/components/homeassistant_hardware/test_switch.py @@ -181,6 +181,7 @@ async def test_switch_default_off_state( ) async def test_switch_restore_state( hass: HomeAssistant, + entity_registry: er.EntityRegistry, switch_config_entry: ConfigEntry, mock_firmware_client, initial_state: str, @@ -204,7 +205,6 @@ async def test_switch_restore_state( ] # Verify entity registry attributes - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures entity_entry = entity_registry.async_get(TEST_SWITCH_ENTITY_ID) assert entity_entry is not None assert entity_entry.entity_category == EntityCategory.CONFIG diff --git a/tests/components/homekit_controller/test_init.py b/tests/components/homekit_controller/test_init.py index 81ea2a686801a..6f8bf6e0932c8 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -243,7 +243,10 @@ async def get_characteristics(self, chars, *args, **kwargs): @pytest.mark.usefixtures("fake_ble_discovery", "fake_ble_pairing") async def test_ble_device_populates_connections( - hass: HomeAssistant, get_next_aid: Callable[[], int], controller + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + get_next_aid: Callable[[], int], + controller, ) -> None: """Test a BLE device populates connections in the device registry.""" aid = get_next_aid() @@ -260,9 +263,8 @@ async def test_ble_device_populates_connections( await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED - dev_reg = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert ( - dev_reg.async_get_device_by_connection( + device_registry.async_get_device_by_connection( ("bluetooth", "AA:BB:CC:DD:EE:FF"), config_entry.entry_id ) is not None diff --git a/tests/components/homematicip_cloud/test_sensor.py b/tests/components/homematicip_cloud/test_sensor.py index a6c5e73e9456b..94d5879164845 100644 --- a/tests/components/homematicip_cloud/test_sensor.py +++ b/tests/components/homematicip_cloud/test_sensor.py @@ -925,7 +925,9 @@ async def test_hmip_water_valve_water_volume_since_open( async def test_hmip_smoke_detector_dirt_level( - hass: HomeAssistant, default_mock_hap_factory: HomeFactory + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + default_mock_hap_factory: HomeFactory, ) -> None: """Test HomematicipSmokeDetectorDirtLevel.""" entity_id = "sensor.rauchwarnmelder_dirt_level" @@ -933,7 +935,6 @@ async def test_hmip_smoke_detector_dirt_level( device_model = "HmIP-SWSD" # Pre-register the entity as enabled before platform loads - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures entity_registry.async_get_or_create( "sensor", DOMAIN, diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index 51fc8cd4cd606..5a47827f67231 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -131,6 +131,7 @@ async def test_load_detect_invalid_token( @pytest.mark.usefixtures("mock_homewizardenergy") async def test_load_creates_repair_issue( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_config_entry: MockConfigEntry, mock_homewizardenergy: MagicMock, ) -> None: @@ -143,8 +144,6 @@ async def test_load_creates_repair_issue( await hass.async_block_till_done() - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - issue = issue_registry.async_get_issue( domain=DOMAIN, issue_id=f"migrate_to_v2_api_{mock_config_entry.entry_id}" ) @@ -157,6 +156,8 @@ async def test_load_creates_repair_issue( @pytest.mark.usefixtures("mock_homewizardenergy") async def test_load_creates_repair_issue_when_name_is_updated( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + issue_registry: ir.IssueRegistry, mock_config_entry: MockConfigEntry, mock_homewizardenergy: MagicMock, ) -> None: @@ -169,7 +170,6 @@ async def test_load_creates_repair_issue_when_name_is_updated( await hass.async_block_till_done() - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue_id = f"migrate_to_v2_api_{mock_config_entry.entry_id}" issue = issue_registry.async_get_issue(domain=DOMAIN, issue_id=issue_id) @@ -179,7 +179,6 @@ async def test_load_creates_repair_issue_when_name_is_updated( assert issue.translation_placeholders["title"] == "Device" # Update the device name - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures device = get_main_device(hass, mock_config_entry) # Update device name @@ -307,6 +306,7 @@ def _capture_issue_event(event: Event[ir.EventIssueRegistryUpdatedData]) -> None @pytest.mark.usefixtures("mock_homewizardenergy") async def test_battery_cloud_issue_stale_issue_cleared_on_reload( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_config_entry: MockConfigEntry, mock_homewizardenergy: MagicMock, ) -> None: @@ -319,12 +319,12 @@ async def test_battery_cloud_issue_stale_issue_cleared_on_reload( mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - assert ir.async_get(hass).async_get_issue(DOMAIN, issue_id) is not None # pylint: disable=home-assistant-tests-registry-fixtures + assert issue_registry.async_get_issue(DOMAIN, issue_id) is not None combined_data.system.cloud_enabled = True await hass.config_entries.async_reload(mock_config_entry.entry_id) await hass.async_block_till_done() - assert ir.async_get(hass).async_get_issue(DOMAIN, issue_id) is None # pylint: disable=home-assistant-tests-registry-fixtures + assert issue_registry.async_get_issue(DOMAIN, issue_id) is None async def test_main_device_registered_before_platform_forwarding( diff --git a/tests/components/hypontech/test_sensor.py b/tests/components/hypontech/test_sensor.py index 5bce4c7ceb8be..53b70c3cb47e0 100644 --- a/tests/components/hypontech/test_sensor.py +++ b/tests/components/hypontech/test_sensor.py @@ -30,6 +30,7 @@ async def test_sensors( async def test_device_manufacturer_uses_oem( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_hyponcloud: AsyncMock, ) -> None: """Test device manufacturer uses the selected OEM.""" @@ -46,7 +47,6 @@ async def test_device_manufacturer_uses_oem( with patch("homeassistant.components.hypontech._PLATFORMS", [Platform.SENSOR]): await setup_integration(hass, mock_config_entry) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures overview_device = device_registry.async_get_device_by_identifier( (DOMAIN, mock_config_entry.unique_id), mock_config_entry.entry_id ) diff --git a/tests/components/imou/test_button.py b/tests/components/imou/test_button.py index 4b8d8e2075e8d..142016d364183 100644 --- a/tests/components/imou/test_button.py +++ b/tests/components/imou/test_button.py @@ -52,11 +52,13 @@ async def test_button_entities_snapshot( @pytest.mark.usefixtures("init_integration") async def test_setup_ignores_unknown_button_types( hass: HomeAssistant, + entity_registry: er.EntityRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Unknown button keys from the API are not turned into entities.""" - registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - entries = er.async_entries_for_config_entry(registry, mock_config_entry.entry_id) + entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) assert len(entries) == 1 assert entries[0].translation_key == PARAM_MUTE From ff5bde37e91673313740460cebbad51e1afe0b15 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Aug 2026 18:52:21 +0200 Subject: [PATCH 08/22] Use registry fixtures in tests (4/8) (#180924) --- tests/components/imou/test_camera.py | 6 ++++-- tests/components/imou/test_init.py | 6 ++++-- tests/components/imou/test_switch.py | 6 ++++-- tests/components/integration/test_init.py | 10 ++++++---- tests/components/intent/test_temperature.py | 1 - tests/components/iss/test_sensor.py | 7 ++++--- tests/components/knx/test_repairs.py | 4 ++-- tests/components/knx/test_telegrams.py | 6 +++--- tests/components/kulersky/test_init.py | 6 +++--- tests/components/lamarzocco/test_init.py | 2 +- 10 files changed, 31 insertions(+), 23 deletions(-) diff --git a/tests/components/imou/test_camera.py b/tests/components/imou/test_camera.py index c269aab3fea46..f5bb225bf3bda 100644 --- a/tests/components/imou/test_camera.py +++ b/tests/components/imou/test_camera.py @@ -93,11 +93,13 @@ async def test_camera_entities_snapshot( @pytest.mark.usefixtures("init_integration") async def test_no_camera_without_channel( hass: HomeAssistant, + entity_registry: er.EntityRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Devices without a channel do not get a camera entity.""" - registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - entries = er.async_entries_for_config_entry(registry, mock_config_entry.entry_id) + entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) assert not any(entry.domain == "camera" for entry in entries) diff --git a/tests/components/imou/test_init.py b/tests/components/imou/test_init.py index 2f8027af5a8e8..f653971c621c0 100644 --- a/tests/components/imou/test_init.py +++ b/tests/components/imou/test_init.py @@ -63,11 +63,13 @@ async def test_setup_entry_failed_on_refresh( @pytest.mark.usefixtures("init_integration") async def test_device_registry_identifiers( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Device registry uses channel-aware identifiers from the default mock devices.""" - registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - devices = dr.async_entries_for_config_entry(registry, mock_config_entry.entry_id) + devices = dr.async_entries_for_config_entry( + device_registry, mock_config_entry.entry_id + ) assert len(devices) == 1 assert (DOMAIN, "d1") in devices[0].identifiers diff --git a/tests/components/imou/test_switch.py b/tests/components/imou/test_switch.py index fa6c5fc83a216..0d10e6c80664d 100644 --- a/tests/components/imou/test_switch.py +++ b/tests/components/imou/test_switch.py @@ -78,11 +78,13 @@ async def test_switch_entities_snapshot( @pytest.mark.usefixtures("init_integration") async def test_setup_ignores_unknown_switch_types( hass: HomeAssistant, + entity_registry: er.EntityRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Unknown switch keys from the API are not turned into entities.""" - registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - entries = er.async_entries_for_config_entry(registry, mock_config_entry.entry_id) + entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) switch_entries = [entry for entry in entries if entry.domain == SWITCH_DOMAIN] assert len(switch_entries) == 1 assert switch_entries[0].translation_key == PARAM_MOTION_DETECT diff --git a/tests/components/integration/test_init.py b/tests/components/integration/test_init.py index 8328c82bf4a88..1a33a219dc53e 100644 --- a/tests/components/integration/test_init.py +++ b/tests/components/integration/test_init.py @@ -148,12 +148,14 @@ async def test_setup_and_remove_config_entry( @pytest.mark.parametrize("platform", ["sensor"]) -async def test_entry_changed(hass: HomeAssistant, platform) -> None: +async def test_entry_changed( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + platform, +) -> None: """Test reconfiguring.""" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - def _create_mock_entity(domain: str, name: str) -> er.RegistryEntry: config_entry = MockConfigEntry( data={}, diff --git a/tests/components/intent/test_temperature.py b/tests/components/intent/test_temperature.py index 20513e29ef2e3..fa6d5db644988 100644 --- a/tests/components/intent/test_temperature.py +++ b/tests/components/intent/test_temperature.py @@ -183,7 +183,6 @@ async def test_get_temperature( # first floor => living room and office # 2nd floor => bedroom # 3rd floor => attic - floor_registry = fr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures first_floor = floor_registry.async_create("First floor") living_room_area = area_registry.async_update( living_room_area.id, floor_id=first_floor.floor_id diff --git a/tests/components/iss/test_sensor.py b/tests/components/iss/test_sensor.py index c03be24e4d8e9..00bf64704311c 100644 --- a/tests/components/iss/test_sensor.py +++ b/tests/components/iss/test_sensor.py @@ -56,16 +56,17 @@ async def test_sensor_attributes_show_on_map_true( async def test_sensor_device_info( - hass: HomeAssistant, init_integration: MockConfigEntry + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test sensor has correct device info.""" - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures entity = entity_registry.async_get("sensor.iss") assert entity is not None assert entity.unique_id == f"{init_integration.entry_id}_people" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures device = device_registry.async_get(entity.device_id) assert device is not None diff --git a/tests/components/knx/test_repairs.py b/tests/components/knx/test_repairs.py index 527ea4ae22251..a375efcc54568 100644 --- a/tests/components/knx/test_repairs.py +++ b/tests/components/knx/test_repairs.py @@ -38,6 +38,7 @@ async def test_create_fix_flow_raises_on_unknown_issue_id(hass: HomeAssistant) - async def test_data_secure_group_key_issue_only_for_configured_group_address( hass: HomeAssistant, knx: KNXTestKit, + issue_registry: ir.IssueRegistry, configured_group_address: str, ) -> None: """Test that repair issue is only created for configured group addresses.""" @@ -50,7 +51,6 @@ async def test_data_secure_group_key_issue_only_for_configured_group_address( } ) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert bool(issue_registry.issues) is False # An issue should only be created if this address is configured. knx.receive_data_secure_issue("1/2/5") @@ -62,6 +62,7 @@ async def test_data_secure_group_key_issue_repair_flow( hass_client: ClientSessionGenerator, hass_ws_client: WebSocketGenerator, knx: KNXTestKit, + issue_registry: ir.IssueRegistry, ) -> None: """Test repair flow for DataSecure group key issue.""" await knx.setup_integration( @@ -76,7 +77,6 @@ async def test_data_secure_group_key_issue_repair_flow( knx.receive_data_secure_issue("11/0/0", source="1.0.1") knx.receive_data_secure_issue("1/2/5", source="1.0.10") knx.receive_data_secure_issue("1/2/5", source="1.0.1") - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue(DOMAIN, REPAIR_ISSUE_DATA_SECURE_GROUP_KEY) assert issue is not None assert issue.translation_placeholders == { diff --git a/tests/components/knx/test_telegrams.py b/tests/components/knx/test_telegrams.py index c600c887a0c11..9f985accabebd 100644 --- a/tests/components/knx/test_telegrams.py +++ b/tests/components/knx/test_telegrams.py @@ -149,6 +149,7 @@ async def test_store_telegram_history_sqlite( async def test_store_telegram_history_error_handling( hass: HomeAssistant, knx: KNXTestKit, + issue_registry: ir.IssueRegistry, side_effect: Exception, ) -> None: """Test storage initialization handling for the different failure modes.""" @@ -162,7 +163,6 @@ async def test_store_telegram_history_error_handling( assert telegrams_module.store is None # Check that the repair issue was created - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue(DOMAIN, REPAIR_ISSUE_TELEGRAM_BACKEND_ERROR) assert issue is not None @@ -170,6 +170,7 @@ async def test_store_telegram_history_error_handling( async def test_store_telegram_history_needs_migration_timeout( hass: HomeAssistant, knx: KNXTestKit, + issue_registry: ir.IssueRegistry, ) -> None: """Test store init aborts when needs_migration times out and retries are off.""" @@ -191,7 +192,6 @@ async def hanging_probe() -> bool: assert telegrams_module.store is None # Check that the repair issue was created - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue(DOMAIN, REPAIR_ISSUE_TELEGRAM_BACKEND_ERROR) assert issue is not None @@ -695,6 +695,7 @@ async def test_nightly_eviction_error_handling( async def test_postgres_backend_init_error( hass: HomeAssistant, knx: KNXTestKit, + issue_registry: ir.IssueRegistry, ) -> None: """Test PostgreSQL backend DSN handling and init failure path.""" dsn = "postgresql://user:secret@db.local:5432/knx" @@ -721,7 +722,6 @@ async def test_postgres_backend_init_error( telegrams_module = hass.data[KNX_MODULE_KEY].telegrams assert telegrams_module.store is None - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert ( issue_registry.async_get_issue(DOMAIN, REPAIR_ISSUE_TELEGRAM_BACKEND_ERROR) is not None diff --git a/tests/components/kulersky/test_init.py b/tests/components/kulersky/test_init.py index b19081dfe84a8..24ce8485e3e88 100644 --- a/tests/components/kulersky/test_init.py +++ b/tests/components/kulersky/test_init.py @@ -11,6 +11,7 @@ async def test_migrate_entry( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, ) -> None: """Test migrate config entry from v1 to v2.""" @@ -22,14 +23,13 @@ async def test_migrate_entry( mock_config_entry_v1.add_to_hass(hass) - dev_reg = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # Create device registry entries for old integration - dev_reg.async_get_or_create( + device_registry.async_get_or_create( config_entry_id=mock_config_entry_v1.entry_id, identifiers={(DOMAIN, "AA:BB:CC:11:22:33")}, name="KuLight 1", ) - dev_reg.async_get_or_create( + device_registry.async_get_or_create( config_entry_id=mock_config_entry_v1.entry_id, identifiers={(DOMAIN, "AA:BB:CC:44:55:66")}, name="KuLight 2", diff --git a/tests/components/lamarzocco/test_init.py b/tests/components/lamarzocco/test_init.py index 93e670f812bb7..b8d474fe0b04e 100644 --- a/tests/components/lamarzocco/test_init.py +++ b/tests/components/lamarzocco/test_init.py @@ -220,6 +220,7 @@ async def test_websocket_closed_on_unload( ) async def test_gateway_version_issue( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mock_config_entry: MockConfigEntry, mock_cloud_client: MagicMock, version: str, @@ -232,7 +233,6 @@ async def test_gateway_version_issue( await async_init_integration(hass, mock_config_entry) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue(DOMAIN, "unsupported_gateway_firmware") assert (issue is not None) == issue_exists From b23e5c84e580c1ba583b9d28f95ce0f6477dfd77 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Aug 2026 18:55:54 +0200 Subject: [PATCH 09/22] Use registry fixtures in tests (2/8) (#180920) --- tests/components/doorbird/test_repairs.py | 6 +++--- tests/components/ecobee/test_climate.py | 15 +++++++++------ tests/components/ecovacs/test_vacuum.py | 3 ++- tests/components/esphome/test_entity.py | 3 +-- tests/components/esphome/test_manager.py | 14 +++++++------- tests/components/ezviz/test_init.py | 4 ++-- tests/components/fritz/test_button.py | 4 ++-- .../google_air_quality/test_services.py | 3 ++- tests/components/growatt_server/test_init.py | 2 +- 9 files changed, 29 insertions(+), 25 deletions(-) diff --git a/tests/components/doorbird/test_repairs.py b/tests/components/doorbird/test_repairs.py index b697c186ef66b..a9fafa3dc65d1 100644 --- a/tests/components/doorbird/test_repairs.py +++ b/tests/components/doorbird/test_repairs.py @@ -15,6 +15,7 @@ async def test_change_schedule_fails( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, doorbird_mocker: DoorbirdMockerType, hass_client: ClientSessionGenerator, ) -> None: @@ -24,9 +25,8 @@ async def test_change_schedule_fails( favorites_side_effect=mock_not_found_exception() ) assert doorbird_entry.entry.state is ConfigEntryState.SETUP_RETRY - issue_reg = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - assert len(issue_reg.issues) == 1 - issue = list(issue_reg.issues.values())[0] + assert len(issue_registry.issues) == 1 + issue = list(issue_registry.issues.values())[0] issue_id = issue.issue_id assert issue.domain == DOMAIN diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 83be62f31e05f..3ae859aafaa20 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -458,14 +458,15 @@ async def test_remote_sensors(hass: HomeAssistant) -> None: async def test_remote_sensor_devices( - hass: HomeAssistant, freezer: FrozenDateTimeFactory + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + freezer: FrozenDateTimeFactory, ) -> None: """Test remote sensor devices.""" await setup_platform(hass, [const.Platform.CLIMATE, const.Platform.SENSOR]) freezer.tick(100) async_fire_time_changed(hass) state = hass.states.get(ENTITY_ID) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures for device in device_registry.devices: if device.name == "Remote Sensor 1": remote_sensor_1_id = device.id @@ -545,7 +546,9 @@ async def test_remote_sensor_ids_names(hass: HomeAssistant) -> None: assert sorted(name_by_user_list) == sorted(["Remote Sensor 1", "ecobee"]) -async def test_remote_sensors_ignore_non_ecobee_devices(hass: HomeAssistant) -> None: +async def test_remote_sensors_ignore_non_ecobee_devices( + hass: HomeAssistant, device_registry: dr.DeviceRegistry +) -> None: """Devices from other integrations sharing a sensor's name are not matched. Regression test: the remote-sensor device lookup matched by device name across @@ -553,7 +556,6 @@ async def test_remote_sensors_ignore_non_ecobee_devices(hass: HomeAssistant) -> an ecobee sensor's name was wrongly reported as a participating sensor. """ await setup_platform(hass, [const.Platform.CLIMATE, const.Platform.SENSOR]) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # A device from another integration that shares the ecobee sensor's name. other_entry = MockConfigEntry(domain="other") @@ -579,11 +581,12 @@ async def test_remote_sensors_ignore_non_ecobee_devices(hass: HomeAssistant) -> assert sorted(name_by_user_list) == sorted(["Remote Sensor 1", "ecobee"]) -async def test_set_sensors_used_in_climate(hass: HomeAssistant) -> None: +async def test_set_sensors_used_in_climate( + hass: HomeAssistant, device_registry: dr.DeviceRegistry +) -> None: """Test set sensors used in climate.""" # Get device_id of remote sensor from the device registry. await setup_platform(hass, [const.Platform.CLIMATE, const.Platform.SENSOR]) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures for device in device_registry.devices: if device.name == "Remote Sensor 1": remote_sensor_1_id = device.id diff --git a/tests/components/ecovacs/test_vacuum.py b/tests/components/ecovacs/test_vacuum.py index 73783514e8ac5..2bccd3df0bb41 100644 --- a/tests/components/ecovacs/test_vacuum.py +++ b/tests/components/ecovacs/test_vacuum.py @@ -375,6 +375,7 @@ async def test_clean_area_room_from_not_current_map( async def test_raise_segment_changed_issue( hass: HomeAssistant, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, controller: EcovacsController, entity_id: str, events: tuple[Event, ...], @@ -390,7 +391,7 @@ async def test_raise_segment_changed_issue( entity_entry = entity_registry.async_get(entity_id) issue_id = f"{vacuum.ISSUE_SEGMENTS_CHANGED}_{entity_entry.id}" - issue = ir.async_get(hass).async_get_issue(vacuum.DOMAIN, issue_id) # pylint: disable=home-assistant-tests-registry-fixtures + issue = issue_registry.async_get_issue(vacuum.DOMAIN, issue_id) assert issue is not None diff --git a/tests/components/esphome/test_entity.py b/tests/components/esphome/test_entity.py index 40c1a8c78ee74..ef7ced5139544 100644 --- a/tests/components/esphome/test_entity.py +++ b/tests/components/esphome/test_entity.py @@ -747,13 +747,12 @@ async def test_deep_sleep_added_after_setup( async def test_entity_assignment_to_sub_device( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, mock_client: APIClient, mock_esphome_device: MockESPHomeDeviceType, ) -> None: """Test entities are assigned to correct sub devices.""" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - # Define sub devices sub_devices = [ SubDeviceInfo(device_id=11111111, name="Motion Sensor", area_id=0), diff --git a/tests/components/esphome/test_manager.py b/tests/components/esphome/test_manager.py index 566a15ff7a68a..8113a9a0e394f 100644 --- a/tests/components/esphome/test_manager.py +++ b/tests/components/esphome/test_manager.py @@ -2385,6 +2385,7 @@ async def test_entry_missing_bluetooth_mac_address( async def test_device_adds_friendly_name( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_client: APIClient, mock_esphome_device: MockESPHomeDeviceType, caplog: pytest.LogCaptureFixture, @@ -2395,8 +2396,7 @@ async def test_device_adds_friendly_name( device_info={"name": "nofriendlyname", "friendly_name": ""}, ) await hass.async_block_till_done() - dev_reg = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - dev = dev_reg.async_get_device_by_connection( + dev = device_registry.async_get_device_by_connection( (dr.CONNECTION_NETWORK_MAC, device.entry.unique_id), device.entry.entry_id ) assert dev.name == "Nofriendlyname" @@ -2418,7 +2418,7 @@ async def test_device_adds_friendly_name( ) await device.mock_connect() await hass.async_block_till_done() - dev = dev_reg.async_get_device_by_connection( + dev = device_registry.async_get_device_by_connection( (dr.CONNECTION_NETWORK_MAC, device.entry.unique_id), device.entry.entry_id ) assert dev.name == "I have a friendly name" @@ -2477,11 +2477,11 @@ async def test_assist_in_progress_issue_deleted( async def test_sub_device_creation( hass: HomeAssistant, area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, mock_client: APIClient, mock_esphome_device: MockESPHomeDeviceType, ) -> None: """Test sub devices are created in device registry.""" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # Define areas areas = [ @@ -2548,11 +2548,11 @@ async def test_sub_device_creation( async def test_sub_device_cleanup( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_client: APIClient, mock_esphome_device: MockESPHomeDeviceType, ) -> None: """Test sub devices are removed when they no longer exist.""" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # Initial sub devices sub_devices_initial = [ @@ -2645,11 +2645,11 @@ async def test_sub_device_cleanup( async def test_sub_device_with_empty_name( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, mock_client: APIClient, mock_esphome_device: MockESPHomeDeviceType, ) -> None: """Test sub devices with empty names are handled correctly.""" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # Define sub devices with empty names sub_devices = [ @@ -2690,11 +2690,11 @@ async def test_sub_device_with_empty_name( async def test_sub_device_references_main_device_area( hass: HomeAssistant, area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, mock_client: APIClient, mock_esphome_device: MockESPHomeDeviceType, ) -> None: """Test sub devices can reference the main device's area.""" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # Define areas - note we don't include area_id=0 in the areas list areas = [ diff --git a/tests/components/ezviz/test_init.py b/tests/components/ezviz/test_init.py index 2468225de4d04..856657f07ac96 100644 --- a/tests/components/ezviz/test_init.py +++ b/tests/components/ezviz/test_init.py @@ -87,6 +87,7 @@ async def test_last_alarm_pic_sensor_not_created( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_ezviz_client: AsyncMock, + entity_registry: er.EntityRegistry, ) -> None: """Test that last_alarm_pic sensor is not created.""" # Mock coordinator data with all sensor fields @@ -115,8 +116,7 @@ async def test_last_alarm_pic_sensor_not_created( assert last_alarm_pic_state is None # But other sensors should be created - registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - battery_entity = registry.async_get("sensor.camera_1_battery") + battery_entity = entity_registry.async_get("sensor.camera_1_battery") assert battery_entity is not None diff --git a/tests/components/fritz/test_button.py b/tests/components/fritz/test_button.py index d140571fe3eb2..35f908dc4da3e 100644 --- a/tests/components/fritz/test_button.py +++ b/tests/components/fritz/test_button.py @@ -271,6 +271,7 @@ async def test_cleanup_button( async def test_cleanup_button_deprecation_issue( hass: HomeAssistant, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, fc_class_mock, fh_class_mock, fs_class_mock, @@ -283,7 +284,6 @@ async def test_cleanup_button_deprecation_issue( await hass.async_block_till_done() assert entry.state is ConfigEntryState.LOADED - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert issue_registry.async_get_issue(DOMAIN, "deprecated_cleanup_button") @@ -291,6 +291,7 @@ async def test_cleanup_button_deprecation_issue( async def test_firmware_update_button_deprecation_issue( hass: HomeAssistant, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, fc_class_mock, fh_class_mock, fs_class_mock, @@ -303,5 +304,4 @@ async def test_firmware_update_button_deprecation_issue( await hass.async_block_till_done() assert entry.state is ConfigEntryState.LOADED - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert issue_registry.async_get_issue(DOMAIN, "deprecated_firmware_update_button") diff --git a/tests/components/google_air_quality/test_services.py b/tests/components/google_air_quality/test_services.py index b6d009a49b528..6a7a0436aafdf 100644 --- a/tests/components/google_air_quality/test_services.py +++ b/tests/components/google_air_quality/test_services.py @@ -26,9 +26,10 @@ async def test_get_forecast_service( mock_config_entry: MockConfigEntry, mock_api: AsyncMock, snapshot: SnapshotAssertion, + device_registry: dr.DeviceRegistry, ) -> None: """Test fetching a forecast for a subentry.""" - device = dr.async_get(hass).async_get_device_by_identifier( # pylint: disable=home-assistant-tests-registry-fixtures + device = device_registry.async_get_device_by_identifier( (DOMAIN, f"{mock_config_entry.entry_id}_home-subentry-id"), mock_config_entry.entry_id, ) diff --git a/tests/components/growatt_server/test_init.py b/tests/components/growatt_server/test_init.py index 2fa5f7781c25e..6618f25bb9153 100644 --- a/tests/components/growatt_server/test_init.py +++ b/tests/components/growatt_server/test_init.py @@ -834,6 +834,7 @@ async def test_dynamic_device_added( mock_growatt_v1_api, mock_config_entry: MockConfigEntry, device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, freezer: FrozenDateTimeFactory, ) -> None: """Test that new devices are dynamically added when discovered during a scan.""" @@ -881,7 +882,6 @@ async def test_dynamic_device_added( # Verify multiple entity types to confirm end-to-end dynamic device support assert hass.states.get("switch.new456789_charge_from_grid") is not None # Additional check: verify entities exist in the entity registry - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures new_device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, "NEW456789"), mock_config_entry.entry_id ) From df2e2cbdafccf30e0e6202368c204426038cc29e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Aug 2026 18:59:47 +0200 Subject: [PATCH 10/22] Use registry fixtures in tests (7/8) (#180930) --- tests/components/sonos/test_init.py | 8 ++++--- tests/components/splunk/test_init.py | 21 ++++++++++++------- .../components/squeezebox/test_config_flow.py | 6 ++++-- tests/components/subaru/test_init.py | 5 +++-- tests/components/synology_dsm/test_sensor.py | 14 +++++++------ tests/components/tag/test_event.py | 2 +- tests/components/tag/test_init.py | 7 +++++-- tests/components/template/test_vacuum.py | 5 +++-- tests/components/threshold/test_init.py | 10 +++++---- 9 files changed, 48 insertions(+), 30 deletions(-) diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index cf9e44771470a..bf06f2a76f2e9 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -98,7 +98,10 @@ async def test_not_configuring_sonos_not_creates_entry(hass: HomeAssistant) -> N async def test_upnp_disabled_discovery( - hass: HomeAssistant, config_entry: MockConfigEntry, soco: MockSoCo + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + config_entry: MockConfigEntry, + soco: MockSoCo, ) -> None: """Test issue creation when discovery processing fails with 403.""" @@ -116,7 +119,6 @@ async def test_upnp_disabled_discovery( assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done(wait_background_tasks=True) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert ( issue_registry.async_get_issue( sonos.DOMAIN, f"{UPNP_ISSUE_ID}_{soco.ip_address}" @@ -127,6 +129,7 @@ async def test_upnp_disabled_discovery( async def test_upnp_disabled_manual_hosts( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, soco_factory: SoCoMockFactory, ) -> None: """Test issue creation when manual host processing fails with 403.""" @@ -145,7 +148,6 @@ async def test_upnp_disabled_manual_hosts( ): await _setup_hass(hass) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue( sonos.DOMAIN, f"{UPNP_ISSUE_ID}_{soco.ip_address}" ) diff --git a/tests/components/splunk/test_init.py b/tests/components/splunk/test_init.py index f92a4021a2de7..981dab79dff9c 100644 --- a/tests/components/splunk/test_init.py +++ b/tests/components/splunk/test_init.py @@ -312,7 +312,9 @@ async def test_event_listener_error_handling( async def test_yaml_filter_only_no_deprecation_issue( - hass: HomeAssistant, mock_hass_splunk: AsyncMock + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + mock_hass_splunk: AsyncMock, ) -> None: """Test YAML with only filter does not create deprecation issue.""" assert await async_setup_component( @@ -334,7 +336,6 @@ async def test_yaml_filter_only_no_deprecation_issue( assert len(entries) == 0 # Verify no deprecation issue was created - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issues = issue_registry.issues assert not any( issue_id[0] == DOMAIN and "deprecated" in issue_id[1] for issue_id in issues @@ -347,7 +348,9 @@ async def test_yaml_filter_only_no_deprecation_issue( @pytest.mark.usefixtures("mock_setup_entry") async def test_yaml_with_connection_creates_deprecation_issue( - hass: HomeAssistant, mock_hass_splunk: AsyncMock + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + mock_hass_splunk: AsyncMock, ) -> None: """Test YAML with connection settings creates deprecation issue.""" assert await async_setup_component( @@ -370,12 +373,13 @@ async def test_yaml_with_connection_creates_deprecation_issue( assert entries[0].source == SOURCE_IMPORT # Verify deprecation issue was created in homeassistant domain - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert (HOMEASSISTANT_DOMAIN, f"deprecated_yaml_{DOMAIN}") in issue_registry.issues async def test_yaml_import_error_creates_specific_issue( - hass: HomeAssistant, mock_hass_splunk: AsyncMock + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + mock_hass_splunk: AsyncMock, ) -> None: """Test YAML import with connection error creates specific issue.""" # Config flow client fails connectivity check @@ -400,7 +404,6 @@ async def test_yaml_import_error_creates_specific_issue( assert len(entries) == 0 # Verify error-specific issue was created - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert ( DOMAIN, "deprecated_yaml_import_issue_cannot_connect", @@ -409,7 +412,10 @@ async def test_yaml_import_error_creates_specific_issue( @pytest.mark.usefixtures("mock_setup_entry") async def test_yaml_import_already_configured_creates_deprecation_issue( - hass: HomeAssistant, mock_hass_splunk: AsyncMock, mock_config_entry: MockConfigEntry + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + mock_hass_splunk: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test YAML import when already configured still creates deprecation issue.""" # Add existing config entry before YAML import @@ -432,5 +438,4 @@ async def test_yaml_import_already_configured_creates_deprecation_issue( await hass.async_block_till_done() # Verify deprecation issue was still created (single_instance_allowed is ok) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert (HOMEASSISTANT_DOMAIN, f"deprecated_yaml_{DOMAIN}") in issue_registry.issues diff --git a/tests/components/squeezebox/test_config_flow.py b/tests/components/squeezebox/test_config_flow.py index d64da72127c22..97c464babf9d7 100644 --- a/tests/components/squeezebox/test_config_flow.py +++ b/tests/components/squeezebox/test_config_flow.py @@ -496,12 +496,14 @@ async def test_dhcp_unknown_player( async def test_dhcp_known_player( - hass: HomeAssistant, dhcp_info: dict[str, Any], mock_config_entry: MockConfigEntry + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + dhcp_info: dict[str, Any], + mock_config_entry: MockConfigEntry, ) -> None: """Test DHCP discovery aborts if player is already registered.""" mock_config_entry.add_to_hass(hass) - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures entity_registry.async_get_or_create( MP_DOMAIN, DOMAIN, "aa:bb:cc:dd:ee:ff", config_entry=mock_config_entry ) diff --git a/tests/components/subaru/test_init.py b/tests/components/subaru/test_init.py index afca01311223b..7d8c7ad64419f 100644 --- a/tests/components/subaru/test_init.py +++ b/tests/components/subaru/test_init.py @@ -60,7 +60,9 @@ async def test_setup_g3(hass: HomeAssistant, subaru_config_entry) -> None: assert check_entry.state is ConfigEntryState.LOADED -async def test_setup_g4(hass: HomeAssistant, subaru_config_entry) -> None: +async def test_setup_g4( + hass: HomeAssistant, entity_registry: er.EntityRegistry, subaru_config_entry +) -> None: """Test setup with a G4 vehicle (2026+ models report api_gen "g4").""" await setup_subaru_config_entry( hass, @@ -74,7 +76,6 @@ async def test_setup_g4(hass: HomeAssistant, subaru_config_entry) -> None: assert check_entry.state is ConfigEntryState.LOADED # Gen4 must receive both Gen2+ and Gen3+ sensor sets; without this, only # the odometer was created on 2026 model year vehicles. - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert entity_registry.async_get_entity_id( "sensor", DOMAIN, f"{TEST_VIN_4_G4}_AVG_FUEL_CONSUMPTION" ) diff --git a/tests/components/synology_dsm/test_sensor.py b/tests/components/synology_dsm/test_sensor.py index aa4fc76141451..ccb687a2d0e20 100644 --- a/tests/components/synology_dsm/test_sensor.py +++ b/tests/components/synology_dsm/test_sensor.py @@ -396,11 +396,11 @@ async def test_uptime_sensor( async def test_hub_device_info_mac_connections( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, setup_dsm_with_usb: MagicMock, ) -> None: """Test that the hub DeviceInfo includes MAC address connections.""" - dev_reg = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - device = dev_reg.async_get_device_by_identifier( + device = device_registry.async_get_device_by_identifier( (DOMAIN, SERIAL), setup_dsm_with_usb.mock_entry.entry_id ) assert device is not None @@ -412,21 +412,23 @@ async def test_hub_device_info_mac_connections( async def test_storage_device_via_device( hass: HomeAssistant, + device_registry: dr.DeviceRegistry, setup_dsm_with_usb: MagicMock, ) -> None: """Test that storage/USB child devices link to the hub via via_device_id.""" - dev_reg = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures entry_id = setup_dsm_with_usb.mock_entry.entry_id - hub_device = dev_reg.async_get_device_by_identifier((DOMAIN, SERIAL), entry_id) + hub_device = device_registry.async_get_device_by_identifier( + (DOMAIN, SERIAL), entry_id + ) assert hub_device is not None - volume_device = dev_reg.async_get_device_by_identifier( + volume_device = device_registry.async_get_device_by_identifier( (DOMAIN, f"{SERIAL}_volume_1"), entry_id ) assert volume_device is not None assert volume_device.via_device_id == hub_device.id - usb_partition_device = dev_reg.async_get_device_by_identifier( + usb_partition_device = device_registry.async_get_device_by_identifier( (DOMAIN, f"{SERIAL}_USB Disk 1 Partition 1"), entry_id ) assert usb_partition_device is not None diff --git a/tests/components/tag/test_event.py b/tests/components/tag/test_event.py index 0a9bd63199897..6a3566ef10930 100644 --- a/tests/components/tag/test_event.py +++ b/tests/components/tag/test_event.py @@ -20,6 +20,7 @@ @pytest.fixture def storage_setup_named_tag( hass: HomeAssistant, + entity_registry: er.EntityRegistry, hass_storage: dict[str, Any], ): """Storage setup for test case of named tags.""" @@ -41,7 +42,6 @@ async def _storage(items=None): } else: hass_storage[DOMAIN] = items - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures entry = entity_registry.async_get_or_create(DOMAIN, DOMAIN, TEST_TAG_ID) entity_registry.async_update_entity(entry.entity_id, name=TEST_TAG_NAME) config = {DOMAIN: {}} diff --git a/tests/components/tag/test_init.py b/tests/components/tag/test_init.py index 9b638fee201ca..6a3467f92d5fd 100644 --- a/tests/components/tag/test_init.py +++ b/tests/components/tag/test_init.py @@ -22,7 +22,11 @@ @pytest.fixture -def storage_setup(hass: HomeAssistant, hass_storage: dict[str, Any]): +def storage_setup( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + hass_storage: dict[str, Any], +): """Storage setup.""" async def _storage(items=None): @@ -44,7 +48,6 @@ async def _storage(items=None): } else: hass_storage[DOMAIN] = items - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures _create_entry(entity_registry, TEST_TAG_ID, TEST_TAG_NAME) _create_entry(entity_registry, TEST_TAG_ID_2, TEST_TAG_NAME_2) config = {DOMAIN: {}} diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index 4621a6304ddd1..aef844ce6e9ee 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -1141,7 +1141,9 @@ async def test_invalid_segments( ) @pytest.mark.usefixtures("setup_vacuum") async def test_raise_segments_changed_issue( - hass: HomeAssistant, entity_registry: er.EntityRegistry + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, ) -> None: """Test that issue is raised on segments change.""" hass.states.async_set(TEST_ATTRIBUTE_ENTITY_ID, "Bedroom") @@ -1160,7 +1162,6 @@ async def test_raise_segments_changed_issue( hass.states.async_set(TEST_ATTRIBUTE_ENTITY_ID, "Bathroom") await hass.async_block_till_done() - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert len(issue_registry.issues) != 0 diff --git a/tests/components/threshold/test_init.py b/tests/components/threshold/test_init.py index ceafee7c78b6a..61e90e00735c9 100644 --- a/tests/components/threshold/test_init.py +++ b/tests/components/threshold/test_init.py @@ -146,12 +146,14 @@ async def test_setup_and_remove_config_entry( @pytest.mark.parametrize("platform", ["sensor"]) -async def test_entry_changed(hass: HomeAssistant, platform) -> None: +async def test_entry_changed( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + platform, +) -> None: """Test reconfiguring.""" - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - def _create_mock_entity(domain: str, name: str) -> er.RegistryEntry: config_entry = MockConfigEntry( data={}, From e85d14fc1186cbe2d8e2e91b2cf334eeac25cc45 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Aug 2026 19:00:10 +0200 Subject: [PATCH 11/22] Use registry fixtures in tests (5/8) (#180926) --- tests/components/logbook/test_init.py | 19 +++++++++---------- tests/components/lovelace/test_dashboard.py | 5 +++-- tests/components/lutron/test_init.py | 15 +++++++++------ tests/components/matter/test_vacuum.py | 8 ++++---- tests/components/mqtt/test_init.py | 2 +- tests/components/mqtt/test_repairs.py | 2 +- tests/components/mqtt/test_vacuum.py | 4 ++-- tests/components/music_assistant/test_init.py | 8 ++++---- .../niko_home_control/test_scene.py | 3 ++- tests/components/nut/test_init.py | 6 ------ 10 files changed, 35 insertions(+), 37 deletions(-) diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 979db3eea4e60..a35d28c90e46e 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -183,12 +183,12 @@ async def test_service_call_create_log_book_entry_no_message( async def test_filter_sensor( - hass_: HomeAssistant, hass_client: ClientSessionGenerator + hass_: HomeAssistant, + entity_registry: er.EntityRegistry, + hass_client: ClientSessionGenerator, ) -> None: """Test numeric sensors are filtered.""" - registry = er.async_get(hass_) # pylint: disable=home-assistant-tests-registry-fixtures - # Unregistered sensor without a unit of measurement - should be in logbook entity_id1 = "sensor.bla" attributes_1 = None @@ -196,7 +196,7 @@ async def test_filter_sensor( entity_id2 = "sensor.blu" attributes_2 = {ATTR_UNIT_OF_MEASUREMENT: "cats"} # Registered sensor with state class - should be excluded from logbook - entity_id3 = registry.async_get_or_create( + entity_id3 = entity_registry.async_get_or_create( "sensor", "test", "unique_3", @@ -205,7 +205,7 @@ async def test_filter_sensor( ).entity_id attributes_3 = None # Registered sensor without state class or unit - should be in logbook - entity_id4 = registry.async_get_or_create( + entity_id4 = entity_registry.async_get_or_create( "sensor", "test", "unique_4", suggested_object_id="ble" ).entity_id attributes_4 = None @@ -3196,6 +3196,7 @@ async def test_logbook_user_id_from_parent_context_state_changes_only( async def test_context_user_ids_lru_eviction( hass: HomeAssistant, + entity_registry: er.EntityRegistry, ) -> None: """Test that the parent context user-id cache is bounded by LRU eviction. @@ -3225,13 +3226,12 @@ async def test_context_user_ids_lru_eviction( for_live_stream=True, ) context_augmenter = logbook.processor.ContextAugmenter(logbook_run) - ent_reg = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures processor = logbook.processor.EventProcessor.__new__( logbook.processor.EventProcessor ) processor.hass = hass - processor.ent_reg = ent_reg + processor.ent_reg = entity_registry processor.logbook_run = logbook_run processor.context_augmenter = context_augmenter @@ -3303,6 +3303,7 @@ async def test_context_user_ids_lru_eviction( async def test_parent_user_attribution_does_not_use_origin_event_fallback( hass: HomeAssistant, + entity_registry: er.EntityRegistry, ) -> None: """Test that parent context lookup doesn't fall back to origin_event. @@ -3353,13 +3354,11 @@ async def test_parent_user_attribution_does_not_use_origin_event_fallback( memoize_new_contexts=False, ) context_augmenter = logbook.processor.ContextAugmenter(logbook_run) - ent_reg = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - processor = logbook.processor.EventProcessor.__new__( logbook.processor.EventProcessor ) processor.hass = hass - processor.ent_reg = ent_reg + processor.ent_reg = entity_registry processor.logbook_run = logbook_run processor.context_augmenter = context_augmenter diff --git a/tests/components/lovelace/test_dashboard.py b/tests/components/lovelace/test_dashboard.py index ad90fa9197937..7491336371bfa 100644 --- a/tests/components/lovelace/test_dashboard.py +++ b/tests/components/lovelace/test_dashboard.py @@ -363,7 +363,9 @@ async def test_lovelace_from_yaml( async def test_lovelace_from_yaml_creates_repair_issue( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + hass_ws_client: WebSocketGenerator, ) -> None: """Test YAML mode creates a repair issue.""" assert await async_setup_component(hass, DOMAIN, {"lovelace": {"mode": "YAML"}}) @@ -372,7 +374,6 @@ async def test_lovelace_from_yaml_creates_repair_issue( assert hass.data[frontend.DATA_PANELS]["lovelace"].config == {"mode": "yaml"} # Repair issue should be created - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue = issue_registry.async_get_issue("lovelace", "yaml_mode_deprecated") assert issue is not None assert issue.severity == ir.IssueSeverity.WARNING diff --git a/tests/components/lutron/test_init.py b/tests/components/lutron/test_init.py index a21cc038534f9..f075f8381594f 100644 --- a/tests/components/lutron/test_init.py +++ b/tests/components/lutron/test_init.py @@ -16,7 +16,10 @@ async def test_setup_entry( - hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_lutron: MagicMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test setting up the integration.""" mock_config_entry.add_to_hass(hass) @@ -29,7 +32,6 @@ async def test_setup_entry( # Verify that the unique ID is generated correctly. # This prevents regression in unique ID generation which would be a breaking change. - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # The light from mock_lutron has uuid="light_uuid" and guid="12345678901" expected_unique_id = "12345678901_light_uuid" entry = entity_registry.async_get("light.test_area_test_light") @@ -68,7 +70,11 @@ async def test_setup_entry_not_ready( async def test_unique_id_migration( - hass: HomeAssistant, mock_lutron: MagicMock, mock_config_entry: MockConfigEntry + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + mock_lutron: MagicMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test migration of legacy unique IDs to the newer UUID-based format. @@ -81,9 +87,6 @@ async def test_unique_id_migration( # Setup registries with an entry using the "legacy" unique ID format. # This simulates a user who had configured the integration in an older version. - entity_registry = er.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - legacy_unique_id = "12345678901_light_legacy_uuid" new_unique_id = "12345678901_light_uuid" diff --git a/tests/components/matter/test_vacuum.py b/tests/components/matter/test_vacuum.py index 1612b430648ef..211803f9a6d4e 100644 --- a/tests/components/matter/test_vacuum.py +++ b/tests/components/matter/test_vacuum.py @@ -480,6 +480,7 @@ async def test_vacuum_clean_area_select_areas_failure( async def test_vacuum_no_issue_on_transient_empty_segments( hass: HomeAssistant, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, matter_client: MagicMock, matter_node: MatterNode, ) -> None: @@ -506,8 +507,7 @@ async def test_vacuum_no_issue_on_transient_empty_segments( set_node_attribute(matter_node, 1, 336, 0, []) await trigger_subscription_callback(hass, matter_client) - issue_reg = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - issue = issue_reg.async_get_issue( + issue = issue_registry.async_get_issue( VACUUM_DOMAIN, f"segments_changed_{entity_entry.id}" ) assert issue is None @@ -517,6 +517,7 @@ async def test_vacuum_no_issue_on_transient_empty_segments( async def test_vacuum_raise_segments_changed_issue( hass: HomeAssistant, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, matter_client: MagicMock, matter_node: MatterNode, ) -> None: @@ -542,8 +543,7 @@ async def test_vacuum_raise_segments_changed_issue( set_node_attribute(matter_node, 1, 97, 4, 0x02) await trigger_subscription_callback(hass, matter_client) - issue_reg = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - issue = issue_reg.async_get_issue( + issue = issue_registry.async_get_issue( VACUUM_DOMAIN, f"segments_changed_{entity_entry.id}" ) assert issue is not None diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 804b433f72642..20e035b7b3b1b 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -2600,6 +2600,7 @@ async def test_mqtt_protocol_successful_migration_to_v5( ) async def test_mqtt_protocol_failed_migration_to_v5( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mqtt_mock_entry: MqttMockHAClientGenerator, caplog: pytest.LogCaptureFixture, current_protocol: str, @@ -2613,7 +2614,6 @@ async def test_mqtt_protocol_failed_migration_to_v5( assert len(events) == 1 assert events[0].data["issue_id"] == "protocol_5_migration" - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert len(issue_registry.issues) == 1 issue = issue_registry.async_get_issue(DOMAIN, "protocol_5_migration") assert issue is not None diff --git a/tests/components/mqtt/test_repairs.py b/tests/components/mqtt/test_repairs.py index c994398dcbeee..4140cbe0df4c7 100644 --- a/tests/components/mqtt/test_repairs.py +++ b/tests/components/mqtt/test_repairs.py @@ -69,6 +69,7 @@ async def test_subentry_reconfigure_export_settings( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, device_registry: dr.DeviceRegistry, + issue_registry: ir.IssueRegistry, hass_client: ClientSessionGenerator, flow_step: str, setup_helper: Coroutine[Any, Any, None], @@ -144,7 +145,6 @@ async def test_subentry_reconfigure_export_settings( # The subentry ID is used as device identifier assert len(events) == 1 issue_id = events[0].data["issue_id"] - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures repair_issue = issue_registry.async_get_issue(DOMAIN, issue_id) assert repair_issue.translation_key == translation_key diff --git a/tests/components/mqtt/test_vacuum.py b/tests/components/mqtt/test_vacuum.py index 02845691c5d03..c8a9d88ea898d 100644 --- a/tests/components/mqtt/test_vacuum.py +++ b/tests/components/mqtt/test_vacuum.py @@ -330,6 +330,7 @@ async def test_command_without_command_topic( @pytest.mark.parametrize("hass_config", [CONFIG_CLEAN_SEGMENTS]) async def test_clean_segments_initial_setup_without_repair_issue( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test setup does not fire repair after segments are received.""" @@ -355,7 +356,6 @@ async def test_clean_segments_initial_setup_without_repair_issue( state.attributes.get(ATTR_SUPPORTED_FEATURES) & vacuum.VacuumEntityFeature.CLEAN_AREA ) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures assert len(issue_registry.issues) == 0 @@ -364,6 +364,7 @@ async def test_clean_segments_command( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test cleaning segments and repair flow.""" @@ -405,7 +406,6 @@ async def test_clean_segments_command( & vacuum.VacuumEntityFeature.CLEAN_AREA ) - issue_registry = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures # We do not expect a repair flow as the segments did not change assert len(issue_registry.issues) == 0 diff --git a/tests/components/music_assistant/test_init.py b/tests/components/music_assistant/test_init.py index f5af4fdc03564..51496fad19551 100644 --- a/tests/components/music_assistant/test_init.py +++ b/tests/components/music_assistant/test_init.py @@ -165,6 +165,7 @@ async def test_player_config_expose_to_ha_toggle( async def test_authentication_required_triggers_reauth( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, music_assistant_client: MagicMock, ) -> None: """Test that AuthenticationRequired exception triggers reauth flow.""" @@ -185,13 +186,13 @@ async def test_authentication_required_triggers_reauth( assert config_entry.state is ConfigEntryState.SETUP_ERROR - issue_reg = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue_id = f"config_entry_reauth_{DOMAIN}_{config_entry.entry_id}" - assert issue_reg.async_get_issue("homeassistant", issue_id) + assert issue_registry.async_get_issue("homeassistant", issue_id) async def test_authentication_required_addon_no_reauth( hass: HomeAssistant, + issue_registry: ir.IssueRegistry, music_assistant_client: MagicMock, ) -> None: """Test that AuthenticationRequired exception does not trigger reauth for addon.""" @@ -214,6 +215,5 @@ async def test_authentication_required_addon_no_reauth( assert config_entry.state is ConfigEntryState.SETUP_ERROR - issue_reg = ir.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures issue_id = f"config_entry_reauth_{DOMAIN}_{config_entry.entry_id}" - assert issue_reg.async_get_issue("homeassistant", issue_id) is None + assert issue_registry.async_get_issue("homeassistant", issue_id) is None diff --git a/tests/components/niko_home_control/test_scene.py b/tests/components/niko_home_control/test_scene.py index 96d3ad9e7334c..38ffa5416cc66 100644 --- a/tests/components/niko_home_control/test_scene.py +++ b/tests/components/niko_home_control/test_scene.py @@ -54,6 +54,7 @@ async def test_activate_scene( async def test_updating( hass: HomeAssistant, + entity_registry: er.EntityRegistry, mock_niko_home_control_connection: AsyncMock, mock_config_entry: MockConfigEntry, scene: AsyncMock, @@ -63,7 +64,7 @@ async def test_updating( # Resolve the created scene entity dynamically entity_entries = er.async_entries_for_config_entry( - er.async_get(hass), # pylint: disable=home-assistant-tests-registry-fixtures + entity_registry, mock_config_entry.entry_id, ) scene_entities = [e for e in entity_entries if e.domain == SCENE_DOMAIN] diff --git a/tests/components/nut/test_init.py b/tests/components/nut/test_init.py index ddbe64e96ea88..482f81dc69913 100644 --- a/tests/components/nut/test_init.py +++ b/tests/components/nut/test_init.py @@ -104,9 +104,6 @@ async def test_remove_device_valid( list_commands_return_value=[], ) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - assert device_registry is not None - device_entry = device_registry.async_get_device_by_identifier( (DOMAIN, mock_serial_number), config_entry.entry_id ) @@ -137,9 +134,6 @@ async def test_remove_device_stale( list_commands_return_value=[], ) - device_registry = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures - assert device_registry is not None - device_entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, "remove-device-id")}, From 73f184ccd22dcf04dfa47efdb2931f7de633cad7 Mon Sep 17 00:00:00 2001 From: darkrain-nl <24763370+darkrain-nl@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:17:20 +0200 Subject: [PATCH 12/22] Bump sofar-modbus to 0.8.0 (#180935) --- homeassistant/components/sofar/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sofar/manifest.json b/homeassistant/components/sofar/manifest.json index e05b884e03fc7..ed8862c8f1363 100644 --- a/homeassistant/components/sofar/manifest.json +++ b/homeassistant/components/sofar/manifest.json @@ -8,5 +8,5 @@ "integration_type": "device", "iot_class": "local_polling", "quality_scale": "silver", - "requirements": ["sofar-modbus==0.7.1"] + "requirements": ["sofar-modbus==0.8.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index f917001f5855f..699f603bd4db4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3111,7 +3111,7 @@ snapcast==2.3.8 soco==0.31.2 # homeassistant.components.sofar -sofar-modbus==0.7.1 +sofar-modbus==0.8.0 # homeassistant.components.solaredge_local solaredge-local==0.2.3 From 1bdf472ac8cd61ce8f1cc8aa3c15f4276865c2bd Mon Sep 17 00:00:00 2001 From: Jordan Harvey Date: Mon, 31 Aug 2026 18:59:35 +0100 Subject: [PATCH 13/22] Add player reports for Nintendo Parental Controls (#180937) --- .../nintendo_parental_controls/icons.json | 6 + .../nintendo_parental_controls/services.py | 92 +++++++++++- .../nintendo_parental_controls/services.yaml | 23 +++ .../nintendo_parental_controls/strings.json | 32 +++- .../nintendo_parental_controls/conftest.py | 4 +- .../nintendo_parental_controls/const.py | 2 + .../snapshots/test_services.ambr | 25 ++++ .../test_services.py | 138 +++++++++++++++++- 8 files changed, 309 insertions(+), 13 deletions(-) create mode 100644 tests/components/nintendo_parental_controls/snapshots/test_services.ambr diff --git a/homeassistant/components/nintendo_parental_controls/icons.json b/homeassistant/components/nintendo_parental_controls/icons.json index b1b4a03b06566..b8f96be3ff63b 100644 --- a/homeassistant/components/nintendo_parental_controls/icons.json +++ b/homeassistant/components/nintendo_parental_controls/icons.json @@ -3,6 +3,12 @@ "add_bonus_time": { "service": "mdi:timer-plus-outline" }, + "device_usage_report": { + "service": "mdi:chart-box-outline" + }, + "player_usage_report": { + "service": "mdi:account-clock-outline" + }, "update_pin_code": { "service": "mdi:lock-open-outline" } diff --git a/homeassistant/components/nintendo_parental_controls/services.py b/homeassistant/components/nintendo_parental_controls/services.py index f1b1ab7bbe10a..5a7d21bcd6c7e 100644 --- a/homeassistant/components/nintendo_parental_controls/services.py +++ b/homeassistant/components/nintendo_parental_controls/services.py @@ -4,15 +4,23 @@ import logging from pynintendoparental.device import Device +from pynintendoparental.enum import SafeLaunchSetting +from pynintendoparental.player import Player import voluptuous as vol -from homeassistant.const import ATTR_DEVICE_ID, CONF_PIN -from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_PIN +from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback from homeassistant.exceptions import ServiceValidationError -from homeassistant.helpers import config_validation as cv, service +from homeassistant.helpers import ( + config_validation as cv, + entity_registry as er, + service, +) +from homeassistant.util.json import JsonValueType from .const import ATTR_BONUS_TIME, DOMAIN from .coordinator import NintendoParentalControlsConfigEntry +from .sensor import NintendoParentalControlsSensor _LOGGER = logging.getLogger(__name__) @@ -22,6 +30,8 @@ class NintendoParentalServices(StrEnum): ADD_BONUS_TIME = "add_bonus_time" UPDATE_PIN_CODE = "update_pin_code" + PLAYER_USAGE_REPORT = "player_usage_report" + DEVICE_USAGE_REPORT = "device_usage_report" @callback @@ -40,6 +50,29 @@ def async_setup_services( } ), ) + hass.services.async_register( + domain=DOMAIN, + service=NintendoParentalServices.PLAYER_USAGE_REPORT, + service_func=async_get_player_usage, + supports_response=SupportsResponse.ONLY, + schema=vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + vol.Required(ATTR_ENTITY_ID): cv.string, + } + ), + ) + hass.services.async_register( + domain=DOMAIN, + service=NintendoParentalServices.DEVICE_USAGE_REPORT, + service_func=async_get_device_usage_report, + supports_response=SupportsResponse.ONLY, + schema=vol.Schema( + { + vol.Required(ATTR_DEVICE_ID): cv.string, + } + ), + ) service.async_register_admin_service( hass, DOMAIN, @@ -76,6 +109,39 @@ def _get_nintendo_device(hass: HomeAssistant, device_id: str) -> Device: ) +def _get_nintendo_player(hass: HomeAssistant, device: Device, entity_id: str) -> Player: + """Return a given player for a given device.""" + prefix = f"{device.device_id}_" + suffix = f"_{NintendoParentalControlsSensor.PLAYER_PLAYING_TIME}" + registry = er.async_get(hass) + entry = registry.async_get(entity_id) + if entry is None or entry.platform != DOMAIN: + raise ServiceValidationError( + translation_domain=DOMAIN, translation_key="invalid_entity" + ) + if entry.unique_id.startswith(prefix) and entry.unique_id.endswith(suffix): + player_id = entry.unique_id[len(prefix) : -len(suffix)] + if player_id in device.players: + return device.players.get_player(player_id) + raise ServiceValidationError( + translation_domain=DOMAIN, translation_key="invalid_player" + ) + + +def _build_player_app_report(player: Player) -> list[dict[str, JsonValueType]]: + """Produce a player application report.""" + return [ + { + "playing_time": app.playing_time, + "name": app.application.name, + "image": app.application.image_url, + "whitelisted": app.application.safe_launch_setting + == SafeLaunchSetting.ALLOW, + } + for app in player.apps + ] + + async def async_add_bonus_time(call: ServiceCall) -> None: """Add bonus time to a device.""" data = call.data @@ -97,3 +163,23 @@ async def async_update_pin_code(call: ServiceCall) -> None: ) device = _get_nintendo_device(call.hass, device_id) return await device.set_new_pin(new_pin) + + +async def async_get_player_usage(call: ServiceCall) -> dict: + """Get player usage.""" + data = call.data + device_id: str = data[ATTR_DEVICE_ID] + entity_id: str = data[ATTR_ENTITY_ID] + device = _get_nintendo_device(call.hass, device_id) + player = _get_nintendo_player(call.hass, device, entity_id) + return {"apps": _build_player_app_report(player)} + + +async def async_get_device_usage_report(call: ServiceCall) -> dict: + """Return the device usage report.""" + data = call.data + device_id: str = data[ATTR_DEVICE_ID] + device = _get_nintendo_device(call.hass, device_id) + return { + player.nickname: _build_player_app_report(player) for player in device.players + } diff --git a/homeassistant/components/nintendo_parental_controls/services.yaml b/homeassistant/components/nintendo_parental_controls/services.yaml index 96a090bd4f0ae..8ec639497d0b6 100644 --- a/homeassistant/components/nintendo_parental_controls/services.yaml +++ b/homeassistant/components/nintendo_parental_controls/services.yaml @@ -29,3 +29,26 @@ update_pin_code: selector: device: integration: nintendo_parental_controls +player_usage_report: + fields: + device_id: + required: true + example: 1234567890abcdef1234567890abcdef + selector: + device: + integration: nintendo_parental_controls + entity_id: + required: true + example: sensor.example_player_used_screen_time + selector: + entity: + integration: nintendo_parental_controls + domain: sensor +device_usage_report: + fields: + device_id: + required: true + example: 1234567890abcdef1234567890abcdef + selector: + device: + integration: nintendo_parental_controls diff --git a/homeassistant/components/nintendo_parental_controls/strings.json b/homeassistant/components/nintendo_parental_controls/strings.json index 001cf104e634d..d33c6a0a2720e 100644 --- a/homeassistant/components/nintendo_parental_controls/strings.json +++ b/homeassistant/components/nintendo_parental_controls/strings.json @@ -90,9 +90,15 @@ "invalid_device": { "message": "The specified device is not a Nintendo device." }, + "invalid_entity": { + "message": "The selected sensor is invalid, make sure you are selecting a Nintendo Parental Controls sensor." + }, "invalid_pin_length": { "message": "The PIN code must be a 4 to 8-digit number between 0000 and 99999999." }, + "invalid_player": { + "message": "The selected sensor is not a player sensor entity. Make sure you have selected the used screen time sensor for a specific player." + }, "no_devices_found": { "message": "No Nintendo devices found for this account." }, @@ -110,18 +116,40 @@ }, "device_id": { "description": "The ID of the device to add bonus time to.", - "example": "1234567890abcdef", "name": "Device" } }, "name": "Add Bonus Time" }, + "device_usage_report": { + "description": "Get today's application usage details for a device.", + "fields": { + "device_id": { + "description": "The ID of the device to get usage details for.", + "name": "Device" + } + }, + "name": "Device usage report" + }, + "player_usage_report": { + "description": "Get today's application usage details for a specific player.", + "fields": { + "device_id": { + "description": "The ID of the device to get player usage details for.", + "name": "Device" + }, + "entity_id": { + "description": "The player entity to get usage for.", + "name": "Player" + } + }, + "name": "Player usage report" + }, "update_pin_code": { "description": "Update the PIN code for the selected Nintendo Switch.", "fields": { "device_id": { "description": "The ID of the device to update the PIN code for.", - "example": "1234567890abcdef", "name": "Device" }, "pin": { diff --git a/tests/components/nintendo_parental_controls/conftest.py b/tests/components/nintendo_parental_controls/conftest.py index 8fa9fef04d05c..a072d294ab68b 100644 --- a/tests/components/nintendo_parental_controls/conftest.py +++ b/tests/components/nintendo_parental_controls/conftest.py @@ -11,7 +11,7 @@ PlayedAppUsage, ) from pynintendoparental.device import Device -from pynintendoparental.enum import DeviceTimerMode +from pynintendoparental.enum import DeviceTimerMode, SafeLaunchSetting from pynintendoparental.player import Player, PlayerRegistry import pytest @@ -38,6 +38,8 @@ def mock_nintendo_app() -> Application: mock_app = MagicMock(spec=Application) mock_app.application_id = "testappid" mock_app.name = "Test Game Name" + mock_app.image_url = "http://example.com/image.png" + mock_app.safe_launch_setting = SafeLaunchSetting.ALLOW return mock_app diff --git a/tests/components/nintendo_parental_controls/const.py b/tests/components/nintendo_parental_controls/const.py index 39590305a7d6f..d361534f5f21d 100644 --- a/tests/components/nintendo_parental_controls/const.py +++ b/tests/components/nintendo_parental_controls/const.py @@ -3,3 +3,5 @@ ACCOUNT_ID = "aabbccddee112233" API_TOKEN = "valid_token" LOGIN_URL = "http://example.com" + +PLAYER_ENTITY_ID = "sensor.home_assistant_test_ha_gamer_used_screen_time" diff --git a/tests/components/nintendo_parental_controls/snapshots/test_services.ambr b/tests/components/nintendo_parental_controls/snapshots/test_services.ambr new file mode 100644 index 0000000000000..95537d89e869d --- /dev/null +++ b/tests/components/nintendo_parental_controls/snapshots/test_services.ambr @@ -0,0 +1,25 @@ +# serializer version: 1 +# name: test_get_device_application_report + dict({ + 'HA Gamer': list([ + dict({ + 'image': 'http://example.com/image.png', + 'name': 'Test Game Name', + 'playing_time': 110, + 'whitelisted': True, + }), + ]), + }) +# --- +# name: test_get_player_application_report + dict({ + 'apps': list([ + dict({ + 'image': 'http://example.com/image.png', + 'name': 'Test Game Name', + 'playing_time': 110, + 'whitelisted': True, + }), + ]), + }) +# --- diff --git a/tests/components/nintendo_parental_controls/test_services.py b/tests/components/nintendo_parental_controls/test_services.py index 46f622875c30a..94d8ccc611474 100644 --- a/tests/components/nintendo_parental_controls/test_services.py +++ b/tests/components/nintendo_parental_controls/test_services.py @@ -4,6 +4,7 @@ from unittest.mock import AsyncMock import pytest +from syrupy.assertion import SnapshotAssertion from homeassistant.components.nintendo_parental_controls.const import ( ATTR_BONUS_TIME, @@ -12,12 +13,13 @@ from homeassistant.components.nintendo_parental_controls.services import ( NintendoParentalServices, ) -from homeassistant.const import ATTR_DEVICE_ID, CONF_PIN +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_PIN from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, Context, HomeAssistant from homeassistant.exceptions import ServiceValidationError, Unauthorized -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from . import setup_integration +from .const import PLAYER_ENTITY_ID from tests.common import MockConfigEntry, MockUser @@ -48,44 +50,64 @@ async def test_add_bonus_time( @pytest.mark.parametrize( - ("service", "payload", "exception_domain", "exception_key"), + ("service", "payload", "return_response", "exception_domain", "exception_key"), [ ( NintendoParentalServices.ADD_BONUS_TIME, {ATTR_DEVICE_ID: "invalid_device", ATTR_BONUS_TIME: 15}, + False, HOMEASSISTANT_DOMAIN, "service_device_not_found", ), ( NintendoParentalServices.UPDATE_PIN_CODE, {ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "1234"}, + False, HOMEASSISTANT_DOMAIN, "service_device_not_found", ), ( NintendoParentalServices.UPDATE_PIN_CODE, {ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "123"}, + False, DOMAIN, "invalid_pin_length", ), ( NintendoParentalServices.UPDATE_PIN_CODE, {ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "123456789"}, + False, DOMAIN, "invalid_pin_length", ), ( NintendoParentalServices.UPDATE_PIN_CODE, {ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "0000"}, + False, HOMEASSISTANT_DOMAIN, "service_device_not_found", ), ( NintendoParentalServices.UPDATE_PIN_CODE, {ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "abc"}, + False, DOMAIN, "invalid_pin_length", ), + ( + NintendoParentalServices.DEVICE_USAGE_REPORT, + {ATTR_DEVICE_ID: "invalid_device"}, + True, + HOMEASSISTANT_DOMAIN, + "service_device_not_found", + ), + ( + NintendoParentalServices.PLAYER_USAGE_REPORT, + {ATTR_DEVICE_ID: "invalid_device", ATTR_ENTITY_ID: PLAYER_ENTITY_ID}, + True, + HOMEASSISTANT_DOMAIN, + "service_device_not_found", + ), ], ) async def test_service_no_device_exceptions( @@ -94,6 +116,7 @@ async def test_service_no_device_exceptions( mock_nintendo_client: AsyncMock, service: NintendoParentalServices, payload: dict[str, Any], + return_response: bool, exception_domain: str, exception_key: str, ) -> None: @@ -101,10 +124,7 @@ async def test_service_no_device_exceptions( await setup_integration(hass, mock_config_entry) with pytest.raises(ServiceValidationError) as err: await hass.services.async_call( - DOMAIN, - service, - payload, - blocking=True, + DOMAIN, service, payload, blocking=True, return_response=return_response ) assert err.value.translation_domain == exception_domain assert err.value.translation_key == exception_key @@ -202,3 +222,107 @@ async def test_update_pin_code_requires_admin( context=Context(user_id=hass_read_only_user.id), ) mock_nintendo_device.set_new_pin.assert_not_called() + + +async def test_get_player_application_report( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, + mock_nintendo_client: AsyncMock, + mock_nintendo_device: AsyncMock, + snapshot: SnapshotAssertion, +) -> None: + """Test device usage report retrieval.""" + await setup_integration(hass, mock_config_entry) + device_entry = device_registry.async_get_device_by_identifier( + (DOMAIN, "testdevid"), mock_config_entry.entry_id + ) + assert device_entry + player_entity = entity_registry.async_get(PLAYER_ENTITY_ID) + assert player_entity + response = await hass.services.async_call( + DOMAIN, + NintendoParentalServices.PLAYER_USAGE_REPORT, + {ATTR_DEVICE_ID: device_entry.id, ATTR_ENTITY_ID: PLAYER_ENTITY_ID}, + blocking=True, + return_response=True, + ) + assert response == snapshot + + +async def test_get_device_application_report( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, + mock_nintendo_client: AsyncMock, + mock_nintendo_device: AsyncMock, + snapshot: SnapshotAssertion, +) -> None: + """Test device usage report retrieval.""" + await setup_integration(hass, mock_config_entry) + device_entry = device_registry.async_get_device_by_identifier( + (DOMAIN, "testdevid"), mock_config_entry.entry_id + ) + assert device_entry + player_entity = entity_registry.async_get(PLAYER_ENTITY_ID) + assert player_entity + response = await hass.services.async_call( + DOMAIN, + NintendoParentalServices.DEVICE_USAGE_REPORT, + { + ATTR_DEVICE_ID: device_entry.id, + }, + blocking=True, + return_response=True, + ) + assert response == snapshot + + +@pytest.mark.parametrize( + ("service", "payload", "return_response", "exception_domain", "exception_key"), + [ + ( + NintendoParentalServices.PLAYER_USAGE_REPORT, + {ATTR_ENTITY_ID: "sensor.not_found"}, + True, + DOMAIN, + "invalid_entity", + ), + ( + NintendoParentalServices.PLAYER_USAGE_REPORT, + {ATTR_ENTITY_ID: "sensor.home_assistant_test_screen_time_remaining"}, + True, + DOMAIN, + "invalid_player", + ), + ], +) +async def test_player_service_failures( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + mock_config_entry: MockConfigEntry, + mock_nintendo_client: AsyncMock, + service: NintendoParentalServices, + payload: dict[str, Any], + return_response: bool, + exception_domain: str, + exception_key: str, +) -> None: + """Test that player specific services raise expected exceptions.""" + await setup_integration(hass, mock_config_entry) + device_entry = device_registry.async_get_device_by_identifier( + (DOMAIN, "testdevid"), mock_config_entry.entry_id + ) + assert device_entry + with pytest.raises(ServiceValidationError) as err: + await hass.services.async_call( + DOMAIN, + service, + {ATTR_DEVICE_ID: device_entry.id, **payload}, + blocking=True, + return_response=return_response, + ) + assert err.value.translation_domain == exception_domain + assert err.value.translation_key == exception_key From 5373e3135551164938eef28e1d3a161e124970e8 Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Mon, 31 Aug 2026 20:16:56 +0200 Subject: [PATCH 14/22] Add Zonneplan integration (#180722) --- .strict-typing | 1 + CODEOWNERS | 2 + .../components/zonneplan/__init__.py | 35 + .../components/zonneplan/config_flow.py | 115 ++ homeassistant/components/zonneplan/const.py | 3 + .../components/zonneplan/coordinator.py | 115 ++ homeassistant/components/zonneplan/entity.py | 29 + homeassistant/components/zonneplan/icons.json | 43 + .../components/zonneplan/manifest.json | 12 + .../components/zonneplan/quality_scale.yaml | 74 + homeassistant/components/zonneplan/sensor.py | 281 ++++ .../components/zonneplan/strings.json | 85 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + mypy.ini | 10 + requirements_all.txt | 3 + tests/components/zonneplan/__init__.py | 1 + tests/components/zonneplan/conftest.py | 89 ++ .../zonneplan/fixtures/get_account.json | 207 +++ ...et_consumer_prices_electricity_hourly.json | 810 +++++++++++ .../get_consumer_prices_gas_daily.json | 22 + .../zonneplan/snapshots/test_sensor.ambr | 1213 +++++++++++++++++ .../components/zonneplan/test_config_flow.py | 167 +++ tests/components/zonneplan/test_init.py | 64 + tests/components/zonneplan/test_sensor.py | 93 ++ 25 files changed, 3481 insertions(+) create mode 100644 homeassistant/components/zonneplan/__init__.py create mode 100644 homeassistant/components/zonneplan/config_flow.py create mode 100644 homeassistant/components/zonneplan/const.py create mode 100644 homeassistant/components/zonneplan/coordinator.py create mode 100644 homeassistant/components/zonneplan/entity.py create mode 100644 homeassistant/components/zonneplan/icons.json create mode 100644 homeassistant/components/zonneplan/manifest.json create mode 100644 homeassistant/components/zonneplan/quality_scale.yaml create mode 100644 homeassistant/components/zonneplan/sensor.py create mode 100644 homeassistant/components/zonneplan/strings.json create mode 100644 tests/components/zonneplan/__init__.py create mode 100644 tests/components/zonneplan/conftest.py create mode 100644 tests/components/zonneplan/fixtures/get_account.json create mode 100644 tests/components/zonneplan/fixtures/get_consumer_prices_electricity_hourly.json create mode 100644 tests/components/zonneplan/fixtures/get_consumer_prices_gas_daily.json create mode 100644 tests/components/zonneplan/snapshots/test_sensor.ambr create mode 100644 tests/components/zonneplan/test_config_flow.py create mode 100644 tests/components/zonneplan/test_init.py create mode 100644 tests/components/zonneplan/test_sensor.py diff --git a/.strict-typing b/.strict-typing index 1360aefca3393..77f85ae03e7de 100644 --- a/.strict-typing +++ b/.strict-typing @@ -674,4 +674,5 @@ homeassistant.components.zeroconf.* homeassistant.components.zinvolt.* homeassistant.components.zodiac.* homeassistant.components.zone.* +homeassistant.components.zonneplan.* homeassistant.components.zwave_js.* diff --git a/CODEOWNERS b/CODEOWNERS index a677b5ac081cb..f25b6c4f27264 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2188,6 +2188,8 @@ CLAUDE.md @home-assistant/core /tests/components/zone/ @home-assistant/core /homeassistant/components/zoneminder/ @rohankapoorcom @nabbi /tests/components/zoneminder/ @rohankapoorcom @nabbi +/homeassistant/components/zonneplan/ @erwindouna +/tests/components/zonneplan/ @erwindouna /homeassistant/components/zwave_js/ @home-assistant/z-wave /tests/components/zwave_js/ @home-assistant/z-wave /homeassistant/components/zwave_me/ @lawfulchaos @Z-Wave-Me @PoltoS diff --git a/homeassistant/components/zonneplan/__init__.py b/homeassistant/components/zonneplan/__init__.py new file mode 100644 index 0000000000000..661bede950e98 --- /dev/null +++ b/homeassistant/components/zonneplan/__init__.py @@ -0,0 +1,35 @@ +"""The Zonneplan integration.""" + +from pyzonneplan import Token, Zonneplan + +from homeassistant.const import CONF_EMAIL, CONF_TOKEN, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .coordinator import ZonneplanConfigEntry, ZonneplanCoordinator + +PLATFORMS: list[Platform] = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ZonneplanConfigEntry) -> bool: + """Set up Zonneplan from a config entry.""" + coordinator = ZonneplanCoordinator( + hass, + entry, + Zonneplan( + email=entry.data[CONF_EMAIL], + session=async_get_clientsession(hass), + token=Token.from_dict(entry.data[CONF_TOKEN]), + ), + ) + await coordinator.async_config_entry_first_refresh() + + entry.runtime_data = coordinator + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ZonneplanConfigEntry) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/zonneplan/config_flow.py b/homeassistant/components/zonneplan/config_flow.py new file mode 100644 index 0000000000000..69a3f320f28ee --- /dev/null +++ b/homeassistant/components/zonneplan/config_flow.py @@ -0,0 +1,115 @@ +"""Config flow for the Zonneplan integration.""" + +import logging +from typing import Any, override + +from pyzonneplan import ( + OtpChallenge, + Zonneplan, + ZonneplanConnectionError, + ZonneplanInvalidOtpError, + ZonneplanTimeoutError, +) +import voluptuous as vol + +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.const import CONF_EMAIL, CONF_TOKEN +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.selector import ( + TextSelector, + TextSelectorConfig, + TextSelectorType, +) + +from .const import DOMAIN + +LOGGER = logging.getLogger(__name__) + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_EMAIL): TextSelector( + TextSelectorConfig(type=TextSelectorType.EMAIL, autocomplete="username") + ), + } +) +STEP_OTP_DATA_SCHEMA = vol.Schema( + { + vol.Required("otp"): TextSelector( + TextSelectorConfig(type=TextSelectorType.NUMBER) + ) + } +) + + +class ZonneplanConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Zonneplan.""" + + _client: Zonneplan + _challenge: OtpChallenge + + @override + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the initial step: request an OTP for the given email.""" + errors: dict[str, str] = {} + if user_input is not None: + self._client = Zonneplan( + email=user_input[CONF_EMAIL], + session=async_get_clientsession(self.hass), + ) + try: + self._challenge = await self._client.async_request_otp( + source_name=self.hass.config.location_name + ) + except ZonneplanConnectionError: + errors["base"] = "cannot_connect" + except ZonneplanTimeoutError: + errors["base"] = "timeout_connect" + except Exception: + LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return await self.async_step_otp() + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) + + async def async_step_otp( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle submission of the mailed one-time password.""" + errors: dict[str, str] = {} + if user_input is not None: + try: + token = await self._client.async_submit_otp( + self._challenge, user_input["otp"] + ) + account = await self._client.async_get_account() + except ZonneplanInvalidOtpError: + errors["base"] = "invalid_auth" + except ZonneplanConnectionError: + errors["base"] = "cannot_connect" + except ZonneplanTimeoutError: + errors["base"] = "timeout_connect" + except Exception: + LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + await self.async_set_unique_id(account.user_account.uuid) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=account.user_account.full_name, + data={ + CONF_EMAIL: account.user_account.email, + CONF_TOKEN: token.as_dict(), + }, + ) + + return self.async_show_form( + step_id="otp", + data_schema=STEP_OTP_DATA_SCHEMA, + errors=errors, + description_placeholders={CONF_EMAIL: self._challenge.email}, + ) diff --git a/homeassistant/components/zonneplan/const.py b/homeassistant/components/zonneplan/const.py new file mode 100644 index 0000000000000..5676e35a7d9b3 --- /dev/null +++ b/homeassistant/components/zonneplan/const.py @@ -0,0 +1,3 @@ +"""Constants for the Zonneplan integration.""" + +DOMAIN = "zonneplan" diff --git a/homeassistant/components/zonneplan/coordinator.py b/homeassistant/components/zonneplan/coordinator.py new file mode 100644 index 0000000000000..9611bf3676771 --- /dev/null +++ b/homeassistant/components/zonneplan/coordinator.py @@ -0,0 +1,115 @@ +"""Coordinator for Zonneplan.""" + +from dataclasses import dataclass +from datetime import timedelta +import logging +from typing import TYPE_CHECKING, override + +from pyzonneplan import ( + Account, + ConsumerPrices, + Zonneplan, + ZonneplanAuthenticationError, + ZonneplanConnectionError, + ZonneplanTimeoutError, +) +from pyzonneplan.const import PriceChart + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_TOKEN +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN + +LOGGER = logging.getLogger(__name__) + +UPDATE_INTERVAL = timedelta(minutes=15) + +type ZonneplanConfigEntry = ConfigEntry[ZonneplanCoordinator] + + +@dataclass(frozen=True, kw_only=True) +class ZonneplanData: + """Data fetched by the Zonneplan coordinator.""" + + account: Account + electricity_prices: ConsumerPrices | None = None + gas_prices: ConsumerPrices | None = None + + +class ZonneplanCoordinator(DataUpdateCoordinator[ZonneplanData]): + """Coordinator to manage fetching Zonneplan account data.""" + + config_entry: ZonneplanConfigEntry + + def __init__( + self, hass: HomeAssistant, entry: ZonneplanConfigEntry, zonneplan: Zonneplan + ) -> None: + """Initialize the coordinator.""" + super().__init__( + hass, + LOGGER, + config_entry=entry, + name=DOMAIN, + update_interval=UPDATE_INTERVAL, + ) + self.zonneplan = zonneplan + + @override + async def _async_update_data(self) -> ZonneplanData: + """Fetch data from the Zonneplan API.""" + try: + account = await self.zonneplan.async_get_account() + electricity_prices: ConsumerPrices | None = None + gas_prices: ConsumerPrices | None = None + + # Depending per contract, fetch the associated consumer prices + for connection in account.connections: + if ( + "electricity" in connection.market_segment + if connection.market_segment is not None + else False + ): + electricity_prices = await self.zonneplan.async_get_consumer_prices( + PriceChart.ELECTRICITY_HOURLY + ) + if ( + "gas" in connection.market_segment + if connection.market_segment is not None + else False + ): + gas_prices = await self.zonneplan.async_get_consumer_prices( + PriceChart.GAS_DAILY + ) + except ZonneplanAuthenticationError as err: + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="invalid_auth", + ) from err + except ZonneplanTimeoutError as err: + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="timeout_connect", + ) from err + except ZonneplanConnectionError as err: + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="cannot_connect", + ) from err + + if TYPE_CHECKING: + assert self.zonneplan.token is not None + + token = self.zonneplan.token.as_dict() + if self.config_entry.data.get(CONF_TOKEN) != token: + self.hass.config_entries.async_update_entry( + self.config_entry, + data={**self.config_entry.data, CONF_TOKEN: token}, + ) + + return ZonneplanData( + account=account, + electricity_prices=electricity_prices, + gas_prices=gas_prices, + ) diff --git a/homeassistant/components/zonneplan/entity.py b/homeassistant/components/zonneplan/entity.py new file mode 100644 index 0000000000000..1e575aee0064d --- /dev/null +++ b/homeassistant/components/zonneplan/entity.py @@ -0,0 +1,29 @@ +"""Base entity for Zonneplan.""" + +from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo +from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import ZonneplanCoordinator + + +class ZonneplanEntity(CoordinatorEntity[ZonneplanCoordinator]): + """Base entity for Zonneplan.""" + + _attr_has_entity_name = True + + def __init__( + self, coordinator: ZonneplanCoordinator, entity_description: EntityDescription + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator) + self.entity_description = entity_description + self._attr_unique_id = ( + f"{coordinator.data.account.user_account.uuid}_{entity_description.key}" + ) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, coordinator.config_entry.entry_id)}, + name="Zonneplan", + entry_type=DeviceEntryType.SERVICE, + ) diff --git a/homeassistant/components/zonneplan/icons.json b/homeassistant/components/zonneplan/icons.json new file mode 100644 index 0000000000000..f029aa3fd913c --- /dev/null +++ b/homeassistant/components/zonneplan/icons.json @@ -0,0 +1,43 @@ +{ + "entity": { + "sensor": { + "current_electricity_price": { + "default": "mdi:cash-multiple" + }, + "electricity_price_low_today_end_time": { + "default": "mdi:clock-end" + }, + "electricity_price_low_today_start_time": { + "default": "mdi:clock-start" + }, + "electricity_price_low_tomorrow_end_time": { + "default": "mdi:clock-end" + }, + "electricity_price_low_tomorrow_start_time": { + "default": "mdi:clock-start" + }, + "electricity_prices_tomorrow_status": { + "default": "mdi:calendar-clock", + "state": { + "available": "mdi:calendar-check", + "incoming": "mdi:calendar-clock" + } + }, + "gas_price_today": { + "default": "mdi:cash-multiple" + }, + "highest_electricity_price_today": { + "default": "mdi:cash-multiple" + }, + "highest_electricity_price_tomorrow": { + "default": "mdi:cash-multiple" + }, + "lowest_electricity_price_today": { + "default": "mdi:cash-multiple" + }, + "lowest_electricity_price_tomorrow": { + "default": "mdi:cash-multiple" + } + } + } +} diff --git a/homeassistant/components/zonneplan/manifest.json b/homeassistant/components/zonneplan/manifest.json new file mode 100644 index 0000000000000..1b2520fb0458c --- /dev/null +++ b/homeassistant/components/zonneplan/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "zonneplan", + "name": "Zonneplan", + "codeowners": ["@erwindouna"], + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/zonneplan", + "integration_type": "hub", + "iot_class": "cloud_polling", + "loggers": ["pyzonneplan"], + "quality_scale": "bronze", + "requirements": ["pyzonneplan==0.1.2"] +} diff --git a/homeassistant/components/zonneplan/quality_scale.yaml b/homeassistant/components/zonneplan/quality_scale.yaml new file mode 100644 index 0000000000000..bb26093c8211e --- /dev/null +++ b/homeassistant/components/zonneplan/quality_scale.yaml @@ -0,0 +1,74 @@ +rules: + # Bronze + action-setup: + status: exempt + comment: This integration does not register any service actions. + appropriate-polling: done + brands: done + common-modules: done + config-flow-test-coverage: done + config-flow: done + dependency-transparency: done + docs-actions: done + docs-conditions: + status: exempt + comment: This integration does not provide any conditions. + docs-high-level-description: done + docs-installation-instructions: done + docs-removal-instructions: done + docs-triggers: + status: exempt + comment: This integration does not provide any triggers. + entity-event-setup: + status: exempt + comment: | + Entities read cached state from the coordinator via CoordinatorEntity + and subscribe to no other events. + entity-unique-id: done + has-entity-name: done + runtime-data: done + test-before-configure: done + test-before-setup: done + unique-config-entry: done + + # Silver + action-exceptions: + status: exempt + comment: This integration does not register any service actions or entity actions. + config-entry-unloading: done + docs-configuration-parameters: todo + docs-installation-parameters: todo + entity-unavailable: done + integration-owner: todo + log-when-unavailable: done + parallel-updates: done + reauthentication-flow: todo + test-coverage: todo + + # Gold + devices: todo + diagnostics: todo + discovery-update-info: todo + discovery: todo + docs-data-update: todo + docs-examples: todo + docs-known-limitations: todo + docs-supported-devices: todo + docs-supported-functions: todo + docs-troubleshooting: todo + docs-use-cases: todo + dynamic-devices: todo + entity-category: todo + entity-device-class: todo + entity-disabled-by-default: todo + entity-translations: done + exception-translations: done + icon-translations: done + reconfiguration-flow: todo + repair-issues: todo + stale-devices: todo + + # Platinum + async-dependency: done + inject-websession: todo + strict-typing: done diff --git a/homeassistant/components/zonneplan/sensor.py b/homeassistant/components/zonneplan/sensor.py new file mode 100644 index 0000000000000..90b455746dab8 --- /dev/null +++ b/homeassistant/components/zonneplan/sensor.py @@ -0,0 +1,281 @@ +"""Sensor platform for Zonneplan.""" + +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timedelta +from typing import override + +from aiozoneinfo import get_time_zone + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, + StateType, +) +from homeassistant.const import UnitOfEnergy, UnitOfVolume +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.util import dt as dt_util + +from .coordinator import ZonneplanConfigEntry, ZonneplanCoordinator +from .entity import ZonneplanEntity + +PARALLEL_UPDATES = 0 + +# It's for Dutchies, so ya... +ZONNEPLAN_TIMEZONE = get_time_zone("Europe/Amsterdam") + + +@dataclass(frozen=True, kw_only=True) +class ZonneplanPriceSensorEntityDescription(SensorEntityDescription): + """Describes a Zonneplan price sensor.""" + + value_fn: Callable[[ZonneplanCoordinator], float | str | datetime | None] + supported_fn: Callable[[ZonneplanCoordinator], bool] | None = None + + +ZONNEPLAN_SENSORS: tuple[ZonneplanPriceSensorEntityDescription, ...] = ( + ZonneplanPriceSensorEntityDescription( + key="current_electricity_price", + translation_key="current_electricity_price", + native_unit_of_measurement=f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + value_fn=lambda coordinator: ( + float(point.price_tax_included.euro) + if ( + point := next( + ( + point + for electricity_prices in (coordinator.data.electricity_prices,) + if electricity_prices is not None + for point in electricity_prices.prices + if point.start_date <= dt_util.utcnow() < point.end_date + ), + None, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="lowest_electricity_price_today", + translation_key="lowest_electricity_price_today", + native_unit_of_measurement=f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + value_fn=lambda coordinator: ( + float(point.price_tax_included.euro) + if coordinator.data.electricity_prices is not None + and ( + point := coordinator.data.electricity_prices.extreme_price( + dt_util.now(ZONNEPLAN_TIMEZONE).date(), + ZONNEPLAN_TIMEZONE, + lowest=True, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="highest_electricity_price_today", + translation_key="highest_electricity_price_today", + native_unit_of_measurement=f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + value_fn=lambda coordinator: ( + float(point.price_tax_included.euro) + if coordinator.data.electricity_prices is not None + and ( + point := coordinator.data.electricity_prices.extreme_price( + dt_util.now(ZONNEPLAN_TIMEZONE).date(), + ZONNEPLAN_TIMEZONE, + lowest=False, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="lowest_electricity_price_tomorrow", + translation_key="lowest_electricity_price_tomorrow", + native_unit_of_measurement=f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + value_fn=lambda coordinator: ( + float(point.price_tax_included.euro) + if coordinator.data.electricity_prices is not None + and ( + point := coordinator.data.electricity_prices.extreme_price( + dt_util.now(ZONNEPLAN_TIMEZONE).date() + timedelta(days=1), + ZONNEPLAN_TIMEZONE, + lowest=True, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="highest_electricity_price_tomorrow", + translation_key="highest_electricity_price_tomorrow", + native_unit_of_measurement=f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + value_fn=lambda coordinator: ( + float(point.price_tax_included.euro) + if coordinator.data.electricity_prices is not None + and ( + point := coordinator.data.electricity_prices.extreme_price( + dt_util.now(ZONNEPLAN_TIMEZONE).date() + timedelta(days=1), + ZONNEPLAN_TIMEZONE, + lowest=False, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="electricity_prices_tomorrow_status", + translation_key="electricity_prices_tomorrow_status", + device_class=SensorDeviceClass.ENUM, + options=["incoming", "available"], + value_fn=lambda coordinator: ( + "available" + if coordinator.data.electricity_prices is not None + and coordinator.data.electricity_prices.prices_for_day( + dt_util.now(ZONNEPLAN_TIMEZONE).date() + timedelta(days=1), + ZONNEPLAN_TIMEZONE, + ) + else "incoming" + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="gas_price_today", + translation_key="gas_price_today", + native_unit_of_measurement=f"EUR/{UnitOfVolume.CUBIC_METERS}", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + value_fn=lambda coordinator: ( + float(point.price_tax_included.euro) + if coordinator.data.gas_prices is not None + and ( + point := next( + iter( + coordinator.data.gas_prices.prices_for_day( + dt_util.now(ZONNEPLAN_TIMEZONE).date(), ZONNEPLAN_TIMEZONE + ) + ), + None, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.gas_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="electricity_price_low_today_start_time", + translation_key="electricity_price_low_today_start_time", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda coordinator: ( + dt_util.as_local(block[0].start_date) + if coordinator.data.electricity_prices is not None + and ( + block := coordinator.data.electricity_prices.price_block( + dt_util.now(ZONNEPLAN_TIMEZONE).date(), + ZONNEPLAN_TIMEZONE, + lowest=True, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="electricity_price_low_today_end_time", + translation_key="electricity_price_low_today_end_time", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda coordinator: ( + dt_util.as_local(block[1].end_date) + if coordinator.data.electricity_prices is not None + and ( + block := coordinator.data.electricity_prices.price_block( + dt_util.now(ZONNEPLAN_TIMEZONE).date(), + ZONNEPLAN_TIMEZONE, + lowest=True, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="electricity_price_low_tomorrow_start_time", + translation_key="electricity_price_low_tomorrow_start_time", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda coordinator: ( + dt_util.as_local(block[0].start_date) + if coordinator.data.electricity_prices is not None + and ( + block := coordinator.data.electricity_prices.price_block( + dt_util.now(ZONNEPLAN_TIMEZONE).date() + timedelta(days=1), + ZONNEPLAN_TIMEZONE, + lowest=True, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), + ZonneplanPriceSensorEntityDescription( + key="electricity_price_low_tomorrow_end_time", + translation_key="electricity_price_low_tomorrow_end_time", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda coordinator: ( + dt_util.as_local(block[1].end_date) + if coordinator.data.electricity_prices is not None + and ( + block := coordinator.data.electricity_prices.price_block( + dt_util.now(ZONNEPLAN_TIMEZONE).date() + timedelta(days=1), + ZONNEPLAN_TIMEZONE, + lowest=True, + ) + ) + else None + ), + supported_fn=lambda coordinator: bool(coordinator.data.electricity_prices), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ZonneplanConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up Zonneplan sensor platform.""" + coordinator = entry.runtime_data + + async_add_entities( + ZonneplanPriceSensor(coordinator, description) + for description in ZONNEPLAN_SENSORS + ) + + +class ZonneplanPriceSensor(ZonneplanEntity, SensorEntity): + """Representation of a Zonneplan electricity price sensor.""" + + entity_description: ZonneplanPriceSensorEntityDescription + + @property + @override + def native_value(self) -> StateType | datetime: + """Return the value of the sensor.""" + return self.entity_description.value_fn(self.coordinator) diff --git a/homeassistant/components/zonneplan/strings.json b/homeassistant/components/zonneplan/strings.json new file mode 100644 index 0000000000000..53f2d57d03d62 --- /dev/null +++ b/homeassistant/components/zonneplan/strings.json @@ -0,0 +1,85 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "timeout_connect": "Timeout while communicating with the Zonneplan API", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "step": { + "otp": { + "data": { + "otp": "One-time password" + }, + "data_description": { + "otp": "Enter the one-time password mailed to {email}." + }, + "description": "Within a few minutes, you will receive an email with a one-time password. Enter the password below to complete the login process." + }, + "user": { + "data": { + "email": "[%key:common::config_flow::data::email%]" + }, + "data_description": { + "email": "Email address for your Zonneplan account." + }, + "description": "Log in to Zonneplan with the email address associated with your account. You will be receiving a one-time password via email to complete the login process." + } + } + }, + "entity": { + "sensor": { + "current_electricity_price": { + "name": "Current electricity price" + }, + "electricity_price_low_today_end_time": { + "name": "Electricity price low today end time" + }, + "electricity_price_low_today_start_time": { + "name": "Electricity price low today start time" + }, + "electricity_price_low_tomorrow_end_time": { + "name": "Electricity price low tomorrow end time" + }, + "electricity_price_low_tomorrow_start_time": { + "name": "Electricity price low tomorrow start time" + }, + "electricity_prices_tomorrow_status": { + "name": "Electricity prices tomorrow status", + "state": { + "available": "Available", + "incoming": "Incoming" + } + }, + "gas_price_today": { + "name": "Gas price daily" + }, + "highest_electricity_price_today": { + "name": "Highest electricity price today" + }, + "highest_electricity_price_tomorrow": { + "name": "Highest electricity price tomorrow" + }, + "lowest_electricity_price_today": { + "name": "Lowest electricity price today" + }, + "lowest_electricity_price_tomorrow": { + "name": "Lowest electricity price tomorrow" + } + } + }, + "exceptions": { + "cannot_connect": { + "message": "Error communicating with the Zonneplan API" + }, + "invalid_auth": { + "message": "An error occurred while trying to authenticate" + }, + "timeout_connect": { + "message": "Timeout while communicating with the Zonneplan API" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 72322aeb918da..022efbd91edf2 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -926,6 +926,7 @@ "zimi", "zinvolt", "zodiac", + "zonneplan", "zwave_js", "zwave_me", ], diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 3ce24b731a4c2..7f190a8098fb0 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -8552,6 +8552,12 @@ "config_flow": false, "iot_class": "local_polling" }, + "zonneplan": { + "name": "Zonneplan", + "integration_type": "hub", + "config_flow": true, + "iot_class": "cloud_polling" + }, "zooz": { "name": "Zooz", "iot_standards": [ diff --git a/mypy.ini b/mypy.ini index 423e23ab14201..45920e6fcb784 100644 --- a/mypy.ini +++ b/mypy.ini @@ -6501,6 +6501,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.zonneplan.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.zwave_js.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 699f603bd4db4..30a7c90d03d2b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2916,6 +2916,9 @@ pyzbar==0.1.9 # homeassistant.components.zerproc pyzerproc==0.4.8 +# homeassistant.components.zonneplan +pyzonneplan==0.1.2 + # homeassistant.components.qbittorrent qbittorrent-api==2026.5.1 diff --git a/tests/components/zonneplan/__init__.py b/tests/components/zonneplan/__init__.py new file mode 100644 index 0000000000000..1ba8c59ebd311 --- /dev/null +++ b/tests/components/zonneplan/__init__.py @@ -0,0 +1 @@ +"""Tests for the Zonneplan integration.""" diff --git a/tests/components/zonneplan/conftest.py b/tests/components/zonneplan/conftest.py new file mode 100644 index 0000000000000..5257a88f415f5 --- /dev/null +++ b/tests/components/zonneplan/conftest.py @@ -0,0 +1,89 @@ +"""Common fixtures for the Zonneplan tests.""" + +from collections.abc import Generator +from datetime import UTC, datetime +from unittest.mock import AsyncMock, patch + +import pytest +from pyzonneplan import Account, ConsumerPrices, OtpChallenge, Token, Zonneplan +from pyzonneplan.const import PriceChart + +from homeassistant.components.zonneplan.const import DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_TOKEN + +from tests.common import MockConfigEntry, load_json_object_fixture + +MOCK_EMAIL = "user@example.com" +MOCK_USER_INPUT = {CONF_EMAIL: MOCK_EMAIL} +MOCK_ACCOUNT = Account.from_dict(load_json_object_fixture("get_account.json", DOMAIN)) + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.zonneplan.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture(autouse=True) +def mock_zonneplan_client() -> Generator[AsyncMock]: + """Mock the Zonneplan client.""" + client = AsyncMock(Zonneplan) + client.token = Token( + access_token="mock-access-token", + refresh_token="mock-refresh-token", + expires_at=datetime(2030, 1, 1, tzinfo=UTC), + ) + client.async_request_otp.return_value = OtpChallenge( + auth_session="mock-auth-session", + code_verifier="mock-code-verifier", + email=MOCK_EMAIL, + ) + client.async_submit_otp.return_value = Token( + access_token="mock-access-token", + refresh_token="mock-refresh-token", + expires_at=datetime(2030, 1, 1, tzinfo=UTC), + ) + client.async_get_account.return_value = MOCK_ACCOUNT + + prices_by_chart = { + PriceChart.ELECTRICITY_HOURLY: ConsumerPrices.from_dict( + load_json_object_fixture( + "get_consumer_prices_electricity_hourly.json", DOMAIN + ) + ), + PriceChart.GAS_DAILY: ConsumerPrices.from_dict( + load_json_object_fixture("get_consumer_prices_gas_daily.json", DOMAIN) + ), + } + client.async_get_consumer_prices.side_effect = lambda chart: prices_by_chart[chart] + + with ( + patch("homeassistant.components.zonneplan.Zonneplan", return_value=client), + patch( + "homeassistant.components.zonneplan.config_flow.Zonneplan", + return_value=client, + ), + ): + yield client + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return a mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + title=MOCK_EMAIL, + unique_id=MOCK_ACCOUNT.user_account.uuid, + entry_id="01JXZKYZ00XZKYZ00XZKYZ00XZ", + data={ + CONF_EMAIL: MOCK_EMAIL, + CONF_TOKEN: Token( + access_token="mock-access-token", + refresh_token="mock-refresh-token", + expires_at=datetime(2030, 1, 1, tzinfo=UTC), + ).as_dict(), + }, + ) diff --git a/tests/components/zonneplan/fixtures/get_account.json b/tests/components/zonneplan/fixtures/get_account.json new file mode 100644 index 0000000000000..497e893a8fdbe --- /dev/null +++ b/tests/components/zonneplan/fixtures/get_account.json @@ -0,0 +1,207 @@ +{ + "user_account": { + "initials": "", + "uuid": "00000000-0000-4000-8000-000000000001", + "email": "user@example.com", + "first_name": null, + "full_name": "No one special", + "is_representative": false + }, + "address_groups": [ + { + "uuid": "00000000-0000-4000-8000-000000000002", + "connections": [ + { + "uuid": "00000000-0000-4000-8000-000000000002", + "ean": "871000000000000001", + "market_segment": "electricity", + "contracts": [ + { + "uuid": "00000000-0000-4000-8000-000000000004", + "label": "Stroom tegen uurprijzen", + "type": "electricity", + "start_date": "2026-09-17T22:00:00.000000Z", + "end_date": null, + "meta": { + "external_contract_id": "00000000-0000-4000-8000-000000000005", + "agreement_date": "2026-08-29", + "original_end_date": null, + "contract_type": "smart", + "proposition_reference": "vast-voorschot-e-2025-12-kwart", + "start_reason": null, + "end_reason": null, + "show_in_contract_screen": true, + "expected_delivery": 4470, + "expected_production": 2520, + "gas_price_ceiling_contract_start_date": null, + "gas_price_ceiling_contract_end_date": null, + "deposit_type": "fixed", + "monthly_advanced_deposit_amount": 930000000 + } + } + ], + "features": [ + { + "code": "E004", + "start_date": "2026-08-29", + "label": "Verbruikshistorie" + } + ], + "buttons": [] + }, + { + "uuid": "00000000-0000-4000-8000-000000000003", + "ean": "871000000000000002", + "market_segment": "gas", + "contracts": [ + { + "uuid": "00000000-0000-4000-8000-000000000006", + "label": "Gas tegen dagprijzen", + "type": "gas", + "start_date": "2026-09-17T22:00:00.000000Z", + "end_date": null, + "meta": { + "external_contract_id": "00000000-0000-4000-8000-000000000007", + "agreement_date": "2026-08-29", + "original_end_date": null, + "contract_type": "smart", + "proposition_reference": "vast-voorschot-g-2024-11", + "start_reason": null, + "end_reason": null, + "show_in_contract_screen": true, + "expected_delivery": 2171, + "expected_production": null, + "gas_price_ceiling_contract_start_date": null, + "gas_price_ceiling_contract_end_date": null, + "deposit_type": "fixed", + "monthly_advanced_deposit_amount": 3500000000 + } + } + ], + "features": [], + "buttons": [] + } + ], + "address": { + "id": "1234AB1", + "zipcode": "1234 AB", + "street": "Teststraat", + "number": "1", + "addition": "", + "city": "Voorbeeldstad", + "sunrise": "2026-08-29T04:39:24.000000Z", + "sunset": "2026-08-29T18:30:23.000000Z" + }, + "is_representative": true, + "organization": { + "number": 12345678, + "name": "No one special", + "phone_numbers": ["+31600000000"], + "emails": ["user@example.com"], + "privacy_service_code": "ABCDEF", + "address": { + "id": "1234AB1", + "zipcode": "1234 AB", + "street": "Teststraat", + "number": "1", + "addition": "", + "city": "Voorbeeldstad", + "sunrise": "2026-08-29T04:39:24.000000Z", + "sunset": "2026-08-29T18:30:23.000000Z" + }, + "debtor": { + "name": "J DOE", + "bank_account_number": "NL00BANK0123456789", + "mandate_reference": "01-01-2026", + "mandate_date": "01-01-2026", + "payment_method": "Automatische Incasso" + } + }, + "organization_uuid": "00000000-0000-4000-8000-000000000008" + } + ], + "buttons": [], + "iar_enabled": false, + "unread_notification_count": 0, + "chat": { + "user": { + "identifier": "00000000-0000-4000-8000-000000000001", + "email": "user@example.com", + "name": " No one special" + }, + "custom_attributes": { + "source": "app" + } + }, + "notification_setting_groups": [ + { + "slug": "general", + "name": "Algemeen", + "description": "Algemene notificaties over Zonneplan van onderwerpen die relevant zijn voor jou", + "settings": [ + { + "slug": "new-updates", + "name": "Algemeen", + "description": "Altijd als eerste op de hoogte", + "push_enabled": true + }, + { + "slug": "conversation-updates", + "name": "Chatberichten", + "description": "Krijg een melding bij een ongelezen bericht", + "push_enabled": true + }, + { + "slug": "p1-sgn-updates", + "name": "Connect P1-meter berichten", + "description": "Updates over de dongel in je slimme meter", + "push_enabled": true + } + ] + }, + { + "slug": "prices", + "name": "Prijsupdates", + "description": "Notificaties bij belangrijke prijsupdates wanneer je klant bent bij Zonneplan Energie", + "settings": [ + { + "slug": "high-energy-price", + "name": "Hoge stroomprijs", + "description": "Melding bij een bijzonder hoge stroomprijs", + "push_enabled": false + }, + { + "slug": "low-energy-price", + "name": "Negatieve stroomprijs", + "description": "Melding als de stroomprijs negatief is", + "push_enabled": false + } + ] + }, + { + "slug": "malfunctions", + "name": "Proactieve monitoring", + "description": "Notificaties bij storingen", + "settings": [ + { + "slug": "battery-malfunction", + "name": "Thuisbatterij", + "description": "Belangrijke updates over je thuisbatterij", + "push_enabled": true + }, + { + "slug": "chargepoint-malfunction", + "name": "Laadpaal", + "description": "Belangrijke updates over je laadpaal", + "push_enabled": true + }, + { + "slug": "solar-malfunction", + "name": "Zonnepanelen", + "description": "Belangrijke updates over je zonnepanelen", + "push_enabled": true + } + ] + } + ] +} diff --git a/tests/components/zonneplan/fixtures/get_consumer_prices_electricity_hourly.json b/tests/components/zonneplan/fixtures/get_consumer_prices_electricity_hourly.json new file mode 100644 index 0000000000000..c221374749565 --- /dev/null +++ b/tests/components/zonneplan/fixtures/get_consumer_prices_electricity_hourly.json @@ -0,0 +1,810 @@ +{ + "chart": { + "range": { + "start_date": "2026-08-28T15:00:00+02:00", + "end_date": "2026-08-30T23:59:59+02:00" + }, + "series": { + "prices": [ + { + "start_date": "2026-08-28T13:00:00+00:00", + "end_date": "2026-08-28T14:00:00+00:00", + "price_tax_included": { + "amount": 2604541 + }, + "price_tax_excluded": { + "amount": 1496060 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-28T14:00:00+00:00", + "end_date": "2026-08-28T15:00:00+00:00", + "price_tax_included": { + "amount": 2653304 + }, + "price_tax_excluded": { + "amount": 1544823 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-28T15:00:00+00:00", + "end_date": "2026-08-28T16:00:00+00:00", + "price_tax_included": { + "amount": 2958497 + }, + "price_tax_excluded": { + "amount": 1850016 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-28T16:00:00+00:00", + "end_date": "2026-08-28T17:00:00+00:00", + "price_tax_included": { + "amount": 3231866 + }, + "price_tax_excluded": { + "amount": 2123385 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-28T17:00:00+00:00", + "end_date": "2026-08-28T18:00:00+00:00", + "price_tax_included": { + "amount": 3389680 + }, + "price_tax_excluded": { + "amount": 2281199 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 896 + } + }, + { + "start_date": "2026-08-28T18:00:00+00:00", + "end_date": "2026-08-28T19:00:00+00:00", + "price_tax_included": { + "amount": 3444160 + }, + "price_tax_excluded": { + "amount": 2335679 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 747 + } + }, + { + "start_date": "2026-08-28T19:00:00+00:00", + "end_date": "2026-08-28T20:00:00+00:00", + "price_tax_included": { + "amount": 3319348 + }, + "price_tax_excluded": { + "amount": 2210867 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 633 + } + }, + { + "start_date": "2026-08-28T20:00:00+00:00", + "end_date": "2026-08-28T21:00:00+00:00", + "price_tax_included": { + "amount": 3264990 + }, + "price_tax_excluded": { + "amount": 2156509 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 689 + } + }, + { + "start_date": "2026-08-28T21:00:00+00:00", + "end_date": "2026-08-28T22:00:00+00:00", + "price_tax_included": { + "amount": 3030734 + }, + "price_tax_excluded": { + "amount": 1922253 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 712 + } + }, + { + "start_date": "2026-08-28T22:00:00+00:00", + "end_date": "2026-08-28T23:00:00+00:00", + "price_tax_included": { + "amount": 2862300 + }, + "price_tax_excluded": { + "amount": 1753819 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 729 + } + }, + { + "start_date": "2026-08-28T23:00:00+00:00", + "end_date": "2026-08-29T00:00:00+00:00", + "price_tax_included": { + "amount": 2659657 + }, + "price_tax_excluded": { + "amount": 1551176 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 781 + } + }, + { + "start_date": "2026-08-29T00:00:00+00:00", + "end_date": "2026-08-29T01:00:00+00:00", + "price_tax_included": { + "amount": 2517542 + }, + "price_tax_excluded": { + "amount": 1409061 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 776 + } + }, + { + "start_date": "2026-08-29T01:00:00+00:00", + "end_date": "2026-08-29T02:00:00+00:00", + "price_tax_included": { + "amount": 2429031 + }, + "price_tax_excluded": { + "amount": 1320550 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 776 + } + }, + { + "start_date": "2026-08-29T02:00:00+00:00", + "end_date": "2026-08-29T03:00:00+00:00", + "price_tax_included": { + "amount": 2423344 + }, + "price_tax_excluded": { + "amount": 1314863 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 672 + } + }, + { + "start_date": "2026-08-29T03:00:00+00:00", + "end_date": "2026-08-29T04:00:00+00:00", + "price_tax_included": { + "amount": 2467116 + }, + "price_tax_excluded": { + "amount": 1358635 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 675 + } + }, + { + "start_date": "2026-08-29T04:00:00+00:00", + "end_date": "2026-08-29T05:00:00+00:00", + "price_tax_included": { + "amount": 2489380 + }, + "price_tax_excluded": { + "amount": 1380899 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 678 + } + }, + { + "start_date": "2026-08-29T05:00:00+00:00", + "end_date": "2026-08-29T06:00:00+00:00", + "price_tax_included": { + "amount": 2533696 + }, + "price_tax_excluded": { + "amount": 1425215 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 674 + } + }, + { + "start_date": "2026-08-29T06:00:00+00:00", + "end_date": "2026-08-29T07:00:00+00:00", + "price_tax_included": { + "amount": 2043011 + }, + "price_tax_excluded": { + "amount": 934530 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 748 + } + }, + { + "start_date": "2026-08-29T07:00:00+00:00", + "end_date": "2026-08-29T08:00:00+00:00", + "price_tax_included": { + "amount": 1371491 + }, + "price_tax_excluded": { + "amount": 263010 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 993 + } + }, + { + "start_date": "2026-08-29T08:00:00+00:00", + "end_date": "2026-08-29T09:00:00+00:00", + "price_tax_included": { + "amount": 1310052 + }, + "price_tax_excluded": { + "amount": 201571 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T09:00:00+00:00", + "end_date": "2026-08-29T10:00:00+00:00", + "price_tax_included": { + "amount": 1301402 + }, + "price_tax_excluded": { + "amount": 192921 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T10:00:00+00:00", + "end_date": "2026-08-29T11:00:00+00:00", + "price_tax_included": { + "amount": 1292962 + }, + "price_tax_excluded": { + "amount": 184481 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T11:00:00+00:00", + "end_date": "2026-08-29T12:00:00+00:00", + "price_tax_included": { + "amount": 1289241 + }, + "price_tax_excluded": { + "amount": 180760 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T12:00:00+00:00", + "end_date": "2026-08-29T13:00:00+00:00", + "price_tax_included": { + "amount": 1302218 + }, + "price_tax_excluded": { + "amount": 193737 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T13:00:00+00:00", + "end_date": "2026-08-29T14:00:00+00:00", + "price_tax_included": { + "amount": 1344145 + }, + "price_tax_excluded": { + "amount": 235664 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T14:00:00+00:00", + "end_date": "2026-08-29T15:00:00+00:00", + "price_tax_included": { + "amount": 1591923 + }, + "price_tax_excluded": { + "amount": 483442 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T15:00:00+00:00", + "end_date": "2026-08-29T16:00:00+00:00", + "price_tax_included": { + "amount": 2542771 + }, + "price_tax_excluded": { + "amount": 1434290 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T16:00:00+00:00", + "end_date": "2026-08-29T17:00:00+00:00", + "price_tax_included": { + "amount": 3139815 + }, + "price_tax_excluded": { + "amount": 2031334 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-29T17:00:00+00:00", + "end_date": "2026-08-29T18:00:00+00:00", + "price_tax_included": { + "amount": 3558867 + }, + "price_tax_excluded": { + "amount": 2450386 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 815 + } + }, + { + "start_date": "2026-08-29T18:00:00+00:00", + "end_date": "2026-08-29T19:00:00+00:00", + "price_tax_included": { + "amount": 3581586 + }, + "price_tax_excluded": { + "amount": 2473105 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 681 + } + }, + { + "start_date": "2026-08-29T19:00:00+00:00", + "end_date": "2026-08-29T20:00:00+00:00", + "price_tax_included": { + "amount": 3388047 + }, + "price_tax_excluded": { + "amount": 2279566 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 612 + } + }, + { + "start_date": "2026-08-29T20:00:00+00:00", + "end_date": "2026-08-29T21:00:00+00:00", + "price_tax_included": { + "amount": 3227177 + }, + "price_tax_excluded": { + "amount": 2118696 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 614 + } + }, + { + "start_date": "2026-08-29T21:00:00+00:00", + "end_date": "2026-08-29T22:00:00+00:00", + "price_tax_included": { + "amount": 3043166 + }, + "price_tax_excluded": { + "amount": 1934685 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 648 + } + }, + { + "start_date": "2026-08-29T22:00:00+00:00", + "end_date": "2026-08-29T23:00:00+00:00", + "price_tax_included": { + "amount": 2725662 + }, + "price_tax_excluded": { + "amount": 1617181 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 677 + } + }, + { + "start_date": "2026-08-29T23:00:00+00:00", + "end_date": "2026-08-30T00:00:00+00:00", + "price_tax_included": { + "amount": 2550545 + }, + "price_tax_excluded": { + "amount": 1442064 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 756 + } + }, + { + "start_date": "2026-08-30T00:00:00+00:00", + "end_date": "2026-08-30T01:00:00+00:00", + "price_tax_included": { + "amount": 2351863 + }, + "price_tax_excluded": { + "amount": 1243382 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 780 + } + }, + { + "start_date": "2026-08-30T01:00:00+00:00", + "end_date": "2026-08-30T02:00:00+00:00", + "price_tax_included": { + "amount": 2233858 + }, + "price_tax_excluded": { + "amount": 1125377 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 824 + } + }, + { + "start_date": "2026-08-30T02:00:00+00:00", + "end_date": "2026-08-30T03:00:00+00:00", + "price_tax_included": { + "amount": 2131008 + }, + "price_tax_excluded": { + "amount": 1022527 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 835 + } + }, + { + "start_date": "2026-08-30T03:00:00+00:00", + "end_date": "2026-08-30T04:00:00+00:00", + "price_tax_included": { + "amount": 2172844 + }, + "price_tax_excluded": { + "amount": 1064363 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 846 + } + }, + { + "start_date": "2026-08-30T04:00:00+00:00", + "end_date": "2026-08-30T05:00:00+00:00", + "price_tax_included": { + "amount": 2173176 + }, + "price_tax_excluded": { + "amount": 1064695 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 860 + } + }, + { + "start_date": "2026-08-30T05:00:00+00:00", + "end_date": "2026-08-30T06:00:00+00:00", + "price_tax_included": { + "amount": 2024256 + }, + "price_tax_excluded": { + "amount": 915775 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 853 + } + }, + { + "start_date": "2026-08-30T06:00:00+00:00", + "end_date": "2026-08-30T07:00:00+00:00", + "price_tax_included": { + "amount": 1503924 + }, + "price_tax_excluded": { + "amount": 395443 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 889 + } + }, + { + "start_date": "2026-08-30T07:00:00+00:00", + "end_date": "2026-08-30T08:00:00+00:00", + "price_tax_included": { + "amount": 1311777 + }, + "price_tax_excluded": { + "amount": 203296 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T08:00:00+00:00", + "end_date": "2026-08-30T09:00:00+00:00", + "price_tax_included": { + "amount": 1307391 + }, + "price_tax_excluded": { + "amount": 198910 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T09:00:00+00:00", + "end_date": "2026-08-30T10:00:00+00:00", + "price_tax_included": { + "amount": 1306666 + }, + "price_tax_excluded": { + "amount": 198185 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T10:00:00+00:00", + "end_date": "2026-08-30T11:00:00+00:00", + "price_tax_included": { + "amount": 1299103 + }, + "price_tax_excluded": { + "amount": 190622 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T11:00:00+00:00", + "end_date": "2026-08-30T12:00:00+00:00", + "price_tax_included": { + "amount": 1295896 + }, + "price_tax_excluded": { + "amount": 187415 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T12:00:00+00:00", + "end_date": "2026-08-30T13:00:00+00:00", + "price_tax_included": { + "amount": 1296108 + }, + "price_tax_excluded": { + "amount": 187627 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T13:00:00+00:00", + "end_date": "2026-08-30T14:00:00+00:00", + "price_tax_included": { + "amount": 1307028 + }, + "price_tax_excluded": { + "amount": 198547 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T14:00:00+00:00", + "end_date": "2026-08-30T15:00:00+00:00", + "price_tax_included": { + "amount": 1314258 + }, + "price_tax_excluded": { + "amount": 205777 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T15:00:00+00:00", + "end_date": "2026-08-30T16:00:00+00:00", + "price_tax_included": { + "amount": 1994732 + }, + "price_tax_excluded": { + "amount": 886251 + }, + "tariff_group": "low", + "sustainability_score": { + "permille": 1000 + } + }, + { + "start_date": "2026-08-30T16:00:00+00:00", + "end_date": "2026-08-30T17:00:00+00:00", + "price_tax_included": { + "amount": 3208694 + }, + "price_tax_excluded": { + "amount": 2100213 + }, + "tariff_group": "normal", + "sustainability_score": { + "permille": 898 + } + }, + { + "start_date": "2026-08-30T17:00:00+00:00", + "end_date": "2026-08-30T18:00:00+00:00", + "price_tax_included": { + "amount": 3535576 + }, + "price_tax_excluded": { + "amount": 2427095 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 584 + } + }, + { + "start_date": "2026-08-30T18:00:00+00:00", + "end_date": "2026-08-30T19:00:00+00:00", + "price_tax_included": { + "amount": 3647561 + }, + "price_tax_excluded": { + "amount": 2539080 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 409 + } + }, + { + "start_date": "2026-08-30T19:00:00+00:00", + "end_date": "2026-08-30T20:00:00+00:00", + "price_tax_included": { + "amount": 3644536 + }, + "price_tax_excluded": { + "amount": 2536055 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 314 + } + }, + { + "start_date": "2026-08-30T20:00:00+00:00", + "end_date": "2026-08-30T21:00:00+00:00", + "price_tax_included": { + "amount": 3504570 + }, + "price_tax_excluded": { + "amount": 2396089 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 348 + } + }, + { + "start_date": "2026-08-30T21:00:00+00:00", + "end_date": "2026-08-30T22:00:00+00:00", + "price_tax_included": { + "amount": 3341552 + }, + "price_tax_excluded": { + "amount": 2233071 + }, + "tariff_group": "high", + "sustainability_score": { + "permille": 374 + } + } + ] + } + } +} diff --git a/tests/components/zonneplan/fixtures/get_consumer_prices_gas_daily.json b/tests/components/zonneplan/fixtures/get_consumer_prices_gas_daily.json new file mode 100644 index 0000000000000..c93bb3745e505 --- /dev/null +++ b/tests/components/zonneplan/fixtures/get_consumer_prices_gas_daily.json @@ -0,0 +1,22 @@ +{ + "chart": { + "range": { + "start_date": "2026-08-28T19:00:00+02:00", + "end_date": "2026-08-30T23:59:59+02:00" + }, + "series": { + "prices": [ + { + "start_date": "2026-08-29T04:00:00+00:00", + "end_date": "2026-08-30T04:00:00+00:00", + "price_tax_included": { + "amount": 16029802 + }, + "price_tax_excluded": { + "amount": 8761816 + } + } + ] + } + } +} diff --git a/tests/components/zonneplan/snapshots/test_sensor.ambr b/tests/components/zonneplan/snapshots/test_sensor.ambr new file mode 100644 index 0000000000000..61461bb493e01 --- /dev/null +++ b/tests/components/zonneplan/snapshots/test_sensor.ambr @@ -0,0 +1,1213 @@ +# serializer version: 1 +# name: test_sensor[prices_incoming][sensor.zonneplan_current_electricity_price-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.zonneplan_current_electricity_price', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Current electricity price', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Current electricity price', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'current_electricity_price', + 'unique_id': '00000000-0000-4000-8000-000000000001_current_electricity_price', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_current_electricity_price-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Current electricity price', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_current_electricity_price', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.2351863', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_today_end_time-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.zonneplan_electricity_price_low_today_end_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low today end time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low today end time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_today_end_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_today_end_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_today_end_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low today end time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_today_end_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2026-08-30T15:00:00+00:00', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_today_start_time-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.zonneplan_electricity_price_low_today_start_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low today start time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low today start time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_today_start_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_today_start_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_today_start_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low today start time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_today_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2026-08-30T07:00:00+00:00', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_tomorrow_end_time-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.zonneplan_electricity_price_low_tomorrow_end_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low tomorrow end time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low tomorrow end time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_tomorrow_end_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_tomorrow_end_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_tomorrow_end_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low tomorrow end time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_tomorrow_end_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_tomorrow_start_time-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.zonneplan_electricity_price_low_tomorrow_start_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low tomorrow start time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low tomorrow start time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_tomorrow_start_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_tomorrow_start_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_price_low_tomorrow_start_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low tomorrow start time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_tomorrow_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_prices_tomorrow_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'incoming', + 'available', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.zonneplan_electricity_prices_tomorrow_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity prices tomorrow status', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity prices tomorrow status', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_prices_tomorrow_status', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_prices_tomorrow_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_electricity_prices_tomorrow_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Zonneplan Electricity prices tomorrow status', + : list([ + 'incoming', + 'available', + ]), + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_prices_tomorrow_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'incoming', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_gas_price_daily-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.zonneplan_gas_price_daily', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Gas price daily', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Gas price daily', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'gas_price_today', + 'unique_id': '00000000-0000-4000-8000-000000000001_gas_price_today', + 'unit_of_measurement': 'EUR/m³', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_gas_price_daily-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Gas price daily', + : , + : 'EUR/m³', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_gas_price_daily', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_highest_electricity_price_today-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.zonneplan_highest_electricity_price_today', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Highest electricity price today', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Highest electricity price today', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'highest_electricity_price_today', + 'unique_id': '00000000-0000-4000-8000-000000000001_highest_electricity_price_today', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_highest_electricity_price_today-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Highest electricity price today', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_highest_electricity_price_today', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.3647561', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_highest_electricity_price_tomorrow-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.zonneplan_highest_electricity_price_tomorrow', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Highest electricity price tomorrow', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Highest electricity price tomorrow', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'highest_electricity_price_tomorrow', + 'unique_id': '00000000-0000-4000-8000-000000000001_highest_electricity_price_tomorrow', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_highest_electricity_price_tomorrow-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Highest electricity price tomorrow', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_highest_electricity_price_tomorrow', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_lowest_electricity_price_today-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.zonneplan_lowest_electricity_price_today', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Lowest electricity price today', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Lowest electricity price today', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'lowest_electricity_price_today', + 'unique_id': '00000000-0000-4000-8000-000000000001_lowest_electricity_price_today', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_lowest_electricity_price_today-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Lowest electricity price today', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_lowest_electricity_price_today', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.1295896', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_lowest_electricity_price_tomorrow-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.zonneplan_lowest_electricity_price_tomorrow', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Lowest electricity price tomorrow', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Lowest electricity price tomorrow', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'lowest_electricity_price_tomorrow', + 'unique_id': '00000000-0000-4000-8000-000000000001_lowest_electricity_price_tomorrow', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_incoming][sensor.zonneplan_lowest_electricity_price_tomorrow-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Lowest electricity price tomorrow', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_lowest_electricity_price_tomorrow', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_current_electricity_price-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.zonneplan_current_electricity_price', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Current electricity price', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Current electricity price', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'current_electricity_price', + 'unique_id': '00000000-0000-4000-8000-000000000001_current_electricity_price', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_current_electricity_price-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Current electricity price', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_current_electricity_price', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.1310052', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_today_end_time-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.zonneplan_electricity_price_low_today_end_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low today end time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low today end time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_today_end_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_today_end_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_today_end_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low today end time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_today_end_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2026-08-29T14:00:00+00:00', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_today_start_time-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.zonneplan_electricity_price_low_today_start_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low today start time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low today start time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_today_start_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_today_start_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_today_start_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low today start time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_today_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2026-08-29T08:00:00+00:00', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_tomorrow_end_time-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.zonneplan_electricity_price_low_tomorrow_end_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low tomorrow end time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low tomorrow end time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_tomorrow_end_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_tomorrow_end_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_tomorrow_end_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low tomorrow end time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_tomorrow_end_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2026-08-30T15:00:00+00:00', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_tomorrow_start_time-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.zonneplan_electricity_price_low_tomorrow_start_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity price low tomorrow start time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity price low tomorrow start time', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_price_low_tomorrow_start_time', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_price_low_tomorrow_start_time', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_price_low_tomorrow_start_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'timestamp', + : 'Zonneplan Electricity price low tomorrow start time', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_price_low_tomorrow_start_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2026-08-30T07:00:00+00:00', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_prices_tomorrow_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'incoming', + 'available', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.zonneplan_electricity_prices_tomorrow_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electricity prices tomorrow status', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Electricity prices tomorrow status', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electricity_prices_tomorrow_status', + 'unique_id': '00000000-0000-4000-8000-000000000001_electricity_prices_tomorrow_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_electricity_prices_tomorrow_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Zonneplan Electricity prices tomorrow status', + : list([ + 'incoming', + 'available', + ]), + }), + 'context': , + 'entity_id': 'sensor.zonneplan_electricity_prices_tomorrow_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'available', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_gas_price_daily-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.zonneplan_gas_price_daily', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Gas price daily', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Gas price daily', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'gas_price_today', + 'unique_id': '00000000-0000-4000-8000-000000000001_gas_price_today', + 'unit_of_measurement': 'EUR/m³', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_gas_price_daily-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Gas price daily', + : , + : 'EUR/m³', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_gas_price_daily', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1.6029802', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_highest_electricity_price_today-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.zonneplan_highest_electricity_price_today', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Highest electricity price today', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Highest electricity price today', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'highest_electricity_price_today', + 'unique_id': '00000000-0000-4000-8000-000000000001_highest_electricity_price_today', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_highest_electricity_price_today-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Highest electricity price today', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_highest_electricity_price_today', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.3581586', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_highest_electricity_price_tomorrow-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.zonneplan_highest_electricity_price_tomorrow', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Highest electricity price tomorrow', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Highest electricity price tomorrow', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'highest_electricity_price_tomorrow', + 'unique_id': '00000000-0000-4000-8000-000000000001_highest_electricity_price_tomorrow', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_highest_electricity_price_tomorrow-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Highest electricity price tomorrow', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_highest_electricity_price_tomorrow', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.3647561', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_lowest_electricity_price_today-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.zonneplan_lowest_electricity_price_today', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Lowest electricity price today', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Lowest electricity price today', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'lowest_electricity_price_today', + 'unique_id': '00000000-0000-4000-8000-000000000001_lowest_electricity_price_today', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_lowest_electricity_price_today-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Lowest electricity price today', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_lowest_electricity_price_today', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.1289241', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_lowest_electricity_price_tomorrow-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.zonneplan_lowest_electricity_price_tomorrow', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Lowest electricity price tomorrow', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Lowest electricity price tomorrow', + 'platform': 'zonneplan', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'lowest_electricity_price_tomorrow', + 'unique_id': '00000000-0000-4000-8000-000000000001_lowest_electricity_price_tomorrow', + 'unit_of_measurement': 'EUR/kWh', + }) +# --- +# name: test_sensor[prices_published][sensor.zonneplan_lowest_electricity_price_tomorrow-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Zonneplan Lowest electricity price tomorrow', + : , + : 'EUR/kWh', + }), + 'context': , + 'entity_id': 'sensor.zonneplan_lowest_electricity_price_tomorrow', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.1295896', + }) +# --- diff --git a/tests/components/zonneplan/test_config_flow.py b/tests/components/zonneplan/test_config_flow.py new file mode 100644 index 0000000000000..f0e3b3e730842 --- /dev/null +++ b/tests/components/zonneplan/test_config_flow.py @@ -0,0 +1,167 @@ +"""Test the Zonneplan config flow.""" + +from unittest.mock import AsyncMock + +import pytest +from pyzonneplan import ( + ZonneplanConnectionError, + ZonneplanInvalidOtpError, + ZonneplanTimeoutError, +) + +from homeassistant import config_entries +from homeassistant.components.zonneplan.const import DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_TOKEN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from .conftest import MOCK_ACCOUNT, MOCK_USER_INPUT + +from tests.common import MockConfigEntry + + +@pytest.mark.usefixtures("mock_setup_entry") +async def test_full_flow(hass: HomeAssistant, mock_zonneplan_client: AsyncMock) -> None: + """Test the full OTP config flow creates an entry.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_USER_INPUT + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "otp" + mock_zonneplan_client.async_request_otp.assert_called_once() + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"otp": "123456"} + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == MOCK_ACCOUNT.user_account.full_name + assert result["data"][CONF_EMAIL] == MOCK_ACCOUNT.user_account.email + assert result["data"][CONF_TOKEN] == mock_zonneplan_client.token.as_dict() + assert result["result"].unique_id == MOCK_ACCOUNT.user_account.uuid + + +@pytest.mark.parametrize( + ("exception", "reason"), + [ + pytest.param(ZonneplanConnectionError("offline"), "cannot_connect"), + pytest.param(ZonneplanTimeoutError("timed out"), "timeout_connect"), + pytest.param(Exception("unexpected"), "unknown"), + ], +) +@pytest.mark.usefixtures("mock_setup_entry") +async def test_step_user_exceptions( + hass: HomeAssistant, + mock_zonneplan_client: AsyncMock, + exception: Exception, + reason: str, +) -> None: + """Test we handle all user step exceptions.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_zonneplan_client.async_request_otp.side_effect = exception + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_USER_INPUT + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": reason} + + mock_zonneplan_client.async_request_otp.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_USER_INPUT + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "otp" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"otp": "123456"} + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == MOCK_ACCOUNT.user_account.full_name + assert result["data"][CONF_EMAIL] == MOCK_ACCOUNT.user_account.email + assert result["data"][CONF_TOKEN] == mock_zonneplan_client.token.as_dict() + assert result["result"].unique_id == MOCK_ACCOUNT.user_account.uuid + + +@pytest.mark.parametrize( + ("exception", "reason"), + [ + pytest.param(ZonneplanConnectionError("offline"), "cannot_connect"), + pytest.param(ZonneplanTimeoutError("timed out"), "timeout_connect"), + pytest.param(ZonneplanInvalidOtpError("bad otp"), "invalid_auth"), + pytest.param(Exception("unexpected"), "unknown"), + ], +) +@pytest.mark.usefixtures("mock_setup_entry") +async def test_step_otp_exceptions( + hass: HomeAssistant, + mock_zonneplan_client: AsyncMock, + exception: Exception, + reason: str, +) -> None: + """Test we handle all OTP step exceptions.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_USER_INPUT + ) + assert result["step_id"] == "otp" + + mock_zonneplan_client.async_submit_otp.side_effect = exception + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"otp": "123456"} + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "otp" + assert result["errors"] == {"base": reason} + + mock_zonneplan_client.async_submit_otp.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"otp": "123456"} + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == MOCK_ACCOUNT.user_account.full_name + assert result["data"][CONF_EMAIL] == MOCK_ACCOUNT.user_account.email + assert result["data"][CONF_TOKEN] == mock_zonneplan_client.token.as_dict() + assert result["result"].unique_id == MOCK_ACCOUNT.user_account.uuid + + +@pytest.mark.usefixtures("mock_setup_entry") +async def test_already_configured( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Test aborting when the account is already configured.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_USER_INPUT + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"otp": "123456"} + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" diff --git a/tests/components/zonneplan/test_init.py b/tests/components/zonneplan/test_init.py new file mode 100644 index 0000000000000..da4cbbfae7214 --- /dev/null +++ b/tests/components/zonneplan/test_init.py @@ -0,0 +1,64 @@ +"""Test the Zonneplan integration setup.""" + +from datetime import timedelta +from unittest.mock import AsyncMock + +import pytest +from pyzonneplan import ( + Token, + ZonneplanAuthenticationError, + ZonneplanConnectionError, + ZonneplanTimeoutError, +) + +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_TOKEN +from homeassistant.core import HomeAssistant +from homeassistant.util import dt as dt_util + +from tests.common import MockConfigEntry + + +@pytest.mark.parametrize( + "exception", + [ + ZonneplanAuthenticationError("bad token"), + ZonneplanTimeoutError("timed out"), + ZonneplanConnectionError("boom"), + ], +) +async def test_setup_entry_update_failed( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_zonneplan_client: AsyncMock, + exception: Exception, +) -> None: + """Test errors while fetching data mark the entry for retry.""" + mock_zonneplan_client.async_get_account.side_effect = exception + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_setup_entry_persists_rotated_token( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_zonneplan_client: AsyncMock, +) -> None: + """Test a rotated refresh token is persisted to the config entry.""" + rotated_token = Token( + access_token="rotated-access-token", + refresh_token="rotated-refresh-token", + expires_at=dt_util.utcnow() + timedelta(hours=1), + ) + mock_zonneplan_client.token = rotated_token + mock_config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + assert mock_config_entry.data[CONF_TOKEN] == rotated_token.as_dict() diff --git a/tests/components/zonneplan/test_sensor.py b/tests/components/zonneplan/test_sensor.py new file mode 100644 index 0000000000000..a731fb9497c43 --- /dev/null +++ b/tests/components/zonneplan/test_sensor.py @@ -0,0 +1,93 @@ +"""Tests for the Zonneplan sensor platform.""" + +import dataclasses +from unittest.mock import AsyncMock, patch + +from freezegun.api import FrozenDateTimeFactory +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.zonneplan import Platform +from homeassistant.const import STATE_UNKNOWN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .conftest import MOCK_ACCOUNT + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.fixture(autouse=True) +def enable_all_entities(entity_registry_enabled_by_default: None) -> None: + """Make sure all entities are enabled.""" + + +@pytest.mark.parametrize( + "frozen_time", + [ + pytest.param("2026-08-29T08:30:00+00:00", id="prices_published"), + pytest.param("2026-08-30T00:30:00+00:00", id="prices_incoming"), + ], +) +async def test_sensor( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + freezer: FrozenDateTimeFactory, + frozen_time: str, +) -> None: + """Test the sensor entities.""" + with patch( + "homeassistant.components.zonneplan.PLATFORMS", + [Platform.SENSOR], + ): + freezer.move_to(frozen_time) + + mock_config_entry.add_to_hass(hass) + 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) + + +@pytest.mark.parametrize( + ("missing_market_segment", "entity_id"), + [ + pytest.param( + "electricity", + "sensor.zonneplan_current_electricity_price", + id="missing_electricity", + ), + pytest.param("gas", "sensor.zonneplan_gas_price_daily", id="missing_gas"), + ], +) +async def test_sensor_unknown_for_missing_market_segment( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_zonneplan_client: AsyncMock, + missing_market_segment: str, + entity_id: str, +) -> None: + """Test a sensor is unknown when its market segment isn't on the account.""" + mock_zonneplan_client.async_get_account.return_value = dataclasses.replace( + MOCK_ACCOUNT, + address_groups=[ + dataclasses.replace( + address_group, + connections=[ + connection + for connection in address_group.connections + if connection.market_segment != missing_market_segment + ], + ) + for address_group in MOCK_ACCOUNT.address_groups + ], + ) + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_UNKNOWN From 987e6b33365f80faeb94b343fae8af7a3d0874a1 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Tue, 1 Sep 2026 04:23:18 +1000 Subject: [PATCH 15/22] Bump tesla-fleet-api to 1.12.1 (#180911) --- homeassistant/components/tesla_fleet/manifest.json | 2 +- homeassistant/components/teslemetry/manifest.json | 2 +- homeassistant/components/tessie/manifest.json | 2 +- requirements_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tesla_fleet/manifest.json b/homeassistant/components/tesla_fleet/manifest.json index aef428c58466c..6a465926a2fb0 100644 --- a/homeassistant/components/tesla_fleet/manifest.json +++ b/homeassistant/components/tesla_fleet/manifest.json @@ -8,5 +8,5 @@ "integration_type": "hub", "iot_class": "cloud_polling", "loggers": ["tesla-fleet-api"], - "requirements": ["tesla-fleet-api==1.10.0"] + "requirements": ["tesla-fleet-api==1.12.1"] } diff --git a/homeassistant/components/teslemetry/manifest.json b/homeassistant/components/teslemetry/manifest.json index 127081b3df87d..3b1cf4c247184 100644 --- a/homeassistant/components/teslemetry/manifest.json +++ b/homeassistant/components/teslemetry/manifest.json @@ -9,5 +9,5 @@ "iot_class": "cloud_polling", "loggers": ["tesla_fleet_api", "teslemetry_stream"], "quality_scale": "platinum", - "requirements": ["tesla-fleet-api==1.10.0", "teslemetry-stream==0.10.0"] + "requirements": ["tesla-fleet-api==1.12.1", "teslemetry-stream==0.10.0"] } diff --git a/homeassistant/components/tessie/manifest.json b/homeassistant/components/tessie/manifest.json index 474746c15d557..92fc08f02ce54 100644 --- a/homeassistant/components/tessie/manifest.json +++ b/homeassistant/components/tessie/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["tessie", "tesla-fleet-api"], "quality_scale": "silver", - "requirements": ["tessie-api==0.1.3", "tesla-fleet-api==1.10.0"] + "requirements": ["tessie-api==0.1.3", "tesla-fleet-api==1.12.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 30a7c90d03d2b..216d7074417d3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3230,7 +3230,7 @@ temperusb==1.6.1 # homeassistant.components.tesla_fleet # homeassistant.components.teslemetry # homeassistant.components.tessie -tesla-fleet-api==1.10.0 +tesla-fleet-api==1.12.1 # homeassistant.components.powerwall tesla-powerwall==0.5.3 From bc893b074ca19070d2872680afafa11e600d8ab1 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:03:06 +0200 Subject: [PATCH 16/22] =?UTF-8?q?Restore=20Husqvarna=20Automower=20work=20?= =?UTF-8?q?area=20sensors=20and=20gate=20by=20WorkAreaTyp=E2=80=A6=20(#180?= =?UTF-8?q?868)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../components/husqvarna_automower/sensor.py | 5 +- .../husqvarna_automower/test_init.py | 2 +- .../husqvarna_automower/test_sensor.py | 66 ++++++++++++++++++- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/sensor.py b/homeassistant/components/husqvarna_automower/sensor.py index 3d5977d63ddc6..8371fd669e567 100644 --- a/homeassistant/components/husqvarna_automower/sensor.py +++ b/homeassistant/components/husqvarna_automower/sensor.py @@ -14,6 +14,7 @@ MowerModes, RestrictedReasons, WorkArea, + WorkAreaType, ) from homeassistant.components.sensor import ( @@ -343,7 +344,7 @@ class WorkAreaSensorEntityDescription(SensorEntityDescription): WorkAreaSensorEntityDescription( key="progress", translation_key_fn=_work_area_translation_key, - exists_fn=lambda data: data.progress is not None, + exists_fn=lambda data: data.type == WorkAreaType.SYSTEMATIC, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, value_fn=attrgetter("progress"), @@ -351,7 +352,7 @@ class WorkAreaSensorEntityDescription(SensorEntityDescription): WorkAreaSensorEntityDescription( key="last_time_completed", translation_key_fn=_work_area_translation_key, - exists_fn=lambda data: data.last_time_completed is not None, + exists_fn=lambda data: data.type == WorkAreaType.SYSTEMATIC, device_class=SensorDeviceClass.TIMESTAMP, value_fn=attrgetter("last_time_completed"), ), diff --git a/tests/components/husqvarna_automower/test_init.py b/tests/components/husqvarna_automower/test_init.py index bf29210df174c..e42bf191d5915 100644 --- a/tests/components/husqvarna_automower/test_init.py +++ b/tests/components/husqvarna_automower/test_init.py @@ -470,7 +470,7 @@ def fake_register_websocket_response( last_time_completed=datetime( 2024, 10, 1, 11, 11, 0, tzinfo=dt_util.get_default_time_zone() ), - type=WorkAreaType.RANDOM, + type=WorkAreaType.SYSTEMATIC, use_global_cutting_height=False, ) } diff --git a/tests/components/husqvarna_automower/test_sensor.py b/tests/components/husqvarna_automower/test_sensor.py index a985d0703ec8c..2a75597ee4e28 100644 --- a/tests/components/husqvarna_automower/test_sensor.py +++ b/tests/components/husqvarna_automower/test_sensor.py @@ -10,13 +10,15 @@ MowerModes, MowerStates, RestrictedReasons, + WorkArea, + WorkAreaType, ) from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL -from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -144,6 +146,68 @@ async def test_work_area_sensor( assert state.state == STATE_UNAVAILABLE +async def test_work_area_sensor_creation( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, + values: dict[str, MowerAttributes], +) -> None: + """Test the work area sensor depending on mower pattern.""" + values[TEST_MOWER_ID].work_area_names.append("new systematic work area") + values[TEST_MOWER_ID].work_area_dict.update({1: "new systematic work area"}) + values[TEST_MOWER_ID].work_areas.update( + { + 1: WorkArea( + name="new systematic work area", + cutting_height=12, + enabled=True, + type=WorkAreaType.SYSTEMATIC, + use_global_cutting_height=False, + ) + } + ) + mock_automower_client.get_status.return_value = values + await setup_integration(hass, mock_config_entry) + freezer.tick(SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get( + "sensor.garden_test_mower_1_new_systematic_work_area_progress" + ) + assert state is not None + assert state.state == STATE_UNKNOWN + state = hass.states.get( + "sensor.garden_test_mower_1_new_systematic_work_area_last_time_completed" + ) + assert state is not None + assert state.state == STATE_UNKNOWN + + values[TEST_MOWER_ID].work_area_names.append("new random work area") + values[TEST_MOWER_ID].work_area_dict.update({2: "new random work area"}) + values[TEST_MOWER_ID].work_areas.update( + { + 2: WorkArea( + name="new random work area", + cutting_height=12, + enabled=True, + type=WorkAreaType.RANDOM, + use_global_cutting_height=False, + ) + } + ) + mock_automower_client.get_status.return_value = values + freezer.tick(SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get("sensor.garden_test_mower_1_new_random_work_area_progress") + assert state is None + state = hass.states.get( + "sensor.garden_test_mower_1_new_random_work_area_last_time_completed" + ) + assert state is None + + async def test_restricted_reason_sensor( hass: HomeAssistant, mock_automower_client: AsyncMock, From 601ed3f198077771834638535ad16fcde84dd0a6 Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Mon, 31 Aug 2026 21:15:08 +0200 Subject: [PATCH 17/22] Add missing quality scale ProxmoxVE (#178429) --- homeassistant/components/proxmoxve/manifest.json | 2 +- homeassistant/components/proxmoxve/quality_scale.yaml | 8 +++++--- script/hassfest/quality_scale.py | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/proxmoxve/manifest.json b/homeassistant/components/proxmoxve/manifest.json index 85ae5fb425d8c..5c8c5a5a2012f 100644 --- a/homeassistant/components/proxmoxve/manifest.json +++ b/homeassistant/components/proxmoxve/manifest.json @@ -7,6 +7,6 @@ "integration_type": "service", "iot_class": "local_polling", "loggers": ["proxmoxer"], - "quality_scale": "legacy", + "quality_scale": "gold", "requirements": ["proxmoxer==2.3.0"] } diff --git a/homeassistant/components/proxmoxve/quality_scale.yaml b/homeassistant/components/proxmoxve/quality_scale.yaml index cceb8f5d2fa1b..4505b330571ab 100644 --- a/homeassistant/components/proxmoxve/quality_scale.yaml +++ b/homeassistant/components/proxmoxve/quality_scale.yaml @@ -10,7 +10,7 @@ rules: config-flow-test-coverage: done config-flow: done dependency-transparency: - status: todo + status: exempt comment: | Unfortunately the dependency lacks PyPi publishing within the workflow. Reference issue 226 in proxmoxer/proxmoxer. docs-actions: @@ -76,9 +76,11 @@ rules: exception-translations: done icon-translations: done reconfiguration-flow: done - repair-issues: todo + repair-issues: + status: exempt + comment: | + No repair issues are defined. stale-devices: done - # Platinum async-dependency: status: todo diff --git a/script/hassfest/quality_scale.py b/script/hassfest/quality_scale.py index aa7f90e53c516..8a8171c148a59 100644 --- a/script/hassfest/quality_scale.py +++ b/script/hassfest/quality_scale.py @@ -1661,7 +1661,6 @@ class Rule: "prosegur", "prowl", "proximity", - "proxmoxve", "prusalink", "ps4", "pulseaudio_loopback", From 7950dd4334c8ff585a89b3f71d9bd03f74865bf2 Mon Sep 17 00:00:00 2001 From: robotsnh <88983536+robotsnh@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:40:44 -0700 Subject: [PATCH 18/22] refactor(`metoffice`): use `dt_util.now` in `weather.py` (#180902) Co-authored-by: Gemini <200291788+gemini-code-assist@users.noreply.github.com> --- homeassistant/components/metoffice/weather.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/metoffice/weather.py b/homeassistant/components/metoffice/weather.py index a5e3a13a68ac6..595db00eaceeb 100644 --- a/homeassistant/components/metoffice/weather.py +++ b/homeassistant/components/metoffice/weather.py @@ -1,6 +1,5 @@ """Support for UK Met Office weather service.""" -from datetime import datetime from typing import Any, cast, override from homeassistant.components.weather import ( @@ -30,6 +29,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.util import dt as dt_util from . import get_device_info from .const import ( @@ -274,9 +274,9 @@ def _async_forecast_daily(self) -> list[WeatherForecast] | None: self.forecast_coordinators["daily"], ) timesteps = coordinator.data.timesteps - start_datetime = datetime.now( # pylint: disable=home-assistant-enforce-now - tz=timesteps[0]["time"].tzinfo - ).replace(hour=0, minute=0, second=0, microsecond=0) + start_datetime = dt_util.now(time_zone=timesteps[0]["time"].tzinfo).replace( + hour=0, minute=0, second=0, microsecond=0 + ) return [ _build_daily_forecast_data(timestep) for timestep in timesteps @@ -293,9 +293,9 @@ def _async_forecast_hourly(self) -> list[WeatherForecast] | None: ) timesteps = coordinator.data.timesteps - start_datetime = datetime.now( # pylint: disable=home-assistant-enforce-now - tz=timesteps[0]["time"].tzinfo - ).replace(minute=0, second=0, microsecond=0) + start_datetime = dt_util.now(time_zone=timesteps[0]["time"].tzinfo).replace( + minute=0, second=0, microsecond=0 + ) return [ _build_hourly_forecast_data(timestep) for timestep in timesteps @@ -311,9 +311,9 @@ def _async_forecast_twice_daily(self) -> list[WeatherForecast] | None: self.forecast_coordinators["twice_daily"], ) timesteps = coordinator.data.timesteps - start_datetime = datetime.now( # pylint: disable=home-assistant-enforce-now - tz=timesteps[0]["time"].tzinfo - ).replace(hour=0, minute=0, second=0, microsecond=0) + start_datetime = dt_util.now(time_zone=timesteps[0]["time"].tzinfo).replace( + hour=0, minute=0, second=0, microsecond=0 + ) return [ _build_twice_daily_forecast_data(timestep) for timestep in timesteps From bfb55cc285c35a9a3027c177061e653f1867aa9c Mon Sep 17 00:00:00 2001 From: Mattie <6250046+MattieGit@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:46:40 +0200 Subject: [PATCH 19/22] Improve code quality of hr_energy_qube (#178280) --- .../components/hr_energy_qube/__init__.py | 38 +------- .../hr_energy_qube/binary_sensor.py | 2 +- .../components/hr_energy_qube/config_flow.py | 2 +- .../components/hr_energy_qube/coordinator.py | 21 +++- .../components/hr_energy_qube/entity.py | 2 +- .../hr_energy_qube/quality_scale.yaml | 6 +- .../components/hr_energy_qube/select.py | 4 +- .../components/hr_energy_qube/sensor.py | 37 +++---- .../components/hr_energy_qube/switch.py | 4 +- .../components/hr_energy_qube/water_heater.py | 11 +-- .../hr_energy_qube/test_binary_sensor.py | 46 +-------- tests/components/hr_energy_qube/test_init.py | 28 ++++++ .../components/hr_energy_qube/test_select.py | 97 ++++--------------- .../components/hr_energy_qube/test_switch.py | 64 +----------- .../hr_energy_qube/test_water_heater.py | 41 +------- 15 files changed, 107 insertions(+), 296 deletions(-) diff --git a/homeassistant/components/hr_energy_qube/__init__.py b/homeassistant/components/hr_energy_qube/__init__.py index 7e74035e23ab1..9d0ca46e0de2a 100644 --- a/homeassistant/components/hr_energy_qube/__init__.py +++ b/homeassistant/components/hr_energy_qube/__init__.py @@ -1,57 +1,23 @@ """The Qube Heat Pump integration.""" -from dataclasses import dataclass - from python_qube_heatpump import QubeClient from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from .const import PLATFORMS from .coordinator import QubeCoordinator - -@dataclass -class QubeData: - """Runtime data for Qube Heat Pump.""" - - coordinator: QubeCoordinator - client: QubeClient - sw_version: str | None - - -type QubeConfigEntry = ConfigEntry[QubeData] +type QubeConfigEntry = ConfigEntry[QubeCoordinator] async def async_setup_entry(hass: HomeAssistant, entry: QubeConfigEntry) -> bool: """Set up Qube Heat Pump from a config entry.""" client = QubeClient(entry.data[CONF_HOST], entry.data[CONF_PORT]) - - # Connect and read software version for device info - sw_version: str | None = None - try: - connected = await client.connect() - if not connected: - await client.close() - raise ConfigEntryNotReady( - f"Unable to connect to Qube heat pump at {entry.data[CONF_HOST]}" - ) - sw_version = await client.async_get_software_version() - except (OSError, TimeoutError) as err: - await client.close() - raise ConfigEntryNotReady( - f"Unable to connect to Qube heat pump at {entry.data[CONF_HOST]}" - ) from err - coordinator = QubeCoordinator(hass, client, entry) - entry.runtime_data = QubeData( - coordinator=coordinator, - client=client, - sw_version=sw_version, - ) + entry.runtime_data = coordinator await coordinator.async_config_entry_first_refresh() await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/hr_energy_qube/binary_sensor.py b/homeassistant/components/hr_energy_qube/binary_sensor.py index ef24d0d69b80c..a200f5e071391 100644 --- a/homeassistant/components/hr_energy_qube/binary_sensor.py +++ b/homeassistant/components/hr_energy_qube/binary_sensor.py @@ -260,7 +260,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube binary sensors.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities( QubeBinarySensor(coordinator, entry, description) diff --git a/homeassistant/components/hr_energy_qube/config_flow.py b/homeassistant/components/hr_energy_qube/config_flow.py index e73b5179d1081..845351e225fd4 100644 --- a/homeassistant/components/hr_energy_qube/config_flow.py +++ b/homeassistant/components/hr_energy_qube/config_flow.py @@ -38,7 +38,7 @@ async def async_step_user( version = await client.async_get_software_version() if version is None: errors["base"] = "not_qube_device" - except OSError, TimeoutError: + except OSError: errors["base"] = "cannot_connect" finally: await client.close() diff --git a/homeassistant/components/hr_energy_qube/coordinator.py b/homeassistant/components/hr_energy_qube/coordinator.py index ef08cb8f0aede..37e695dc7a4b9 100644 --- a/homeassistant/components/hr_energy_qube/coordinator.py +++ b/homeassistant/components/hr_energy_qube/coordinator.py @@ -31,6 +31,8 @@ class QubeData: class QubeCoordinator(DataUpdateCoordinator[QubeData]): """Qube Heat Pump data coordinator.""" + sw_version: str | None = None + def __init__( self, hass: HomeAssistant, client: QubeClient, entry: ConfigEntry ) -> None: @@ -44,6 +46,23 @@ def __init__( config_entry=entry, ) + @override + async def _async_setup(self) -> None: + """Connect to the device and read its software version.""" + try: + connected = await self.client.connect() + if not connected: + await self.client.close() + raise UpdateFailed( + f"Unable to connect to Qube heat pump at {self.client.host}" + ) + self.sw_version = await self.client.async_get_software_version() + except OSError as err: + await self.client.close() + raise UpdateFailed( + f"Unable to connect to Qube heat pump at {self.client.host}" + ) from err + @override async def _async_update_data(self) -> QubeData: """Fetch data from the device.""" @@ -51,7 +70,7 @@ async def _async_update_data(self) -> QubeData: state = await self.client.get_all_data() switches = await self.client.read_all_switches() sg_ready_mode = await self.client.get_sg_ready_mode() - except (ConnectionError, TimeoutError, OSError) as exc: + except OSError as exc: raise UpdateFailed( f"Error communicating with Qube heat pump: {exc}" ) from exc diff --git a/homeassistant/components/hr_energy_qube/entity.py b/homeassistant/components/hr_energy_qube/entity.py index 100df9db859dd..d4eb17bc3fb09 100644 --- a/homeassistant/components/hr_energy_qube/entity.py +++ b/homeassistant/components/hr_energy_qube/entity.py @@ -28,5 +28,5 @@ def __init__( identifiers={(DOMAIN, entry.entry_id)}, manufacturer="Qube", model="Heat Pump", - sw_version=entry.runtime_data.sw_version, + sw_version=coordinator.sw_version, ) diff --git a/homeassistant/components/hr_energy_qube/quality_scale.yaml b/homeassistant/components/hr_energy_qube/quality_scale.yaml index 6e344d1657ea5..9652c324fe896 100644 --- a/homeassistant/components/hr_energy_qube/quality_scale.yaml +++ b/homeassistant/components/hr_energy_qube/quality_scale.yaml @@ -42,7 +42,7 @@ rules: docs-installation-parameters: done entity-unavailable: done integration-owner: done - log-when-unavailable: todo + log-when-unavailable: done parallel-updates: done reauthentication-flow: status: exempt @@ -64,9 +64,9 @@ rules: dynamic-devices: status: exempt comment: Single device per config entry. - entity-category: todo + entity-category: done entity-device-class: done - entity-disabled-by-default: todo + entity-disabled-by-default: done entity-translations: done exception-translations: todo icon-translations: todo diff --git a/homeassistant/components/hr_energy_qube/select.py b/homeassistant/components/hr_energy_qube/select.py index 2d43d6c044872..16bc20b4c0a1d 100644 --- a/homeassistant/components/hr_energy_qube/select.py +++ b/homeassistant/components/hr_energy_qube/select.py @@ -23,7 +23,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube select entities.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities([QubeSGReadySelect(coordinator, entry)]) @@ -59,7 +59,7 @@ async def async_select_option(self, option: str) -> None: """Set the SG Ready mode.""" try: success = await self.coordinator.client.set_sg_ready_mode(option) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_command_failed", diff --git a/homeassistant/components/hr_energy_qube/sensor.py b/homeassistant/components/hr_energy_qube/sensor.py index 787bcbca37d96..51ccdc39ad920 100644 --- a/homeassistant/components/hr_energy_qube/sensor.py +++ b/homeassistant/components/hr_energy_qube/sensor.py @@ -4,6 +4,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, override +from python_qube_heatpump import STATUS_CODE_MAP + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -31,21 +33,18 @@ from . import QubeConfigEntry from .coordinator import QubeCoordinator -# Status code to state mapping +# Status code to state mapping, derived from the library's status code map. STATUS_MAP: dict[int, str] = { - 1: "standby", - 2: "alarm", - 6: "keyboard_off", - 8: "compressor_startup", - 9: "compressor_shutdown", - 14: "standby", - 15: "cooling", - 16: "heating", - 17: "start_fail", - 18: "standby", - 22: "heating_dhw", + code: status.value for code, status in STATUS_CODE_MAP.items() } +# Options list for the status sensor: unique status strings from STATUS_MAP, +# in first-seen order. StatusCode also defines ANTI_LEGIONELLA and UNKNOWN, +# but those are not part of STATUS_CODE_MAP (only surfaced via the library's +# resolve_status() helper, which this integration does not use), so they are +# excluded here automatically rather than needing an explicit filter. +STATUS_OPTIONS: list[str] = list(dict.fromkeys(STATUS_MAP.values())) + @dataclass(frozen=True, kw_only=True) class QubeSensorEntityDescription(SensorEntityDescription): @@ -226,17 +225,7 @@ def _status_value(data: QubeData) -> StateType: key="status_heatpump", translation_key="status_heatpump", device_class=SensorDeviceClass.ENUM, - options=[ - "standby", - "alarm", - "keyboard_off", - "compressor_startup", - "compressor_shutdown", - "cooling", - "heating", - "start_fail", - "heating_dhw", - ], + options=STATUS_OPTIONS, value_fn=_status_value, ), ) @@ -248,7 +237,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube sensors.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities( QubeSensor(coordinator, entry, description) for description in SENSOR_TYPES diff --git a/homeassistant/components/hr_energy_qube/switch.py b/homeassistant/components/hr_energy_qube/switch.py index 0b0b205b9ff80..1109b5cd8a22f 100644 --- a/homeassistant/components/hr_energy_qube/switch.py +++ b/homeassistant/components/hr_energy_qube/switch.py @@ -55,7 +55,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube switches.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities( QubeSwitch(coordinator, entry, description) for description in SWITCH_TYPES @@ -98,7 +98,7 @@ async def _async_write_switch(self, value: bool) -> None: register_key = self.entity_description.register_key try: success = await self.coordinator.client.write_switch(register_key, value) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_command_failed", diff --git a/homeassistant/components/hr_energy_qube/water_heater.py b/homeassistant/components/hr_energy_qube/water_heater.py index 19f5e208fb949..ab14b20eff9ce 100644 --- a/homeassistant/components/hr_energy_qube/water_heater.py +++ b/homeassistant/components/hr_energy_qube/water_heater.py @@ -3,6 +3,7 @@ from typing import Any, override from homeassistant.components.water_heater import ( + ATTR_TEMPERATURE, STATE_HEAT_PUMP, STATE_PERFORMANCE, WaterHeaterEntity, @@ -34,7 +35,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube water heater.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities([QubeWaterHeater(coordinator, entry)]) @@ -86,14 +87,12 @@ def current_operation(self) -> str | None: @override async def async_set_temperature(self, **kwargs: Any) -> None: """Set the target DHW temperature.""" - temperature = kwargs.get("temperature") - if temperature is None: - return + temperature = kwargs[ATTR_TEMPERATURE] try: success = await self.coordinator.client.write_setpoint( DHW_SETPOINT_KEY, temperature ) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_temperature_failed", @@ -111,7 +110,7 @@ async def async_set_operation_mode(self, operation_mode: str) -> None: boost = operation_mode == STATE_PERFORMANCE try: success = await self.coordinator.client.write_switch(DHW_BOOST_KEY, boost) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_command_failed", diff --git a/tests/components/hr_energy_qube/test_binary_sensor.py b/tests/components/hr_energy_qube/test_binary_sensor.py index f7ef3737f16af..438d282b4795a 100644 --- a/tests/components/hr_energy_qube/test_binary_sensor.py +++ b/tests/components/hr_energy_qube/test_binary_sensor.py @@ -1,19 +1,17 @@ """Tests for the Qube Heat Pump binary sensor platform.""" -from datetime import timedelta -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform @pytest.mark.usefixtures("entity_registry_enabled_by_default") @@ -32,41 +30,3 @@ async def test_entities( await setup_integration(hass, mock_config_entry) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) - - -@pytest.mark.parametrize( - ("side_effect", "return_value"), - [ - (ConnectionError("Connection lost"), None), - (None, None), - ], -) -async def test_binary_sensor_unavailable_on_coordinator_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, -) -> None: - """Test binary sensors become unavailable when coordinator fails.""" - await setup_integration(hass, mock_config_entry) - - # Verify binary sensors are available after setup - states = hass.states.async_all("binary_sensor") - assert len(states) > 0 - assert all(s.state != STATE_UNAVAILABLE for s in states) - - # Make the next fetch fail - mock_qube_client.get_all_data = AsyncMock( - side_effect=side_effect, return_value=return_value - ) - - # Skip time to trigger coordinator refresh - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - # All binary sensors should be unavailable - states = hass.states.async_all("binary_sensor") - assert all(s.state == STATE_UNAVAILABLE for s in states) diff --git a/tests/components/hr_energy_qube/test_init.py b/tests/components/hr_energy_qube/test_init.py index 24f6653e34ce3..07946826c6a1d 100644 --- a/tests/components/hr_energy_qube/test_init.py +++ b/tests/components/hr_energy_qube/test_init.py @@ -2,6 +2,8 @@ from unittest.mock import MagicMock +import pytest + from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant @@ -25,3 +27,29 @@ async def test_setup_and_unload_entry( assert mock_config_entry.state is ConfigEntryState.NOT_LOADED mock_qube_client.close.assert_called_once() + + +@pytest.mark.parametrize( + ("connect_result", "connect_error"), + [ + (False, None), + (None, OSError("Connection refused")), + ], +) +async def test_setup_entry_connection_failure( + hass: HomeAssistant, + mock_qube_client: MagicMock, + mock_config_entry: MockConfigEntry, + connect_result: bool | None, + connect_error: Exception | None, +) -> None: + """Test setup failure when the device cannot be reached.""" + if connect_error is not None: + mock_qube_client.connect.side_effect = connect_error + else: + mock_qube_client.connect.return_value = connect_result + + await setup_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + mock_qube_client.close.assert_called_once() diff --git a/tests/components/hr_energy_qube/test_select.py b/tests/components/hr_energy_qube/test_select.py index 915505b8ab265..7a9ebeddb75c2 100644 --- a/tests/components/hr_energy_qube/test_select.py +++ b/tests/components/hr_energy_qube/test_select.py @@ -1,9 +1,7 @@ """Tests for the Qube Heat Pump select platform.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion @@ -12,29 +10,16 @@ DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform +from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform - -async def _setup_select( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, -) -> str: - """Set up the select platform and return the entity_id.""" - with patch("homeassistant.components.hr_energy_qube.PLATFORMS", [Platform.SELECT]): - await setup_integration(hass, mock_config_entry) - entity_registry = er.async_get(hass) - entries = er.async_entries_for_config_entry( - entity_registry, mock_config_entry.entry_id - ) - assert len(entries) == 1 - return entries[0].entity_id +ENTITY_ID = "select.qube_heat_pump_smart_grid_ready_mode" async def test_entities( @@ -59,84 +44,42 @@ async def test_select_option( option: str, ) -> None: """Test selecting an SG Ready mode.""" - entity_id = await _setup_select(hass, mock_config_entry) + await setup_integration(hass, mock_config_entry) await hass.services.async_call( SELECT_DOMAIN, SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: option}, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_OPTION: option}, blocking=True, ) mock_qube_client.set_sg_ready_mode.assert_awaited_once_with(option) -async def test_select_option_connection_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test select raises HomeAssistantError on connection error.""" - entity_id = await _setup_select(hass, mock_config_entry) - - mock_qube_client.set_sg_ready_mode = AsyncMock(side_effect=ConnectionError) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "plus"}, - blocking=True, - ) - - -async def test_select_option_write_failure( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test select raises HomeAssistantError when write fails.""" - entity_id = await _setup_select(hass, mock_config_entry) - - mock_qube_client.set_sg_ready_mode = AsyncMock(return_value=False) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "plus"}, - blocking=True, - ) - - @pytest.mark.parametrize( ("side_effect", "return_value"), [ - (ConnectionError("Connection lost"), None), - (None, None), + (ConnectionError, None), + (None, False), ], ) -async def test_select_unavailable_on_coordinator_error( +async def test_select_option_error( hass: HomeAssistant, mock_qube_client: MagicMock, mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, + side_effect: type[Exception] | None, + return_value: bool | None, ) -> None: - """Test select becomes unavailable when coordinator fails.""" - entity_id = await _setup_select(hass, mock_config_entry) - - state = hass.states.get(entity_id) - assert state is not None - assert state.state != STATE_UNAVAILABLE + """Test select raises HomeAssistantError on connection error or write failure.""" + await setup_integration(hass, mock_config_entry) - mock_qube_client.get_all_data = AsyncMock( + mock_qube_client.set_sg_ready_mode = AsyncMock( side_effect=side_effect, return_value=return_value ) - - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - state = hass.states.get(entity_id) - assert state is not None - assert state.state == STATE_UNAVAILABLE + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_OPTION: "plus"}, + blocking=True, + ) diff --git a/tests/components/hr_energy_qube/test_switch.py b/tests/components/hr_energy_qube/test_switch.py index 60fa5f9f52241..7da5536702836 100644 --- a/tests/components/hr_energy_qube/test_switch.py +++ b/tests/components/hr_energy_qube/test_switch.py @@ -1,19 +1,15 @@ """Tests for the Qube Heat Pump switch platform.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.components.hr_energy_qube.const import DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_UNAVAILABLE, Platform, ) from homeassistant.core import HomeAssistant @@ -22,19 +18,9 @@ from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform -HEATING_DEMAND_KEY = "heating_demand" - - -def _get_entity_id( - entity_registry: er.EntityRegistry, entry: MockConfigEntry, key: str -) -> str: - """Look up entity_id by key and config entry.""" - unique_id = f"{entry.entry_id}-{key}" - entity_id = entity_registry.async_get_entity_id(SWITCH_DOMAIN, DOMAIN, unique_id) - assert entity_id is not None - return entity_id +ENTITY_ID = "switch.qube_heat_pump_heating_demand" async def test_entities( @@ -63,7 +49,6 @@ async def test_entities( ) async def test_turn_on_off( hass: HomeAssistant, - entity_registry: er.EntityRegistry, mock_qube_client: MagicMock, mock_config_entry: MockConfigEntry, service: str, @@ -72,11 +57,10 @@ async def test_turn_on_off( """Test turning a switch on and off.""" await setup_integration(hass, mock_config_entry) - entity_id = _get_entity_id(entity_registry, mock_config_entry, HEATING_DEMAND_KEY) await hass.services.async_call( SWITCH_DOMAIN, service, - {ATTR_ENTITY_ID: entity_id}, + {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True, ) @@ -94,7 +78,6 @@ async def test_turn_on_off( ) async def test_turn_on_error( hass: HomeAssistant, - entity_registry: er.EntityRegistry, mock_qube_client: MagicMock, mock_config_entry: MockConfigEntry, side_effect: type[Exception] | None, @@ -106,49 +89,10 @@ async def test_turn_on_error( mock_qube_client.write_switch = AsyncMock( side_effect=side_effect, return_value=return_value ) - entity_id = _get_entity_id(entity_registry, mock_config_entry, HEATING_DEMAND_KEY) with pytest.raises(HomeAssistantError): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: entity_id}, + {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True, ) - - -@pytest.mark.parametrize( - ("side_effect", "return_value"), - [ - (ConnectionError("Connection lost"), None), - (None, None), - ], -) -async def test_switch_unavailable_on_coordinator_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, -) -> None: - """Test switches become unavailable when coordinator fails.""" - await setup_integration(hass, mock_config_entry) - - # Verify switches are available after setup - states = hass.states.async_all("switch") - assert len(states) > 0 - assert all(s.state != STATE_UNAVAILABLE for s in states) - - # Make the next fetch fail - mock_qube_client.get_all_data = AsyncMock( - side_effect=side_effect, return_value=return_value - ) - - # Skip time to trigger coordinator refresh - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - # All switches should be unavailable - states = hass.states.async_all("switch") - assert all(s.state == STATE_UNAVAILABLE for s in states) diff --git a/tests/components/hr_energy_qube/test_water_heater.py b/tests/components/hr_energy_qube/test_water_heater.py index 71bb65265105a..6519ed4baa91a 100644 --- a/tests/components/hr_energy_qube/test_water_heater.py +++ b/tests/components/hr_energy_qube/test_water_heater.py @@ -1,9 +1,7 @@ """Tests for the Qube Heat Pump water heater platform.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion @@ -16,14 +14,14 @@ STATE_HEAT_PUMP, STATE_PERFORMANCE, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform +from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform ENTITY_ID = "water_heater.qube_heat_pump_domestic_hot_water" @@ -124,38 +122,3 @@ async def test_current_operation_boost( state = hass.states.get(ENTITY_ID) assert state is not None assert state.attributes["operation_mode"] == STATE_PERFORMANCE - - -@pytest.mark.parametrize( - ("side_effect", "return_value"), - [ - (ConnectionError("Connection lost"), None), - (None, None), - ], -) -async def test_water_heater_unavailable_on_coordinator_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, -) -> None: - """Test water heater becomes unavailable when coordinator fails.""" - await setup_integration(hass, mock_config_entry) - - state = hass.states.get(ENTITY_ID) - assert state is not None - assert state.state != STATE_UNAVAILABLE - - mock_qube_client.get_all_data = AsyncMock( - side_effect=side_effect, return_value=return_value - ) - - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - state = hass.states.get(ENTITY_ID) - assert state is not None - assert state.state == STATE_UNAVAILABLE From ffbdca81eccec1f94dd7d35409cd50c18120c940 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:00:38 +0200 Subject: [PATCH 20/22] Bump grpcio to 1.83.1 and protobuf to v7 (#180614) --- homeassistant/package_constraints.txt | 8 ++++---- requirements_test.txt | 2 +- script/gen_requirements_all.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bd730e2826c34..408082a05d003 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -88,9 +88,9 @@ httplib2>=0.19.0 # gRPC is an implicit dependency that we want to make explicit so we manage # upgrades intentionally. It is a large package to build from source and we # want to ensure we have wheels built. -grpcio==1.78.0 -grpcio-status==1.78.0 -grpcio-reflection==1.78.0 +grpcio==1.83.1 +grpcio-status==1.83.1 +grpcio-reflection==1.83.1 # boto3 and botocore are shared requirements of multiple integrations, # with botocore also being a transitive dependency of aiobotocore, which @@ -156,7 +156,7 @@ iso4217!=1.10.20220401 # protobuf must be in package constraints for the wheel # builder to build binary wheels -protobuf==6.33.6 +protobuf==7.36.0 # faust-cchardet: Ensure we have a version we can build wheels # 2.1.18 is the first version that works with our wheel builder diff --git a/requirements_test.txt b/requirements_test.txt index 3d59ef0d753a4..cfc9c09f9ac05 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -47,7 +47,7 @@ types-caldav==1.3.0.20250516 types-chardet==0.1.5 types-decorator==5.2.0.20260712 types-pexpect==4.9.0.20260518 -types-protobuf==6.32.1.20260221 +types-protobuf==7.35.1.20260827 types-psutil==7.2.2.20260518 types-pyserial==3.5.0.20260712 types-python-dateutil==2.9.0.20260716 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 0b335ad544516..ea12c30d2ddb5 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -72,9 +72,9 @@ # gRPC is an implicit dependency that we want to make explicit so we manage # upgrades intentionally. It is a large package to build from source and we # want to ensure we have wheels built. -grpcio==1.78.0 -grpcio-status==1.78.0 -grpcio-reflection==1.78.0 +grpcio==1.83.1 +grpcio-status==1.83.1 +grpcio-reflection==1.83.1 # boto3 and botocore are shared requirements of multiple integrations, # with botocore also being a transitive dependency of aiobotocore, which @@ -140,7 +140,7 @@ # protobuf must be in package constraints for the wheel # builder to build binary wheels -protobuf==6.33.6 +protobuf==7.36.0 # faust-cchardet: Ensure we have a version we can build wheels # 2.1.18 is the first version that works with our wheel builder From 6cd5e225334ccda9729a8f9d628deade45d07ed3 Mon Sep 17 00:00:00 2001 From: John Pettitt Date: Mon, 31 Aug 2026 13:39:40 -0700 Subject: [PATCH 21/22] Remove Subaru unique_id migration for HA<=2022.10 (#180944) Co-authored-by: Claude Sonnet 5 --- homeassistant/components/subaru/sensor.py | 56 +-------------- tests/components/subaru/test_sensor.py | 86 ----------------------- 2 files changed, 1 insertion(+), 141 deletions(-) diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index 4fff8efb10bae..a3d1f6386f94d 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -15,7 +15,6 @@ SensorEntityDescription, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( PERCENTAGE, EntityCategory, @@ -23,8 +22,7 @@ UnitOfPressure, UnitOfVolume, ) -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry as er +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util.unit_conversion import DistanceConverter, VolumeConverter @@ -226,7 +224,6 @@ async def async_setup_entry( coordinator = config_entry.runtime_data.coordinator vehicle_info = config_entry.runtime_data.vehicles entities = [] - await _async_migrate_entries(hass, config_entry) for info in vehicle_info.values(): entities.extend(create_vehicle_sensors(info, coordinator)) async_add_entities(entities) @@ -305,54 +302,3 @@ def native_unit_of_measurement(self) -> str | None: ): return FUEL_CONSUMPTION_LITERS_PER_HUNDRED_KILOMETERS return self.entity_description.native_unit_of_measurement - - -async def _async_migrate_entries( - hass: HomeAssistant, config_entry: ConfigEntry -) -> None: - """Migrate sensor entries from HA<=2022.10 to use preferred unique_id.""" - entity_registry = er.async_get(hass) - - replacements = { - "ODOMETER": sc.ODOMETER, - "AVG FUEL CONSUMPTION": sc.AVG_FUEL_CONSUMPTION, - "RANGE": sc.DIST_TO_EMPTY, - "TIRE PRESSURE FL": sc.TIRE_PRESSURE_FL, - "TIRE PRESSURE FR": sc.TIRE_PRESSURE_FR, - "TIRE PRESSURE RL": sc.TIRE_PRESSURE_RL, - "TIRE PRESSURE RR": sc.TIRE_PRESSURE_RR, - "FUEL LEVEL": sc.REMAINING_FUEL_PERCENT, - "EV RANGE": sc.EV_DISTANCE_TO_EMPTY, - "EV BATTERY LEVEL": sc.EV_STATE_OF_CHARGE_PERCENT, - "EV TIME TO FULL CHARGE": sc.EV_TIME_TO_FULLY_CHARGED_UTC, - } - - @callback - def update_unique_id(entry: er.RegistryEntry) -> dict[str, Any] | None: - id_split = entry.unique_id.split("_") - key = id_split[1].upper() if len(id_split) == 2 else None - - if key not in replacements or id_split[1] == replacements[key]: - return None - - new_unique_id = entry.unique_id.replace(id_split[1], replacements[key]) - _LOGGER.debug( - "Migrating entity '%s' unique_id from '%s' to '%s'", - entry.entity_id, - entry.unique_id, - new_unique_id, - ) - if existing_entity_id := entity_registry.async_get_entity_id( - entry.domain, entry.platform, new_unique_id - ): - _LOGGER.debug( - "Cannot migrate to unique_id '%s', already exists for '%s'", - new_unique_id, - existing_entity_id, - ) - return None - return { - "new_unique_id": new_unique_id, - } - - await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id) diff --git a/tests/components/subaru/test_sensor.py b/tests/components/subaru/test_sensor.py index 66e6b4c250649..75d763320400d 100644 --- a/tests/components/subaru/test_sensor.py +++ b/tests/components/subaru/test_sensor.py @@ -47,92 +47,6 @@ async def test_sensors_missing_vin_data(hass: HomeAssistant, ev_entry) -> None: _assert_data(hass, EXPECTED_STATE_EV_UNAVAILABLE) -@pytest.mark.parametrize( - ("entitydata", "old_unique_id", "new_unique_id"), - [ - ( - { - "domain": SENSOR_DOMAIN, - "platform": DOMAIN, - "unique_id": f"{TEST_VIN_2_EV}_Avg fuel consumption", - }, - f"{TEST_VIN_2_EV}_Avg fuel consumption", - f"{TEST_VIN_2_EV}_{API_GEN_2_SENSORS[0].key}", - ), - ], -) -async def test_sensor_migrate_unique_ids( - hass: HomeAssistant, - entity_registry: er.EntityRegistry, - entitydata, - old_unique_id, - new_unique_id, - subaru_config_entry, -) -> None: - """Test successful migration of entity unique_ids.""" - entity: er.RegistryEntry = entity_registry.async_get_or_create( - **entitydata, - config_entry=subaru_config_entry, - ) - assert entity.unique_id == old_unique_id - - await setup_subaru_config_entry(hass, subaru_config_entry) - - entity_migrated = entity_registry.async_get(entity.entity_id) - assert entity_migrated - assert entity_migrated.unique_id == new_unique_id - - -@pytest.mark.parametrize( - ("entitydata", "old_unique_id", "new_unique_id"), - [ - ( - { - "domain": SENSOR_DOMAIN, - "platform": DOMAIN, - "unique_id": f"{TEST_VIN_2_EV}_Avg fuel consumption", - }, - f"{TEST_VIN_2_EV}_Avg fuel consumption", - f"{TEST_VIN_2_EV}_{API_GEN_2_SENSORS[0].key}", - ) - ], -) -async def test_sensor_migrate_unique_ids_duplicate( - hass: HomeAssistant, - entity_registry: er.EntityRegistry, - entitydata, - old_unique_id, - new_unique_id, - subaru_config_entry, -) -> None: - """Test unsuccessful migration of entity unique_ids due to duplicate.""" - entity: er.RegistryEntry = entity_registry.async_get_or_create( - **entitydata, - config_entry=subaru_config_entry, - ) - assert entity.unique_id == old_unique_id - - # create existing entry with new_unique_id that conflicts with migrate - existing_entity = entity_registry.async_get_or_create( - SENSOR_DOMAIN, - DOMAIN, - unique_id=new_unique_id, - config_entry=subaru_config_entry, - ) - - await setup_subaru_config_entry(hass, subaru_config_entry) - - entity_migrated = entity_registry.async_get(entity.entity_id) - assert entity_migrated - assert entity_migrated.unique_id == old_unique_id - - entity_not_changed = entity_registry.async_get(existing_entity.entity_id) - assert entity_not_changed - assert entity_not_changed.unique_id == new_unique_id - - assert entity_migrated != entity_not_changed - - def _assert_data(hass: HomeAssistant, expected_state: dict[str, Any]) -> None: sensor_list = EV_SENSORS sensor_list.extend(API_GEN_2_SENSORS) From 1c7a6b6def8539ef3884bc72c08b972d43c9fd2e Mon Sep 17 00:00:00 2001 From: Tom Matheussen <13683094+Tommatheussen@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:43:48 +0200 Subject: [PATCH 22/22] Bump satel_integra to 1.4.0 (#180960) --- homeassistant/components/satel_integra/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/satel_integra/manifest.json b/homeassistant/components/satel_integra/manifest.json index 1520a7c87bafc..b57efec331d7a 100644 --- a/homeassistant/components/satel_integra/manifest.json +++ b/homeassistant/components/satel_integra/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["satel_integra"], "quality_scale": "bronze", - "requirements": ["satel-integra==1.3.1"] + "requirements": ["satel-integra==1.4.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 216d7074417d3..f60c6f1e3c376 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3025,7 +3025,7 @@ samsungtvws[async,encrypted]==3.0.5 sanix==1.0.6 # homeassistant.components.satel_integra -satel-integra==1.3.1 +satel-integra==1.4.0 # homeassistant.components.screenlogic screenlogicpy==0.10.2