Support local push updates for most ScreenLogic entities (#87438)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Kevin Worrel 2023-02-06 18:13:36 -08:00 committed by GitHub
parent 4fbb14ecc7
commit 687d326bb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 352 additions and 252 deletions

View File

@ -1008,6 +1008,7 @@ omit =
homeassistant/components/screenlogic/__init__.py homeassistant/components/screenlogic/__init__.py
homeassistant/components/screenlogic/binary_sensor.py homeassistant/components/screenlogic/binary_sensor.py
homeassistant/components/screenlogic/climate.py homeassistant/components/screenlogic/climate.py
homeassistant/components/screenlogic/entity.py
homeassistant/components/screenlogic/light.py homeassistant/components/screenlogic/light.py
homeassistant/components/screenlogic/number.py homeassistant/components/screenlogic/number.py
homeassistant/components/screenlogic/sensor.py homeassistant/components/screenlogic/sensor.py

View File

@ -1,12 +1,12 @@
"""The Screenlogic integration.""" """The Screenlogic integration."""
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any
from screenlogicpy import ScreenLogicError, ScreenLogicGateway from screenlogicpy import ScreenLogicError, ScreenLogicGateway
from screenlogicpy.const import ( from screenlogicpy.const import (
DATA as SL_DATA, DATA as SL_DATA,
EQUIPMENT, EQUIPMENT,
ON_OFF,
SL_GATEWAY_IP, SL_GATEWAY_IP,
SL_GATEWAY_NAME, SL_GATEWAY_NAME,
SL_GATEWAY_PORT, SL_GATEWAY_PORT,
@ -17,15 +17,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL, Platform from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from .config_flow import async_discover_gateways_by_unique_id, name_for_mac from .config_flow import async_discover_gateways_by_unique_id, name_for_mac
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
@ -52,12 +45,12 @@ PLATFORMS = [
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Screenlogic from a config entry.""" """Set up Screenlogic from a config entry."""
gateway = ScreenLogicGateway()
connect_info = await async_get_connect_info(hass, entry) connect_info = await async_get_connect_info(hass, entry)
gateway = ScreenLogicGateway(**connect_info)
try: try:
await gateway.async_connect() await gateway.async_connect(**connect_info)
except ScreenLogicError as ex: except ScreenLogicError as ex:
_LOGGER.error("Error while connecting to the gateway %s: %s", connect_info, ex) _LOGGER.error("Error while connecting to the gateway %s: %s", connect_info, ex)
raise ConfigEntryNotReady from ex raise ConfigEntryNotReady from ex
@ -119,11 +112,16 @@ async def async_get_connect_info(hass: HomeAssistant, entry: ConfigEntry):
class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator): class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage the data update for the Screenlogic component.""" """Class to manage the data update for the Screenlogic component."""
def __init__(self, hass, *, config_entry, gateway): def __init__(
self,
hass: HomeAssistant,
*,
config_entry: ConfigEntry,
gateway: ScreenLogicGateway,
) -> None:
"""Initialize the Screenlogic Data Update Coordinator.""" """Initialize the Screenlogic Data Update Coordinator."""
self.config_entry = config_entry self.config_entry = config_entry
self.gateway = gateway self.gateway = gateway
self.screenlogic_data = {}
interval = timedelta( interval = timedelta(
seconds=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) seconds=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
@ -140,17 +138,34 @@ class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator):
), ),
) )
@property
def gateway_data(self) -> dict[str | int, Any]:
"""Return the gateway data."""
return self.gateway.get_data()
async def _async_update_configured_data(self):
"""Update data sets based on equipment config."""
equipment_flags = self.gateway.get_data()[SL_DATA.KEY_CONFIG]["equipment_flags"]
if not self.gateway.is_client:
await self.gateway.async_get_status()
if equipment_flags & EQUIPMENT.FLAG_INTELLICHEM:
await self.gateway.async_get_chemistry()
await self.gateway.async_get_pumps()
if equipment_flags & EQUIPMENT.FLAG_CHLORINATOR:
await self.gateway.async_get_scg()
async def _async_update_data(self): async def _async_update_data(self):
"""Fetch data from the Screenlogic gateway.""" """Fetch data from the Screenlogic gateway."""
try: try:
await self.gateway.async_update() await self._async_update_configured_data()
except ScreenLogicError as error: except ScreenLogicError as error:
_LOGGER.warning("Update error - attempting reconnect: %s", error) _LOGGER.warning("Update error - attempting reconnect: %s", error)
await self._async_reconnect_update_data() await self._async_reconnect_update_data()
except ScreenLogicWarning as warn: except ScreenLogicWarning as warn:
raise UpdateFailed(f"Incomplete update: {warn}") from warn raise UpdateFailed(f"Incomplete update: {warn}") from warn
return self.gateway.get_data() return None
async def _async_reconnect_update_data(self): async def _async_reconnect_update_data(self):
"""Attempt to reconnect to the gateway and fetch data.""" """Attempt to reconnect to the gateway and fetch data."""
@ -159,125 +174,9 @@ class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator):
await self.gateway.async_disconnect() await self.gateway.async_disconnect()
connect_info = await async_get_connect_info(self.hass, self.config_entry) connect_info = await async_get_connect_info(self.hass, self.config_entry)
self.gateway = ScreenLogicGateway(**connect_info) await self.gateway.async_connect(**connect_info)
await self.gateway.async_update() await self._async_update_configured_data()
except (ScreenLogicError, ScreenLogicWarning) as ex: except (ScreenLogicError, ScreenLogicWarning) as ex:
raise UpdateFailed(ex) from ex raise UpdateFailed(ex) from ex
class ScreenlogicEntity(CoordinatorEntity[ScreenlogicDataUpdateCoordinator]):
"""Base class for all ScreenLogic entities."""
def __init__(self, coordinator, data_key, enabled=True):
"""Initialize of the entity."""
super().__init__(coordinator)
self._data_key = data_key
self._enabled_default = enabled
@property
def entity_registry_enabled_default(self):
"""Entity enabled by default."""
return self._enabled_default
@property
def mac(self):
"""Mac address."""
return self.coordinator.config_entry.unique_id
@property
def unique_id(self):
"""Entity Unique ID."""
return f"{self.mac}_{self._data_key}"
@property
def config_data(self):
"""Shortcut for config data."""
return self.coordinator.data["config"]
@property
def gateway(self):
"""Return the gateway."""
return self.coordinator.gateway
@property
def gateway_name(self):
"""Return the configured name of the gateway."""
return self.gateway.name
@property
def device_info(self) -> DeviceInfo:
"""Return device information for the controller."""
controller_type = self.config_data["controller_type"]
hardware_type = self.config_data["hardware_type"]
try:
equipment_model = EQUIPMENT.CONTROLLER_HARDWARE[controller_type][
hardware_type
]
except KeyError:
equipment_model = f"Unknown Model C:{controller_type} H:{hardware_type}"
return DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, self.mac)},
manufacturer="Pentair",
model=equipment_model,
name=self.gateway_name,
sw_version=self.gateway.version,
)
async def _async_refresh(self):
"""Refresh the data from the gateway."""
await self.coordinator.async_refresh()
# Second debounced refresh to catch any secondary
# changes in the device
await self.coordinator.async_request_refresh()
async def _async_refresh_timed(self, now):
"""Refresh from a timed called."""
await self.coordinator.async_request_refresh()
class ScreenLogicCircuitEntity(ScreenlogicEntity):
"""ScreenLogic circuit entity."""
_attr_has_entity_name = True
@property
def name(self):
"""Get the name of the switch."""
return self.circuit["name"]
@property
def is_on(self) -> bool:
"""Get whether the switch is in on state."""
return self.circuit["value"] == ON_OFF.ON
async def async_turn_on(self, **kwargs) -> None:
"""Send the ON command."""
await self._async_set_circuit(ON_OFF.ON)
async def async_turn_off(self, **kwargs) -> None:
"""Send the OFF command."""
await self._async_set_circuit(ON_OFF.OFF)
# Turning off spa or pool circuit may require more time for the
# heater to reflect changes depending on the pool controller,
# so we schedule an extra refresh a bit farther out
if self._data_key in PRIMARY_CIRCUIT_IDS:
async_call_later(
self.hass, HEATER_COOLDOWN_DELAY, self._async_refresh_timed
)
async def _async_set_circuit(self, circuit_value) -> None:
if await self.gateway.async_set_circuit(self._data_key, circuit_value):
_LOGGER.debug("Turn %s %s", self._data_key, circuit_value)
await self._async_refresh()
else:
_LOGGER.warning(
"Failed to set_circuit %s %s", self._data_key, circuit_value
)
@property
def circuit(self):
"""Shortcut to access the circuit."""
return self.coordinator.data[SL_DATA.KEY_CIRCUITS][self._data_key]

