diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8128116..f4e4415c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Versions from 0.40 and up +## v0.57.5 + +- Bump plugwise to [v1.7.7](https://github.com/plugwise/python-plugwise/releases/tag/v1.7.7) and adapt, small improvements + ## v0.57.4 - Maintenance chores on CI diff --git a/custom_components/plugwise/__init__.py b/custom_components/plugwise/__init__.py index 5c6070980..3d509da98 100644 --- a/custom_components/plugwise/__init__.py +++ b/custom_components/plugwise/__init__.py @@ -50,10 +50,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> config_entry_id=entry.entry_id, identifiers={(DOMAIN, str(coordinator.api.gateway_id))}, manufacturer="Plugwise", - model=coordinator.api.smile_model, - model_id=coordinator.api.smile_model_id, - name=coordinator.api.smile_name, - sw_version=str(coordinator.api.smile_version), + model=coordinator.api.smile.model, + model_id=coordinator.api.smile.model_id, + name=coordinator.api.smile.name, + sw_version=str(coordinator.api.smile.version), ) # required for adding the entity-less P1 Gateway async def delete_notification( @@ -62,7 +62,7 @@ async def delete_notification( """Service: delete the Plugwise Notification.""" LOGGER.debug( "Service delete PW Notification called for %s", - coordinator.api.smile_name, + coordinator.api.smile.name, ) try: await coordinator.api.delete_notification() @@ -70,7 +70,7 @@ async def delete_notification( except PlugwiseError: LOGGER.debug( "Failed to delete the Plugwise Notification for %s", - coordinator.api.smile_name, + coordinator.api.smile.name, ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/custom_components/plugwise/climate.py b/custom_components/plugwise/climate.py index 72fa54b69..7d889b8a8 100644 --- a/custom_components/plugwise/climate.py +++ b/custom_components/plugwise/climate.py @@ -73,7 +73,7 @@ def _add_entities() -> None: return entities: list[PlugwiseClimateEntity] = [] - gateway_name = coordinator.api.smile_name + gateway_name = coordinator.api.smile.name for device_id in coordinator.new_devices: device = coordinator.data[device_id] if gateway_name == "Adam": @@ -139,7 +139,7 @@ def __init__( self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE if ( self.coordinator.api.cooling_present - and coordinator.api.smile_name != "Adam" + and coordinator.api.smile.name != "Adam" ): self._attr_supported_features = ( ClimateEntityFeature.TARGET_TEMPERATURE_RANGE diff --git a/custom_components/plugwise/config_flow.py b/custom_components/plugwise/config_flow.py index f080902db..9a14e7fa1 100644 --- a/custom_components/plugwise/config_flow.py +++ b/custom_components/plugwise/config_flow.py @@ -245,10 +245,10 @@ async def async_step_user( api, errors = await verify_connection(self.hass, user_input) if api: await self.async_set_unique_id( - api.smile_hostname or api.gateway_id, raise_on_progress=False + api.smile.hostname or api.gateway_id, raise_on_progress=False ) self._abort_if_unique_id_configured() - return self.async_create_entry(title=api.smile_name, data=user_input) + return self.async_create_entry(title=api.smile.name, data=user_input) configure_input = self.discovery_info or user_input return self.async_show_form( @@ -278,7 +278,7 @@ async def async_step_reconfigure( api, errors = await verify_connection(self.hass, full_input) if api: await self.async_set_unique_id( - api.smile_hostname or api.gateway_id, raise_on_progress=False + api.smile.hostname or api.gateway_id, raise_on_progress=False ) self._abort_if_unique_id_mismatch(reason="not_the_same_smile") return self.async_update_reload_and_abort( @@ -317,7 +317,7 @@ def __init__(self, config_entry: ConfigEntry) -> None: self.options = deepcopy(dict(config_entry.options)) def _create_options_schema(self, coordinator: PlugwiseDataUpdateCoordinator) -> vol.Schema: - interval = DEFAULT_SCAN_INTERVAL[coordinator.api.smile_type] # pw-beta options + interval = DEFAULT_SCAN_INTERVAL[coordinator.api.smile.type] # pw-beta options schema = { vol.Optional( CONF_SCAN_INTERVAL, @@ -325,7 +325,7 @@ def _create_options_schema(self, coordinator: PlugwiseDataUpdateCoordinator) -> ): vol.All(cv.positive_int, vol.Clamp(min=10)), } # pw-beta - if coordinator.api.smile_type == THERMOSTAT: + if coordinator.api.smile.type == THERMOSTAT: schema.update({ vol.Optional( CONF_HOMEKIT_EMULATION, diff --git a/custom_components/plugwise/coordinator.py b/custom_components/plugwise/coordinator.py index a2973dc7c..ec17d4e02 100644 --- a/custom_components/plugwise/coordinator.py +++ b/custom_components/plugwise/coordinator.py @@ -83,7 +83,7 @@ async def _connect(self) -> None: self._connected = isinstance(version, Version) if self._connected: self.update_interval = DEFAULT_SCAN_INTERVAL.get( - self.api.smile_type, timedelta(seconds=60) + self.api.smile.type, timedelta(seconds=60) ) # pw-beta options scan-interval if (custom_time := self.config_entry.options.get(CONF_SCAN_INTERVAL)) is not None: self.update_interval = timedelta( @@ -125,7 +125,7 @@ async def _async_update_data(self) -> dict[str, GwEntityData]: translation_key="unsupported_firmware", ) from err - LOGGER.debug(f"{self.api.smile_name} data: %s", data) + LOGGER.debug(f"{self.api.smile.name} data: %s", data) await self._async_add_remove_devices(data) return data @@ -154,17 +154,17 @@ async def _async_remove_devices(self, data: dict[str, GwEntityData]) -> None: # Then remove the connected orphaned device(s) for device_entry in device_list: for identifier in device_entry.identifiers: - if identifier[0] == DOMAIN: - if ( - device_entry.via_device_id == via_device_id - and identifier[1] not in data - ): - device_reg.async_update_device( - device_entry.id, remove_config_entry_id=self.config_entry.entry_id - ) - LOGGER.debug( - "Removed %s device/zone %s %s from device_registry", - DOMAIN, - device_entry.model, - identifier[1], - ) + if ( + identifier[0] == DOMAIN + and device_entry.via_device_id == via_device_id + and identifier[1] not in data + ): + device_reg.async_update_device( + device_entry.id, remove_config_entry_id=self.config_entry.entry_id + ) + LOGGER.debug( + "Removed %s device/zone %s %s from device_registry", + DOMAIN, + device_entry.model, + identifier[1], + ) diff --git a/custom_components/plugwise/entity.py b/custom_components/plugwise/entity.py index 5ffbc5cf5..17705b13a 100644 --- a/custom_components/plugwise/entity.py +++ b/custom_components/plugwise/entity.py @@ -60,7 +60,7 @@ def __init__( manufacturer=data.get(VENDOR), model=data.get(MODEL), model_id=data.get(MODEL_ID), - name=coordinator.api.smile_name, + name=coordinator.api.smile.name, sw_version=data.get(FIRMWARE), hw_version=data.get(HARDWARE), ) diff --git a/custom_components/plugwise/manifest.json b/custom_components/plugwise/manifest.json index cc7819379..8cf329ec2 100644 --- a/custom_components/plugwise/manifest.json +++ b/custom_components/plugwise/manifest.json @@ -7,7 +7,7 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["plugwise"], - "requirements": ["plugwise==1.7.6"], - "version": "0.57.4", + "requirements": ["plugwise==1.7.7"], + "version": "0.57.5", "zeroconf": ["_plugwise._tcp.local."] } diff --git a/pyproject.toml b/pyproject.toml index f4db6c427..1b5a6bada 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "plugwise-beta" -version = "0.57.4" +version = "0.57.5" description = "Plugwise beta custom-component" readme = "README.md" requires-python = ">=3.13" diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index 1148b7942..de96a5560 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -17,11 +17,20 @@ CONF_USERNAME, ) from homeassistant.core import HomeAssistant +from munch import Munch from packaging.version import Version from tests.common import MockConfigEntry, load_fixture +def build_smile(**attrs): + """Build smile Munch from provided attributes.""" + smile = Munch() + for k, v in attrs.items(): + setattr(smile, k, v) + return smile + + def _read_json(environment: str, call: str) -> dict[str, Any]: """Undecode the json data.""" fixture = load_fixture(f"plugwise/{environment}/{call}.json") @@ -101,17 +110,19 @@ def mock_smile_config_flow() -> Generator[MagicMock]: with patch( "homeassistant.components.plugwise.config_flow.Smile", autospec=True, - ) as smile_mock: - smile = smile_mock.return_value + ) as api_mock: + api = api_mock.return_value - smile.connect.return_value = Version("4.3.2") - smile.smile_hostname = "smile12345" - smile.smile_model = "Test Model" - smile.smile_model_id = "Test Model ID" - smile.smile_name = "Test Smile Name" - smile.smile_version = "4.3.2" + api.connect.return_value = Version("4.3.2") + api.smile = build_smile( + hostname = "smile12345", + model = "Test Model", + model_id = "Test Model ID", + name = "Test Smile Name", + version = "4.3.2", + ) - yield smile + yield api @pytest.fixture @@ -122,28 +133,30 @@ def mock_smile_adam() -> Generator[MagicMock]: with ( patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True - ) as smile_mock, + ) as api_mock, patch( "homeassistant.components.plugwise.config_flow.Smile", - new=smile_mock, + new=api_mock, ), ): - smile = smile_mock.return_value - - smile.async_update.return_value = data - smile.cooling_present = False - smile.connect.return_value = Version("3.0.15") - smile.gateway_id = "fe799307f1624099878210aa0b9f1475" - smile.heater_id = "90986d591dcd426cae3ec3e8111ff730" - smile.reboot = True - smile.smile_hostname = "smile98765" - smile.smile_model = "Gateway" - smile.smile_model_id = "smile_open_therm" - smile.smile_name = "Adam" - smile.smile_type = "thermostat" - smile.smile_version = "3.0.15" - - yield smile + api = api_mock.return_value + + api.async_update.return_value = data + api.cooling_present = False + api.connect.return_value = Version("3.0.15") + api.gateway_id = "fe799307f1624099878210aa0b9f1475" + api.heater_id = "90986d591dcd426cae3ec3e8111ff730" + api.reboot = True + api.smile = build_smile( + hostname = "smile98765", + model = "Gateway", + model_id = "smile_open_therm", + name = "Adam", + type = "thermostat", + version = "3.0.15", + ) + + yield api @pytest.fixture @@ -152,23 +165,25 @@ def mock_smile_adam_heat_cool(chosen_env: str, cooling_present: bool) -> Generat data = _read_json(chosen_env, "data") with patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True - ) as smile_mock: - smile = smile_mock.return_value - - smile.async_update.return_value = data - smile.connect.return_value = Version("3.6.4") - smile.cooling_present = cooling_present - smile.gateway_id = "da224107914542988a88561b4452b0f6" - smile.heater_id = "056ee145a816487eaa69243c3280f8bf" - smile.reboot = True - smile.smile_version = "3.6.4" - smile.smile_type = "thermostat" - smile.smile_hostname = "smile98765" - smile.smile_model = "Gateway" - smile.smile_model_id = "smile_open_therm" - smile.smile_name = "Adam" - - yield smile + ) as api_mock: + api = api_mock.return_value + + api.async_update.return_value = data + api.connect.return_value = Version("3.6.4") + api.cooling_present = cooling_present + api.gateway_id = "da224107914542988a88561b4452b0f6" + api.heater_id = "056ee145a816487eaa69243c3280f8bf" + api.reboot = True + api.smile = build_smile( + hostname = "smile98765", + model = "Gateway", + model_id = "smile_open_therm", + name = "Adam", + type = "thermostat", + version = "3.6.4", + ) + + yield api @pytest.fixture @@ -178,23 +193,25 @@ def mock_smile_adam_4() -> Generator[MagicMock]: data = _read_json(chosen_env, "data") with patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True - ) as smile_mock: - smile = smile_mock.return_value - - smile.async_update.return_value = data - smile.connect.return_value = Version("3.2.8") - smile.cooling_present = False - smile.gateway_id = "b5c2386c6f6342669e50fe49dd05b188" - smile.heater_id = "e4684553153b44afbef2200885f379dc" - smile.reboot = True - smile.smile_hostname = "smile98765" - smile.smile_model = "Gateway" - smile.smile_model_id = "smile_open_therm" - smile.smile_name = "Adam" - smile.smile_type = "thermostat" - smile.smile_version = "3.2.8" - - yield smile + ) as api_mock: + api = api_mock.return_value + + api.async_update.return_value = data + api.connect.return_value = Version("3.2.8") + api.cooling_present = False + api.gateway_id = "b5c2386c6f6342669e50fe49dd05b188" + api.heater_id = "e4684553153b44afbef2200885f379dc" + api.reboot = True + api.smile = build_smile( + hostname = "smile98765", + model = "Gateway", + model_id = "smile_open_therm", + name = "Adam", + type = "thermostat", + version = "3.2.8", + ) + + yield api @pytest.fixture @@ -203,23 +220,25 @@ def mock_smile_anna(chosen_env: str, cooling_present: bool) -> Generator[MagicMo data = _read_json(chosen_env, "data") with patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True - ) as smile_mock: - smile = smile_mock.return_value - - smile.async_update.return_value = data - smile.connect.return_value = Version("4.0.15") - smile.cooling_present = cooling_present - smile.gateway_id = "015ae9ea3f964e668e490fa39da3870b" - smile.heater_id = "1cbf783bb11e4a7c8a6843dee3a86927" - smile.reboot = True - smile.smile_version = "4.0.15" - smile.smile_type = "thermostat" - smile.smile_hostname = "smile98765" - smile.smile_model = "Gateway" - smile.smile_model_id = "smile_thermo" - smile.smile_name = "Smile Anna" - - yield smile + ) as api_mock: + api = api_mock.return_value + + api.async_update.return_value = data + api.connect.return_value = Version("4.0.15") + api.cooling_present = cooling_present + api.gateway_id = "015ae9ea3f964e668e490fa39da3870b" + api.heater_id = "1cbf783bb11e4a7c8a6843dee3a86927" + api.reboot = True + api.smile = build_smile( + hostname = "smile98765", + model = "Gateway", + model_id = "smile_thermo", + name = "Smile Anna", + type = "thermostat", + version = "4.0.15", + ) + + yield api @pytest.fixture @@ -228,22 +247,24 @@ def mock_smile_p1(chosen_env: str, gateway_id: str) -> Generator[MagicMock]: data = _read_json(chosen_env, "data") with patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True - ) as smile_mock: - smile = smile_mock.return_value - - smile.async_update.return_value = data - smile.connect.return_value = Version("4.4.2") - smile.gateway_id = gateway_id - smile.heater_id = None - smile.reboot = True - smile.smile_hostname = "smile98765" - smile.smile_model = "Gateway" - smile.smile_model_id = "smile" - smile.smile_name = "Smile P1" - smile.smile_type = "power" - smile.smile_version = "4.4.2" - - yield smile + ) as api_mock: + api = api_mock.return_value + + api.async_update.return_value = data + api.connect.return_value = Version("4.4.2") + api.gateway_id = gateway_id + api.heater_id = None + api.reboot = True + api.smile = build_smile( + hostname = "smile98765", + model = "Gateway", + model_id = "smile", + name = "Smile P1", + type = "power", + version = "4.4.2", + ) + + yield api @pytest.fixture @@ -253,22 +274,24 @@ def mock_smile_legacy_anna() -> Generator[MagicMock]: data = _read_json(chosen_env, "data") with patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True - ) as smile_mock: - smile = smile_mock.return_value - - smile.async_update.return_value = data - smile.connect.return_value = Version("1.8.22") - smile.gateway_id = "0000aaaa0000aaaa0000aaaa0000aa00" - smile.heater_id = "04e4cbfe7f4340f090f85ec3b9e6a950" - smile.reboot = False - smile.smile_hostname = "smile98765" - smile.smile_model = "Gateway" - smile.smile_model_id = None - smile.smile_name = "Smile Anna" - smile.smile_type = "thermostat" - smile.smile_version = "1.8.22" - - yield smile + ) as api_mock: + api = api_mock.return_value + + api.async_update.return_value = data + api.connect.return_value = Version("1.8.22") + api.gateway_id = "0000aaaa0000aaaa0000aaaa0000aa00" + api.heater_id = "04e4cbfe7f4340f090f85ec3b9e6a950" + api.reboot = False + api.smile = build_smile( + hostname = "smile98765", + model = "Gateway", + model_id = None, + name = "Smile Anna", + type = "thermostat", + version = "1.8.22", + ) + + yield api @pytest.fixture @@ -278,22 +301,24 @@ def mock_stretch() -> Generator[MagicMock]: data = _read_json(chosen_env, "data") with patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True - ) as smile_mock: - smile = smile_mock.return_value - - smile.async_update.return_value = data - smile.connect.return_value = Version("3.1.11") - smile.gateway_id = "259882df3c05415b99c2d962534ce820" - smile.heater_id = None - smile.reboot = False - smile.smile_hostname = "stretch98765" - smile.smile_model = "Gateway" - smile.smile_model_id = None - smile.smile_name = "Stretch" - smile.smile_type = "stretch" - smile.smile_version = "3.1.11" - - yield smile + ) as api_mock: + api = api_mock.return_value + + api.async_update.return_value = data + api.connect.return_value = Version("3.1.11") + api.gateway_id = "259882df3c05415b99c2d962534ce820" + api.heater_id = None + api.reboot = False + api.smile = build_smile( + hostname = "stretch98765", + model = "Gateway", + model_id = None, + name = "Stretch", + type = "stretch", + version = "3.1.11", + ) + + yield api @pytest.fixture diff --git a/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json b/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json index 7c38b1b21..06459a117 100644 --- a/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json +++ b/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json @@ -531,6 +531,19 @@ "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A11" }, + "e8ef2a01ed3b4139a53bf749204fe6b4": { + "dev_class": "switching", + "members": [ + "02cf28bfec924855854c544690a609ef", + "4a810418d5394b3f82727340b91ba740" + ], + "model": "Switchgroup", + "name": "Test", + "switches": { + "relay": true + }, + "vendor": "Plugwise" + }, "f1fee6043d3642a9b0a65297455f008e": { "available": true, "binary_sensors": { diff --git a/tests/components/plugwise/snapshots/test_diagnostics.ambr b/tests/components/plugwise/snapshots/test_diagnostics.ambr index 92ed327b8..4aa367bc1 100644 --- a/tests/components/plugwise/snapshots/test_diagnostics.ambr +++ b/tests/components/plugwise/snapshots/test_diagnostics.ambr @@ -579,6 +579,19 @@ 'vendor': 'Plugwise', 'zigbee_mac_address': 'ABCD012345670A11', }), + 'e8ef2a01ed3b4139a53bf749204fe6b4': dict({ + 'dev_class': 'switching', + 'members': list([ + '02cf28bfec924855854c544690a609ef', + '4a810418d5394b3f82727340b91ba740', + ]), + 'model': 'Switchgroup', + 'name': 'Test', + 'switches': dict({ + 'relay': True, + }), + 'vendor': 'Plugwise', + }), 'f1fee6043d3642a9b0a65297455f008e': dict({ 'available': True, 'binary_sensors': dict({ diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index 9db611514..777d2c282 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -504,7 +504,7 @@ async def test_reconfigure_flow_other_smile( mock_config_entry: MockConfigEntry, ) -> None: """Test reconfigure flow aborts on other Smile ID.""" - mock_smile_adam.smile_hostname = TEST_SMILE_ID + mock_smile_adam.smile.hostname = TEST_SMILE_ID result = await _start_reconfigure_flow(hass, mock_config_entry, TEST_HOST) diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index 5133a0839..a307b7b78 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -328,7 +328,7 @@ async def test_update_device( == 9 ) item_list: list[str] = [] - for device_entry in list(device_registry.devices.values()): + for device_entry in device_registry.devices.values(): item_list.extend(x[1] for x in device_entry.identifiers) assert "01234567890abcdefghijklmnopqrstu" in item_list @@ -355,6 +355,6 @@ async def test_update_device( == 8 ) item_list: list[str] = [] - for device_entry in list(device_registry.devices.values()): + for device_entry in device_registry.devices.values(): item_list.extend(x[1] for x in device_entry.identifiers) assert "1772a4ea304041adb83f357b751341ff" not in item_list