diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9ff73a7b5..f1175d808 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ default_language_version: repos: # Run manually in CI skipping the branch checks - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.0 + rev: v0.12.1 hooks: - id: ruff name: "Ruff check" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d47c3971..858855752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.7.7 + +- Implement code quality improvements as suggested by SonarCloud via [#762](https://github.com/plugwise/python-plugwise/pull/762), [#763](https://github.com/plugwise/python-plugwise/pull/763), [#764](https://github.com/plugwise/python-plugwise/pull/764), and [#765](https://github.com/plugwise/python-plugwise/pull/765) + ## v1.7.6 - Maintenance chores (mostly reworking Github CI Actions) backporting from efforts on Python Plugwise [USB: #264](https://github.com/plugwise/python-plugwise-usb/pull/264) after porting our progress using [USB: #263](https://github.com/plugwise/python-plugwise-usb/pull/263) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 9c56faa6a..47abbd837 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -5,6 +5,8 @@ from __future__ import annotations +from typing import cast + from plugwise.constants import ( DEFAULT_LEGACY_TIMEOUT, DEFAULT_PORT, @@ -36,6 +38,7 @@ import aiohttp from defusedxml import ElementTree as etree +from munch import Munch from packaging.version import Version, parse @@ -72,16 +75,17 @@ def __init__( self._smile_api: SmileAPI | SmileLegacyAPI self._stretch_v2 = False self._target_smile: str = NONE - self.smile_hostname: str = NONE - self.smile_hw_version: str | None = None - self.smile_legacy = False - self.smile_mac_address: str | None = None - self.smile_model: str = NONE - self.smile_model_id: str | None = None - self.smile_name: str = NONE - self.smile_type: str = NONE - self.smile_version: Version = Version("0.0.0") - self.smile_zigbee_mac_address: str | None = None + self.smile: Munch = Munch() + self.smile.hostname = NONE + self.smile.hw_version = None + self.smile.legacy = False + self.smile.mac_address = None + self.smile.model = NONE + self.smile.model_id = None + self.smile.name = NONE + self.smile.type = NONE + self.smile.version = Version("0.0.0") + self.smile.zigbee_mac_address = None @property def cooling_present(self) -> bool: @@ -109,7 +113,7 @@ def reboot(self) -> bool: All non-legacy devices support gateway-rebooting. """ - return not self.smile_legacy + return not self.smile.legacy async def connect(self) -> Version: """Connect to the Plugwise Gateway and determine its name, type, version, and other data.""" @@ -158,16 +162,9 @@ async def connect(self) -> Version: self._opentherm_device, self._request, self._schedule_old_states, - self.smile_hostname, - self.smile_hw_version, - self.smile_mac_address, - self.smile_model, - self.smile_model_id, - self.smile_name, - self.smile_type, - self.smile_version, + self.smile, ) - if not self.smile_legacy + if not self.smile.legacy else SmileLegacyAPI( self._is_thermostat, self._loc_data, @@ -176,21 +173,14 @@ async def connect(self) -> Version: self._request, self._stretch_v2, self._target_smile, - self.smile_hostname, - self.smile_hw_version, - self.smile_mac_address, - self.smile_model, - self.smile_name, - self.smile_type, - self.smile_version, - self.smile_zigbee_mac_address, + self.smile, ) ) # Update all endpoints on first connect await self._smile_api.full_xml_update() - return self.smile_version + return cast(Version, self.smile.version) async def _smile_detect( self, result: etree.Element, dsmrmain: etree.Element @@ -203,15 +193,17 @@ async def _smile_detect( if (gateway := result.find("./gateway")) is not None: if (v_model := gateway.find("vendor_model")) is not None: model = v_model.text - self.smile_version = parse(gateway.find("firmware_version").text) - self.smile_hw_version = gateway.find("hardware_version").text - self.smile_hostname = gateway.find("hostname").text - self.smile_mac_address = gateway.find("mac_address").text - self.smile_model_id = gateway.find("vendor_model").text + self.smile.version = parse(gateway.find("firmware_version").text) + self.smile.hw_version = gateway.find("hardware_version").text + self.smile.hostname = gateway.find("hostname").text + self.smile.mac_address = gateway.find("mac_address").text + self.smile.model_id = gateway.find("vendor_model").text else: model = await self._smile_detect_legacy(result, dsmrmain, model) - if model == "Unknown" or self.smile_version is None: # pragma: no cover + if model == "Unknown" or self.smile.version == Version( + "0.0.0" + ): # pragma: no cover # Corner case check LOGGER.error( "Unable to find model or version information, please create" @@ -219,7 +211,7 @@ async def _smile_detect( ) raise UnsupportedDeviceError - version_major = str(self.smile_version.major) + version_major = str(self.smile.version.major) self._target_smile = f"{model}_v{version_major}" LOGGER.debug("Plugwise identified as %s", self._target_smile) if self._target_smile not in SMILES: @@ -230,7 +222,7 @@ async def _smile_detect( ) raise UnsupportedDeviceError - if not self.smile_legacy: + if not self.smile.legacy: self._timeout = DEFAULT_TIMEOUT if self._target_smile in ("smile_open_therm_v2", "smile_thermo_v3"): @@ -240,14 +232,14 @@ async def _smile_detect( ) # pragma: no cover raise UnsupportedDeviceError # pragma: no cover - self.smile_model = "Gateway" - self.smile_name = SMILES[self._target_smile].smile_name - self.smile_type = SMILES[self._target_smile].smile_type + self.smile.model = "Gateway" + self.smile.name = SMILES[self._target_smile].smile_name + self.smile.type = SMILES[self._target_smile].smile_type - if self.smile_type == "stretch": + if self.smile.type == "stretch": self._stretch_v2 = int(version_major) == 2 - if self.smile_type == "thermostat": + if self.smile.type == "thermostat": self._is_thermostat = True # For Adam, Anna, determine the system capabilities: # Find the connected heating/cooling device (heater_central), @@ -275,13 +267,13 @@ async def _smile_detect_legacy( return_model = model # Stretch: find the MAC of the zigbee master_controller (= Stick) if (network := result.find("./module/protocols/master_controller")) is not None: - self.smile_zigbee_mac_address = network.find("mac_address").text + self.smile.zigbee_mac_address = network.find("mac_address").text # Find the active MAC in case there is an orphaned Stick if zb_networks := result.findall("./network"): for zb_network in zb_networks: if zb_network.find("./nodes/network_router") is not None: network = zb_network.find("./master_controller") - self.smile_zigbee_mac_address = network.find("mac_address").text + self.smile.zigbee_mac_address = network.find("mac_address").text # Legacy Anna or Stretch: if ( @@ -289,22 +281,22 @@ async def _smile_detect_legacy( or network is not None ): system = await self._request(SYSTEM) - self.smile_version = parse(system.find("./gateway/firmware").text) + self.smile.version = parse(system.find("./gateway/firmware").text) return_model = str(system.find("./gateway/product").text) - self.smile_hostname = system.find("./gateway/hostname").text + self.smile.hostname = system.find("./gateway/hostname").text # If wlan0 contains data it's active, eth0 should be checked last as is preferred for network in ("wlan0", "eth0"): locator = f"./{network}/mac" if (net_locator := system.find(locator)) is not None: - self.smile_mac_address = net_locator.text + self.smile.mac_address = net_locator.text # P1 legacy: elif dsmrmain is not None: status = await self._request(STATUS) - self.smile_version = parse(status.find("./system/version").text) + self.smile.version = parse(status.find("./system/version").text) return_model = str(status.find("./system/product").text) - self.smile_hostname = status.find("./network/hostname").text - self.smile_mac_address = status.find("./network/mac_address").text + self.smile.hostname = status.find("./network/hostname").text + self.smile.mac_address = status.find("./network/mac_address").text else: # pragma: no cover # No cornercase, just end of the line LOGGER.error( @@ -313,7 +305,7 @@ async def _smile_detect_legacy( ) raise ResponseError - self.smile_legacy = True + self.smile.legacy = True return return_model async def async_update(self) -> dict[str, GwEntityData]: diff --git a/plugwise/common.py b/plugwise/common.py index c261eeb4b..9534a2263 100644 --- a/plugwise/common.py +++ b/plugwise/common.py @@ -10,6 +10,7 @@ from plugwise.constants import ( ANNA, NONE, + PRIORITY_DEVICE_CLASSES, SPECIAL_PLUG_TYPES, SWITCH_GROUP_TYPES, ApplianceType, @@ -55,17 +56,16 @@ def __init__(self) -> None: self._heater_id: str = NONE self._on_off_device: bool self.gw_entities: dict[str, GwEntityData] = {} - self.smile_name: str - self.smile_type: str + self.smile: Munch @property def heater_id(self) -> str: """Return the heater-id.""" return self._heater_id - def smile(self, name: str) -> bool: + def check_name(self, name: str) -> bool: """Helper-function checking the smile-name.""" - return self.smile_name == name + return bool(self.smile.name == name) def _appl_heater_central_info( self, @@ -153,6 +153,16 @@ def _create_gw_entities(self, appl: Munch) -> None: self.gw_entities[appl.entity_id][appl_key] = value self._count += 1 + def _reorder_devices(self) -> None: + """Place the gateway and optional heater_central devices as 1st and 2nd.""" + reordered = {} + for dev_class in PRIORITY_DEVICE_CLASSES: + for entity_id, entity in dict(self.gw_entities).items(): + if entity["dev_class"] == dev_class: + reordered[entity_id] = self.gw_entities.pop(entity_id) + break + self.gw_entities = {**reordered, **self.gw_entities} + def _entity_switching_group(self, entity: GwEntityData, data: GwEntityData) -> None: """Helper-function for _get_device_zone_data(). @@ -173,7 +183,7 @@ def _get_group_switches(self) -> dict[str, GwEntityData]: """ switch_groups: dict[str, GwEntityData] = {} # P1 and Anna don't have switchgroups - if self.smile_type == "power" or self.smile(ANNA): + if self.smile.type == "power" or self.check_name(ANNA): return switch_groups for group in self._domain_objects.findall("./group"): diff --git a/plugwise/constants.py b/plugwise/constants.py index 4e1becd56..4710229e8 100644 --- a/plugwise/constants.py +++ b/plugwise/constants.py @@ -86,7 +86,7 @@ MODULE_LOCATOR: Final = "./logs/point_log/*[@id]" NONE: Final = "None" OFF: Final = "off" -PRIORITY_DEVICE_CLASSES = ("heater_central", "gateway") +PRIORITY_DEVICE_CLASSES = ("gateway", "heater_central") # XML data paths APPLIANCES: Final = "/core/appliances" diff --git a/plugwise/data.py b/plugwise/data.py index 97ec5b51a..5fafb14ff 100644 --- a/plugwise/data.py +++ b/plugwise/data.py @@ -35,7 +35,7 @@ def _all_entity_data(self) -> None: Collect data for each entity and add to self.gw_entities. """ self._update_gw_entities() - if self.smile(ADAM): + if self.check_name(ADAM): self._update_zones() self.gw_entities.update(self._zones) @@ -86,7 +86,7 @@ def _detect_low_batteries(self) -> list[str]: mac_pattern = re.compile(r"(?:[0-9A-F]{2}){8}") matches = ["Battery", "below"] if self._notifications: - for msg_id, notification in list(self._notifications.items()): + for msg_id, notification in self._notifications.copy().items(): mac_address: str | None = None message: str | None = notification.get("message") warning: str | None = notification.get("warning") @@ -111,7 +111,7 @@ def _add_or_update_notifications( """Helper-function adding or updating the Plugwise notifications.""" if ( entity_id == self._gateway_id - and (self._is_thermostat or self.smile_type == "power") + and (self._is_thermostat or self.smile.type == "power") ) or ( "binary_sensors" in entity and "plugwise_notification" in entity["binary_sensors"] @@ -124,7 +124,7 @@ def _update_for_cooling(self, entity: GwEntityData) -> None: """Helper-function for adding/updating various cooling-related values.""" # For Anna and heating + cooling, replace setpoint with setpoint_high/_low if ( - self.smile(ANNA) + self.check_name(ANNA) and self._cooling_present and entity["dev_class"] == "thermostat" ): @@ -194,11 +194,11 @@ def _get_entity_data(self, entity_id: str) -> GwEntityData: # Switching groups data self._entity_switching_group(entity, data) # Adam data - if self.smile(ADAM): + if self.check_name(ADAM): self._get_adam_data(entity, data) # Thermostat data for Anna (presets, temperatures etc) - if self.smile(ANNA) and entity["dev_class"] == "thermostat": + if self.check_name(ANNA) and entity["dev_class"] == "thermostat": self._climate_data(entity_id, entity, data) self._get_anna_control_state(data) @@ -232,12 +232,12 @@ def _get_adam_data(self, entity: GwEntityData, data: GwEntityData) -> None: if self._on_off_device and isinstance(self._heating_valves(), int): data["binary_sensors"]["heating_state"] = self._heating_valves() != 0 # Add cooling_enabled binary_sensor - if "binary_sensors" in data: - if ( - "cooling_enabled" not in data["binary_sensors"] - and self._cooling_present - ): - data["binary_sensors"]["cooling_enabled"] = self._cooling_enabled + if ( + "binary_sensors" in data + and "cooling_enabled" not in data["binary_sensors"] + and self._cooling_present + ): + data["binary_sensors"]["cooling_enabled"] = self._cooling_enabled # Show the allowed regulation_modes and gateway_modes if entity["dev_class"] == "gateway": diff --git a/plugwise/helper.py b/plugwise/helper.py index 87c4e986e..16ef1f0a3 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -28,7 +28,6 @@ NONE, OFF, P1_MEASUREMENTS, - PRIORITY_DEVICE_CLASSES, TEMP_CELSIUS, THERMOSTAT_CLASSES, TOGGLES, @@ -85,11 +84,7 @@ def __init__(self) -> None: self._gateway_id: str = NONE self._zones: dict[str, GwEntityData] self.gw_entities: dict[str, GwEntityData] - self.smile_hw_version: str | None - self.smile_mac_address: str | None - self.smile_model: str - self.smile_model_id: str | None - self.smile_version: version.Version + self.smile: Munch = Munch() @property def gateway_id(self) -> str: @@ -160,11 +155,11 @@ def _all_appliances(self) -> None: self._create_gw_entities(appl) - if self.smile_type == "power": + if self.smile.type == "power": self._get_p1_smartmeter_info() # Sort the gw_entities - self._sort_gw_entities() + self._reorder_devices() def _get_p1_smartmeter_info(self) -> None: """For P1 collect the connected SmartMeter info from the Home/building location. @@ -197,18 +192,6 @@ def _get_p1_smartmeter_info(self) -> None: self._create_gw_entities(appl) - def _sort_gw_entities(self) -> None: - """Place the gateway and optional heater_central entities as 1st and 2nd.""" - for dev_class in PRIORITY_DEVICE_CLASSES: - for entity_id, entity in dict(self.gw_entities).items(): - if entity["dev_class"] == dev_class: - priority_entity = entity - self.gw_entities.pop(entity_id) - other_entities = self.gw_entities - priority_entities = {entity_id: priority_entity} - self.gw_entities = {**priority_entities, **other_entities} - break - def _all_locations(self) -> None: """Collect all locations.""" loc = Munch() @@ -268,16 +251,16 @@ def _appliance_info_finder(self, appl: Munch, appliance: etree.Element) -> Munch def _appl_gateway_info(self, appl: Munch, appliance: etree.Element) -> Munch: """Helper-function for _appliance_info_finder().""" self._gateway_id = appliance.attrib["id"] - appl.firmware = str(self.smile_version) - appl.hardware = self.smile_hw_version - appl.mac = self.smile_mac_address - appl.model = self.smile_model - appl.model_id = self.smile_model_id - appl.name = self.smile_name + appl.firmware = str(self.smile.version) + appl.hardware = self.smile.hw_version + appl.mac = self.smile.mac_address + appl.model = self.smile.model + appl.model_id = self.smile.model_id + appl.name = self.smile.name appl.vendor_name = "Plugwise" # Adam: collect the ZigBee MAC address of the Smile - if self.smile(ADAM): + if self.check_name(ADAM): if ( found := self._domain_objects.find(".//protocols/zig_bee_coordinator") ) is not None: @@ -346,7 +329,7 @@ def _get_measurement_data(self, entity_id: str) -> GwEntityData: # Get P1 smartmeter data from LOCATIONS entity = self.gw_entities[entity_id] # !! DON'T CHANGE below two if-lines, will break stuff !! - if self.smile_type == "power": + if self.smile.type == "power": if entity["dev_class"] == "smartmeter": data.update(self._power_data_from_location()) @@ -383,7 +366,7 @@ def _get_measurement_data(self, entity_id: str) -> GwEntityData: data.pop("c_heating_state") self._count -= 1 - if self._is_thermostat and self.smile(ANNA): + if self._is_thermostat and self.check_name(ANNA): self._update_anna_cooling(entity_id, data) self._cleanup_data(data) @@ -484,7 +467,7 @@ def _get_actuator_functionalities( item == "thermostat" and ( entity["dev_class"] != "climate" - if self.smile(ADAM) + if self.check_name(ADAM) else entity["dev_class"] != "thermostat" ) ): @@ -539,7 +522,7 @@ def _get_actuator_mode( Collect the requested gateway mode. """ - if not (self.smile(ADAM) and entity_id == self._gateway_id): + if not (self.check_name(ADAM) and entity_id == self._gateway_id): return None if (search := search_actuator_functionalities(appliance, key)) is not None: @@ -605,10 +588,10 @@ def _process_on_off_device_c_heating_state(self, data: GwEntityData) -> None: Solution for Core issue #81839. """ - if self.smile(ANNA): + if self.check_name(ANNA): data["binary_sensors"]["heating_state"] = data["c_heating_state"] - if self.smile(ADAM): + if self.check_name(ADAM): # First count when not present, then create and init to False. # When present init to False if "heating_state" not in data["binary_sensors"]: @@ -723,7 +706,7 @@ def _scan_thermostats(self) -> None: for entity_id, entity in self.gw_entities.items(): self._rank_thermostat(thermo_matching, loc_id, entity_id, entity) - for loc_id, loc_data in list(self._thermo_locs.items()): + for loc_id, loc_data in self._thermo_locs.items(): if loc_data["primary_prio"] != 0: self._zones[loc_id] = { "dev_class": "climate", @@ -799,8 +782,8 @@ def _control_state(self, data: GwEntityData, loc_id: str) -> str | bool: # Handle missing control_state in regulation_mode off for firmware >= 3.2.0 (issue #776) # In newer firmware versions, default to "off" when control_state is not present - if self.smile_version != version.Version("0.0.0"): - if self.smile_version >= version.parse("3.2.0"): + if self.smile.version != version.Version("0.0.0"): + if self.smile.version >= version.parse("3.2.0"): return "off" # Older Adam firmware does not have the control_state xml-key diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index e22b489e5..d5e6918ab 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -23,7 +23,6 @@ NONE, OFF, P1_LEGACY_MEASUREMENTS, - PRIORITY_DEVICE_CLASSES, TEMP_CELSIUS, THERMOSTAT_CLASSES, UOM, @@ -47,7 +46,6 @@ # This way of importing aiohttp is because of patch/mocking in testing (aiohttp timeouts) from defusedxml import ElementTree as etree from munch import Munch -from packaging.version import Version def etree_to_dict(element: etree.Element) -> dict[str, str]: @@ -73,10 +71,7 @@ def __init__(self) -> None: self._modules: etree.Element self._stretch_v2: bool self.gw_entities: dict[str, GwEntityData] = {} - self.smile_mac_address: str | None - self.smile_model: str - self.smile_version: Version - self.smile_zigbee_mac_address: str | None + self.smile: Munch = Munch() @property def gateway_id(self) -> str: @@ -95,7 +90,7 @@ def _all_appliances(self) -> None: self._create_legacy_gateway() # For legacy P1 collect the connected SmartMeter info - if self.smile_type == "power": + if self.smile.type == "power": appl = Munch() self._p1_smartmeter_info_finder(appl) # Legacy P1 has no more devices @@ -140,17 +135,7 @@ def _all_appliances(self) -> None: continue # pragma: no cover self._create_gw_entities(appl) - - # Place the gateway and optional heater_central devices as 1st and 2nd - for dev_class in PRIORITY_DEVICE_CLASSES: - for entity_id, entity in dict(self.gw_entities).items(): - if entity["dev_class"] == dev_class: - tmp_entity = entity - self.gw_entities.pop(entity_id) - cleared_dict = self.gw_entities - add_to_front = {entity_id: tmp_entity} - self.gw_entities = {**add_to_front, **cleared_dict} - break + self._reorder_devices() def _all_locations(self) -> None: """Collect all locations.""" @@ -167,13 +152,13 @@ def _all_locations(self) -> None: loc.loc_id = location.attrib["id"] # Filter the valid single location for P1 legacy: services not empty locator = "./services" - if self.smile_type == "power" and len(location.find(locator)) == 0: + if self.smile.type == "power" and len(location.find(locator)) == 0: continue if loc.name == "Home": self._home_loc_id = loc.loc_id # Replace location-name for P1 legacy, can contain privacy-related info - if self.smile_type == "power": + if self.smile.type == "power": loc.name = "Home" self._home_loc_id = loc.loc_id @@ -185,18 +170,18 @@ def _create_legacy_gateway(self) -> None: Use the home_location or FAKE_APPL as entity id. """ self._gateway_id = self._home_loc_id - if self.smile_type == "power": + if self.smile.type == "power": self._gateway_id = FAKE_APPL self.gw_entities[self._gateway_id] = {"dev_class": "gateway"} self._count += 1 for key, value in { - "firmware": str(self.smile_version), + "firmware": str(self.smile.version), "location": self._home_loc_id, - "mac_address": self.smile_mac_address, - "model": self.smile_model, - "name": self.smile_name, - "zigbee_mac_address": self.smile_zigbee_mac_address, + "mac_address": self.smile.mac_address, + "model": self.smile.model, + "name": self.smile.name, + "zigbee_mac_address": self.smile.zigbee_mac_address, "vendor": "Plugwise", }.items(): if value is not None: @@ -224,14 +209,14 @@ def _energy_entity_info_finder(self, appliance: etree, appl: Munch) -> Munch: Collect energy entity info (Smartmeter, Circle, Stealth, etc.): firmware, model and vendor name. """ - if self.smile_type in ("power", "stretch"): + if self.smile.type in ("power", "stretch"): locator = "./services/electricity_point_meter" module_data = self._get_module_data( appliance, locator, self._modules, legacy=True ) appl.zigbee_mac = module_data["zigbee_mac_address"] # Filter appliance without zigbee_mac, it's an orphaned device - if appl.zigbee_mac is None and self.smile_type != "power": + if appl.zigbee_mac is None and self.smile.type != "power": return None appl.hardware = module_data["hardware_version"] @@ -253,7 +238,7 @@ def _p1_smartmeter_info_finder(self, appl: Munch) -> None: appl.entity_id = loc_id appl.location = loc_id appl.mac = None - appl.model = self.smile_model + appl.model = self.smile.model appl.model_id = None appl.name = "P1" appl.pwclass = "smartmeter" @@ -272,7 +257,7 @@ def _get_measurement_data(self, entity_id: str) -> GwEntityData: # Get P1 smartmeter data from MODULES entity = self.gw_entities[entity_id] # !! DON'T CHANGE below two if-lines, will break stuff !! - if self.smile_type == "power": + if self.smile.type == "power": if entity["dev_class"] == "smartmeter": data.update(self._power_data_from_modules()) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index fc4f3a2c5..61f219521 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -27,7 +27,6 @@ from plugwise.legacy.data import SmileLegacyData from munch import Munch -from packaging.version import Version class SmileLegacyAPI(SmileLegacyData): @@ -44,14 +43,7 @@ def __init__( _request: Callable[..., Awaitable[Any]], _stretch_v2: bool, _target_smile: str, - smile_hostname: str, - smile_hw_version: str | None, - smile_mac_address: str | None, - smile_model: str, - smile_name: str, - smile_type: str, - smile_version: Version, - smile_zigbee_mac_address: str | None, + smile: Munch, ) -> None: """Set the constructor for this class.""" super().__init__() @@ -63,14 +55,7 @@ def __init__( self._request = _request self._stretch_v2 = _stretch_v2 self._target_smile = _target_smile - self.smile_hostname = smile_hostname - self.smile_hw_version = smile_hw_version - self.smile_mac_address = smile_mac_address - self.smile_model = smile_model - self.smile_name = smile_name - self.smile_type = smile_type - self.smile_version = smile_version - self.smile_zigbee_mac_address = smile_zigbee_mac_address + self.smile = smile self._first_update = True self._previous_day_number: str = "0" @@ -86,7 +71,7 @@ async def full_xml_update(self) -> None: self._locations = await self._request(LOCATIONS) self._modules = await self._request(MODULES) # P1 legacy has no appliances - if self.smile_type != "power": + if self.smile.type != "power": self._appliances = await self._request(APPLIANCES) def get_all_gateway_entities(self) -> None: diff --git a/plugwise/smile.py b/plugwise/smile.py index 0947401d4..970181ca1 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -35,7 +35,27 @@ # Dict as class from munch import Munch -from packaging.version import Version + + +def model_to_switch_items(model: str, state: str, switch: Munch) -> tuple[str, Munch]: + """Translate state and switch attributes based on model name. + + Helper function for set_switch_state(). + """ + match model: + case "dhw_cm_switch": + switch.device = "toggle" + switch.func_type = "toggle_functionality" + switch.act_type = "domestic_hot_water_comfort_mode" + case "cooling_ena_switch": + switch.device = "toggle" + switch.func_type = "toggle_functionality" + switch.act_type = "cooling_enabled" + case "lock": + switch.func = "lock" + state = "true" if state == STATE_ON else "false" + + return state, switch class SmileAPI(SmileData): @@ -54,14 +74,7 @@ def __init__( _opentherm_device: bool, _request: Callable[..., Awaitable[Any]], _schedule_old_states: dict[str, dict[str, str]], - smile_hostname: str | None, - smile_hw_version: str | None, - smile_mac_address: str | None, - smile_model: str, - smile_model_id: str | None, - smile_name: str, - smile_type: str, - smile_version: Version, + smile: Munch, ) -> None: """Set the constructor for this class.""" super().__init__() @@ -74,14 +87,7 @@ def __init__( self._opentherm_device = _opentherm_device self._request = _request self._schedule_old_states = _schedule_old_states - self.smile_hostname = smile_hostname - self.smile_hw_version = smile_hw_version - self.smile_mac_address = smile_mac_address - self.smile_model = smile_model - self.smile_model_id = smile_model_id - self.smile_name = smile_name - self.smile_type = smile_type - self.smile_version = smile_version + self.smile = smile self.therms_with_offset_func: list[str] = [] @property @@ -107,7 +113,7 @@ def get_all_gateway_entities(self) -> None: self.therms_with_offset_func = ( self._get_appliances_with_offset_functionality() ) - if self.smile(ADAM): + if self.check_name(ADAM): self._scan_thermostats() if group_data := self._get_group_switches(): @@ -340,7 +346,7 @@ async def set_schedule_state( template = ( '