Screenlogic service refactor (#109041)

This commit is contained in:
Kevin Worrel 2024-02-05 00:05:28 -08:00 committed by GitHub
parent b56dd3f808
commit 0bfef71f1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 170 additions and 80 deletions

View file

@ -9,13 +9,14 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
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 .services import async_load_screenlogic_services
from .util import generate_unique_id
_LOGGER = logging.getLogger(__name__)
@ -36,6 +37,16 @@ PLATFORMS = [
Platform.SWITCH,
]
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Screenlogic."""
async_load_screenlogic_services(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Screenlogic from a config entry."""
@ -62,8 +73,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
async_load_screenlogic_services(hass, entry)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@ -77,8 +86,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.gateway.async_disconnect()
hass.data[DOMAIN].pop(entry.entry_id)
async_unload_screenlogic_services(hass)
return unload_ok

View file

@ -20,6 +20,8 @@ DOMAIN = "screenlogic"
DEFAULT_SCAN_INTERVAL = 30
MIN_SCAN_INTERVAL = 10
ATTR_CONFIG_ENTRY = "config_entry"
SERVICE_SET_COLOR_MODE = "set_color_mode"
ATTR_COLOR_MODE = "color_mode"
SUPPORTED_COLOR_MODES = {slugify(cm.name): cm.value for cm in COLOR_MODE}

View file

@ -6,14 +6,19 @@ from screenlogicpy import ScreenLogicError
from screenlogicpy.device_const.system import EQUIPMENT_FLAG
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import (
config_validation as cv,
issue_registry as ir,
selector,
)
from homeassistant.helpers.service import async_extract_config_entry_ids
from .const import (
ATTR_COLOR_MODE,
ATTR_CONFIG_ENTRY,
ATTR_RUNTIME,
DOMAIN,
MAX_RUNTIME,
@ -27,44 +32,103 @@ from .coordinator import ScreenlogicDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
SET_COLOR_MODE_SCHEMA = cv.make_entity_service_schema(
BASE_SERVICE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_COLOR_MODE): vol.In(SUPPORTED_COLOR_MODES),
},
vol.Required(ATTR_CONFIG_ENTRY): selector.ConfigEntrySelector(
{
"integration": DOMAIN,
}
)
}
)
TURN_ON_SUPER_CHLOR_SCHEMA = cv.make_entity_service_schema(
SET_COLOR_MODE_SCHEMA = vol.All(
vol.Schema(
{
vol.Optional(ATTR_RUNTIME, default=24): vol.Clamp(
min=MIN_RUNTIME, max=MAX_RUNTIME
vol.Optional(ATTR_CONFIG_ENTRY): selector.ConfigEntrySelector(
{
"integration": DOMAIN,
}
),
**cv.ENTITY_SERVICE_FIELDS,
vol.Required(ATTR_COLOR_MODE): vol.In(SUPPORTED_COLOR_MODES),
}
),
cv.has_at_least_one_key(ATTR_CONFIG_ENTRY, *cv.ENTITY_SERVICE_FIELDS),
)
TURN_ON_SUPER_CHLOR_SCHEMA = BASE_SERVICE_SCHEMA.extend(
{
vol.Optional(ATTR_RUNTIME, default=24): vol.All(
vol.Coerce(int), vol.Clamp(min=MIN_RUNTIME, max=MAX_RUNTIME)
),
}
)
@callback
def async_load_screenlogic_services(hass: HomeAssistant, entry: ConfigEntry):
def async_load_screenlogic_services(hass: HomeAssistant):
"""Set up services for the ScreenLogic integration."""
async def extract_screenlogic_config_entry_ids(service_call: ServiceCall):
if not (
screenlogic_entry_ids := [
entry_id
for entry_id in await async_extract_config_entry_ids(hass, service_call)
if (entry := hass.config_entries.async_get_entry(entry_id))
and entry.domain == DOMAIN
]
screenlogic_entry_ids := await async_extract_config_entry_ids(
hass, service_call
)
):
raise HomeAssistantError(
raise ServiceValidationError(
f"Failed to call service '{service_call.service}'. Config entry for "
"target not found"
)
return screenlogic_entry_ids
async def get_coordinators(
service_call: ServiceCall,
) -> list[ScreenlogicDataUpdateCoordinator]:
entry_ids: set[str]
if entry_id := service_call.data.get(ATTR_CONFIG_ENTRY):
entry_ids = {entry_id}
else:
ir.async_create_issue(
hass,
DOMAIN,
"service_target_deprecation",
breaks_in_ha_version="2024.8.0",
is_fixable=True,
is_persistent=True,
severity=ir.IssueSeverity.WARNING,
translation_key="service_target_deprecation",
)
entry_ids = await extract_screenlogic_config_entry_ids(service_call)
coordinators: list[ScreenlogicDataUpdateCoordinator] = []
for entry_id in entry_ids:
config_entry: ConfigEntry | None = hass.config_entries.async_get_entry(
entry_id
)
if not config_entry:
raise ServiceValidationError(
f"Failed to call service '{service_call.service}'. Config entry "
f"'{entry_id}' not found"
)
if not config_entry.domain == DOMAIN:
raise ServiceValidationError(
f"Failed to call service '{service_call.service}'. Config entry "
f"'{entry_id}' is not a {DOMAIN} config"
)
if not config_entry.state == ConfigEntryState.LOADED:
raise ServiceValidationError(
f"Failed to call service '{service_call.service}'. Config entry "
f"'{entry_id}' not loaded"
)
coordinators.append(hass.data[DOMAIN][entry_id])
return coordinators
async def async_set_color_mode(service_call: ServiceCall) -> None:
color_num = SUPPORTED_COLOR_MODES[service_call.data[ATTR_COLOR_MODE]]
for entry_id in await extract_screenlogic_config_entry_ids(service_call):
coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][entry_id]
coordinator: ScreenlogicDataUpdateCoordinator
for coordinator in await get_coordinators(service_call):
_LOGGER.debug(
"Service %s called on %s with mode %s",
SERVICE_SET_COLOR_MODE,
@ -83,13 +147,19 @@ def async_load_screenlogic_services(hass: HomeAssistant, entry: ConfigEntry):
is_on: bool,
runtime: int | None = None,
) -> None:
for entry_id in await extract_screenlogic_config_entry_ids(service_call):
coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][entry_id]
coordinator: ScreenlogicDataUpdateCoordinator
for coordinator in await get_coordinators(service_call):
if EQUIPMENT_FLAG.CHLORINATOR not in coordinator.gateway.equipment_flags:
raise ServiceValidationError(
f"Equipment configuration for {coordinator.gateway.name} does not"
f" support {service_call.service}"
)
rt_log = f" with runtime {runtime}" if runtime else ""
_LOGGER.debug(
"Service %s called on %s with runtime %s",
SERVICE_START_SUPER_CHLORINATION,
"Service %s called on %s%s",
service_call.service,
coordinator.gateway.name,
runtime,
rt_log,
)
try:
await coordinator.gateway.async_set_scg_config(
@ -107,16 +177,10 @@ def async_load_screenlogic_services(hass: HomeAssistant, entry: ConfigEntry):
async def async_stop_super_chlor(service_call: ServiceCall) -> None:
await async_set_super_chlor(service_call, False)
if not hass.services.has_service(DOMAIN, SERVICE_SET_COLOR_MODE):
hass.services.async_register(
DOMAIN, SERVICE_SET_COLOR_MODE, async_set_color_mode, SET_COLOR_MODE_SCHEMA
)
coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
equipment_flags = coordinator.gateway.equipment_flags
if EQUIPMENT_FLAG.CHLORINATOR in equipment_flags:
if not hass.services.has_service(DOMAIN, SERVICE_START_SUPER_CHLORINATION):
hass.services.async_register(
DOMAIN,
SERVICE_START_SUPER_CHLORINATION,
@ -124,26 +188,9 @@ def async_load_screenlogic_services(hass: HomeAssistant, entry: ConfigEntry):
TURN_ON_SUPER_CHLOR_SCHEMA,
)
if not hass.services.has_service(DOMAIN, SERVICE_STOP_SUPER_CHLORINATION):
hass.services.async_register(
DOMAIN,
SERVICE_STOP_SUPER_CHLORINATION,
async_stop_super_chlor,
BASE_SERVICE_SCHEMA,
)
@callback
def async_unload_screenlogic_services(hass: HomeAssistant):
"""Unload services for the ScreenLogic integration."""
if not hass.data[DOMAIN]:
_LOGGER.debug("Unloading all ScreenLogic services")
for service in hass.services.async_services_for_domain(DOMAIN):
hass.services.async_remove(DOMAIN, service)
elif not any(
EQUIPMENT_FLAG.CHLORINATOR in coordinator.gateway.equipment_flags
for coordinator in hass.data[DOMAIN].values()
):
_LOGGER.debug("Unloading ScreenLogic chlorination services")
hass.services.async_remove(DOMAIN, SERVICE_START_SUPER_CHLORINATION)
hass.services.async_remove(DOMAIN, SERVICE_STOP_SUPER_CHLORINATION)

View file

@ -1,9 +1,11 @@
# ScreenLogic Services
set_color_mode:
target:
device:
integration: screenlogic
fields:
config_entry:
required: false
selector:
config_entry:
integration: screenlogic
color_mode:
required: true
selector:
@ -32,10 +34,12 @@ set_color_mode:
- thumper
- white
start_super_chlorination:
target:
device:
integration: screenlogic
fields:
config_entry:
required: true
selector:
config_entry:
integration: screenlogic
runtime:
default: 24
selector:
@ -45,6 +49,9 @@ start_super_chlorination:
unit_of_measurement: hours
mode: slider
stop_super_chlorination:
target:
device:
fields:
config_entry:
required: true
selector:
config_entry:
integration: screenlogic

View file

@ -41,6 +41,10 @@
"name": "Set Color Mode",
"description": "Sets the color mode for all color-capable lights attached to this ScreenLogic gateway.",
"fields": {
"config_entry": {
"name": "Config Entry",
"description": "The config entry to use for this service."
},
"color_mode": {
"name": "Color Mode",
"description": "The ScreenLogic color mode to set."
@ -51,6 +55,10 @@
"name": "Start Super Chlorination",
"description": "Begins super chlorination, running for the specified period or 24 hours if none is specified.",
"fields": {
"config_entry": {
"name": "Config Entry",
"description": "The config entry to use for this service."
},
"runtime": {
"name": "Run Time",
"description": "Number of hours for super chlorination to run."
@ -59,7 +67,26 @@
},
"stop_super_chlorination": {
"name": "Stop Super Chlorination",
"description": "Stops super chlorination."
"description": "Stops super chlorination.",
"fields": {
"config_entry": {
"name": "Config Entry",
"description": "The config entry to use for this service."
}
}
}
},
"issues": {
"service_target_deprecation": {
"title": "Deprecating use of target for ScreenLogic services",
"fix_flow": {
"step": {
"confirm": {
"title": "Deprecating target for ScreenLogic services",
"description": "Use of an Area, Device, or Entity as a target for ScreenLogic services is being deprecated. Instead, use `config_entry` with the entry_id of the desired ScreenLogic integration.\n\nPlease update your automations and scripts and select **submit** to fix this issue."
}
}
}
}
}
}