Reorganize Guardian services (#58632)

This commit is contained in:
Aaron Bach 2021-12-02 12:47:15 -07:00 committed by GitHub
parent 9aa0994809
commit 3f2519bedf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 232 additions and 164 deletions

View File

@ -2,14 +2,23 @@
from __future__ import annotations
import asyncio
from collections.abc import Awaitable
from collections.abc import Awaitable, Callable
from typing import cast
from aioguardian import Client
from aioguardian.errors import GuardianError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_FILENAME,
CONF_IP_ADDRESS,
CONF_PORT,
CONF_URL,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
from homeassistant.helpers.update_coordinator import (
@ -28,16 +37,66 @@ from .const import (
DATA_CLIENT,
DATA_COORDINATOR,
DATA_COORDINATOR_PAIRED_SENSOR,
DATA_PAIRED_SENSOR_MANAGER,
DOMAIN,
LOGGER,
SIGNAL_PAIRED_SENSOR_COORDINATOR_ADDED,
)
from .util import GuardianDataUpdateCoordinator
DATA_PAIRED_SENSOR_MANAGER = "paired_sensor_manager"
SERVICE_NAME_DISABLE_AP = "disable_ap"
SERVICE_NAME_ENABLE_AP = "enable_ap"
SERVICE_NAME_PAIR_SENSOR = "pair_sensor"
SERVICE_NAME_REBOOT = "reboot"
SERVICE_NAME_RESET_VALVE_DIAGNOSTICS = "reset_valve_diagnostics"
SERVICE_NAME_UNPAIR_SENSOR = "unpair_sensor"
SERVICE_NAME_UPGRADE_FIRMWARE = "upgrade_firmware"
SERVICES = (
SERVICE_NAME_DISABLE_AP,
SERVICE_NAME_ENABLE_AP,
SERVICE_NAME_PAIR_SENSOR,
SERVICE_NAME_REBOOT,
SERVICE_NAME_RESET_VALVE_DIAGNOSTICS,
SERVICE_NAME_UNPAIR_SENSOR,
SERVICE_NAME_UPGRADE_FIRMWARE,
)
SERVICE_PAIR_UNPAIR_SENSOR_SCHEMA = vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(CONF_UID): cv.string,
}
)
SERVICE_UPGRADE_FIRMWARE_SCHEMA = vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Optional(CONF_URL): cv.url,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_FILENAME): cv.string,
},
)
PLATFORMS = ["binary_sensor", "sensor", "switch"]
@callback
def async_get_entry_id_for_service_call(hass: HomeAssistant, call: ServiceCall) -> str:
"""Get the entry ID related to a service call (by device ID)."""
device_id = call.data[CONF_DEVICE_ID]
device_registry = dr.async_get(hass)
if device_entry := device_registry.async_get(device_id):
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.entry_id in device_entry.config_entries:
return entry.entry_id
raise ValueError(f"No client for device ID: {device_id}")
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Elexa Guardian from a config entry."""
client = Client(entry.data[CONF_IP_ADDRESS], port=entry.data[CONF_PORT])
@ -95,6 +154,97 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Set up all of the Guardian entity platforms:
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
@callback
def extract_client(func: Callable) -> Callable:
"""Define a decorator to get the correct client for a service call."""
async def wrapper(call: ServiceCall) -> None:
"""Wrap the service function."""
entry_id = async_get_entry_id_for_service_call(hass, call)
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
try:
async with client:
await func(call, client)
except GuardianError as err:
LOGGER.error("Error while executing %s: %s", func.__name__, err)
return wrapper
@extract_client
async def async_disable_ap(call: ServiceCall, client: Client) -> None:
"""Disable the onboard AP."""
await client.wifi.disable_ap()
@extract_client
async def async_enable_ap(call: ServiceCall, client: Client) -> None:
"""Enable the onboard AP."""
await client.wifi.enable_ap()
@extract_client
async def async_pair_sensor(call: ServiceCall, client: Client) -> None:
"""Add a new paired sensor."""
entry_id = async_get_entry_id_for_service_call(hass, call)
paired_sensor_manager = hass.data[DOMAIN][entry_id][DATA_PAIRED_SENSOR_MANAGER]
uid = call.data[CONF_UID]
await client.sensor.pair_sensor(uid)
await paired_sensor_manager.async_pair_sensor(uid)
@extract_client
async def async_reboot(call: ServiceCall, client: Client) -> None:
"""Reboot the valve controller."""
await client.system.reboot()
@extract_client
async def async_reset_valve_diagnostics(call: ServiceCall, client: Client) -> None:
"""Fully reset system motor diagnostics."""
await client.valve.reset()
@extract_client
async def async_unpair_sensor(call: ServiceCall, client: Client) -> None:
"""Remove a paired sensor."""
entry_id = async_get_entry_id_for_service_call(hass, call)
paired_sensor_manager = hass.data[DOMAIN][entry_id][DATA_PAIRED_SENSOR_MANAGER]
uid = call.data[CONF_UID]
await client.sensor.unpair_sensor(uid)
await paired_sensor_manager.async_unpair_sensor(uid)
@extract_client
async def async_upgrade_firmware(call: ServiceCall, client: Client) -> None:
"""Upgrade the device firmware."""
await client.system.upgrade_firmware(
url=call.data[CONF_URL],
port=call.data[CONF_PORT],
filename=call.data[CONF_FILENAME],
)
for service_name, schema, method in (
(SERVICE_NAME_DISABLE_AP, None, async_disable_ap),
(SERVICE_NAME_ENABLE_AP, None, async_enable_ap),
(
SERVICE_NAME_PAIR_SENSOR,
SERVICE_PAIR_UNPAIR_SENSOR_SCHEMA,
async_pair_sensor,
),
(SERVICE_NAME_REBOOT, None, async_reboot),
(SERVICE_NAME_RESET_VALVE_DIAGNOSTICS, None, async_reset_valve_diagnostics),
(
SERVICE_NAME_UNPAIR_SENSOR,
SERVICE_PAIR_UNPAIR_SENSOR_SCHEMA,
async_unpair_sensor,
),
(
SERVICE_NAME_UPGRADE_FIRMWARE,
SERVICE_UPGRADE_FIRMWARE_SCHEMA,
async_upgrade_firmware,
),
):
if hass.services.has_service(DOMAIN, service_name):
continue
hass.services.async_register(DOMAIN, service_name, method, schema=schema)
return True
@ -104,6 +254,17 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# If this is the last loaded instance of Guardian, deregister any services
# defined during integration setup:
for service_name in SERVICES:
hass.services.async_remove(DOMAIN, service_name)
return unload_ok

View File

@ -17,6 +17,5 @@ CONF_UID = "uid"
DATA_CLIENT = "client"
DATA_COORDINATOR = "coordinator"
DATA_COORDINATOR_PAIRED_SENSOR = "coordinator_paired_sensor"
DATA_PAIRED_SENSOR_MANAGER = "paired_sensor_manager"
SIGNAL_PAIRED_SENSOR_COORDINATOR_ADDED = "guardian_paired_sensor_coordinator_added_{0}"

View File

@ -2,25 +2,36 @@
disable_ap:
name: Disable AP
description: Disable the device's onboard access point.
target:
entity:
integration: guardian
domain: switch
fields:
device_id:
name: Valve Controller
description: The valve controller whose AP should be disabled
required: true
selector:
device:
integration: guardian
enable_ap:
name: Enable AP
description: Enable the device's onboard access point.
target:
entity:
integration: guardian
domain: switch
pair_sensor:
name: Pair sensor
description: Add a new paired sensor to the valve controller.
target:
entity:
integration: guardian
domain: switch
fields:
device_id:
name: Valve Controller
description: The valve controller whose AP should be enabled
required: true
selector:
device:
integration: guardian
pair_sensor:
name: Pair Sensor
description: Add a new paired sensor to the valve controller.
fields:
device_id:
name: Valve Controller
description: The valve controller to add the sensor to
required: true
selector:
device:
integration: guardian
uid:
name: UID
description: The UID of the paired sensor
@ -31,25 +42,36 @@ pair_sensor:
reboot:
name: Reboot
description: Reboot the device.
target:
entity:
integration: guardian
domain: switch
reset_valve_diagnostics:
name: Reset valve diagnostics
description: Fully (and irrecoverably) reset all valve diagnostics.
target:
entity:
integration: guardian
domain: switch
unpair_sensor:
name: Unpair sensor
description: Remove a paired sensor from the valve controller.
target:
entity:
integration: guardian
domain: switch
fields:
device_id:
name: Valve Controller
description: The valve controller to reboot
required: true
selector:
device:
integration: guardian
reset_valve_diagnostics:
name: Reset Valve Diagnostics
description: Fully (and irrecoverably) reset all valve diagnostics.
fields:
device_id:
name: Valve Controller
description: The valve controller whose diagnostics should be reset
required: true
selector:
device:
integration: guardian
unpair_sensor:
name: Unpair Sensor
description: Remove a paired sensor from the valve controller.
fields:
device_id:
name: Valve Controller
description: The valve controller to remove the sensor from
required: true
selector:
device:
integration: guardian
uid:
name: UID
description: The UID of the paired sensor
@ -60,11 +82,14 @@ unpair_sensor:
upgrade_firmware:
name: Upgrade firmware
description: Upgrade the device firmware.
target:
entity:
integration: guardian
domain: switch
fields:
device_id:
name: Valve Controller
description: The valve controller whose firmware should be upgraded
required: true
selector:
device:
integration: guardian
url:
name: URL
description: The URL of the server hosting the firmware file.
@ -76,7 +101,9 @@ upgrade_firmware:
description: The port on which the firmware file is served.
example: 443
selector:
text:
number:
min: 1
max: 65535
filename:
name: Filename
description: The firmware filename.

View File

@ -5,40 +5,21 @@ from typing import Any
from aioguardian import Client
from aioguardian.errors import GuardianError
import voluptuous as vol
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_FILENAME, CONF_PORT, CONF_URL
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import ValveControllerEntity
from .const import (
API_VALVE_STATUS,
CONF_UID,
DATA_CLIENT,
DATA_COORDINATOR,
DATA_PAIRED_SENSOR_MANAGER,
DOMAIN,
LOGGER,
)
from .const import API_VALVE_STATUS, DATA_CLIENT, DATA_COORDINATOR, DOMAIN, LOGGER
ATTR_AVG_CURRENT = "average_current"
ATTR_INST_CURRENT = "instantaneous_current"
ATTR_INST_CURRENT_DDT = "instantaneous_current_ddt"
ATTR_TRAVEL_COUNT = "travel_count"
SERVICE_DISABLE_AP = "disable_ap"
SERVICE_ENABLE_AP = "enable_ap"
SERVICE_PAIR_SENSOR = "pair_sensor"
SERVICE_REBOOT = "reboot"
SERVICE_RESET_VALVE_DIAGNOSTICS = "reset_valve_diagnostics"
SERVICE_UNPAIR_SENSOR = "unpair_sensor"
SERVICE_UPGRADE_FIRMWARE = "upgrade_firmware"
SWITCH_KIND_VALVE = "valve"
SWITCH_DESCRIPTION_VALVE = SwitchEntityDescription(
@ -52,31 +33,6 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Guardian switches based on a config entry."""
platform = entity_platform.async_get_current_platform()
for service_name, schema, method in (
(SERVICE_DISABLE_AP, {}, "async_disable_ap"),
(SERVICE_ENABLE_AP, {}, "async_enable_ap"),
(SERVICE_PAIR_SENSOR, {vol.Required(CONF_UID): cv.string}, "async_pair_sensor"),
(SERVICE_REBOOT, {}, "async_reboot"),
(SERVICE_RESET_VALVE_DIAGNOSTICS, {}, "async_reset_valve_diagnostics"),
(
SERVICE_UPGRADE_FIRMWARE,
{
vol.Optional(CONF_URL): cv.url,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_FILENAME): cv.string,
},
"async_upgrade_firmware",
),
(
SERVICE_UNPAIR_SENSOR,
{vol.Required(CONF_UID): cv.string},
"async_unpair_sensor",
),
):
platform.async_register_entity_service(service_name, schema, method)
async_add_entities(
[
ValveControllerSwitch(
@ -135,78 +91,6 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity):
}
)
async def async_disable_ap(self) -> None:
"""Disable the device's onboard access point."""
try:
async with self._client:
await self._client.wifi.disable_ap()
except GuardianError as err:
LOGGER.error("Error while disabling valve controller AP: %s", err)
async def async_enable_ap(self) -> None:
"""Enable the device's onboard access point."""
try:
async with self._client:
await self._client.wifi.enable_ap()
except GuardianError as err:
LOGGER.error("Error while enabling valve controller AP: %s", err)
async def async_pair_sensor(self, *, uid: str) -> None:
"""Add a new paired sensor."""
try:
async with self._client:
await self._client.sensor.pair_sensor(uid)
except GuardianError as err:
LOGGER.error("Error while adding paired sensor: %s", err)
return
await self.hass.data[DOMAIN][self._entry.entry_id][
DATA_PAIRED_SENSOR_MANAGER
].async_pair_sensor(uid)
async def async_reboot(self) -> None:
"""Reboot the device."""
try:
async with self._client:
await self._client.system.reboot()
except GuardianError as err:
LOGGER.error("Error while rebooting valve controller: %s", err)
async def async_reset_valve_diagnostics(self) -> None:
"""Fully reset system motor diagnostics."""
try:
async with self._client:
await self._client.valve.reset()
except GuardianError as err:
LOGGER.error("Error while resetting valve diagnostics: %s", err)
async def async_unpair_sensor(self, *, uid: str) -> None:
"""Add a new paired sensor."""
try:
async with self._client:
await self._client.sensor.unpair_sensor(uid)
except GuardianError as err:
LOGGER.error("Error while removing paired sensor: %s", err)
return
await self.hass.data[DOMAIN][self._entry.entry_id][
DATA_PAIRED_SENSOR_MANAGER
].async_unpair_sensor(uid)
async def async_upgrade_firmware(
self, *, url: str, port: int, filename: str
) -> None:
"""Upgrade the device firmware."""
try:
async with self._client:
await self._client.system.upgrade_firmware(
url=url,
port=port,
filename=filename,
)
except GuardianError as err:
LOGGER.error("Error while upgrading firmware: %s", err)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the valve off (closed)."""
try:

View File

@ -15,9 +15,6 @@
"port": "Port"
},
"description": "Configure a local Elexa Guardian device."
},
"zeroconf_confirm": {
"description": "Do you want to set up this Guardian device?"
}
}
}