diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ec8c9c7b19e648..26888a930e33c1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -517,6 +517,7 @@ jobs: env.HA_SHORT_VERSION }}- - name: Install additional OS dependencies if: steps.cache-venv.outputs.cache-hit != 'true' + timeout-minutes: 5 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -578,6 +579,7 @@ jobs: - base steps: - name: Install additional OS dependencies + timeout-minutes: 5 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -877,6 +879,7 @@ jobs: name: Split tests for full run steps: - name: Install additional OS dependencies + timeout-minutes: 5 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -937,6 +940,7 @@ jobs: Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) steps: - name: Install additional OS dependencies + timeout-minutes: 5 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -1070,6 +1074,7 @@ jobs: Run ${{ matrix.mariadb-group }} tests Python ${{ matrix.python-version }} steps: - name: Install additional OS dependencies + timeout-minutes: 5 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -1210,6 +1215,7 @@ jobs: Run ${{ matrix.postgresql-group }} tests Python ${{ matrix.python-version }} steps: - name: Install additional OS dependencies + timeout-minutes: 5 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -1371,6 +1377,7 @@ jobs: Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) steps: - name: Install additional OS dependencies + timeout-minutes: 5 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update diff --git a/homeassistant/components/fan/strings.json b/homeassistant/components/fan/strings.json index c4951e88c91b26..485d6aa4b59ee5 100644 --- a/homeassistant/components/fan/strings.json +++ b/homeassistant/components/fan/strings.json @@ -14,6 +14,9 @@ "toggle": "[%key:common::device_automation::action_type::toggle%]", "turn_on": "[%key:common::device_automation::action_type::turn_on%]", "turn_off": "[%key:common::device_automation::action_type::turn_off%]" + }, + "extra_fields": { + "for": "[%key:common::device_automation::extra_fields::for%]" } }, "entity_component": { diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index becab5a18c5157..d74bf1f30b7f96 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20250903.2"] + "requirements": ["home-assistant-frontend==20250903.3"] } diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index 8bc3d84bd50391..e6f431727d0194 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -10,6 +10,6 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["aiohue"], - "requirements": ["aiohue==4.7.4"], + "requirements": ["aiohue==4.7.5"], "zeroconf": ["_hue._tcp.local."] } diff --git a/homeassistant/components/light/strings.json b/homeassistant/components/light/strings.json index 7a53f2569e7812..a17d6793b83e2a 100644 --- a/homeassistant/components/light/strings.json +++ b/homeassistant/components/light/strings.json @@ -57,7 +57,8 @@ }, "extra_fields": { "brightness_pct": "Brightness", - "flash": "Flash" + "flash": "Flash", + "for": "[%key:common::device_automation::extra_fields::for%]" } }, "entity_component": { diff --git a/homeassistant/components/remote/strings.json b/homeassistant/components/remote/strings.json index 09b270b968766e..0c6cf98de7f3f0 100644 --- a/homeassistant/components/remote/strings.json +++ b/homeassistant/components/remote/strings.json @@ -14,6 +14,9 @@ "changed_states": "[%key:common::device_automation::trigger_type::changed_states%]", "turned_on": "[%key:common::device_automation::trigger_type::turned_on%]", "turned_off": "[%key:common::device_automation::trigger_type::turned_off%]" + }, + "extra_fields": { + "for": "[%key:common::device_automation::extra_fields::for%]" } }, "entity_component": { diff --git a/homeassistant/components/switch/strings.json b/homeassistant/components/switch/strings.json index b73cf8f849dcc5..be5aa09cf342bb 100644 --- a/homeassistant/components/switch/strings.json +++ b/homeassistant/components/switch/strings.json @@ -14,6 +14,9 @@ "changed_states": "[%key:common::device_automation::trigger_type::changed_states%]", "turned_on": "[%key:common::device_automation::trigger_type::turned_on%]", "turned_off": "[%key:common::device_automation::trigger_type::turned_off%]" + }, + "extra_fields": { + "for": "[%key:common::device_automation::extra_fields::for%]" } }, "entity_component": { diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 229c890cecb96b..0a5a71ffc3a090 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -3,8 +3,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any, NamedTuple -from urllib.parse import urlsplit +from typing import Any, NamedTuple from tuya_sharing import ( CustomerDevice, @@ -12,7 +11,6 @@ SharingDeviceListener, SharingTokenListener, ) -from tuya_sharing.mq import SharingMQ, SharingMQConfig from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -47,81 +45,13 @@ class HomeAssistantTuyaData(NamedTuple): listener: SharingDeviceListener -if TYPE_CHECKING: - import paho.mqtt.client as mqtt - - -class ManagerCompat(Manager): - """Extended Manager class from the Tuya device sharing SDK. - - The extension ensures compatibility a paho-mqtt client version >= 2.1.0. - It overrides extend refresh_mq method to ensure correct paho.mqtt client calls. - - This code can be removed when a version of tuya-device-sharing with - https://github.com/tuya/tuya-device-sharing-sdk/pull/25 is available. - """ - - def refresh_mq(self): - """Refresh the MQTT connection.""" - if self.mq is not None: - self.mq.stop() - self.mq = None - - home_ids = [home.id for home in self.user_homes] - device = [ - device - for device in self.device_map.values() - if hasattr(device, "id") and getattr(device, "set_up", False) - ] - - sharing_mq = SharingMQCompat(self.customer_api, home_ids, device) - sharing_mq.start() - sharing_mq.add_message_listener(self.on_message) - self.mq = sharing_mq - - -class SharingMQCompat(SharingMQ): - """Extended SharingMQ class from the Tuya device sharing SDK. - - The extension ensures compatibility a paho-mqtt client version >= 2.1.0. - It overrides _start method to ensure correct paho.mqtt client calls. - - This code can be removed when a version of tuya-device-sharing with - https://github.com/tuya/tuya-device-sharing-sdk/pull/25 is available. - """ - - def _start(self, mq_config: SharingMQConfig) -> mqtt.Client: - """Start the MQTT client.""" - # We don't import on the top because some integrations - # should be able to optionally rely on MQTT. - import paho.mqtt.client as mqtt # noqa: PLC0415 - - mqttc = mqtt.Client(client_id=mq_config.client_id) - mqttc.username_pw_set(mq_config.username, mq_config.password) - mqttc.user_data_set({"mqConfig": mq_config}) - mqttc.on_connect = self._on_connect - mqttc.on_message = self._on_message - mqttc.on_subscribe = self._on_subscribe - mqttc.on_log = self._on_log - mqttc.on_disconnect = self._on_disconnect - - url = urlsplit(mq_config.url) - if url.scheme == "ssl": - mqttc.tls_set() - - mqttc.connect(url.hostname, url.port) - - mqttc.loop_start() - return mqttc - - async def async_setup_entry(hass: HomeAssistant, entry: TuyaConfigEntry) -> bool: """Async setup hass config entry.""" if CONF_APP_TYPE in entry.data: raise ConfigEntryAuthFailed("Authentication failed. Please re-authenticate.") token_listener = TokenListener(hass, entry) - manager = ManagerCompat( + manager = Manager( TUYA_CLIENT_ID, entry.data[CONF_USER_CODE], entry.data[CONF_TERMINAL_ID], diff --git a/homeassistant/components/update/strings.json b/homeassistant/components/update/strings.json index 5194965cf69f76..a90f5c8a998b16 100644 --- a/homeassistant/components/update/strings.json +++ b/homeassistant/components/update/strings.json @@ -5,6 +5,9 @@ "changed_states": "{entity_name} update availability changed", "turned_on": "{entity_name} got an update available", "turned_off": "{entity_name} became up-to-date" + }, + "extra_fields": { + "for": "[%key:common::device_automation::extra_fields::for%]" } }, "entity_component": { diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0af377b8dbb307..97777f0975519d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -38,7 +38,7 @@ habluetooth==5.3.0 hass-nabucasa==1.1.0 hassil==3.2.0 home-assistant-bluetooth==1.13.1 -home-assistant-frontend==20250903.2 +home-assistant-frontend==20250903.3 home-assistant-intents==2025.9.3 httpx==0.28.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 640002581bebd2..f4a62ba55af988 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -277,7 +277,7 @@ aiohomekit==3.2.15 aiohttp_sse==2.2.0 # homeassistant.components.hue -aiohue==4.7.4 +aiohue==4.7.5 # homeassistant.components.imap aioimaplib==2.0.1 @@ -1178,7 +1178,7 @@ hole==0.9.0 holidays==0.79 # homeassistant.components.frontend -home-assistant-frontend==20250903.2 +home-assistant-frontend==20250903.3 # homeassistant.components.conversation home-assistant-intents==2025.9.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 841b840f236d7e..7ee496142ffe6c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -262,7 +262,7 @@ aiohomekit==3.2.15 aiohttp_sse==2.2.0 # homeassistant.components.hue -aiohue==4.7.4 +aiohue==4.7.5 # homeassistant.components.imap aioimaplib==2.0.1 @@ -1027,7 +1027,7 @@ hole==0.9.0 holidays==0.79 # homeassistant.components.frontend -home-assistant-frontend==20250903.2 +home-assistant-frontend==20250903.3 # homeassistant.components.conversation home-assistant-intents==2025.9.3 diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index 425680eac90cfe..57da7cf0b915be 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -5,9 +5,9 @@ from typing import Any from unittest.mock import patch -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import DeviceListener, ManagerCompat +from homeassistant.components.tuya import DeviceListener from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -116,6 +116,7 @@ "dj_zputiamzanuk6yky", # https://github.com/home-assistant/core/issues/149704 "dlq_0tnvg2xaisqdadcf", # https://github.com/home-assistant/core/issues/102769 "dlq_cnpkf4xdmd9v49iq", # https://github.com/home-assistant/core/pull/149320 + "dlq_dikb3dp6", # https://github.com/home-assistant/core/pull/151601 "dlq_jdj6ccklup7btq3a", # https://github.com/home-assistant/core/issues/143209 "dlq_kxdr6su0c55p7bbo", # https://github.com/home-assistant/core/issues/143499 "dlq_r9kg2g1uhhyicycb", # https://github.com/home-assistant/core/issues/149650 @@ -264,7 +265,7 @@ async def async_send_device_update( async def initialize_entry( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: CustomerDevice | list[CustomerDevice], ) -> None: @@ -277,8 +278,6 @@ async def initialize_entry( mock_config_entry.add_to_hass(hass) # Initialize the component - with patch( - "homeassistant.components.tuya.ManagerCompat", return_value=mock_manager - ): + with patch("homeassistant.components.tuya.Manager", return_value=mock_manager): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/tuya/conftest.py b/tests/components/tuya/conftest.py index 08ede9b73d92ec..74604aa153b0f9 100644 --- a/tests/components/tuya/conftest.py +++ b/tests/components/tuya/conftest.py @@ -6,9 +6,14 @@ from unittest.mock import MagicMock, patch import pytest -from tuya_sharing import CustomerApi, CustomerDevice, DeviceFunction, DeviceStatusRange +from tuya_sharing import ( + CustomerApi, + CustomerDevice, + DeviceFunction, + DeviceStatusRange, + Manager, +) -from homeassistant.components.tuya import ManagerCompat from homeassistant.components.tuya.const import ( CONF_APP_TYPE, CONF_ENDPOINT, @@ -56,7 +61,7 @@ def mock_config_entry() -> MockConfigEntry: @pytest.fixture async def mock_loaded_entry( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> MockConfigEntry: @@ -69,7 +74,7 @@ async def mock_loaded_entry( # Initialize the component with ( - patch("homeassistant.components.tuya.ManagerCompat", return_value=mock_manager), + patch("homeassistant.components.tuya.Manager", return_value=mock_manager), ): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -114,9 +119,9 @@ def mock_tuya_login_control() -> Generator[MagicMock]: @pytest.fixture -def mock_manager() -> ManagerCompat: +def mock_manager() -> Manager: """Mock Tuya Manager.""" - manager = MagicMock(spec=ManagerCompat) + manager = MagicMock(spec=Manager) manager.device_map = {} manager.mq = MagicMock() manager.mq.client = MagicMock() @@ -209,9 +214,7 @@ async def _create_device(hass: HomeAssistant, mock_device_code: str) -> Customer @pytest.fixture -def mock_listener( - hass: HomeAssistant, mock_manager: ManagerCompat -) -> MockDeviceListener: +def mock_listener(hass: HomeAssistant, mock_manager: Manager) -> MockDeviceListener: """Create a DeviceListener for testing.""" listener = MockDeviceListener(hass, mock_manager) mock_manager.add_device_listener(listener) diff --git a/tests/components/tuya/fixtures/dlq_dikb3dp6.json b/tests/components/tuya/fixtures/dlq_dikb3dp6.json index 80f6581805a6d2..a32878b8b52bb4 100644 --- a/tests/components/tuya/fixtures/dlq_dikb3dp6.json +++ b/tests/components/tuya/fixtures/dlq_dikb3dp6.json @@ -1,10 +1,8 @@ { "endpoint": "https://apigw.tuyaus.com", - "terminal_id": "1747852059900mCJdQO", "mqtt_connected": true, "disabled_by": null, "disabled_polling": false, - "id": "eb40f2a309edea3892f0o2", "name": "Medidor de Energia", "category": "dlq", "product_id": "dikb3dp6", diff --git a/tests/components/tuya/snapshots/test_init.ambr b/tests/components/tuya/snapshots/test_init.ambr index 2757e54d929726..9d3bc4165e8641 100644 --- a/tests/components/tuya/snapshots/test_init.ambr +++ b/tests/components/tuya/snapshots/test_init.ambr @@ -712,6 +712,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[6pd3bkidqld] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + '6pd3bkidqld', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Metering_3PN_ZB', + 'model_id': 'dikb3dp6', + 'name': 'Medidor de Energia', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[6tbtkuv3tal1aesfjxq] DeviceRegistryEntrySnapshot({ 'area_id': None, diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index 19d16f3893eafd..ffb7e8f4bada84 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -9031,6 +9031,678 @@ 'state': '16.0', }) # --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_current-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_a_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase A current', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_a_current', + 'unique_id': 'tuya.6pd3bkidqldphase_aelectriccurrent', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Medidor de Energia Phase A current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_a_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '13.641', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_a_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase A power', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_a_power', + 'unique_id': 'tuya.6pd3bkidqldphase_apower', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Medidor de Energia Phase A power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_a_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3.183', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_voltage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_a_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase A voltage', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_a_voltage', + 'unique_id': 'tuya.6pd3bkidqldphase_avoltage', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'Medidor de Energia Phase A voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_a_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '232.1', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_current-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_b_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase B current', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_b_current', + 'unique_id': 'tuya.6pd3bkidqldphase_belectriccurrent', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Medidor de Energia Phase B current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_b_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '22.007', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_b_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase B power', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_b_power', + 'unique_id': 'tuya.6pd3bkidqldphase_bpower', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Medidor de Energia Phase B power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_b_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '5.228', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_voltage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_b_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase B voltage', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_b_voltage', + 'unique_id': 'tuya.6pd3bkidqldphase_bvoltage', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'Medidor de Energia Phase B voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_b_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '235.4', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_current-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_c_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase C current', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_c_current', + 'unique_id': 'tuya.6pd3bkidqldphase_celectriccurrent', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Medidor de Energia Phase C current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_c_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '9.119', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_c_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase C power', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_c_power', + 'unique_id': 'tuya.6pd3bkidqldphase_cpower', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Medidor de Energia Phase C power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_c_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2.064', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_voltage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_phase_c_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Phase C voltage', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'phase_c_voltage', + 'unique_id': 'tuya.6pd3bkidqldphase_cvoltage', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'Medidor de Energia Phase C voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_phase_c_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '229.2', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_supply_frequency-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.medidor_de_energia_supply_frequency', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Supply frequency', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'supply_frequency', + 'unique_id': 'tuya.6pd3bkidqldsupply_frequency', + 'unit_of_measurement': 'Hz', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_supply_frequency-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'frequency', + 'friendly_name': 'Medidor de Energia Supply frequency', + 'state_class': , + 'unit_of_measurement': 'Hz', + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_supply_frequency', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '60.02', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_total_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_total_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total energy', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'total_energy', + 'unique_id': 'tuya.6pd3bkidqldforward_energy_total', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_total_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Medidor de Energia Total energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_total_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '135.4', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_total_production-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.medidor_de_energia_total_production', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total production', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'total_production', + 'unique_id': 'tuya.6pd3bkidqldreverse_energy_total', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_total_production-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Medidor de Energia Total production', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.medidor_de_energia_total_production', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '125.52', + }) +# --- # name: test_platform_setup_and_discovery[sensor.meter_phase_a_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/test_alarm_control_panel.py b/tests/components/tuya/test_alarm_control_panel.py index 53721b1add0a7f..20e189acd23f9b 100644 --- a/tests/components/tuya/test_alarm_control_panel.py +++ b/tests/components/tuya/test_alarm_control_panel.py @@ -5,9 +5,8 @@ from unittest.mock import patch from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -20,7 +19,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.ALARM_CONTROL_PANEL]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_binary_sensor.py b/tests/components/tuya/test_binary_sensor.py index 4da79effde7018..a06b585c8a2ef0 100644 --- a/tests/components/tuya/test_binary_sensor.py +++ b/tests/components/tuya/test_binary_sensor.py @@ -6,9 +6,8 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -22,7 +21,7 @@ @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -51,7 +50,7 @@ async def test_platform_setup_and_discovery( @patch("homeassistant.components.tuya.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_bitmap( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, mock_listener: MockDeviceListener, diff --git a/tests/components/tuya/test_button.py b/tests/components/tuya/test_button.py index e9a7b43e103248..971aa877e3f07e 100644 --- a/tests/components/tuya/test_button.py +++ b/tests/components/tuya/test_button.py @@ -5,9 +5,8 @@ from unittest.mock import patch from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -20,7 +19,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.BUTTON]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_camera.py b/tests/components/tuya/test_camera.py index 94295fe119117f..4c2dc5e35cae85 100644 --- a/tests/components/tuya/test_camera.py +++ b/tests/components/tuya/test_camera.py @@ -6,9 +6,8 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -31,7 +30,7 @@ def mock_getrandbits(): @patch("homeassistant.components.tuya.PLATFORMS", [Platform.CAMERA]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_climate.py b/tests/components/tuya/test_climate.py index a0da9359ea31a6..769078361f8622 100644 --- a/tests/components/tuya/test_climate.py +++ b/tests/components/tuya/test_climate.py @@ -6,7 +6,7 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager from homeassistant.components.climate import ( ATTR_FAN_MODE, @@ -17,7 +17,6 @@ SERVICE_SET_HUMIDITY, SERVICE_SET_TEMPERATURE, ) -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceNotSupported @@ -31,7 +30,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.CLIMATE]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -49,7 +48,7 @@ async def test_platform_setup_and_discovery( ) async def test_set_temperature( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -79,7 +78,7 @@ async def test_set_temperature( ) async def test_fan_mode_windspeed( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -110,7 +109,7 @@ async def test_fan_mode_windspeed( ) async def test_fan_mode_no_valid_code( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -144,7 +143,7 @@ async def test_fan_mode_no_valid_code( ) async def test_set_humidity_not_supported( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/components/tuya/test_cover.py b/tests/components/tuya/test_cover.py index 7206aaf1cffe33..bc46ed45f9f5c6 100644 --- a/tests/components/tuya/test_cover.py +++ b/tests/components/tuya/test_cover.py @@ -6,7 +6,7 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, @@ -18,7 +18,6 @@ SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_TILT_POSITION, ) -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceNotSupported @@ -32,7 +31,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.COVER]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -51,7 +50,7 @@ async def test_platform_setup_and_discovery( @patch("homeassistant.components.tuya.PLATFORMS", [Platform.COVER]) async def test_open_service( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -85,7 +84,7 @@ async def test_open_service( @patch("homeassistant.components.tuya.PLATFORMS", [Platform.COVER]) async def test_close_service( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -118,7 +117,7 @@ async def test_close_service( ) async def test_set_position( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -160,7 +159,7 @@ async def test_set_position( @patch("homeassistant.components.tuya.PLATFORMS", [Platform.COVER]) async def test_percent_state_on_cover( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, percent_control: int, @@ -185,7 +184,7 @@ async def test_percent_state_on_cover( ) async def test_set_tilt_position_not_supported( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/components/tuya/test_diagnostics.py b/tests/components/tuya/test_diagnostics.py index f07c2faa229e28..aff84edf231486 100644 --- a/tests/components/tuya/test_diagnostics.py +++ b/tests/components/tuya/test_diagnostics.py @@ -5,9 +5,8 @@ import pytest from syrupy.assertion import SnapshotAssertion from syrupy.filters import props -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.components.tuya.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr @@ -25,7 +24,7 @@ @pytest.mark.parametrize("mock_device_code", ["rqbj_4iqe2hsfyd86kwwc"]) async def test_entry_diagnostics( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, hass_client: ClientSessionGenerator, @@ -46,7 +45,7 @@ async def test_entry_diagnostics( @pytest.mark.parametrize("mock_device_code", ["rqbj_4iqe2hsfyd86kwwc"]) async def test_device_diagnostics( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, hass_client: ClientSessionGenerator, diff --git a/tests/components/tuya/test_event.py b/tests/components/tuya/test_event.py index 6e493ae41c0bfa..ec69b58781e315 100644 --- a/tests/components/tuya/test_event.py +++ b/tests/components/tuya/test_event.py @@ -5,9 +5,8 @@ from unittest.mock import patch from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -20,7 +19,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.EVENT]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_fan.py b/tests/components/tuya/test_fan.py index 992c989e3521c6..d45103ddd0500d 100644 --- a/tests/components/tuya/test_fan.py +++ b/tests/components/tuya/test_fan.py @@ -5,9 +5,8 @@ from unittest.mock import patch from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -20,7 +19,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.FAN]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_humidifier.py b/tests/components/tuya/test_humidifier.py index c38e5521990937..2cdf5534b08f6a 100644 --- a/tests/components/tuya/test_humidifier.py +++ b/tests/components/tuya/test_humidifier.py @@ -6,7 +6,7 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager from homeassistant.components.humidifier import ( ATTR_HUMIDITY, @@ -15,7 +15,6 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, ) -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError @@ -29,7 +28,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.HUMIDIFIER]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -47,7 +46,7 @@ async def test_platform_setup_and_discovery( ) async def test_turn_on( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -74,7 +73,7 @@ async def test_turn_on( ) async def test_turn_off( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -101,7 +100,7 @@ async def test_turn_off( ) async def test_set_humidity( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -131,7 +130,7 @@ async def test_set_humidity( ) async def test_turn_on_unsupported( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -166,7 +165,7 @@ async def test_turn_on_unsupported( ) async def test_turn_off_unsupported( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -201,7 +200,7 @@ async def test_turn_off_unsupported( ) async def test_set_humidity_unsupported( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/components/tuya/test_init.py b/tests/components/tuya/test_init.py index 545a5a7f07cc92..a3ac054902fe07 100644 --- a/tests/components/tuya/test_init.py +++ b/tests/components/tuya/test_init.py @@ -3,9 +3,8 @@ from __future__ import annotations from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.components.tuya.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -17,7 +16,7 @@ async def test_device_registry( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: CustomerDevice, device_registry: dr.DeviceRegistry, diff --git a/tests/components/tuya/test_light.py b/tests/components/tuya/test_light.py index e87eb1393850ff..45067f779b732d 100644 --- a/tests/components/tuya/test_light.py +++ b/tests/components/tuya/test_light.py @@ -7,7 +7,7 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -16,7 +16,6 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, ) -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -29,7 +28,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.LIGHT]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -92,7 +91,7 @@ async def test_platform_setup_and_discovery( ) async def test_turn_on_white( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, turn_on_input: dict[str, Any], @@ -125,7 +124,7 @@ async def test_turn_on_white( ) async def test_turn_off( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/components/tuya/test_number.py b/tests/components/tuya/test_number.py index 89124fdf65fd0e..e5587ade008b51 100644 --- a/tests/components/tuya/test_number.py +++ b/tests/components/tuya/test_number.py @@ -6,14 +6,13 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager from homeassistant.components.number import ( ATTR_VALUE, DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE, ) -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError @@ -27,7 +26,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.NUMBER]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -45,7 +44,7 @@ async def test_platform_setup_and_discovery( ) async def test_set_value( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -75,7 +74,7 @@ async def test_set_value( ) async def test_set_value_no_function( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/components/tuya/test_select.py b/tests/components/tuya/test_select.py index c35963528d4e21..ecc9570584d331 100644 --- a/tests/components/tuya/test_select.py +++ b/tests/components/tuya/test_select.py @@ -6,14 +6,13 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager from homeassistant.components.select import ( ATTR_OPTION, DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION, ) -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError @@ -27,7 +26,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.SELECT]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -45,7 +44,7 @@ async def test_platform_setup_and_discovery( ) async def test_select_option( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -75,7 +74,7 @@ async def test_select_option( ) async def test_select_invalid_option( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/components/tuya/test_sensor.py b/tests/components/tuya/test_sensor.py index a5d61ea47a68ba..034f19ea7ae4dd 100644 --- a/tests/components/tuya/test_sensor.py +++ b/tests/components/tuya/test_sensor.py @@ -6,9 +6,8 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -22,7 +21,7 @@ @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_siren.py b/tests/components/tuya/test_siren.py index 1043c0a3a0f1d7..465d5eab63118a 100644 --- a/tests/components/tuya/test_siren.py +++ b/tests/components/tuya/test_siren.py @@ -5,9 +5,8 @@ from unittest.mock import patch from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -20,7 +19,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.SIREN]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_switch.py b/tests/components/tuya/test_switch.py index e763fe3bd91872..20138b7f0f2fea 100644 --- a/tests/components/tuya/test_switch.py +++ b/tests/components/tuya/test_switch.py @@ -5,9 +5,8 @@ from unittest.mock import patch from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -20,7 +19,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.SWITCH]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, diff --git a/tests/components/tuya/test_vacuum.py b/tests/components/tuya/test_vacuum.py index 5ee5b965137dcd..545a9b2bc8b369 100644 --- a/tests/components/tuya/test_vacuum.py +++ b/tests/components/tuya/test_vacuum.py @@ -6,9 +6,8 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.components.vacuum import ( DOMAIN as VACUUM_DOMAIN, SERVICE_RETURN_TO_BASE, @@ -25,7 +24,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.VACUUM]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -43,7 +42,7 @@ async def test_platform_setup_and_discovery( ) async def test_return_home( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/components/tuya/test_valve.py b/tests/components/tuya/test_valve.py index 73ccfba7fc49a7..b532bacffa872c 100644 --- a/tests/components/tuya/test_valve.py +++ b/tests/components/tuya/test_valve.py @@ -6,9 +6,8 @@ import pytest from syrupy.assertion import SnapshotAssertion -from tuya_sharing import CustomerDevice +from tuya_sharing import CustomerDevice, Manager -from homeassistant.components.tuya import ManagerCompat from homeassistant.components.valve import ( DOMAIN as VALVE_DOMAIN, SERVICE_CLOSE_VALVE, @@ -26,7 +25,7 @@ @patch("homeassistant.components.tuya.PLATFORMS", [Platform.VALVE]) async def test_platform_setup_and_discovery( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_devices: list[CustomerDevice], entity_registry: er.EntityRegistry, @@ -44,7 +43,7 @@ async def test_platform_setup_and_discovery( ) async def test_open_valve( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: @@ -73,7 +72,7 @@ async def test_open_valve( ) async def test_close_valve( hass: HomeAssistant, - mock_manager: ManagerCompat, + mock_manager: Manager, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, ) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 130ce74dd5b229..a07e659378aa90 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -99,6 +99,7 @@ translation as translation_helper, ) from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from homeassistant.helpers.translation import _TranslationsCacheData from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component @@ -2090,3 +2091,21 @@ def disable_block_async_io() -> Generator[None]: blocking_call.object, blocking_call.function, blocking_call.original_func ) calls.clear() + + +# Ensure that incorrectly formatted mac addresses are rejected during +# DhcpServiceInfo initialisation +_real_dhcp_service_info_init = DhcpServiceInfo.__init__ + + +def _dhcp_service_info_init(self: DhcpServiceInfo, *args: Any, **kwargs: Any) -> None: + """Override __init__ for DhcpServiceInfo. + + Ensure that the macaddress is always in lowercase and without colons to match DHCP service. + """ + _real_dhcp_service_info_init(self, *args, **kwargs) + if self.macaddress != self.macaddress.lower().replace(":", ""): + raise ValueError("macaddress is not correctly formatted") + + +DhcpServiceInfo.__init__ = _dhcp_service_info_init diff --git a/tests/hassfest/__init__.py b/tests/hassfest/__init__.py index 1ec5a22a567c51..75d263fa3c89d5 100644 --- a/tests/hassfest/__init__.py +++ b/tests/hassfest/__init__.py @@ -1 +1,19 @@ """Tests for hassfest.""" + +from pathlib import Path + +from script.hassfest.model import Config, Integration + + +def get_integration(domain: str, config: Config): + """Helper function for creating hassfest integration model instances.""" + return Integration( + Path(domain), + _config=config, + _manifest={ + "domain": domain, + "name": domain, + "documentation": "https://example.com", + "codeowners": ["@awesome"], + }, + ) diff --git a/tests/hassfest/conftest.py b/tests/hassfest/conftest.py new file mode 100644 index 00000000000000..86305b799f6772 --- /dev/null +++ b/tests/hassfest/conftest.py @@ -0,0 +1,26 @@ +"""Fixtures for hassfest tests.""" + +from pathlib import Path +from unittest.mock import patch + +import pytest + +from script.hassfest.model import Config, Integration + + +@pytest.fixture +def config(): + """Fixture for hassfest Config.""" + return Config( + root=Path(".").absolute(), + specific_integrations=None, + action="validate", + requirements=True, + ) + + +@pytest.fixture +def mock_core_integration(): + """Mock Integration to be a core one.""" + with patch.object(Integration, "core", return_value=True): + yield diff --git a/tests/hassfest/test_conditions.py b/tests/hassfest/test_conditions.py new file mode 100644 index 00000000000000..09046c0007f871 --- /dev/null +++ b/tests/hassfest/test_conditions.py @@ -0,0 +1,154 @@ +"""Tests for hassfest conditions.""" + +import io +import json +from pathlib import Path +from unittest.mock import patch + +import pytest + +from homeassistant.util.yaml.loader import parse_yaml +from script.hassfest import conditions +from script.hassfest.model import Config + +from . import get_integration + +CONDITION_DESCRIPTION_FILENAME = "conditions.yaml" +CONDITION_ICONS_FILENAME = "icons.json" +CONDITION_STRINGS_FILENAME = "strings.json" + +CONDITION_DESCRIPTIONS = { + "valid": { + CONDITION_DESCRIPTION_FILENAME: """ + _: + fields: + after: + example: sunrise + selector: + select: + options: + - sunrise + - sunset + after_offset: + selector: + time: null + """, + CONDITION_ICONS_FILENAME: {"conditions": {"_": {"condition": "mdi:flash"}}}, + CONDITION_STRINGS_FILENAME: { + "conditions": { + "_": { + "name": "Sun", + "description": "When the sun is above/below the horizon", + "description_configured": "When a the sun rises or sets.", + "fields": { + "after": {"name": "After event", "description": "The event."}, + "after_offset": { + "name": "Offset", + "description": "The offset.", + }, + }, + } + } + }, + "errors": [], + }, + "yaml_missing_colon": { + CONDITION_DESCRIPTION_FILENAME: """ + test: + fields + entity: + selector: + entity: + """, + "errors": ["Invalid conditions.yaml"], + }, + "invalid_conditions_schema": { + CONDITION_DESCRIPTION_FILENAME: """ + invalid_condition: + fields: + entity: + selector: + invalid_selector: null + """, + "errors": ["Unknown selector type invalid_selector"], + }, + "missing_strings_and_icons": { + CONDITION_DESCRIPTION_FILENAME: """ + sun: + fields: + after: + example: sunrise + selector: + select: + options: + - sunrise + - sunset + translation_key: after + after_offset: + selector: + time: null + """, + CONDITION_ICONS_FILENAME: {"conditions": {}}, + CONDITION_STRINGS_FILENAME: { + "conditions": { + "sun": { + "fields": { + "after_offset": {}, + }, + } + } + }, + "errors": [ + "has no icon", + "has no name", + "has no description", + "field after with no name", + "field after with no description", + "field after with a selector with a translation key", + "field after_offset with no name", + "field after_offset with no description", + ], + }, +} + + +@pytest.mark.usefixtures("mock_core_integration") +def test_validate(config: Config) -> None: + """Test validate version with no key.""" + + def _load_yaml(fname, secrets=None): + domain, yaml_file = fname.split("/") + assert yaml_file == CONDITION_DESCRIPTION_FILENAME + + condition_descriptions = CONDITION_DESCRIPTIONS[domain][yaml_file] + with io.StringIO(condition_descriptions) as file: + return parse_yaml(file) + + def _patched_path_read_text(path: Path): + domain = path.parent.name + filename = path.name + + return json.dumps(CONDITION_DESCRIPTIONS[domain][filename]) + + integrations = { + domain: get_integration(domain, config) for domain in CONDITION_DESCRIPTIONS + } + + with ( + patch("script.hassfest.conditions.grep_dir", return_value=True), + patch("pathlib.Path.is_file", return_value=True), + patch("pathlib.Path.read_text", _patched_path_read_text), + patch("annotatedyaml.loader.load_yaml", side_effect=_load_yaml), + ): + conditions.validate(integrations, config) + + assert not config.errors + + for domain, description in CONDITION_DESCRIPTIONS.items(): + assert len(integrations[domain].errors) == len(description["errors"]), ( + f"Domain '{domain}' has unexpected errors: {integrations[domain].errors}" + ) + for error, expected_error in zip( + integrations[domain].errors, description["errors"], strict=True + ): + assert expected_error in error.error diff --git a/tests/hassfest/test_triggers.py b/tests/hassfest/test_triggers.py index 236e6f961344e9..9cf327a0e0edeb 100644 --- a/tests/hassfest/test_triggers.py +++ b/tests/hassfest/test_triggers.py @@ -9,7 +9,9 @@ from homeassistant.util.yaml.loader import parse_yaml from script.hassfest import triggers -from script.hassfest.model import Config, Integration +from script.hassfest.model import Config + +from . import get_integration TRIGGER_DESCRIPTION_FILENAME = "triggers.yaml" TRIGGER_ICONS_FILENAME = "icons.json" @@ -107,38 +109,6 @@ } -@pytest.fixture -def config(): - """Fixture for hassfest Config.""" - return Config( - root=Path(".").absolute(), - specific_integrations=None, - action="validate", - requirements=True, - ) - - -@pytest.fixture -def mock_core_integration(): - """Mock Integration to be a core one.""" - with patch.object(Integration, "core", return_value=True): - yield - - -def get_integration(domain: str, config: Config): - """Fixture for hassfest integration model.""" - return Integration( - Path(domain), - _config=config, - _manifest={ - "domain": domain, - "name": domain, - "documentation": "https://example.com", - "codeowners": ["@awesome"], - }, - ) - - @pytest.mark.usefixtures("mock_core_integration") def test_validate(config: Config) -> None: """Test validate version with no key.""" diff --git a/tests/helpers/test_service_info.py b/tests/helpers/test_service_info.py new file mode 100644 index 00000000000000..249ceb0e63781c --- /dev/null +++ b/tests/helpers/test_service_info.py @@ -0,0 +1,23 @@ +"""Test service_info helpers.""" + +import pytest + +from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo + +# Ensure that incorrectly formatted mac addresses are rejected, even +# on a constant outside of a test +try: + _ = DhcpServiceInfo(ip="", hostname="", macaddress="AA:BB:CC:DD:EE:FF") +except ValueError: + pass +else: + raise RuntimeError( + "DhcpServiceInfo incorrectly formatted mac address was not rejected. " + "Please ensure that the DhcpServiceInfo is correctly patched." + ) + + +def test_invalid_macaddress() -> None: + """Test that DhcpServiceInfo raises ValueError for unformatted macaddress.""" + with pytest.raises(ValueError): + DhcpServiceInfo(ip="", hostname="", macaddress="AA:BB:CC:DD:EE:FF")