View File

@ -1,5 +1,5 @@
"""Support for a ScreenLogic Binary Sensor.""" """Support for a ScreenLogic Binary Sensor."""
from screenlogicpy.const import DATA as SL_DATA, DEVICE_TYPE, EQUIPMENT, ON_OFF from screenlogicpy.const import CODE, DATA as SL_DATA, DEVICE_TYPE, EQUIPMENT, ON_OFF
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
@ -10,8 +10,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ScreenlogicEntity from . import ScreenlogicDataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN
from .entity import ScreenlogicEntity, ScreenLogicPushEntity
SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = {DEVICE_TYPE.ALARM: BinarySensorDeviceClass.PROBLEM} SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = {DEVICE_TYPE.ALARM: BinarySensorDeviceClass.PROBLEM}
@ -29,69 +30,70 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up entry.""" """Set up entry."""
entities = [] entities: list[ScreenLogicBinarySensorEntity] = []
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
gateway_data = coordinator.gateway_data
chemistry = gateway_data[SL_DATA.KEY_CHEMISTRY]
config = gateway_data[SL_DATA.KEY_CONFIG]
# Generic binary sensor # Generic binary sensor
entities.append(ScreenLogicBinarySensor(coordinator, "chem_alarm")) entities.append(
ScreenLogicStatusBinarySensor(coordinator, "chem_alarm", CODE.STATUS_CHANGED)
)
entities.extend( entities.extend(
[ [
ScreenlogicConfigBinarySensor(coordinator, cfg_sensor) ScreenlogicConfigBinarySensor(coordinator, cfg_sensor, CODE.STATUS_CHANGED)
for cfg_sensor in coordinator.data[SL_DATA.KEY_CONFIG] for cfg_sensor in config
if cfg_sensor in SUPPORTED_CONFIG_BINARY_SENSORS if cfg_sensor in SUPPORTED_CONFIG_BINARY_SENSORS
] ]
) )
if ( if config["equipment_flags"] & EQUIPMENT.FLAG_INTELLICHEM:
coordinator.data[SL_DATA.KEY_CONFIG]["equipment_flags"]
& EQUIPMENT.FLAG_INTELLICHEM
):
# IntelliChem alarm sensors # IntelliChem alarm sensors
entities.extend( entities.extend(
[ [
ScreenlogicChemistryAlarmBinarySensor(coordinator, chem_alarm) ScreenlogicChemistryAlarmBinarySensor(
for chem_alarm in coordinator.data[SL_DATA.KEY_CHEMISTRY][ coordinator, chem_alarm, CODE.CHEMISTRY_CHANGED
SL_DATA.KEY_ALERTS )
] for chem_alarm in chemistry[SL_DATA.KEY_ALERTS]
if chem_alarm != "_raw" if not chem_alarm.startswith("_")
] ]
) )
# Intellichem notification sensors # Intellichem notification sensors
entities.extend( entities.extend(
[ [
ScreenlogicChemistryNotificationBinarySensor(coordinator, chem_notif) ScreenlogicChemistryNotificationBinarySensor(
for chem_notif in coordinator.data[SL_DATA.KEY_CHEMISTRY][ coordinator, chem_notif, CODE.CHEMISTRY_CHANGED
SL_DATA.KEY_NOTIFICATIONS )
] for chem_notif in chemistry[SL_DATA.KEY_NOTIFICATIONS]
if chem_notif != "_raw" if not chem_notif.startswith("_")
] ]
) )
if ( if config["equipment_flags"] & EQUIPMENT.FLAG_CHLORINATOR:
coordinator.data[SL_DATA.KEY_CONFIG]["equipment_flags"]
& EQUIPMENT.FLAG_CHLORINATOR
):
# SCG binary sensor # SCG binary sensor
entities.append(ScreenlogicSCGBinarySensor(coordinator, "scg_status")) entities.append(ScreenlogicSCGBinarySensor(coordinator, "scg_status"))
async_add_entities(entities) async_add_entities(entities)
class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity): class ScreenLogicBinarySensorEntity(ScreenlogicEntity, BinarySensorEntity):
"""Representation of the basic ScreenLogic binary sensor entity.""" """Base class for all ScreenLogic binary sensor entities."""
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_entity_category = EntityCategory.DIAGNOSTIC _attr_entity_category = EntityCategory.DIAGNOSTIC
@property @property
def name(self): def name(self) -> str | None:
"""Return the sensor name.""" """Return the sensor name."""
return self.sensor["name"] return self.sensor["name"]
@property @property
def device_class(self): def device_class(self) -> BinarySensorDeviceClass | None:
"""Return the device class.""" """Return the device class."""
device_type = self.sensor.get("device_type") device_type = self.sensor.get("device_type")
return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_type) return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_type)
@ -102,46 +104,58 @@ class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity):
return self.sensor["value"] == ON_OFF.ON return self.sensor["value"] == ON_OFF.ON
@property @property
def sensor(self): def sensor(self) -> dict:
"""Shortcut to access the sensor data.""" """Shortcut to access the sensor data."""
return self.coordinator.data[SL_DATA.KEY_SENSORS][self._data_key] return self.gateway_data[SL_DATA.KEY_SENSORS][self._data_key]
class ScreenlogicChemistryAlarmBinarySensor(ScreenLogicBinarySensor): class ScreenLogicStatusBinarySensor(
ScreenLogicBinarySensorEntity, ScreenLogicPushEntity
):
"""Representation of a basic ScreenLogic sensor entity."""
class ScreenlogicChemistryAlarmBinarySensor(
ScreenLogicBinarySensorEntity, ScreenLogicPushEntity
):
"""Representation of a ScreenLogic IntelliChem alarm binary sensor entity.""" """Representation of a ScreenLogic IntelliChem alarm binary sensor entity."""
@property @property
def sensor(self): def sensor(self) -> dict:
"""Shortcut to access the sensor data.""" """Shortcut to access the sensor data."""
return self.coordinator.data[SL_DATA.KEY_CHEMISTRY][SL_DATA.KEY_ALERTS][ return self.gateway_data[SL_DATA.KEY_CHEMISTRY][SL_DATA.KEY_ALERTS][
self._data_key self._data_key
] ]
class ScreenlogicChemistryNotificationBinarySensor(ScreenLogicBinarySensor): class ScreenlogicChemistryNotificationBinarySensor(
ScreenLogicBinarySensorEntity, ScreenLogicPushEntity
):
"""Representation of a ScreenLogic IntelliChem notification binary sensor entity.""" """Representation of a ScreenLogic IntelliChem notification binary sensor entity."""
@property @property
def sensor(self): def sensor(self) -> dict:
"""Shortcut to access the sensor data.""" """Shortcut to access the sensor data."""
return self.coordinator.data[SL_DATA.KEY_CHEMISTRY][SL_DATA.KEY_NOTIFICATIONS][ return self.gateway_data[SL_DATA.KEY_CHEMISTRY][SL_DATA.KEY_NOTIFICATIONS][
self._data_key self._data_key
] ]
class ScreenlogicSCGBinarySensor(ScreenLogicBinarySensor): class ScreenlogicSCGBinarySensor(ScreenLogicBinarySensorEntity):
"""Representation of a ScreenLogic SCG binary sensor entity.""" """Representation of a ScreenLogic SCG binary sensor entity."""
@property @property
def sensor(self): def sensor(self) -> dict:
"""Shortcut to access the sensor data.""" """Shortcut to access the sensor data."""
return self.coordinator.data[SL_DATA.KEY_SCG][self._data_key] return self.gateway_data[SL_DATA.KEY_SCG][self._data_key]
class ScreenlogicConfigBinarySensor(ScreenLogicBinarySensor): class ScreenlogicConfigBinarySensor(
ScreenLogicBinarySensorEntity, ScreenLogicPushEntity
):
"""Representation of a ScreenLogic config data binary sensor entity.""" """Representation of a ScreenLogic config data binary sensor entity."""
@property @property
def sensor(self): def sensor(self) -> dict:
"""Shortcut to access the sensor data.""" """Shortcut to access the sensor data."""
return self.coordinator.data[SL_DATA.KEY_CONFIG][self._data_key] return self.gateway_data[SL_DATA.KEY_CONFIG][self._data_key]

View File

@ -2,7 +2,7 @@
import logging import logging
from typing import Any from typing import Any
from screenlogicpy.const import DATA as SL_DATA, EQUIPMENT, HEAT_MODE from screenlogicpy.const import CODE, DATA as SL_DATA, EQUIPMENT, HEAT_MODE
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_PRESET_MODE, ATTR_PRESET_MODE,
@ -18,8 +18,9 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from . import ScreenlogicEntity from . import ScreenlogicDataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN
from .entity import ScreenLogicPushEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -40,15 +41,17 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up entry.""" """Set up entry."""
entities = [] entities = []
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
for body in coordinator.data[SL_DATA.KEY_BODIES]: for body in coordinator.gateway_data[SL_DATA.KEY_BODIES]:
entities.append(ScreenLogicClimate(coordinator, body)) entities.append(ScreenLogicClimate(coordinator, body))
async_add_entities(entities) async_add_entities(entities)
class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): class ScreenLogicClimate(ScreenLogicPushEntity, ClimateEntity, RestoreEntity):
"""Represents a ScreenLogic climate entity.""" """Represents a ScreenLogic climate entity."""
_attr_has_entity_name = True _attr_has_entity_name = True
@ -60,10 +63,10 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity):
def __init__(self, coordinator, body): def __init__(self, coordinator, body):
"""Initialize a ScreenLogic climate entity.""" """Initialize a ScreenLogic climate entity."""
super().__init__(coordinator, body) super().__init__(coordinator, body, CODE.STATUS_CHANGED)
self._configured_heat_modes = [] self._configured_heat_modes = []
# Is solar listed as available equipment? # Is solar listed as available equipment?
if self.coordinator.data["config"]["equipment_flags"] & EQUIPMENT.FLAG_SOLAR: if self.gateway_data["config"]["equipment_flags"] & EQUIPMENT.FLAG_SOLAR:
self._configured_heat_modes.extend( self._configured_heat_modes.extend(
[HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERRED] [HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERRED]
) )
@ -126,7 +129,7 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity):
return HEAT_MODE.NAME_FOR_NUM[self.body["heat_mode"]["value"]] return HEAT_MODE.NAME_FOR_NUM[self.body["heat_mode"]["value"]]
@property @property
def preset_modes(self): def preset_modes(self) -> list[str]:
"""All available presets.""" """All available presets."""
return [ return [
HEAT_MODE.NAME_FOR_NUM[mode_num] for mode_num in self._configured_heat_modes HEAT_MODE.NAME_FOR_NUM[mode_num] for mode_num in self._configured_heat_modes
@ -137,15 +140,14 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity):
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}") raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}")
if await self.gateway.async_set_heat_temp( if not await self.gateway.async_set_heat_temp(
int(self._data_key), int(temperature) int(self._data_key), int(temperature)
): ):
await self._async_refresh()
else:
raise HomeAssistantError( raise HomeAssistantError(
f"Failed to set_temperature {temperature} on body" f"Failed to set_temperature {temperature} on body"
f" {self.body['body_type']['value']}" f" {self.body['body_type']['value']}"
) )
_LOGGER.debug("Set temperature for body %s to %s", self._data_key, temperature)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the operation mode.""" """Set the operation mode."""
@ -154,13 +156,12 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity):
else: else:
mode = HEAT_MODE.NUM_FOR_NAME[self.preset_mode] mode = HEAT_MODE.NUM_FOR_NAME[self.preset_mode]
if await self.gateway.async_set_heat_mode(int(self._data_key), int(mode)): if not await self.gateway.async_set_heat_mode(int(self._data_key), int(mode)):
await self._async_refresh()
else:
raise HomeAssistantError( raise HomeAssistantError(
f"Failed to set_hvac_mode {mode} on body" f"Failed to set_hvac_mode {mode} on body"
f" {self.body['body_type']['value']}" f" {self.body['body_type']['value']}"
) )
_LOGGER.debug("Set hvac_mode on body %s to %s", self._data_key, mode)
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode.""" """Set the preset mode."""
@ -169,13 +170,12 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity):
if self.hvac_mode == HVACMode.OFF: if self.hvac_mode == HVACMode.OFF:
return return
if await self.gateway.async_set_heat_mode(int(self._data_key), int(mode)): if not await self.gateway.async_set_heat_mode(int(self._data_key), int(mode)):
await self._async_refresh()
else:
raise HomeAssistantError( raise HomeAssistantError(
f"Failed to set_preset_mode {mode} on body" f"Failed to set_preset_mode {mode} on body"
f" {self.body['body_type']['value']}" f" {self.body['body_type']['value']}"
) )
_LOGGER.debug("Set preset_mode on body %s to %s", self._data_key, mode)
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Run when entity is about to be added.""" """Run when entity is about to be added."""
@ -206,4 +206,4 @@ class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity):
@property @property
def body(self): def body(self):
"""Shortcut to access body data.""" """Shortcut to access body data."""
return self.coordinator.data[SL_DATA.KEY_BODIES][self._data_key] return self.gateway_data[SL_DATA.KEY_BODIES][self._data_key]

View File

@ -19,6 +19,6 @@ async def async_get_config_entry_diagnostics(
return { return {
"config_entry": config_entry.as_dict(), "config_entry": config_entry.as_dict(),
"data": coordinator.data, "data": coordinator.gateway.get_data(),
"debug": coordinator.gateway.get_debug(), "debug": coordinator.gateway.get_debug(),
} }

View File

@ -0,0 +1,138 @@
"""Base ScreenLogicEntity definitions."""
import logging
from typing import Any
# from screenlogicpy import ScreenLogicError, ScreenLogicGateway
from screenlogicpy.const import DATA as SL_DATA, EQUIPMENT, ON_OFF
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import ScreenlogicDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
class ScreenlogicEntity(CoordinatorEntity[ScreenlogicDataUpdateCoordinator]):
"""Base class for all ScreenLogic entities."""
def __init__(self, coordinator, data_key, enabled=True):
"""Initialize of the entity."""
super().__init__(coordinator)
self._data_key = data_key
self._attr_entity_registry_enabled_default = enabled
self._attr_unique_id = f"{self.mac}_{self._data_key}"
controller_type = self.config_data["controller_type"]
hardware_type = self.config_data["hardware_type"]
try:
equipment_model = EQUIPMENT.CONTROLLER_HARDWARE[controller_type][
hardware_type
]
except KeyError:
equipment_model = f"Unknown Model C:{controller_type} H:{hardware_type}"
self._attr_device_info = DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, self.mac)},
manufacturer="Pentair",
model=equipment_model,
name=self.gateway_name,
sw_version=self.gateway.version,
)
@property
def mac(self):
"""Mac address."""
return self.coordinator.config_entry.unique_id
@property
def config_data(self):
"""Shortcut for config data."""
return self.gateway_data[SL_DATA.KEY_CONFIG]
@property
def gateway(self):
"""Return the gateway."""
return self.coordinator.gateway
@property
def gateway_data(self) -> dict[str | int, Any]:
"""Return the gateway data."""
return self.gateway.get_data()
@property
def gateway_name(self):
"""Return the configured name of the gateway."""
return self.gateway.name
async def _async_refresh(self):
"""Refresh the data from the gateway."""
await self.coordinator.async_refresh()
# Second debounced refresh to catch any secondary
# changes in the device
await self.coordinator.async_request_refresh()
async def _async_refresh_timed(self, now):
"""Refresh from a timed called."""
await self.coordinator.async_request_refresh()
class ScreenLogicPushEntity(ScreenlogicEntity):
"""Base class for all ScreenLogic push entities."""
def __init__(self, coordinator, data_key, message_code, enabled=True):
"""Initialize the entity."""
super().__init__(coordinator, data_key, enabled)
self._update_message_code = message_code
@callback
def _async_data_updated(self) -> None:
"""Handle data updates."""
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
self.async_on_remove(
await self.gateway.async_subscribe_client(
self._async_data_updated, self._update_message_code
)
)
class ScreenLogicCircuitEntity(ScreenLogicPushEntity):
"""Base class for all ScreenLogic switch and light entities."""
_attr_has_entity_name = True
@property
def name(self):
"""Get the name of the switch."""
return self.circuit["name"]
@property
def is_on(self) -> bool:
"""Get whether the switch is in on state."""
return self.circuit["value"] == ON_OFF.ON
async def async_turn_on(self, **kwargs) -> None:
"""Send the ON command."""
await self._async_set_circuit(ON_OFF.ON)
async def async_turn_off(self, **kwargs) -> None:
"""Send the OFF command."""
await self._async_set_circuit(ON_OFF.OFF)
async def _async_set_circuit(self, circuit_value) -> None:
if not await self.gateway.async_set_circuit(self._data_key, circuit_value):
raise HomeAssistantError(
f"Failed to set_circuit {self._data_key} {circuit_value}"
)
_LOGGER.debug("Turn %s %s", self._data_key, circuit_value)
@property
def circuit(self) -> dict[str | int, Any]:
"""Shortcut to access the circuit."""
return self.gateway_data[SL_DATA.KEY_CIRCUITS][self._data_key]

View File

@ -1,15 +1,16 @@
"""Support for a ScreenLogic light 'circuit' switch.""" """Support for a ScreenLogic light 'circuit' switch."""
import logging import logging
from screenlogicpy.const import DATA as SL_DATA, GENERIC_CIRCUIT_NAMES from screenlogicpy.const import CODE, DATA as SL_DATA, GENERIC_CIRCUIT_NAMES
from homeassistant.components.light import ColorMode, LightEntity from homeassistant.components.light import ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ScreenLogicCircuitEntity from . import ScreenlogicDataUpdateCoordinator
from .const import DOMAIN, LIGHT_CIRCUIT_FUNCTIONS from .const import DOMAIN, LIGHT_CIRCUIT_FUNCTIONS
from .entity import ScreenLogicCircuitEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,13 +21,19 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up entry.""" """Set up entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
circuits = coordinator.gateway_data[SL_DATA.KEY_CIRCUITS]
async_add_entities( async_add_entities(
[ [
ScreenLogicLight( ScreenLogicLight(
coordinator, circuit_num, circuit["name"] not in GENERIC_CIRCUIT_NAMES coordinator,
circuit_num,
CODE.STATUS_CHANGED,
circuit["name"] not in GENERIC_CIRCUIT_NAMES,
) )
for circuit_num, circuit in coordinator.data[SL_DATA.KEY_CIRCUITS].items() for circuit_num, circuit in circuits.items()
if circuit["function"] in LIGHT_CIRCUIT_FUNCTIONS if circuit["function"] in LIGHT_CIRCUIT_FUNCTIONS
] ]
) )

View File

@ -3,7 +3,7 @@
"name": "Pentair ScreenLogic", "name": "Pentair ScreenLogic",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/screenlogic", "documentation": "https://www.home-assistant.io/integrations/screenlogic",
"requirements": ["screenlogicpy==0.6.4"], "requirements": ["screenlogicpy==0.7.0"],
"codeowners": ["@dieselrabbit", "@bdraco"], "codeowners": ["@dieselrabbit", "@bdraco"],
"dhcp": [ "dhcp": [
{ "registered_devices": true }, { "registered_devices": true },
@ -12,6 +12,6 @@
"macaddress": "00C033*" "macaddress": "00C033*"
} }
], ],
"iot_class": "local_polling", "iot_class": "local_push",
"loggers": ["screenlogicpy"] "loggers": ["screenlogicpy"]
} }

View File

@ -9,8 +9,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ScreenlogicEntity from . import ScreenlogicDataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN
from .entity import ScreenlogicEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,13 +29,15 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up entry.""" """Set up entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][
equipment_flags = coordinator.data[SL_DATA.KEY_CONFIG]["equipment_flags"] config_entry.entry_id
]
equipment_flags = coordinator.gateway_data[SL_DATA.KEY_CONFIG]["equipment_flags"]
if equipment_flags & EQUIPMENT.FLAG_CHLORINATOR: if equipment_flags & EQUIPMENT.FLAG_CHLORINATOR:
async_add_entities( async_add_entities(
[ [
ScreenLogicNumber(coordinator, scg_level) ScreenLogicNumber(coordinator, scg_level)
for scg_level in coordinator.data[SL_DATA.KEY_SCG] for scg_level in coordinator.gateway_data[SL_DATA.KEY_SCG]
if scg_level in SUPPORTED_SCG_NUMBERS if scg_level in SUPPORTED_SCG_NUMBERS
] ]
) )
@ -65,7 +68,7 @@ class ScreenLogicNumber(ScreenlogicEntity, NumberEntity):
# both existing level values and override the one that changed. # both existing level values and override the one that changed.
levels = {} levels = {}
for level in SUPPORTED_SCG_NUMBERS: for level in SUPPORTED_SCG_NUMBERS:
levels[level] = self.coordinator.data[SL_DATA.KEY_SCG][level]["value"] levels[level] = self.gateway_data[SL_DATA.KEY_SCG][level]["value"]
levels[self._data_key] = int(value) levels[self._data_key] = int(value)
if await self.coordinator.gateway.async_set_scg_config( if await self.coordinator.gateway.async_set_scg_config(
@ -88,4 +91,4 @@ class ScreenLogicNumber(ScreenlogicEntity, NumberEntity):
@property @property
def sensor(self) -> dict: def sensor(self) -> dict:
"""Shortcut to access the level sensor data.""" """Shortcut to access the level sensor data."""
return self.coordinator.data[SL_DATA.KEY_SCG][self._data_key] return self.gateway_data[SL_DATA.KEY_SCG][self._data_key]

View File

@ -1,9 +1,13 @@
"""Support for a ScreenLogic Sensor.""" """Support for a ScreenLogic Sensor."""
from typing import Any
from screenlogicpy.const import ( from screenlogicpy.const import (
CHEM_DOSING_STATE, CHEM_DOSING_STATE,
CODE,
DATA as SL_DATA, DATA as SL_DATA,
DEVICE_TYPE, DEVICE_TYPE,
EQUIPMENT, EQUIPMENT,
STATE_TYPE,
UNIT, UNIT,
) )
@ -26,8 +30,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ScreenlogicEntity from . import ScreenlogicDataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN
from .entity import ScreenlogicEntity, ScreenLogicPushEntity
SUPPORTED_BASIC_SENSORS = ( SUPPORTED_BASIC_SENSORS = (
"air_temperature", "air_temperature",
@ -68,12 +73,18 @@ SUPPORTED_PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM")
SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = { SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = {
DEVICE_TYPE.DURATION: SensorDeviceClass.DURATION, DEVICE_TYPE.DURATION: SensorDeviceClass.DURATION,
DEVICE_TYPE.ENUM: SensorDeviceClass.ENUM,
DEVICE_TYPE.ENERGY: SensorDeviceClass.POWER, DEVICE_TYPE.ENERGY: SensorDeviceClass.POWER,
DEVICE_TYPE.POWER: SensorDeviceClass.POWER, DEVICE_TYPE.POWER: SensorDeviceClass.POWER,
DEVICE_TYPE.TEMPERATURE: SensorDeviceClass.TEMPERATURE, DEVICE_TYPE.TEMPERATURE: SensorDeviceClass.TEMPERATURE,
DEVICE_TYPE.VOLUME: SensorDeviceClass.VOLUME, DEVICE_TYPE.VOLUME: SensorDeviceClass.VOLUME,
} }
SL_STATE_TYPE_TO_HA_STATE_CLASS = {
STATE_TYPE.MEASUREMENT: SensorStateClass.MEASUREMENT,
STATE_TYPE.TOTAL_INCREASING: SensorStateClass.TOTAL_INCREASING,
}
SL_UNIT_TO_HA_UNIT = { SL_UNIT_TO_HA_UNIT = {
UNIT.CELSIUS: UnitOfTemperature.CELSIUS, UNIT.CELSIUS: UnitOfTemperature.CELSIUS,
UNIT.FAHRENHEIT: UnitOfTemperature.FAHRENHEIT, UNIT.FAHRENHEIT: UnitOfTemperature.FAHRENHEIT,
@ -93,14 +104,18 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up entry.""" """Set up entry."""
entities = [] entities: list[ScreenLogicSensorEntity] = []
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][
equipment_flags = coordinator.data[SL_DATA.KEY_CONFIG]["equipment_flags"] config_entry.entry_id
]
equipment_flags = coordinator.gateway_data[SL_DATA.KEY_CONFIG]["equipment_flags"]
# Generic sensors # Generic push sensors
for sensor_name in coordinator.data[SL_DATA.KEY_SENSORS]: for sensor_name in coordinator.gateway_data[SL_DATA.KEY_SENSORS]:
if sensor_name in SUPPORTED_BASIC_SENSORS: if sensor_name in SUPPORTED_BASIC_SENSORS:
entities.append(ScreenLogicSensor(coordinator, sensor_name)) entities.append(
ScreenLogicStatusSensor(coordinator, sensor_name, CODE.STATUS_CHANGED)
)
# While these values exist in the chemistry data, their last value doesn't # While these values exist in the chemistry data, their last value doesn't
# persist there when the pump is off/there is no flow. Pulling them from # persist there when the pump is off/there is no flow. Pulling them from
@ -109,10 +124,12 @@ async def async_setup_entry(
equipment_flags & EQUIPMENT.FLAG_INTELLICHEM equipment_flags & EQUIPMENT.FLAG_INTELLICHEM
and sensor_name in SUPPORTED_BASIC_CHEM_SENSORS and sensor_name in SUPPORTED_BASIC_CHEM_SENSORS
): ):
entities.append(ScreenLogicSensor(coordinator, sensor_name)) entities.append(
ScreenLogicStatusSensor(coordinator, sensor_name, CODE.STATUS_CHANGED)
)
# Pump sensors # Pump sensors
for pump_num, pump_data in coordinator.data[SL_DATA.KEY_PUMPS].items(): for pump_num, pump_data in coordinator.gateway_data[SL_DATA.KEY_PUMPS].items():
if pump_data["data"] != 0 and "currentWatts" in pump_data: if pump_data["data"] != 0 and "currentWatts" in pump_data:
for pump_key in pump_data: for pump_key in pump_data:
enabled = True enabled = True
@ -129,14 +146,16 @@ async def async_setup_entry(
# IntelliChem sensors # IntelliChem sensors
if equipment_flags & EQUIPMENT.FLAG_INTELLICHEM: if equipment_flags & EQUIPMENT.FLAG_INTELLICHEM:
for chem_sensor_name in coordinator.data[SL_DATA.KEY_CHEMISTRY]: for chem_sensor_name in coordinator.gateway_data[SL_DATA.KEY_CHEMISTRY]:
enabled = True enabled = True
if equipment_flags & EQUIPMENT.FLAG_CHLORINATOR: if equipment_flags & EQUIPMENT.FLAG_CHLORINATOR:
if chem_sensor_name in ("salt_tds_ppm",): if chem_sensor_name in ("salt_tds_ppm",):
enabled = False enabled = False
if chem_sensor_name in SUPPORTED_CHEM_SENSORS: if chem_sensor_name in SUPPORTED_CHEM_SENSORS:
entities.append( entities.append(
ScreenLogicChemistrySensor(coordinator, chem_sensor_name, enabled) ScreenLogicChemistrySensor(
coordinator, chem_sensor_name, CODE.CHEMISTRY_CHANGED, enabled
)
) )
# SCG sensors # SCG sensors
@ -144,7 +163,7 @@ async def async_setup_entry(
entities.extend( entities.extend(
[ [
ScreenLogicSCGSensor(coordinator, scg_sensor) ScreenLogicSCGSensor(coordinator, scg_sensor)
for scg_sensor in coordinator.data[SL_DATA.KEY_SCG] for scg_sensor in coordinator.gateway_data[SL_DATA.KEY_SCG]
if scg_sensor in SUPPORTED_SCG_SENSORS if scg_sensor in SUPPORTED_SCG_SENSORS
] ]
) )
@ -152,54 +171,66 @@ async def async_setup_entry(
async_add_entities(entities) async_add_entities(entities)
class ScreenLogicSensor(ScreenlogicEntity, SensorEntity): class ScreenLogicSensorEntity(ScreenlogicEntity, SensorEntity):
"""Representation of the basic ScreenLogic sensor entity.""" """Base class for all ScreenLogic sensor entities."""
_attr_has_entity_name = True _attr_has_entity_name = True
@property @property
def name(self): def name(self) -> str | None:
"""Name of the sensor.""" """Name of the sensor."""
return self.sensor["name"] return self.sensor["name"]
@property @property
def native_unit_of_measurement(self): def native_unit_of_measurement(self) -> str | None:
"""Return the unit of measurement.""" """Return the unit of measurement."""
sl_unit = self.sensor.get("unit") sl_unit = self.sensor.get("unit")
return SL_UNIT_TO_HA_UNIT.get(sl_unit, sl_unit) return SL_UNIT_TO_HA_UNIT.get(sl_unit, sl_unit)
@property @property
def device_class(self): def device_class(self) -> SensorDeviceClass | None:
"""Device class of the sensor.""" """Device class of the sensor."""
device_type = self.sensor.get("device_type") device_type = self.sensor.get("device_type")
return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_type) return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_type)
@property @property
def entity_category(self): def entity_category(self) -> EntityCategory | None:
"""Entity Category of the sensor.""" """Entity Category of the sensor."""
return ( return (
None if self._data_key == "air_temperature" else EntityCategory.DIAGNOSTIC None if self._data_key == "air_temperature" else EntityCategory.DIAGNOSTIC
) )
@property @property
def state_class(self): def state_class(self) -> SensorStateClass | None:
"""Return the state class of the sensor.""" """Return the state class of the sensor."""
state_type = self.sensor.get("state_type")
if self._data_key == "scg_super_chlor_timer": if self._data_key == "scg_super_chlor_timer":
return None return None
return SensorStateClass.MEASUREMENT return SL_STATE_TYPE_TO_HA_STATE_CLASS.get(
state_type, SensorStateClass.MEASUREMENT
)
@property @property
def native_value(self): def options(self) -> list[str] | None:
"""Return a set of possible options."""
return self.sensor.get("enum_options")
@property
def native_value(self) -> str | int | float:
"""State of the sensor.""" """State of the sensor."""
return self.sensor["value"] return self.sensor["value"]
@property @property
def sensor(self): def sensor(self) -> dict[str | int, Any]:
"""Shortcut to access the sensor data.""" """Shortcut to access the sensor data."""
return self.coordinator.data[SL_DATA.KEY_SENSORS][self._data_key] return self.gateway_data[SL_DATA.KEY_SENSORS][self._data_key]
class ScreenLogicPumpSensor(ScreenLogicSensor): class ScreenLogicStatusSensor(ScreenLogicSensorEntity, ScreenLogicPushEntity):
"""Representation of a basic ScreenLogic sensor entity."""
class ScreenLogicPumpSensor(ScreenLogicSensorEntity):
"""Representation of a ScreenLogic pump sensor entity.""" """Representation of a ScreenLogic pump sensor entity."""
def __init__(self, coordinator, pump, key, enabled=True): def __init__(self, coordinator, pump, key, enabled=True):
@ -209,21 +240,21 @@ class ScreenLogicPumpSensor(ScreenLogicSensor):
self._key = key self._key = key
@property @property
def sensor(self): def sensor(self) -> dict[str | int, Any]:
"""Shortcut to access the pump sensor data.""" """Shortcut to access the pump sensor data."""
return self.coordinator.data[SL_DATA.KEY_PUMPS][self._pump_id][self._key] return self.gateway_data[SL_DATA.KEY_PUMPS][self._pump_id][self._key]
class ScreenLogicChemistrySensor(ScreenLogicSensor): class ScreenLogicChemistrySensor(ScreenLogicSensorEntity, ScreenLogicPushEntity):
"""Representation of a ScreenLogic IntelliChem sensor entity.""" """Representation of a ScreenLogic IntelliChem sensor entity."""
def __init__(self, coordinator, key, enabled=True): def __init__(self, coordinator, key, message_code, enabled=True):
"""Initialize of the pump sensor.""" """Initialize of the pump sensor."""
super().__init__(coordinator, f"chem_{key}", enabled) super().__init__(coordinator, f"chem_{key}", message_code, enabled)
self._key = key self._key = key
@property @property
def native_value(self): def native_value(self) -> str | int | float:
"""State of the sensor.""" """State of the sensor."""
value = self.sensor["value"] value = self.sensor["value"]
if "dosing_state" in self._key: if "dosing_state" in self._key:
@ -231,15 +262,15 @@ class ScreenLogicChemistrySensor(ScreenLogicSensor):
return (value - 1) if "supply" in self._data_key else value return (value - 1) if "supply" in self._data_key else value
@property @property
def sensor(self): def sensor(self) -> dict[str | int, Any]:
"""Shortcut to access the pump sensor data.""" """Shortcut to access the pump sensor data."""
return self.coordinator.data[SL_DATA.KEY_CHEMISTRY][self._key] return self.gateway_data[SL_DATA.KEY_CHEMISTRY][self._key]
class ScreenLogicSCGSensor(ScreenLogicSensor): class ScreenLogicSCGSensor(ScreenLogicSensorEntity):
"""Representation of ScreenLogic SCG sensor entity.""" """Representation of ScreenLogic SCG sensor entity."""
@property @property
def sensor(self): def sensor(self) -> dict[str | int, Any]:
"""Shortcut to access the pump sensor data.""" """Shortcut to access the pump sensor data."""
return self.coordinator.data[SL_DATA.KEY_SCG][self._data_key] return self.gateway_data[SL_DATA.KEY_SCG][self._data_key]

View File

@ -1,15 +1,16 @@
"""Support for a ScreenLogic 'circuit' switch.""" """Support for a ScreenLogic 'circuit' switch."""
import logging import logging
from screenlogicpy.const import DATA as SL_DATA, GENERIC_CIRCUIT_NAMES from screenlogicpy.const import CODE, DATA as SL_DATA, GENERIC_CIRCUIT_NAMES
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ScreenLogicCircuitEntity from . import ScreenlogicDataUpdateCoordinator
from .const import DOMAIN, LIGHT_CIRCUIT_FUNCTIONS from .const import DOMAIN, LIGHT_CIRCUIT_FUNCTIONS
from .entity import ScreenLogicCircuitEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,13 +21,19 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up entry.""" """Set up entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
circuits = coordinator.gateway_data[SL_DATA.KEY_CIRCUITS]
async_add_entities( async_add_entities(
[ [
ScreenLogicSwitch( ScreenLogicSwitch(
coordinator, circuit_num, circuit["name"] not in GENERIC_CIRCUIT_NAMES coordinator,
circuit_num,
CODE.STATUS_CHANGED,
circuit["name"] not in GENERIC_CIRCUIT_NAMES,
) )
for circuit_num, circuit in coordinator.data[SL_DATA.KEY_CIRCUITS].items() for circuit_num, circuit in circuits.items()
if circuit["function"] not in LIGHT_CIRCUIT_FUNCTIONS if circuit["function"] not in LIGHT_CIRCUIT_FUNCTIONS
] ]
) )

View File

@ -4717,7 +4717,7 @@
"name": "Pentair ScreenLogic", "name": "Pentair ScreenLogic",
"integration_type": "hub", "integration_type": "hub",
"config_flow": true, "config_flow": true,
"iot_class": "local_polling" "iot_class": "local_push"
}, },
"scsgate": { "scsgate": {
"name": "SCSGate", "name": "SCSGate",

View File

@ -2297,7 +2297,7 @@ satel_integra==0.3.7
scapy==2.5.0 scapy==2.5.0
# homeassistant.components.screenlogic # homeassistant.components.screenlogic
screenlogicpy==0.6.4 screenlogicpy==0.7.0
# homeassistant.components.scsgate # homeassistant.components.scsgate
scsgate==0.1.0 scsgate==0.1.0

View File

@ -1618,7 +1618,7 @@ samsungtvws[async,encrypted]==2.5.0
scapy==2.5.0 scapy==2.5.0
# homeassistant.components.screenlogic # homeassistant.components.screenlogic
screenlogicpy==0.6.4 screenlogicpy==0.7.0
# homeassistant.components.backup # homeassistant.components.backup
securetar==2022.2.0 securetar==2022.2.0