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.const import 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 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 homeassistant.util import slugify
from .const import DOMAIN from .const import DOMAIN
from .coordinator import ScreenlogicDataUpdateCoordinator, async_get_connect_info from .coordinator import ScreenlogicDataUpdateCoordinator, async_get_connect_info
from .data import ENTITY_MIGRATIONS 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 from .util import generate_unique_id
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -36,6 +37,16 @@ PLATFORMS = [
Platform.SWITCH, 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: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Screenlogic from a config entry.""" """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 hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
async_load_screenlogic_services(hass, entry)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
@ -77,8 +86,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.gateway.async_disconnect() await coordinator.gateway.async_disconnect()
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
async_unload_screenlogic_services(hass)
return unload_ok return unload_ok

View file

@ -20,6 +20,8 @@ DOMAIN = "screenlogic"
DEFAULT_SCAN_INTERVAL = 30 DEFAULT_SCAN_INTERVAL = 30
MIN_SCAN_INTERVAL = 10 MIN_SCAN_INTERVAL = 10
ATTR_CONFIG_ENTRY = "config_entry"
SERVICE_SET_COLOR_MODE = "set_color_mode" SERVICE_SET_COLOR_MODE = "set_color_mode"
ATTR_COLOR_MODE = "color_mode" ATTR_COLOR_MODE = "color_mode"
SUPPORTED_COLOR_MODES = {slugify(cm.name): cm.value for cm in 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 from screenlogicpy.device_const.system import EQUIPMENT_FLAG
import voluptuous as vol 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.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
import homeassistant.helpers.config_validation as cv from homeassistant.helpers import (
config_validation as cv,
issue_registry as ir,
selector,
)
from homeassistant.helpers.service import async_extract_config_entry_ids from homeassistant.helpers.service import async_extract_config_entry_ids
from .const import ( from .const import (
ATTR_COLOR_MODE, ATTR_COLOR_MODE,
ATTR_CONFIG_ENTRY,
ATTR_RUNTIME, ATTR_RUNTIME,
DOMAIN, DOMAIN,
MAX_RUNTIME, MAX_RUNTIME,
@ -27,44 +32,103 @@ from .coordinator import ScreenlogicDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _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_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.Clamp( vol.Optional(ATTR_RUNTIME, default=24): vol.All(
min=MIN_RUNTIME, max=MAX_RUNTIME vol.Coerce(int), vol.Clamp(min=MIN_RUNTIME, max=MAX_RUNTIME)
), ),
} }
) )
@callback @callback
def async_load_screenlogic_services(hass: HomeAssistant, entry: ConfigEntry): def async_load_screenlogic_services(hass: HomeAssistant):
"""Set up services for the ScreenLogic integration.""" """Set up services for the ScreenLogic integration."""
async def extract_screenlogic_config_entry_ids(service_call: ServiceCall): async def extract_screenlogic_config_entry_ids(service_call: ServiceCall):
if not ( if not (
screenlogic_entry_ids := [ screenlogic_entry_ids := await async_extract_config_entry_ids(
entry_id hass, service_call
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
]
): ):
raise HomeAssistantError( raise ServiceValidationError(
f"Failed to call service '{service_call.service}'. Config entry for" f"Failed to call service '{service_call.service}'. Config entry for "
" target not found" "target not found"
) )
return screenlogic_entry_ids 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: async def async_set_color_mode(service_call: ServiceCall) -> None:
color_num = SUPPORTED_COLOR_MODES[service_call.data[ATTR_COLOR_MODE]] 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
coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][entry_id] for coordinator in await get_coordinators(service_call):
_LOGGER.debug( _LOGGER.debug(
"Service %s called on %s with mode %s", "Service %s called on %s with mode %s",
SERVICE_SET_COLOR_MODE, SERVICE_SET_COLOR_MODE,
@ -83,13 +147,19 @@ def async_load_screenlogic_services(hass: HomeAssistant, entry: ConfigEntry):
is_on: bool, is_on: bool,
runtime: int | None = None, runtime: int | None = None,
) -> None: ) -> None:
for entry_id in await extract_screenlogic_config_entry_ids(service_call): coordinator: ScreenlogicDataUpdateCoordinator
coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][entry_id] 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( _LOGGER.debug(
"Service %s called on %s with runtime %s", "Service %s called on %s%s",
SERVICE_START_SUPER_CHLORINATION, service_call.service,
coordinator.gateway.name, coordinator.gateway.name,
runtime, rt_log,
) )
try: try:
await coordinator.gateway.async_set_scg_config( await coordinator.gateway.async_set_scg_config(
@ -107,43 +177,20 @@ def async_load_screenlogic_services(hass: HomeAssistant, entry: ConfigEntry):
async def async_stop_super_chlor(service_call: ServiceCall) -> None: async def async_stop_super_chlor(service_call: ServiceCall) -> None:
await async_set_super_chlor(service_call, False) await async_set_super_chlor(service_call, False)
if not hass.services.has_service(DOMAIN, SERVICE_SET_COLOR_MODE): hass.services.async_register(
hass.services.async_register( DOMAIN, SERVICE_SET_COLOR_MODE, async_set_color_mode, SET_COLOR_MODE_SCHEMA
DOMAIN, SERVICE_SET_COLOR_MODE, async_set_color_mode, SET_COLOR_MODE_SCHEMA )
)
coordinator: ScreenlogicDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] hass.services.async_register(
equipment_flags = coordinator.gateway.equipment_flags DOMAIN,
SERVICE_START_SUPER_CHLORINATION,
async_start_super_chlor,
TURN_ON_SUPER_CHLOR_SCHEMA,
)
if EQUIPMENT_FLAG.CHLORINATOR in equipment_flags: hass.services.async_register(
if not hass.services.has_service(DOMAIN, SERVICE_START_SUPER_CHLORINATION): DOMAIN,
hass.services.async_register( SERVICE_STOP_SUPER_CHLORINATION,
DOMAIN, async_stop_super_chlor,
SERVICE_START_SUPER_CHLORINATION, BASE_SERVICE_SCHEMA,
async_start_super_chlor, )
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,
)
@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 # ScreenLogic Services
set_color_mode: set_color_mode:
target:
device:
integration: screenlogic
fields: fields:
config_entry:
required: false
selector:
config_entry:
integration: screenlogic
color_mode: color_mode:
required: true required: true
selector: selector:
@ -32,10 +34,12 @@ set_color_mode:
- thumper - thumper
- white - white
start_super_chlorination: start_super_chlorination:
target:
device:
integration: screenlogic
fields: fields:
config_entry:
required: true
selector:
config_entry:
integration: screenlogic
runtime: runtime:
default: 24 default: 24
selector: selector:
@ -45,6 +49,9 @@ start_super_chlorination:
unit_of_measurement: hours unit_of_measurement: hours
mode: slider mode: slider
stop_super_chlorination: stop_super_chlorination:
target: fields:
device: config_entry:
integration: screenlogic required: true
selector:
config_entry:
integration: screenlogic

View file

@ -41,6 +41,10 @@
"name": "Set Color Mode", "name": "Set Color Mode",
"description": "Sets the color mode for all color-capable lights attached to this ScreenLogic gateway.", "description": "Sets the color mode for all color-capable lights attached to this ScreenLogic gateway.",
"fields": { "fields": {
"config_entry": {
"name": "Config Entry",
"description": "The config entry to use for this service."
},
"color_mode": { "color_mode": {
"name": "Color Mode", "name": "Color Mode",
"description": "The ScreenLogic color mode to set." "description": "The ScreenLogic color mode to set."
@ -51,6 +55,10 @@
"name": "Start Super Chlorination", "name": "Start Super Chlorination",
"description": "Begins super chlorination, running for the specified period or 24 hours if none is specified.", "description": "Begins super chlorination, running for the specified period or 24 hours if none is specified.",
"fields": { "fields": {
"config_entry": {
"name": "Config Entry",
"description": "The config entry to use for this service."
},
"runtime": { "runtime": {
"name": "Run Time", "name": "Run Time",
"description": "Number of hours for super chlorination to run." "description": "Number of hours for super chlorination to run."
@ -59,7 +67,26 @@
}, },
"stop_super_chlorination": { "stop_super_chlorination": {
"name": "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."
}
}
}
} }
} }
} }