From 6afb0c7542749c5381f6b9a9d29686d5e6f90c11 Mon Sep 17 00:00:00 2001 From: Yannic Labonte Date: Mon, 12 Feb 2024 00:16:41 +0100 Subject: [PATCH] Fix/add ability to operate two or more instances of the integration --- README.md | 9 + .../proconip_pool_controller/binary_sensor.py | 180 ++++++++++++++---- .../proconip_pool_controller/const.py | 3 +- .../proconip_pool_controller/coordinator.py | 3 +- .../proconip_pool_controller/entity.py | 1 + .../proconip_pool_controller/manifest.json | 6 +- .../proconip_pool_controller/number.py | 42 ++-- .../proconip_pool_controller/select.py | 26 ++- .../proconip_pool_controller/sensor.py | 106 +++++++---- .../proconip_pool_controller/switch.py | 45 +++-- hacs.json | 4 +- 11 files changed, 305 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 5ad2375..c7cc2a7 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,15 @@ +## Changelog + +### Version 1.2.0 (2024-02-12) +WARNING: This update will create new entities. I could not find a way to remove the old entities programatically, so I apologize, but you will have to remove the obsolete entities manually (you can easily filter for them and remove all at once). + +* Require Home Assistant Core 2024.2.1 or newer. +* Fix configuration/setup bug (issue #28). +* Fix multi instance support. + ## Support for this integration If you have trouble with this integration and want to get support, you can open an [issue on github][issues], so others diff --git a/custom_components/proconip_pool_controller/binary_sensor.py b/custom_components/proconip_pool_controller/binary_sensor.py index c915e19..b74dbd3 100644 --- a/custom_components/proconip_pool_controller/binary_sensor.py +++ b/custom_components/proconip_pool_controller/binary_sensor.py @@ -1,30 +1,65 @@ """Binary sensor platform for ProCon.IP Pool Controller.""" + from __future__ import annotations +from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.helpers.entity_platform import AddEntitiesCallback + from .const import DOMAIN +from .coordinator import ProconipPoolControllerDataUpdateCoordinator from .entity import ProconipPoolControllerEntity -async def async_setup_entry(hass, entry, async_add_devices): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_devices: AddEntitiesCallback +) -> None: """Set up the binary_sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_devices( [ - ProconipChlorineDosageEnabledBinarySensor(coordinator), - ProconipElectrolysisEnabledBinarySensor(coordinator), - ProconipPhMinusDosageEnabledBinarySensor(coordinator), - ProconipPhPlusDosageEnabledBinarySensor(coordinator), - ProconipTcpIpBoostEnabledBinarySensor(coordinator), - ProconipSdCardEnabledBinarySensor(coordinator), - ProconipDmxEnabledBinarySensor(coordinator), - ProconipAvatarEnabledBinarySensor(coordinator), - ProconipRelayExtensionEnabledBinarySensor(coordinator), - ProconipHighBusLoadEnabledBinarySensor(coordinator), - ProconipFlowSensorEnabledBinarySensor(coordinator), - ProconipRepeatedMailEnabledBinarySensor(coordinator), - ProconipDmxExtensionEnabledBinarySensor(coordinator), + ProconipChlorineDosageEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipElectrolysisEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipPhMinusDosageEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipPhPlusDosageEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipTcpIpBoostEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipSdCardEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipDmxEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipAvatarEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipRelayExtensionEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipHighBusLoadEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipFlowSensorEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipRepeatedMailEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), + ProconipDmxExtensionEnabledBinarySensor( + coordinator=coordinator, instance_id=entry.entry_id + ), ] ) @@ -36,10 +71,15 @@ class ProconipChlorineDosageEnabledBinarySensor( _attr_name = "Chlorine Dosage enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_chlorine_dosage_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_chlorine_dosage_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_chlorine_dosage_enabled() @@ -51,10 +91,15 @@ class ProconipElectrolysisEnabledBinarySensor( _attr_name = "Electrolysis enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_electrolysis_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_electrolysis_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_electrolysis_enabled() @@ -66,10 +111,15 @@ class ProconipPhMinusDosageEnabledBinarySensor( _attr_name = "pH Minus Dosage enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_ph_minus_dosage_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_ph_minus_dosage_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_ph_minus_dosage_enabled() @@ -81,10 +131,15 @@ class ProconipPhPlusDosageEnabledBinarySensor( _attr_name = "pH Plus Dosage enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_ph_plus_dosage_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_ph_plus_dosage_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_ph_plus_dosage_enabled() @@ -96,10 +151,15 @@ class ProconipTcpIpBoostEnabledBinarySensor( _attr_name = "TCP/IP Boost enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_tcpip_boost_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_tcpip_boost_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_tcpip_boost_enabled() @@ -111,10 +171,15 @@ class ProconipSdCardEnabledBinarySensor( _attr_name = "SD Card enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_sd_card_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_sd_card_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_sd_card_enabled() @@ -124,10 +189,15 @@ class ProconipDmxEnabledBinarySensor(ProconipPoolControllerEntity, BinarySensorE _attr_name = "DMX enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_dmx_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_dmx_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_dmx_enabled() @@ -139,10 +209,15 @@ class ProconipAvatarEnabledBinarySensor( _attr_name = "Avatar enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_avatar_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_avatar_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_avatar_enabled() @@ -154,10 +229,15 @@ class ProconipRelayExtensionEnabledBinarySensor( _attr_name = "Relay Extension enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_relay_extension_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_relay_extension_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_relay_extension_enabled() @@ -169,10 +249,15 @@ class ProconipHighBusLoadEnabledBinarySensor( _attr_name = "High Bus Load enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_high_bus_load_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_high_bus_load_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_high_bus_load_enabled() @@ -184,10 +269,15 @@ class ProconipFlowSensorEnabledBinarySensor( _attr_name = "Flow Sensor enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_flow_sensor_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_flow_sensor_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_flow_sensor_enabled() @@ -199,10 +289,15 @@ class ProconipRepeatedMailEnabledBinarySensor( _attr_name = "Repeated Mails enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_repeated_mails_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_repeated_mails_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_repeated_mails_enabled() @@ -214,9 +309,14 @@ class ProconipDmxExtensionEnabledBinarySensor( _attr_name = "DMX Extension enabled" _attr_icon = "mdi:check-circle" - _attr_unique_id = "is_dmx_extension_enabled" + + def __init__( + self, coordinator: ProconipPoolControllerDataUpdateCoordinator, instance_id: str + ) -> None: + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"is_dmx_extension_enabled_{instance_id}" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.is_dmx_extension_enabled() diff --git a/custom_components/proconip_pool_controller/const.py b/custom_components/proconip_pool_controller/const.py index 7b5dc1a..3802ed5 100644 --- a/custom_components/proconip_pool_controller/const.py +++ b/custom_components/proconip_pool_controller/const.py @@ -1,11 +1,12 @@ """Constants for proconip.""" + from logging import Logger, getLogger LOGGER: Logger = getLogger(__package__) NAME = "ProCon.IP Pool Controller" DOMAIN = "proconip_pool_controller" -VERSION = "1.1.0" +VERSION = "1.2.0" ATTRIBUTION = ( "Data provided by your Pool Digital ProCon.IP (https://www.pooldigital.de)" ) diff --git a/custom_components/proconip_pool_controller/coordinator.py b/custom_components/proconip_pool_controller/coordinator.py index bd99d05..cfb5015 100644 --- a/custom_components/proconip_pool_controller/coordinator.py +++ b/custom_components/proconip_pool_controller/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for ProCon.IP Pool Controller.""" + from __future__ import annotations from datetime import timedelta @@ -51,7 +52,7 @@ def __init__( async def proconip_update_method(self) -> GetStateData: """Update data via library.""" - data: GetStateData = None + data: GetStateData | None = None try: data = await self.client.async_get_data() except BadCredentialsException as exception: diff --git a/custom_components/proconip_pool_controller/entity.py b/custom_components/proconip_pool_controller/entity.py index 43c30fc..15e601a 100644 --- a/custom_components/proconip_pool_controller/entity.py +++ b/custom_components/proconip_pool_controller/entity.py @@ -1,4 +1,5 @@ """BlueprintEntity class.""" + from __future__ import annotations from homeassistant.helpers.entity import DeviceInfo diff --git a/custom_components/proconip_pool_controller/manifest.json b/custom_components/proconip_pool_controller/manifest.json index c55cdf0..280d986 100644 --- a/custom_components/proconip_pool_controller/manifest.json +++ b/custom_components/proconip_pool_controller/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/ylabonte/proconip-hass/issues", "requirements": [ - "proconip==1.3.0" + "proconip>=1.3.0" ], - "version": "1.1.0" -} \ No newline at end of file + "version": "1.2.0" +} diff --git a/custom_components/proconip_pool_controller/number.py b/custom_components/proconip_pool_controller/number.py index 3cfa43b..bfd7d6d 100644 --- a/custom_components/proconip_pool_controller/number.py +++ b/custom_components/proconip_pool_controller/number.py @@ -1,18 +1,26 @@ """Switch platform for proconip.""" + from __future__ import annotations import asyncio -from homeassistant.components.number import NumberEntity +from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.components.number import NumberEntity, NumberMode +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import ProconipPoolControllerDataUpdateCoordinator from .entity import ProconipPoolControllerEntity -async def async_setup_entry(hass, entry, async_add_devices): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_devices: AddEntitiesCallback +) -> None: """Set up the select platform.""" - coordinator: ProconipPoolControllerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: ProconipPoolControllerDataUpdateCoordinator = hass.data[DOMAIN][ + entry.entry_id + ] number_of_relays = 16 if coordinator.data.is_relay_extension_enabled() else 8 relays = [] for i in range(number_of_relays): @@ -21,6 +29,7 @@ async def async_setup_entry(hass, entry, async_add_devices): ProconipPoolControllerDosageRelayTimer( coordinator=coordinator, relay_no=i + 1, + instance_id=entry.entry_id, ) ) # else: @@ -38,7 +47,7 @@ class ProconipPoolControllerDosageRelayTimer( ): """ProCon.IP Pool Controller relay dosage relay timer class.""" - _attr_mode = "box" + _attr_mode = NumberMode.BOX _attr_native_max_value = 600 _attr_native_min_value = 5 _attr_native_step = 1 @@ -49,20 +58,21 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, relay_no: int, + instance_id: str, ) -> None: """Initialize the switch class.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._relay_id = relay_no - 1 - self._relay = coordinator.data.get_relay(self._relay_id) + self._relay = coordinator.data.get_relay(relay_id=self._relay_id) self._attr_name = f"Relay No. {relay_no} Dosage ({self._relay.name})" - self._attr_unique_id = f"relay_dosage_{relay_no}" + self._attr_unique_id = f"relay_dosage_{relay_no}_{instance_id}" @property def icon(self) -> str | None: """Return icon depending on current state.""" return ( "mdi:toggle-switch-variant" - if self.coordinator.data.get_relay(self._relay_id).is_on() + if self.coordinator.data.get_relay(relay_id=self._relay_id).is_on() else "mdi:toggle-switch-variant-off" ) @@ -77,19 +87,25 @@ async def countdown(self, value: int | None = None) -> None: self._countdown_value = value while self._countdown_value > 0: self._countdown_value -= 1 - await asyncio.sleep(1) + await asyncio.sleep(delay=1) async def async_set_native_value(self, value: float) -> None: """Set dosage timer.""" countdown_value = int(value) if self.coordinator.data.chlorine_dosage_relay_id == self._relay_id: - await self.coordinator.client.async_start_chlorine_dosage(countdown_value) + await self.coordinator.client.async_start_chlorine_dosage( + duration_in_seconds=countdown_value + ) elif self.coordinator.data.ph_minus_dosage_relay_id == self._relay_id: - await self.coordinator.client.async_start_ph_minus_dosage(countdown_value) + await self.coordinator.client.async_start_ph_minus_dosage( + duration_in_seconds=countdown_value + ) elif self.coordinator.data.ph_plus_dosage_relay_id == self._relay_id: - await self.coordinator.client.async_start_ph_plus_dosage(countdown_value) + await self.coordinator.client.async_start_ph_plus_dosage( + duration_in_seconds=countdown_value + ) loop = asyncio.get_event_loop() - loop.create_task(self.countdown(countdown_value)) + loop.create_task(coro=self.countdown(value=countdown_value)) return await self.coordinator.async_request_refresh() diff --git a/custom_components/proconip_pool_controller/select.py b/custom_components/proconip_pool_controller/select.py index 8c4eb24..fa51510 100644 --- a/custom_components/proconip_pool_controller/select.py +++ b/custom_components/proconip_pool_controller/select.py @@ -1,14 +1,20 @@ """Switch platform for proconip.""" + from __future__ import annotations +from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry from homeassistant.components.select import SelectEntity +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import ProconipPoolControllerDataUpdateCoordinator from .entity import ProconipPoolControllerEntity -async def async_setup_entry(hass, entry, async_add_devices): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_devices: AddEntitiesCallback +) -> None: """Set up the select platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] number_of_relays = 16 if coordinator.data.is_relay_extension_enabled() else 8 @@ -19,6 +25,7 @@ async def async_setup_entry(hass, entry, async_add_devices): coordinator=coordinator, relay_no=i + 1, available=not coordinator.data.is_dosage_relay(relay_id=i), + instance_id=entry.entry_id, ) ) async_add_devices(relays) @@ -32,11 +39,12 @@ def __init__( coordinator: ProconipPoolControllerDataUpdateCoordinator, relay_no: int, available: bool, + instance_id: str, ) -> None: """Initialize the switch class.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._relay_id = relay_no - 1 - self._relay = coordinator.data.get_relay(self._relay_id) + self._relay = coordinator.data.get_relay(relay_id=self._relay_id) self._attr_available = available self._attr_entity_registry_visible_default = ( not self.coordinator.data.is_dosage_relay(relay_id=self._relay_id) @@ -51,21 +59,21 @@ def __init__( "off", ] ) - self._attr_unique_id = f"relay_select_{relay_no}" + self._attr_unique_id = f"relay_select_{relay_no}_{instance_id}" @property def icon(self) -> str | None: """Return icon depending on current option/state.""" return ( "mdi:toggle-switch-variant" - if self.coordinator.data.get_relay(self._relay_id).is_on() + if self.coordinator.data.get_relay(relay_id=self._relay_id).is_on() else "mdi:toggle-switch-variant-off" ) @property def current_option(self) -> str | None: """Return currently selected option.""" - relay = self.coordinator.data.get_relay(self._relay_id) + relay = self.coordinator.data.get_relay(relay_id=self._relay_id) if relay.is_auto_mode(): return "auto" if relay.is_on(): @@ -75,9 +83,9 @@ def current_option(self) -> str | None: async def async_select_option(self, option: str) -> None: """Change the selected option.""" if option == "auto": - await self.coordinator.client.async_switch_to_auto(self._relay_id) + await self.coordinator.client.async_switch_to_auto(relay_id=self._relay_id) elif option == "on": - await self.coordinator.client.async_switch_on(self._relay_id) + await self.coordinator.client.async_switch_on(relay_id=self._relay_id) else: - await self.coordinator.client.async_switch_off(self._relay_id) + await self.coordinator.client.async_switch_off(relay_id=self._relay_id) await self.coordinator.async_request_refresh() diff --git a/custom_components/proconip_pool_controller/sensor.py b/custom_components/proconip_pool_controller/sensor.py index 8f46130..27a4aac 100644 --- a/custom_components/proconip_pool_controller/sensor.py +++ b/custom_components/proconip_pool_controller/sensor.py @@ -1,6 +1,10 @@ """Sensor platform for proconip.""" + from __future__ import annotations +from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.components.sensor import SensorEntity, SensorDeviceClass from .const import DOMAIN @@ -8,37 +12,49 @@ from .entity import ProconipPoolControllerEntity -async def async_setup_entry(hass, entry, async_add_devices): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_devices: AddEntitiesCallback +) -> None: """Set up the sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] sensor_entities = [ - ProconipRedoxSensor(coordinator), - ProconipPhSensor(coordinator), + ProconipRedoxSensor(coordinator=coordinator, instance_id=entry.entry_id), + ProconipPhSensor(coordinator=coordinator, instance_id=entry.entry_id), ] for i in range(5): sensor_entities.append( - ProconipAnalogSensor(coordinator=coordinator, sensor_no=i + 1) + ProconipAnalogSensor( + coordinator=coordinator, sensor_no=i + 1, instance_id=entry.entry_id + ) ) for i in range(4): sensor_entities.append( - ProconipDigitalInputSensor(coordinator=coordinator, sensor_no=i + 1) + ProconipDigitalInputSensor( + coordinator=coordinator, sensor_no=i + 1, instance_id=entry.entry_id + ) ) for i in range(8): sensor_entities.append( - ProconipTemperatureSensor(coordinator=coordinator, sensor_no=i + 1) + ProconipTemperatureSensor( + coordinator=coordinator, sensor_no=i + 1, instance_id=entry.entry_id + ) ) for i in range(3): sensor_entities.append( - ProconipCanisterSensor(coordinator=coordinator, canister_no=i + 1) + ProconipCanisterSensor( + coordinator=coordinator, canister_no=i + 1, instance_id=entry.entry_id + ) ) sensor_entities.append( ProconipCanisterConsumptionSensor( - coordinator=coordinator, canister_no=i + 1 + coordinator=coordinator, canister_no=i + 1, instance_id=entry.entry_id ) ) for i in range(8): sensor_entities.append( - ProconipRelayStateSensor(coordinator=coordinator, relay_no=i + 1) + ProconipRelayStateSensor( + coordinator=coordinator, relay_no=i + 1, instance_id=entry.entry_id + ) ) async_add_devices(sensor_entities) @@ -50,11 +66,18 @@ class ProconipRedoxSensor(ProconipPoolControllerEntity, SensorEntity): _attr_name = "Redox sensor" _attr_state_class = "measurement" _attr_suggested_display_precision = 1 - _attr_unique_id = "redox_electrode" - suggested_display_precision = 1 + + def __init__( + self, + coordinator: ProconipPoolControllerDataUpdateCoordinator, + instance_id: str, + ) -> None: + """Initialize new redox sensor.""" + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"redox_electrode_{instance_id}" @property - def native_value(self) -> str: + def native_value(self) -> float: """Return the native value of the sensor.""" return self.coordinator.data.redox_electrode.value @@ -71,11 +94,18 @@ class ProconipPhSensor(ProconipPoolControllerEntity, SensorEntity): _attr_name = "pH sensor" _attr_state_class = "measurement" _attr_suggested_display_precision = 2 - _attr_unique_id = "ph_electrode" - suggested_display_precision = 2 + + def __init__( + self, + coordinator: ProconipPoolControllerDataUpdateCoordinator, + instance_id: str, + ) -> None: + """Initialize new pH sensor.""" + super().__init__(coordinator=coordinator) + self._attr_unique_id = f"ph_electrode_{instance_id}" @property - def native_value(self) -> str: + def native_value(self) -> float: """Return the native value of the sensor.""" return self.coordinator.data.ph_electrode.value @@ -98,17 +128,18 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, sensor_no: int, + instance_id: str, ) -> None: """Initialize new temperature sensor.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._sensor_no = sensor_no self._sensor = self.coordinator.data.temperature_objects[self._sensor_no - 1] self._attr_entity_registry_visible_default = self._sensor.name != "n.a." self._attr_name = f"Temperature No. {sensor_no}: {self._sensor.name}" - self._attr_unique_id = f"temperature_{sensor_no}" + self._attr_unique_id = f"temperature_{sensor_no}_{instance_id}" @property - def native_value(self) -> str: + def native_value(self) -> float: """Return the native value of the sensor.""" return self.coordinator.data.temperature_objects[self._sensor_no - 1].value @@ -124,17 +155,18 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, sensor_no: int, + instance_id: str, ) -> None: """Initialize new temperature sensor.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._adc_no = sensor_no self._adc = self.coordinator.data.analog_objects[self._adc_no - 1] self._attr_entity_registry_visible_default = self._adc.name != "n.a." self._attr_name = f"Analog No. {sensor_no}: {self._adc.name}" - self._attr_unique_id = f"analog_{sensor_no}" + self._attr_unique_id = f"analog_{sensor_no}_{instance_id}" @property - def native_value(self) -> str: + def native_value(self) -> float: """Return the native value of the sensor.""" return self.coordinator.data.analog_objects[self._adc_no - 1].value @@ -155,19 +187,20 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, sensor_no: int, + instance_id: str, ) -> None: """Initialize new temperature sensor.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._digital_input_no = sensor_no self._digital_input = self.coordinator.data.digital_input_objects[ self._digital_input_no - 1 ] self._attr_entity_registry_visible_default = self._digital_input.name != "n.a." self._attr_name = f"Digital Input No. {sensor_no}: {self._digital_input.name}" - self._attr_unique_id = f"digital_input_{sensor_no}" + self._attr_unique_id = f"digital_input_{sensor_no}_{instance_id}" @property - def native_value(self) -> str: + def native_value(self) -> float: """Return the native value of the sensor.""" return self.coordinator.data.digital_input_objects[ self._digital_input_no - 1 @@ -190,9 +223,10 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, canister_no: int, + instance_id: str, ) -> None: """Initialize new temperature sensor.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._canister_no = canister_no self._canister = self.coordinator.data.canister_objects[self._canister_no - 1] match (canister_no): @@ -209,10 +243,10 @@ def __init__( self.coordinator.data.is_ph_plus_dosage_enabled() ) self._attr_name = f"Canister {self._canister.name}" - self._attr_unique_id = f"canister_{canister_no}" + self._attr_unique_id = f"canister_{canister_no}_{instance_id}" @property - def native_value(self) -> str: + def native_value(self) -> float: """Return the native value of the sensor.""" return self.coordinator.data.canister_objects[self._canister_no - 1].value @@ -233,9 +267,10 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, canister_no: int, + instance_id: str, ) -> None: """Initialize new temperature sensor.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._canister_no = canister_no self._canister = self.coordinator.data.consumption_objects[ self._canister_no - 1 @@ -254,10 +289,10 @@ def __init__( self.coordinator.data.is_ph_plus_dosage_enabled() ) self._attr_name = f"Canister consumption {self._canister.name}" - self._attr_unique_id = f"canister_consumption_{canister_no}" + self._attr_unique_id = f"canister_consumption_{canister_no}_{instance_id}" @property - def native_value(self) -> str: + def native_value(self) -> float: """Return the native value of the sensor.""" return self.coordinator.data.consumption_objects[self._canister_no - 1].value @@ -276,18 +311,19 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, relay_no: int, + instance_id: str, ) -> None: - """Initialize new temperature sensor.""" - super().__init__(coordinator) + """Initialize new relay state sensor.""" + super().__init__(coordinator=coordinator) self._relay_id = relay_no - 1 - self._relay = self.coordinator.data.get_relay(self._relay_id) + self._relay = self.coordinator.data.get_relay(relay_id=self._relay_id) self._attr_entity_registry_visible_default = ( not self.coordinator.data.is_dosage_relay(relay_id=self._relay_id) ) self._attr_name = f"Relay No. {relay_no} ({self._relay.name}) State" - self._attr_unique_id = f"relay_state_{relay_no}" + self._attr_unique_id = f"relay_state_{relay_no}_{instance_id}" @property def native_value(self) -> str: """Return the native value of the sensor.""" - return self.coordinator.data.get_relay(self._relay_id).display_value + return self.coordinator.data.get_relay(relay_id=self._relay_id).display_value diff --git a/custom_components/proconip_pool_controller/switch.py b/custom_components/proconip_pool_controller/switch.py index b658918..06fa196 100644 --- a/custom_components/proconip_pool_controller/switch.py +++ b/custom_components/proconip_pool_controller/switch.py @@ -1,6 +1,10 @@ """Switch platform for proconip.""" + from __future__ import annotations +from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.components.switch import SwitchEntity, SwitchDeviceClass from .const import DOMAIN @@ -8,9 +12,13 @@ from .entity import ProconipPoolControllerEntity -async def async_setup_entry(hass, entry, async_add_devices): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_devices: AddEntitiesCallback +) -> None: """Set up the switch platform.""" - coordinator: ProconipPoolControllerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: ProconipPoolControllerDataUpdateCoordinator = hass.data[DOMAIN][ + entry.entry_id + ] number_of_relays = 16 if coordinator.data.is_relay_extension_enabled() else 8 relays = [] for i in range(number_of_relays): @@ -19,10 +27,13 @@ async def async_setup_entry(hass, entry, async_add_devices): coordinator=coordinator, relay_no=i + 1, available=not coordinator.is_active_dosage_relay(relay_id=i), + instance_id=entry.entry_id, ) ) relays.append( - ProconipPoolControllerRelayMode(coordinator=coordinator, relay_no=i + 1) + ProconipPoolControllerRelayMode( + coordinator=coordinator, relay_no=i + 1, instance_id=entry.entry_id + ) ) async_add_devices(relays) @@ -37,32 +48,33 @@ def __init__( coordinator: ProconipPoolControllerDataUpdateCoordinator, relay_no: int, available: bool, + instance_id: str, ) -> None: """Initialize the switch class.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._attr_available = available self._relay_id = relay_no - 1 - self._relay = coordinator.data.get_relay(self._relay_id) + self._relay = coordinator.data.get_relay(relay_id=self._relay_id) self._attr_entity_registry_visible_default = ( not self.coordinator.data.is_dosage_relay(relay_id=self._relay_id) ) self._attr_name = f"Relay No. {relay_no} ({self._relay.name})" - self._attr_unique_id = f"relay_{relay_no}" + self._attr_unique_id = f"relay_{relay_no}_{instance_id}" async def async_turn_on(self, **kwargs) -> None: # pylint: disable=unused-argument """Turn on the switch.""" - await self.coordinator.client.async_switch_on(self._relay_id) + await self.coordinator.client.async_switch_on(relay_id=self._relay_id) await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs) -> None: # pylint: disable=unused-argument """Turn off the switch.""" - await self.coordinator.client.async_switch_off(self._relay_id) + await self.coordinator.client.async_switch_off(relay_id=self._relay_id) await self.coordinator.async_request_refresh() @property def is_on(self) -> bool: """Return true if the switch is on.""" - return self.coordinator.data.get_relay(self._relay_id).is_on() + return self.coordinator.data.get_relay(relay_id=self._relay_id).is_on() class ProconipPoolControllerRelayMode(ProconipPoolControllerEntity, SwitchEntity): @@ -74,31 +86,32 @@ def __init__( self, coordinator: ProconipPoolControllerDataUpdateCoordinator, relay_no: int, + instance_id: str, ) -> None: """Initialize new relay mode.""" - super().__init__(coordinator) + super().__init__(coordinator=coordinator) self._relay_id = relay_no - 1 - self._relay = coordinator.data.get_relay(self._relay_id) + self._relay = coordinator.data.get_relay(relay_id=self._relay_id) self._attr_entity_registry_visible_default = ( not self.coordinator.data.is_dosage_relay(relay_id=self._relay_id) ) self._attr_name = f"Relay No. {relay_no} ({self._relay.name}) Auto-Mode" - self._attr_unique_id = f"relay_{relay_no}_auto" + self._attr_unique_id = f"relay_{relay_no}_auto_mode_{instance_id}" async def async_turn_on(self, **kwargs) -> None: # pylint: disable=unused-argument """Set relay to auto mode.""" - await self.coordinator.client.async_switch_to_auto(self._relay_id) + await self.coordinator.client.async_switch_to_auto(relay_id=self._relay_id) await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs) -> None: # pylint: disable=unused-argument """Set relay to manual mode.""" if self._relay.is_on(): - await self.coordinator.client.async_switch_on(self._relay_id) + await self.coordinator.client.async_switch_on(relay_id=self._relay_id) else: - await self.coordinator.client.async_switch_off(self._relay_id) + await self.coordinator.client.async_switch_off(relay_id=self._relay_id) await self.coordinator.async_request_refresh() @property def is_on(self) -> bool: """Return true if the switch is on.""" - return self.coordinator.data.get_relay(self._relay_id).is_auto_mode() + return self.coordinator.data.get_relay(relay_id=self._relay_id).is_auto_mode() diff --git a/hacs.json b/hacs.json index 405aa5e..b4d21e0 100644 --- a/hacs.json +++ b/hacs.json @@ -2,7 +2,7 @@ "name": "ProCon.IP Pool Controller", "filename": "proconip_pool_controller.zip", "hide_default_branch": true, - "homeassistant": "2023.6.0", + "homeassistant": "2024.2.1", "render_readme": true, "zip_release": true -} \ No newline at end of file +}