Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump screenlogicpy to v0.9.0 #92475

Merged
merged 47 commits into from
Sep 9, 2023
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
861d9a2
Use EntityDescriptions
dieselrabbit May 4, 2023
265ddfe
Fix import in test_config_flow
dieselrabbit May 4, 2023
6589877
remove unused function
dieselrabbit May 4, 2023
fb85643
Merge remote-tracking branch 'upstream/dev' into sl-ent-desc
dieselrabbit May 4, 2023
65d2859
Replace regex
dieselrabbit May 4, 2023
94ceef5
Rename TemplateData
dieselrabbit May 5, 2023
4525182
Use dataclass for processed ent data
dieselrabbit May 5, 2023
d2efba9
Add data/const to coverage exclusion
dieselrabbit May 5, 2023
05cca6a
Merge branch 'dev' into sl-ent-desc
dieselrabbit May 15, 2023
cbf2cd7
Merge remote-tracking branch 'upstream/dev' into sl-ent-desc
dieselrabbit May 17, 2023
aedfa50
Merge branch 'dev' into sl-ent-desc
bdraco Jun 6, 2023
f17efca
Merge branch 'dev' into sl-ent-desc
dieselrabbit Jul 7, 2023
0ab68aa
Merge branch 'dev' into sl-ent-desc
dieselrabbit Jul 23, 2023
a3710a0
Push more entity setup into shared code
dieselrabbit Jul 23, 2023
6161689
Outdent entity migration block
dieselrabbit Jul 23, 2023
a80f8b3
Unpack multi-line ternarys
dieselrabbit Jul 23, 2023
4f87ac0
Test for entity migration
dieselrabbit Jul 24, 2023
c9e60b6
Refactor migration test for more coverage
dieselrabbit Jul 24, 2023
0ca4197
Use enum.StrEnum
dieselrabbit Jul 24, 2023
cbde7c4
Remove unnecessary try
dieselrabbit Jul 25, 2023
b4f209f
Additional test coverage
dieselrabbit Jul 25, 2023
e38fdc7
Merge branch 'dev' into sl-ent-desc
dieselrabbit Jul 27, 2023
8a23445
Merge branch 'dev' into sl-ent-desc
bdraco Aug 6, 2023
9626c19
Merge branch 'dev' into sl-ent-desc
dieselrabbit Aug 14, 2023
8f04232
Merge branch 'dev' into sl-ent-desc
dieselrabbit Aug 14, 2023
1479a0b
Move coordinator. Support excluded entity removal
dieselrabbit Aug 14, 2023
7da19af
Test for unused entity removal
dieselrabbit Aug 14, 2023
96c9f9c
Use connections for device mac
dieselrabbit Aug 14, 2023
d376563
Test coverage for data.py
dieselrabbit Aug 15, 2023
c7b6b34
Split integration patching to seperate test
dieselrabbit Aug 15, 2023
70f34d0
data test coverage
dieselrabbit Aug 16, 2023
6a4b9ab
Merge branch 'dev' into sl-ent-desc
dieselrabbit Aug 16, 2023
386285e
diagnostics test
dieselrabbit Aug 18, 2023
c05bab8
Refactor tests
dieselrabbit Aug 21, 2023
39eeaa6
Merge branch 'dev' into sl-ent-desc
dieselrabbit Aug 21, 2023
cb1f8c5
Logger f-strings
dieselrabbit Aug 21, 2023
67da32b
Break out steps in process_supported_values
dieselrabbit Aug 22, 2023
3fec089
Merge branch 'dev' into sl-ent-desc
dieselrabbit Aug 23, 2023
6c2ca17
Refactor supported entity data config and setup
dieselrabbit Aug 26, 2023
f927a62
Merge branch 'dev' into sl-ent-desc
dieselrabbit Aug 26, 2023
8afba66
Merge branch 'dev' into sl-ent-desc
dieselrabbit Aug 27, 2023
d2dd29a
Move generate_unique_id and cleanup_excluded_entity to util.py
dieselrabbit Aug 27, 2023
f1a6d0d
Docstring improvements
dieselrabbit Aug 27, 2023
97026b2
Use common build_base_entity_description
dieselrabbit Aug 28, 2023
b7d396f
Gateway rediscovery failure logged to debug
dieselrabbit Aug 28, 2023
fcdf96b
Merge branch 'dev' into sl-ent-desc
bdraco Aug 29, 2023
c7d089b
Merge branch 'dev' into sl-ent-desc
bdraco Sep 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -1066,9 +1066,10 @@ omit =
homeassistant/components/saj/sensor.py
homeassistant/components/satel_integra/*
homeassistant/components/schluter/*
homeassistant/components/screenlogic/__init__.py
homeassistant/components/screenlogic/binary_sensor.py
homeassistant/components/screenlogic/climate.py
homeassistant/components/screenlogic/coordinator.py
homeassistant/components/screenlogic/const.py
homeassistant/components/screenlogic/entity.py
homeassistant/components/screenlogic/light.py
homeassistant/components/screenlogic/number.py
Expand Down
184 changes: 94 additions & 90 deletions homeassistant/components/screenlogic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
"""The Screenlogic integration."""
from datetime import timedelta
import logging
from typing import Any

from screenlogicpy import ScreenLogicError, ScreenLogicGateway
from screenlogicpy.const import (
DATA as SL_DATA,
EQUIPMENT,
SL_GATEWAY_IP,
SL_GATEWAY_NAME,
SL_GATEWAY_PORT,
)
from screenlogicpy.const.data import SHARED_VALUES

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL, Platform
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.helpers import entity_registry as er
from homeassistant.util import slugify

from .config_flow import async_discover_gateways_by_unique_id, name_for_mac
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
from .const import DOMAIN
from .coordinator import ScreenlogicDataUpdateCoordinator, async_get_connect_info
from .data import ENTITY_MIGRATIONS
from .services import async_load_screenlogic_services, async_unload_screenlogic_services
from .util import generate_unique_id

_LOGGER = logging.getLogger(__name__)

Expand All @@ -44,12 +39,16 @@

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Screenlogic from a config entry."""

await _async_migrate_entries(hass, entry)

gateway = ScreenLogicGateway()

connect_info = await async_get_connect_info(hass, entry)

try:
await gateway.async_connect(**connect_info)
await gateway.async_update()
except ScreenLogicError as ex:
raise ConfigEntryNotReady(ex.msg) from ex

Expand Down Expand Up @@ -88,83 +87,88 @@ async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None
await hass.config_entries.async_reload(entry.entry_id)


async def async_get_connect_info(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, str | int]:
"""Construct connect_info from configuration entry and returns it to caller."""
mac = entry.unique_id
# Attempt to rediscover gateway to follow IP changes
discovered_gateways = await async_discover_gateways_by_unique_id(hass)
if mac in discovered_gateways:
return discovered_gateways[mac]

_LOGGER.warning("Gateway rediscovery failed")
# Static connection defined or fallback from discovery
return {
SL_GATEWAY_NAME: name_for_mac(mac),
SL_GATEWAY_IP: entry.data[CONF_IP_ADDRESS],
SL_GATEWAY_PORT: entry.data[CONF_PORT],
}


class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Class to manage the data update for the Screenlogic component."""

def __init__(
self,
hass: HomeAssistant,
*,
config_entry: ConfigEntry,
gateway: ScreenLogicGateway,
) -> None:
"""Initialize the Screenlogic Data Update Coordinator."""
self.config_entry = config_entry
self.gateway = gateway

interval = timedelta(
seconds=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
)
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=interval,
# Debounced option since the device takes
# a moment to reflect the knock-on changes
request_refresh_debouncer=Debouncer(
hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False
),
async def _async_migrate_entries(
hass: HomeAssistant, config_entry: ConfigEntry
) -> None:
"""Migrate to new entity names."""
entity_registry = er.async_get(hass)

for entry in er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
):
source_mac, source_key = entry.unique_id.split("_", 1)

source_index = None
if (
len(key_parts := source_key.rsplit("_", 1)) == 2
and key_parts[1].isdecimal()
):
source_key, source_index = key_parts

_LOGGER.debug(
"Checking migration status for '%s' against key '%s'",
entry.unique_id,
source_key,
)

@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) -> None:
"""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) -> None:
"""Fetch data from the Screenlogic gateway."""
assert self.config_entry is not None
try:
if not self.gateway.is_connected:
connect_info = await async_get_connect_info(
self.hass, self.config_entry
)
await self.gateway.async_connect(**connect_info)
if source_key not in ENTITY_MIGRATIONS:
continue

await self._async_update_configured_data()
except ScreenLogicError as ex:
if self.gateway.is_connected:
await self.gateway.async_disconnect()
raise UpdateFailed(ex.msg) from ex
_LOGGER.debug(
"Evaluating migration of '%s' from migration key '%s'",
entry.entity_id,
source_key,
)
migrations = ENTITY_MIGRATIONS[source_key]
updates: dict[str, Any] = {}
new_key = migrations["new_key"]
if new_key in SHARED_VALUES:
if (device := migrations.get("device")) is None:
_LOGGER.debug(
"Shared key '%s' is missing required migration data 'device'",
new_key,
)
continue
assert device is not None and (
dieselrabbit marked this conversation as resolved.
Show resolved Hide resolved
device != "pump" or (device == "pump" and source_index is not None)
)
new_unique_id = (
f"{source_mac}_{generate_unique_id(device, source_index, new_key)}"
)
else:
new_unique_id = entry.unique_id.replace(source_key, new_key)

if new_unique_id and new_unique_id != entry.unique_id:
if existing_entity_id := entity_registry.async_get_entity_id(
entry.domain, entry.platform, new_unique_id
):
_LOGGER.debug(
"Cannot migrate '%s' to unique_id '%s', already exists for entity '%s'. Aborting",
entry.unique_id,
new_unique_id,
existing_entity_id,
)
continue
updates["new_unique_id"] = new_unique_id

if (old_name := migrations.get("old_name")) is not None:
assert old_name
dieselrabbit marked this conversation as resolved.
Show resolved Hide resolved
new_name = migrations["new_name"]
if (s_old_name := slugify(old_name)) in entry.entity_id:
new_entity_id = entry.entity_id.replace(s_old_name, slugify(new_name))
if new_entity_id and new_entity_id != entry.entity_id:
updates["new_entity_id"] = new_entity_id

if entry.original_name and old_name in entry.original_name:
new_original_name = entry.original_name.replace(old_name, new_name)
if new_original_name and new_original_name != entry.original_name:
updates["original_name"] = new_original_name

if updates:
_LOGGER.debug(
"Migrating entity '%s' unique_id from '%s' to '%s'",
entry.entity_id,
entry.unique_id,
new_unique_id,
)
entity_registry.async_update_entity(entry.entity_id, **updates)
Loading