ScreenLogic cleanups (#48136)
* ScreenLogic cleanup. Bump screenlogicpy to 0.2.0. Move heating functions from water_heater to climate platform. Address notes from original PR. * Fix temperature attribute * Addressing notes. Bump screenlogicpy to 0.2.1. Made device_types constants. Made (known) equipment flags constants. Used dict.get() in places where None is the default. Return fast with good _last_preset. * Update homeassistant/components/screenlogic/climate.py Let base entity handle state property. Co-authored-by: J. Nick Koston <nick@koston.org> * Patch integration setup functions. * Exception, ATTR_TEMPERATURE notes Co-authored-by: J. Nick Koston <nick@koston.org>pull/48186/head
parent
346a724ac3
commit
fb48fd7d10
|
@ -840,9 +840,9 @@ omit =
|
||||||
homeassistant/components/scrape/sensor.py
|
homeassistant/components/scrape/sensor.py
|
||||||
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/sensor.py
|
homeassistant/components/screenlogic/sensor.py
|
||||||
homeassistant/components/screenlogic/switch.py
|
homeassistant/components/screenlogic/switch.py
|
||||||
homeassistant/components/screenlogic/water_heater.py
|
|
||||||
homeassistant/components/scsgate/*
|
homeassistant/components/scsgate/*
|
||||||
homeassistant/components/scsgate/cover.py
|
homeassistant/components/scsgate/cover.py
|
||||||
homeassistant/components/sendgrid/notify.py
|
homeassistant/components/sendgrid/notify.py
|
||||||
|
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
|
|
||||||
from screenlogicpy import ScreenLogicError, ScreenLogicGateway
|
from screenlogicpy import ScreenLogicError, ScreenLogicGateway
|
||||||
from screenlogicpy.const import (
|
from screenlogicpy.const import (
|
||||||
CONTROLLER_HARDWARE,
|
EQUIPMENT,
|
||||||
SL_GATEWAY_IP,
|
SL_GATEWAY_IP,
|
||||||
SL_GATEWAY_NAME,
|
SL_GATEWAY_NAME,
|
||||||
SL_GATEWAY_PORT,
|
SL_GATEWAY_PORT,
|
||||||
|
@ -28,7 +28,7 @@ from .const import DEFAULT_SCAN_INTERVAL, DISCOVERED_GATEWAYS, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS = ["switch", "sensor", "binary_sensor", "water_heater"]
|
PLATFORMS = ["switch", "sensor", "binary_sensor", "climate"]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: dict):
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
@ -59,11 +59,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
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
|
||||||
except AttributeError as ex:
|
|
||||||
_LOGGER.exception(
|
|
||||||
"Unexpected error while connecting to the gateway %s", connect_info
|
|
||||||
)
|
|
||||||
raise ConfigEntryNotReady from ex
|
|
||||||
|
|
||||||
coordinator = ScreenlogicDataUpdateCoordinator(
|
coordinator = ScreenlogicDataUpdateCoordinator(
|
||||||
hass, config_entry=entry, gateway=gateway
|
hass, config_entry=entry, gateway=gateway
|
||||||
|
@ -91,7 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
device_data["pump"].append(pump)
|
device_data["pump"].append(pump)
|
||||||
|
|
||||||
for body in coordinator.data["bodies"]:
|
for body in coordinator.data["bodies"]:
|
||||||
device_data["water_heater"].append(body)
|
device_data["body"].append(body)
|
||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = {
|
hass.data[DOMAIN][entry.entry_id] = {
|
||||||
"coordinator": coordinator,
|
"coordinator": coordinator,
|
||||||
|
@ -151,18 +146,18 @@ class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
"""Fetch data from the Screenlogic gateway."""
|
"""Fetch data from the Screenlogic gateway."""
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(self.gateway.update)
|
await self.hass.async_add_executor_job(self.gateway.update)
|
||||||
return self.gateway.get_data()
|
|
||||||
except ScreenLogicError as error:
|
except ScreenLogicError as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(error) from error
|
||||||
|
return self.gateway.get_data()
|
||||||
|
|
||||||
|
|
||||||
class ScreenlogicEntity(CoordinatorEntity):
|
class ScreenlogicEntity(CoordinatorEntity):
|
||||||
"""Base class for all ScreenLogic entities."""
|
"""Base class for all ScreenLogic entities."""
|
||||||
|
|
||||||
def __init__(self, coordinator, datakey):
|
def __init__(self, coordinator, data_key):
|
||||||
"""Initialize of the entity."""
|
"""Initialize of the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._data_key = datakey
|
self._data_key = data_key
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mac(self):
|
def mac(self):
|
||||||
|
@ -192,11 +187,11 @@ class ScreenlogicEntity(CoordinatorEntity):
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
"""Return device information for the controller."""
|
"""Return device information for the controller."""
|
||||||
controller_type = self.config_data["controler_type"]
|
controller_type = self.config_data["controller_type"]
|
||||||
hardware_type = self.config_data["hardware_type"]
|
hardware_type = self.config_data["hardware_type"]
|
||||||
return {
|
return {
|
||||||
"connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)},
|
"connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)},
|
||||||
"name": self.gateway_name,
|
"name": self.gateway_name,
|
||||||
"manufacturer": "Pentair",
|
"manufacturer": "Pentair",
|
||||||
"model": CONTROLLER_HARDWARE[controller_type][hardware_type],
|
"model": EQUIPMENT.CONTROLLER_HARDWARE[controller_type][hardware_type],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
"""Support for a ScreenLogic Binary Sensor."""
|
"""Support for a ScreenLogic Binary Sensor."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from screenlogicpy.const import ON_OFF
|
from screenlogicpy.const import DEVICE_TYPE, ON_OFF
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity
|
from homeassistant.components.binary_sensor import (
|
||||||
|
DEVICE_CLASS_PROBLEM,
|
||||||
|
BinarySensorEntity,
|
||||||
|
)
|
||||||
|
|
||||||
from . import ScreenlogicEntity
|
from . import ScreenlogicEntity
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = {DEVICE_TYPE.ALARM: DEVICE_CLASS_PROBLEM}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up entry."""
|
"""Set up entry."""
|
||||||
|
@ -19,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
|
||||||
for binary_sensor in data["devices"]["binary_sensor"]:
|
for binary_sensor in data["devices"]["binary_sensor"]:
|
||||||
entities.append(ScreenLogicBinarySensor(coordinator, binary_sensor))
|
entities.append(ScreenLogicBinarySensor(coordinator, binary_sensor))
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity):
|
class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity):
|
||||||
|
@ -33,10 +38,8 @@ class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity):
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the device class."""
|
"""Return the device class."""
|
||||||
device_class = self.sensor.get("hass_type")
|
device_class = self.sensor.get("device_type")
|
||||||
if device_class in DEVICE_CLASSES:
|
return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class)
|
||||||
return device_class
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
|
@ -46,9 +49,4 @@ class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity):
|
||||||
@property
|
@property
|
||||||
def sensor(self):
|
def sensor(self):
|
||||||
"""Shortcut to access the sensor data."""
|
"""Shortcut to access the sensor data."""
|
||||||
return self.sensor_data[self._data_key]
|
return self.coordinator.data["sensors"][self._data_key]
|
||||||
|
|
||||||
@property
|
|
||||||
def sensor_data(self):
|
|
||||||
"""Shortcut to access the sensors data."""
|
|
||||||
return self.coordinator.data["sensors"]
|
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
"""Support for a ScreenLogic heating device."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from screenlogicpy.const import EQUIPMENT, HEAT_MODE
|
||||||
|
|
||||||
|
from homeassistant.components.climate import ClimateEntity
|
||||||
|
from homeassistant.components.climate.const import (
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
|
CURRENT_HVAC_HEAT,
|
||||||
|
CURRENT_HVAC_IDLE,
|
||||||
|
CURRENT_HVAC_OFF,
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
SUPPORT_PRESET_MODE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
|
|
||||||
|
from . import ScreenlogicEntity
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||||
|
|
||||||
|
SUPPORTED_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT]
|
||||||
|
|
||||||
|
SUPPORTED_PRESETS = [
|
||||||
|
HEAT_MODE.SOLAR,
|
||||||
|
HEAT_MODE.SOLAR_PREFERRED,
|
||||||
|
HEAT_MODE.HEATER,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up entry."""
|
||||||
|
entities = []
|
||||||
|
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
coordinator = data["coordinator"]
|
||||||
|
|
||||||
|
for body in data["devices"]["body"]:
|
||||||
|
entities.append(ScreenLogicClimate(coordinator, body))
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity):
|
||||||
|
"""Represents a ScreenLogic climate entity."""
|
||||||
|
|
||||||
|
def __init__(self, coordinator, body):
|
||||||
|
"""Initialize a ScreenLogic climate entity."""
|
||||||
|
super().__init__(coordinator, body)
|
||||||
|
self._configured_heat_modes = []
|
||||||
|
# Is solar listed as available equipment?
|
||||||
|
if self.coordinator.data["config"]["equipment_flags"] & EQUIPMENT.FLAG_SOLAR:
|
||||||
|
self._configured_heat_modes.extend(
|
||||||
|
[HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERRED]
|
||||||
|
)
|
||||||
|
self._configured_heat_modes.append(HEAT_MODE.HEATER)
|
||||||
|
self._last_preset = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Name of the heater."""
|
||||||
|
ent_name = self.body["heat_status"]["name"]
|
||||||
|
return f"{self.gateway_name} {ent_name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self) -> float:
|
||||||
|
"""Minimum allowed temperature."""
|
||||||
|
return self.body["min_set_point"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self) -> float:
|
||||||
|
"""Maximum allowed temperature."""
|
||||||
|
return self.body["max_set_point"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self) -> float:
|
||||||
|
"""Return water temperature."""
|
||||||
|
return self.body["last_temperature"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self) -> float:
|
||||||
|
"""Target temperature."""
|
||||||
|
return self.body["heat_set_point"]["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self) -> str:
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
if self.config_data["is_celcius"]["value"] == 1:
|
||||||
|
return TEMP_CELSIUS
|
||||||
|
return TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self) -> str:
|
||||||
|
"""Return the current hvac mode."""
|
||||||
|
if self.body["heat_mode"]["value"] > 0:
|
||||||
|
return HVAC_MODE_HEAT
|
||||||
|
return HVAC_MODE_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self):
|
||||||
|
"""Return th supported hvac modes."""
|
||||||
|
return SUPPORTED_MODES
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> str:
|
||||||
|
"""Return the current action of the heater."""
|
||||||
|
if self.body["heat_status"]["value"] > 0:
|
||||||
|
return CURRENT_HVAC_HEAT
|
||||||
|
if self.hvac_mode == HVAC_MODE_HEAT:
|
||||||
|
return CURRENT_HVAC_IDLE
|
||||||
|
return CURRENT_HVAC_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str:
|
||||||
|
"""Return current/last preset mode."""
|
||||||
|
if self.hvac_mode == HVAC_MODE_OFF:
|
||||||
|
return HEAT_MODE.NAME_FOR_NUM[self._last_preset]
|
||||||
|
return HEAT_MODE.NAME_FOR_NUM[self.body["heat_mode"]["value"]]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self):
|
||||||
|
"""All available presets."""
|
||||||
|
return [
|
||||||
|
HEAT_MODE.NAME_FOR_NUM[mode_num] for mode_num in self._configured_heat_modes
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Supported features of the heater."""
|
||||||
|
return SUPPORTED_FEATURES
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs) -> None:
|
||||||
|
"""Change the setpoint of the heater."""
|
||||||
|
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
|
||||||
|
raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}")
|
||||||
|
|
||||||
|
if await self.hass.async_add_executor_job(
|
||||||
|
self.gateway.set_heat_temp, int(self._data_key), int(temperature)
|
||||||
|
):
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
else:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Failed to set_temperature {temperature} on body {self.body['body_type']['value']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_set_hvac_mode(self, hvac_mode) -> None:
|
||||||
|
"""Set the operation mode."""
|
||||||
|
if hvac_mode == HVAC_MODE_OFF:
|
||||||
|
mode = HEAT_MODE.OFF
|
||||||
|
else:
|
||||||
|
mode = HEAT_MODE.NUM_FOR_NAME[self.preset_mode]
|
||||||
|
if await self.hass.async_add_executor_job(
|
||||||
|
self.gateway.set_heat_mode, int(self._data_key), int(mode)
|
||||||
|
):
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
else:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Failed to set_hvac_mode {mode} on body {self.body['body_type']['value']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set the preset mode."""
|
||||||
|
_LOGGER.debug("Setting last_preset to %s", HEAT_MODE.NUM_FOR_NAME[preset_mode])
|
||||||
|
self._last_preset = mode = HEAT_MODE.NUM_FOR_NAME[preset_mode]
|
||||||
|
if self.hvac_mode == HVAC_MODE_OFF:
|
||||||
|
return
|
||||||
|
if await self.hass.async_add_executor_job(
|
||||||
|
self.gateway.set_heat_mode, int(self._data_key), int(mode)
|
||||||
|
):
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
else:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
f"Failed to set_preset_mode {mode} on body {self.body['body_type']['value']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Run when entity is about to be added."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
|
_LOGGER.debug("Startup last preset is %s", self._last_preset)
|
||||||
|
if self._last_preset is not None:
|
||||||
|
return
|
||||||
|
prev_state = await self.async_get_last_state()
|
||||||
|
if (
|
||||||
|
prev_state is not None
|
||||||
|
and prev_state.attributes.get(ATTR_PRESET_MODE) is not None
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Startup setting last_preset to %s from prev_state",
|
||||||
|
HEAT_MODE.NUM_FOR_NAME[prev_state.attributes.get(ATTR_PRESET_MODE)],
|
||||||
|
)
|
||||||
|
self._last_preset = HEAT_MODE.NUM_FOR_NAME[
|
||||||
|
prev_state.attributes.get(ATTR_PRESET_MODE)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Startup setting last_preset to default (%s)",
|
||||||
|
self._configured_heat_modes[0],
|
||||||
|
)
|
||||||
|
self._last_preset = self._configured_heat_modes[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self):
|
||||||
|
"""Shortcut to access body data."""
|
||||||
|
return self.coordinator.data["bodies"][self._data_key]
|
|
@ -120,7 +120,7 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
mac = user_input[GATEWAY_SELECT_KEY]
|
mac = user_input[GATEWAY_SELECT_KEY]
|
||||||
selected_gateway = self.discovered_gateways[mac]
|
selected_gateway = self.discovered_gateways[mac]
|
||||||
await self.async_set_unique_id(mac)
|
await self.async_set_unique_id(mac, raise_on_progress=False)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=name_for_mac(mac),
|
title=name_for_mac(mac),
|
||||||
|
@ -164,7 +164,7 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
errors[CONF_IP_ADDRESS] = "cannot_connect"
|
errors[CONF_IP_ADDRESS] = "cannot_connect"
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
await self.async_set_unique_id(mac)
|
await self.async_set_unique_id(mac, raise_on_progress=False)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=name_for_mac(mac),
|
title=name_for_mac(mac),
|
||||||
|
|
|
@ -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.1.2"],
|
"requirements": ["screenlogicpy==0.2.1"],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@dieselrabbit"
|
"@dieselrabbit"
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
"""Support for a ScreenLogic Sensor."""
|
"""Support for a ScreenLogic Sensor."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.sensor import DEVICE_CLASSES
|
from screenlogicpy.const import DEVICE_TYPE
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE
|
||||||
|
|
||||||
from . import ScreenlogicEntity
|
from . import ScreenlogicEntity
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
@ -10,6 +12,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM")
|
PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM")
|
||||||
|
|
||||||
|
SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = {
|
||||||
|
DEVICE_TYPE.TEMPERATURE: DEVICE_CLASS_TEMPERATURE,
|
||||||
|
DEVICE_TYPE.ENERGY: DEVICE_CLASS_POWER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up entry."""
|
"""Set up entry."""
|
||||||
|
@ -19,11 +26,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
# Generic sensors
|
# Generic sensors
|
||||||
for sensor in data["devices"]["sensor"]:
|
for sensor in data["devices"]["sensor"]:
|
||||||
entities.append(ScreenLogicSensor(coordinator, sensor))
|
entities.append(ScreenLogicSensor(coordinator, sensor))
|
||||||
|
# Pump sensors
|
||||||
for pump in data["devices"]["pump"]:
|
for pump in data["devices"]["pump"]:
|
||||||
for pump_key in PUMP_SENSORS:
|
for pump_key in PUMP_SENSORS:
|
||||||
entities.append(ScreenLogicPumpSensor(coordinator, pump, pump_key))
|
entities.append(ScreenLogicPumpSensor(coordinator, pump, pump_key))
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class ScreenLogicSensor(ScreenlogicEntity):
|
class ScreenLogicSensor(ScreenlogicEntity):
|
||||||
|
@ -42,10 +50,8 @@ class ScreenLogicSensor(ScreenlogicEntity):
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Device class of the sensor."""
|
"""Device class of the sensor."""
|
||||||
device_class = self.sensor.get("hass_type")
|
device_class = self.sensor.get("device_type")
|
||||||
if device_class in DEVICE_CLASSES:
|
return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class)
|
||||||
return device_class
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
|
@ -56,12 +62,7 @@ class ScreenLogicSensor(ScreenlogicEntity):
|
||||||
@property
|
@property
|
||||||
def sensor(self):
|
def sensor(self):
|
||||||
"""Shortcut to access the sensor data."""
|
"""Shortcut to access the sensor data."""
|
||||||
return self.sensor_data[self._data_key]
|
return self.coordinator.data["sensors"][self._data_key]
|
||||||
|
|
||||||
@property
|
|
||||||
def sensor_data(self):
|
|
||||||
"""Shortcut to access the sensors data."""
|
|
||||||
return self.coordinator.data["sensors"]
|
|
||||||
|
|
||||||
|
|
||||||
class ScreenLogicPumpSensor(ScreenlogicEntity):
|
class ScreenLogicPumpSensor(ScreenlogicEntity):
|
||||||
|
@ -86,10 +87,8 @@ class ScreenLogicPumpSensor(ScreenlogicEntity):
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the device class."""
|
"""Return the device class."""
|
||||||
device_class = self.pump_sensor.get("hass_type")
|
device_class = self.pump_sensor.get("device_type")
|
||||||
if device_class in DEVICE_CLASSES:
|
return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class)
|
||||||
return device_class
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
|
@ -99,9 +98,4 @@ class ScreenLogicPumpSensor(ScreenlogicEntity):
|
||||||
@property
|
@property
|
||||||
def pump_sensor(self):
|
def pump_sensor(self):
|
||||||
"""Shortcut to access the pump sensor data."""
|
"""Shortcut to access the pump sensor data."""
|
||||||
return self.pumps_data[self._pump_id][self._key]
|
return self.coordinator.data["pumps"][self._pump_id][self._key]
|
||||||
|
|
||||||
@property
|
|
||||||
def pumps_data(self):
|
|
||||||
"""Shortcut to access the pump data."""
|
|
||||||
return self.coordinator.data["pumps"]
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
|
||||||
for switch in data["devices"]["switch"]:
|
for switch in data["devices"]["switch"]:
|
||||||
entities.append(ScreenLogicSwitch(coordinator, switch))
|
entities.append(ScreenLogicSwitch(coordinator, switch))
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity):
|
class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity):
|
||||||
|
@ -47,17 +47,14 @@ class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity):
|
||||||
if await self.hass.async_add_executor_job(
|
if await self.hass.async_add_executor_job(
|
||||||
self.gateway.set_circuit, self._data_key, circuit_value
|
self.gateway.set_circuit, self._data_key, circuit_value
|
||||||
):
|
):
|
||||||
_LOGGER.debug("Screenlogic turn %s %s", circuit_value, self._data_key)
|
_LOGGER.debug("Turn %s %s", self._data_key, circuit_value)
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("Screenlogic turn %s %s error", circuit_value, self._data_key)
|
_LOGGER.warning(
|
||||||
|
"Failed to set_circuit %s %s", self._data_key, circuit_value
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def circuit(self):
|
def circuit(self):
|
||||||
"""Shortcut to access the circuit."""
|
"""Shortcut to access the circuit."""
|
||||||
return self.circuits_data[self._data_key]
|
return self.coordinator.data["circuits"][self._data_key]
|
||||||
|
|
||||||
@property
|
|
||||||
def circuits_data(self):
|
|
||||||
"""Shortcut to access the circuits data."""
|
|
||||||
return self.coordinator.data["circuits"]
|
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
"""Support for a ScreenLogic Water Heater."""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from screenlogicpy.const import HEAT_MODE
|
|
||||||
|
|
||||||
from homeassistant.components.water_heater import (
|
|
||||||
SUPPORT_OPERATION_MODE,
|
|
||||||
SUPPORT_TARGET_TEMPERATURE,
|
|
||||||
WaterHeaterEntity,
|
|
||||||
)
|
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
|
||||||
|
|
||||||
from . import ScreenlogicEntity
|
|
||||||
from .const import DOMAIN
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
|
|
||||||
|
|
||||||
HEAT_MODE_NAMES = HEAT_MODE.Names
|
|
||||||
|
|
||||||
MODE_NAME_TO_MODE_NUM = {
|
|
||||||
HEAT_MODE_NAMES[num]: num for num in range(len(HEAT_MODE_NAMES))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
||||||
"""Set up entry."""
|
|
||||||
entities = []
|
|
||||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
|
||||||
coordinator = data["coordinator"]
|
|
||||||
|
|
||||||
for body in data["devices"]["water_heater"]:
|
|
||||||
entities.append(ScreenLogicWaterHeater(coordinator, body))
|
|
||||||
async_add_entities(entities, True)
|
|
||||||
|
|
||||||
|
|
||||||
class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity):
|
|
||||||
"""Represents the heating functions for a body of water."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Name of the water heater."""
|
|
||||||
ent_name = self.body["heat_status"]["name"]
|
|
||||||
return f"{self.gateway_name} {ent_name}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self) -> str:
|
|
||||||
"""State of the water heater."""
|
|
||||||
return HEAT_MODE.GetFriendlyName(self.body["heat_status"]["value"])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_temp(self) -> float:
|
|
||||||
"""Minimum allowed temperature."""
|
|
||||||
return self.body["min_set_point"]["value"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_temp(self) -> float:
|
|
||||||
"""Maximum allowed temperature."""
|
|
||||||
return self.body["max_set_point"]["value"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_temperature(self) -> float:
|
|
||||||
"""Return water temperature."""
|
|
||||||
return self.body["last_temperature"]["value"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target_temperature(self) -> float:
|
|
||||||
"""Target temperature."""
|
|
||||||
return self.body["heat_set_point"]["value"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def temperature_unit(self) -> str:
|
|
||||||
"""Return the unit of measurement."""
|
|
||||||
if self.config_data["is_celcius"]["value"] == 1:
|
|
||||||
return TEMP_CELSIUS
|
|
||||||
return TEMP_FAHRENHEIT
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_operation(self) -> str:
|
|
||||||
"""Return operation."""
|
|
||||||
return HEAT_MODE_NAMES[self.body["heat_mode"]["value"]]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def operation_list(self):
|
|
||||||
"""All available operations."""
|
|
||||||
supported_heat_modes = [HEAT_MODE.OFF]
|
|
||||||
# Is solar listed as available equipment?
|
|
||||||
if self.coordinator.data["config"]["equipment_flags"] & 0x1:
|
|
||||||
supported_heat_modes.extend([HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERED])
|
|
||||||
supported_heat_modes.append(HEAT_MODE.HEATER)
|
|
||||||
|
|
||||||
return [HEAT_MODE_NAMES[mode_num] for mode_num in supported_heat_modes]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self):
|
|
||||||
"""Supported features of the water heater."""
|
|
||||||
return SUPPORTED_FEATURES
|
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs) -> None:
|
|
||||||
"""Change the setpoint of the heater."""
|
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
|
||||||
if await self.hass.async_add_executor_job(
|
|
||||||
self.gateway.set_heat_temp, int(self._data_key), int(temperature)
|
|
||||||
):
|
|
||||||
await self.coordinator.async_request_refresh()
|
|
||||||
else:
|
|
||||||
_LOGGER.error("Screenlogic set_temperature error")
|
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode) -> None:
|
|
||||||
"""Set the operation mode."""
|
|
||||||
mode = MODE_NAME_TO_MODE_NUM[operation_mode]
|
|
||||||
if await self.hass.async_add_executor_job(
|
|
||||||
self.gateway.set_heat_mode, int(self._data_key), int(mode)
|
|
||||||
):
|
|
||||||
await self.coordinator.async_request_refresh()
|
|
||||||
else:
|
|
||||||
_LOGGER.error("Screenlogic set_operation_mode error")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def body(self):
|
|
||||||
"""Shortcut to access body data."""
|
|
||||||
return self.bodies_data[self._data_key]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bodies_data(self):
|
|
||||||
"""Shortcut to access bodies data."""
|
|
||||||
return self.coordinator.data["bodies"]
|
|
|
@ -2009,7 +2009,7 @@ scapy==2.4.4
|
||||||
schiene==0.23
|
schiene==0.23
|
||||||
|
|
||||||
# homeassistant.components.screenlogic
|
# homeassistant.components.screenlogic
|
||||||
screenlogicpy==0.1.2
|
screenlogicpy==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.scsgate
|
# homeassistant.components.scsgate
|
||||||
scsgate==0.1.0
|
scsgate==0.1.0
|
||||||
|
|
|
@ -1040,7 +1040,7 @@ samsungtvws==1.6.0
|
||||||
scapy==2.4.4
|
scapy==2.4.4
|
||||||
|
|
||||||
# homeassistant.components.screenlogic
|
# homeassistant.components.screenlogic
|
||||||
screenlogicpy==0.1.2
|
screenlogicpy==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.emulated_kasa
|
# homeassistant.components.emulated_kasa
|
||||||
# homeassistant.components.sense
|
# homeassistant.components.sense
|
||||||
|
|
|
@ -101,9 +101,40 @@ async def test_flow_discover_error(hass):
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
assert result["step_id"] == "gateway_entry"
|
assert result["step_id"] == "gateway_entry"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry, patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.login.create_socket",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.login.gateway_connect",
|
||||||
|
return_value="00-C0-33-01-01-01",
|
||||||
|
):
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result3["type"] == "create_entry"
|
||||||
|
assert result3["title"] == "Pentair: 01-01-01"
|
||||||
|
assert result3["data"] == {
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
}
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_dhcp(hass):
|
async def test_dhcp(hass):
|
||||||
"""Test DHCP discovery flow."""
|
"""Test DHCP discovery flow."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": "dhcp"},
|
context={"source": "dhcp"},
|
||||||
|
@ -116,6 +147,36 @@ async def test_dhcp(hass):
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "gateway_entry"
|
assert result["step_id"] == "gateway_entry"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry, patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.login.create_socket",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.screenlogic.config_flow.login.gateway_connect",
|
||||||
|
return_value="00-C0-33-01-01-01",
|
||||||
|
):
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result3["type"] == "create_entry"
|
||||||
|
assert result3["title"] == "Pentair: 01-01-01"
|
||||||
|
assert result3["data"] == {
|
||||||
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
|
CONF_PORT: 80,
|
||||||
|
}
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_form_manual_entry(hass):
|
async def test_form_manual_entry(hass):
|
||||||
"""Test we get the form."""
|
"""Test we get the form."""
|
||||||
|
@ -148,6 +209,11 @@ async def test_form_manual_entry(hass):
|
||||||
assert result2["step_id"] == "gateway_entry"
|
assert result2["step_id"] == "gateway_entry"
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry, patch(
|
||||||
"homeassistant.components.screenlogic.config_flow.login.create_socket",
|
"homeassistant.components.screenlogic.config_flow.login.create_socket",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
), patch(
|
), patch(
|
||||||
|
@ -169,6 +235,8 @@ async def test_form_manual_entry(hass):
|
||||||
CONF_IP_ADDRESS: "1.1.1.1",
|
CONF_IP_ADDRESS: "1.1.1.1",
|
||||||
CONF_PORT: 80,
|
CONF_PORT: 80,
|
||||||
}
|
}
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_form_cannot_connect(hass):
|
async def test_form_cannot_connect(hass):
|
||||||
|
@ -195,9 +263,18 @@ async def test_form_cannot_connect(hass):
|
||||||
|
|
||||||
async def test_option_flow(hass):
|
async def test_option_flow(hass):
|
||||||
"""Test config flow options."""
|
"""Test config flow options."""
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
|
entry = MockConfigEntry(domain=DOMAIN)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup", return_value=True
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
|
@ -213,9 +290,18 @@ async def test_option_flow(hass):
|
||||||
|
|
||||||
async def test_option_flow_defaults(hass):
|
async def test_option_flow_defaults(hass):
|
||||||
"""Test config flow options."""
|
"""Test config flow options."""
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
|
entry = MockConfigEntry(domain=DOMAIN)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup", return_value=True
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
|
@ -232,9 +318,18 @@ async def test_option_flow_defaults(hass):
|
||||||
|
|
||||||
async def test_option_flow_input_floor(hass):
|
async def test_option_flow_input_floor(hass):
|
||||||
"""Test config flow options."""
|
"""Test config flow options."""
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
|
entry = MockConfigEntry(domain=DOMAIN)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup", return_value=True
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.screenlogic.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
|
|
Loading…
Reference in New Issue