Reorganize RainMachine services (#57145)

* Reorganize RainMachine services

* Code review

* Ensure integration services aren't tied to a particular config entry

* Cleanup

* linting

* Code review

* Code review

* Code review

* Code review
pull/57349/head
Aaron Bach 2021-10-08 12:03:47 -06:00 committed by GitHub
parent eba1d7d16a
commit 0364405595
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 191 additions and 188 deletions

View File

@ -9,16 +9,18 @@ from typing import Any
from regenmaschine import Client
from regenmaschine.controller import Controller
from regenmaschine.errors import RainMachineError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ATTRIBUTION,
CONF_DEVICE_ID,
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv
import homeassistant.helpers.device_registry as dr
@ -44,6 +46,8 @@ from .const import (
LOGGER,
)
CONF_SECONDS = "seconds"
DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC"
DEFAULT_ICON = "mdi:water"
DEFAULT_SSL = True
@ -53,6 +57,39 @@ CONFIG_SCHEMA = cv.deprecated(DOMAIN)
PLATFORMS = ["binary_sensor", "sensor", "switch"]
SERVICE_NAME_PAUSE_WATERING = "pause_watering"
SERVICE_NAME_STOP_ALL = "stop_all"
SERVICE_NAME_UNPAUSE_WATERING = "unpause_watering"
SERVICE_SCHEMA = vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
}
)
SERVICE_PAUSE_WATERING_SCHEMA = SERVICE_SCHEMA.extend(
{
vol.Required(CONF_SECONDS): cv.positive_int,
}
)
@callback
def async_get_controller_for_service_call(
hass: HomeAssistant, call: ServiceCall
) -> Controller:
"""Get the controller related to a service call (by device ID)."""
controllers: dict[str, Controller] = hass.data[DOMAIN][DATA_CONTROLLER]
device_id = call.data[CONF_DEVICE_ID]
device_registry = dr.async_get(hass)
if device_entry := device_registry.async_get(device_id):
for entry_id in device_entry.config_entries:
if entry_id in controllers:
return controllers[entry_id]
raise ValueError(f"No controller for device ID: {device_id}")
async def async_update_programs_and_zones(
hass: HomeAssistant, entry: ConfigEntry
@ -158,6 +195,37 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
async def async_pause_watering(call: ServiceCall) -> None:
"""Pause watering for a set number of seconds."""
controller = async_get_controller_for_service_call(hass, call)
await controller.watering.pause_all(call.data[CONF_SECONDS])
await async_update_programs_and_zones(hass, entry)
async def async_stop_all(call: ServiceCall) -> None:
"""Stop all watering."""
controller = async_get_controller_for_service_call(hass, call)
await controller.watering.stop_all()
await async_update_programs_and_zones(hass, entry)
async def async_unpause_watering(call: ServiceCall) -> None:
"""Unpause watering."""
controller = async_get_controller_for_service_call(hass, call)
await controller.watering.unpause_all()
await async_update_programs_and_zones(hass, entry)
for service_name, schema, method in (
(
SERVICE_NAME_PAUSE_WATERING,
SERVICE_PAUSE_WATERING_SCHEMA,
async_pause_watering,
),
(SERVICE_NAME_STOP_ALL, SERVICE_SCHEMA, async_stop_all),
(SERVICE_NAME_UNPAUSE_WATERING, SERVICE_SCHEMA, async_unpause_watering),
):
if hass.services.has_service(DOMAIN, service_name):
continue
hass.services.async_register(DOMAIN, service_name, method, schema=schema)
return True
@ -166,6 +234,17 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN][DATA_COORDINATOR].pop(entry.entry_id)
if len(hass.config_entries.async_entries(DOMAIN)) == 1:
# If this is the last instance of RainMachine, deregister any services defined
# during integration setup:
for service_name in (
SERVICE_NAME_PAUSE_WATERING,
SERVICE_NAME_STOP_ALL,
SERVICE_NAME_UNPAUSE_WATERING,
):
hass.services.async_remove(DOMAIN, service_name)
return unload_ok

View File

@ -1,79 +1,46 @@
# Describes the format for available RainMachine services
disable_program:
name: Disable program
description: Disable a program.
name: Disable Program
description: Disable a program
target:
entity:
integration: rainmachine
domain: switch
fields:
program_id:
name: Program ID
description: The program to disable.
required: true
selector:
number:
min: 1
max: 255
disable_zone:
name: Disable zone
description: Disable a zone.
name: Disable Zone
description: Disable a zone
target:
entity:
integration: rainmachine
domain: switch
fields:
zone_id:
name: Zone ID
description: The zone to disable.
required: true
selector:
number:
min: 1
max: 255
enable_program:
name: Enable program
description: Enable a program.
name: Enable Program
description: Enable a program
target:
entity:
integration: rainmachine
domain: switch
fields:
program_id:
name: Program ID
description: The program to enable.
required: true
selector:
number:
min: 1
max: 255
enable_zone:
name: Enable zone
description: Enable a zone.
name: Enable Zone
description: Enable a zone
target:
entity:
integration: rainmachine
domain: switch
pause_watering:
name: Pause All Watering
description: Pause all watering activities for a number of seconds
fields:
zone_id:
name: Zone ID
description: The zone to enable.
device_id:
name: Controller
description: The controller whose watering activities should be paused
required: true
selector:
number:
min: 1
max: 255
pause_watering:
name: Pause watering
description: Pause all watering for a number of seconds.
target:
entity:
integration: rainmachine
domain: switch
fields:
device:
integration: rainmachine
seconds:
name: Seconds
description: The time to pause.
name: Duration
description: The amount of time (in seconds) to pause watering
required: true
selector:
number:
@ -81,40 +48,23 @@ pause_watering:
max: 86400
unit_of_measurement: seconds
start_program:
name: Start program
description: Start a program.
name: Start Program
description: Start a program
target:
entity:
integration: rainmachine
domain: switch
fields:
program_id:
name: Program ID
description: The program to start.
required: true
selector:
number:
min: 1
max: 255
start_zone:
name: Start zone
description: Start a zone for a set number of seconds.
name: Start Zone
description: Start a zone
target:
entity:
integration: rainmachine
domain: switch
fields:
zone_id:
name: Zone ID
description: The zone to start.
required: true
selector:
number:
min: 1
max: 255
zone_run_time:
name: Zone run time
description: The number of seconds to run the zone.
name: Run Time
description: The amount of time (in seconds) to run the zone
default: 600
selector:
number:
@ -122,48 +72,38 @@ start_zone:
max: 86400
mode: box
stop_all:
name: Stop all
description: Stop all watering activities.
target:
entity:
integration: rainmachine
domain: switch
name: Stop All Watering
description: Stop all watering activities
fields:
device_id:
name: Controller
description: The controller whose watering activities should be stopped
required: true
selector:
device:
integration: rainmachine
stop_program:
name: Stop program
description: Stop a program.
name: Stop Program
description: Stop a program
target:
entity:
integration: rainmachine
domain: switch
fields:
program_id:
name: Program ID
description: The program to stop.
required: true
selector:
number:
min: 1
max: 255
stop_zone:
name: Stop zone
description: Stop a zone.
name: Stop Zone
description: Stop a zone
target:
entity:
integration: rainmachine
domain: switch
unpause_watering:
name: Unpause All Watering
description: Unpause all paused watering activities
fields:
zone_id:
name: Zone ID
description: The zone to stop.
device_id:
name: Controller
description: The controller whose watering activities should be unpaused
required: true
selector:
number:
min: 1
max: 255
unpause_watering:
name: Unpause watering
description: Unpause all watering.
target:
entity:
integration: rainmachine
domain: switch
device:
integration: rainmachine

View File

@ -51,10 +51,6 @@ ATTR_TIME_REMAINING = "time_remaining"
ATTR_VEGETATION_TYPE = "vegetation_type"
ATTR_ZONES = "zones"
CONF_PROGRAM_ID = "program_id"
CONF_SECONDS = "seconds"
CONF_ZONE_ID = "zone_id"
DEFAULT_ICON = "mdi:water"
DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
@ -112,9 +108,6 @@ VEGETATION_MAP = {
99: "Other",
}
SWITCH_TYPE_PROGRAM = "program"
SWITCH_TYPE_ZONE = "zone"
@dataclass
class RainMachineSwitchDescriptionMixin:
@ -136,42 +129,23 @@ async def async_setup_entry(
"""Set up RainMachine switches based on a config entry."""
platform = entity_platform.async_get_current_platform()
alter_program_schema = {vol.Required(CONF_PROGRAM_ID): cv.positive_int}
alter_zone_schema = {vol.Required(CONF_ZONE_ID): cv.positive_int}
for service_name, schema, method in (
("disable_program", alter_program_schema, "async_disable_program"),
("disable_zone", alter_zone_schema, "async_disable_zone"),
("enable_program", alter_program_schema, "async_enable_program"),
("enable_zone", alter_zone_schema, "async_enable_zone"),
(
"pause_watering",
{vol.Required(CONF_SECONDS): cv.positive_int},
"async_pause_watering",
),
(
"start_program",
{vol.Required(CONF_PROGRAM_ID): cv.positive_int},
"async_start_program",
),
("disable_program", {}, "async_disable_program"),
("disable_zone", {}, "async_disable_zone"),
("enable_program", {}, "async_enable_program"),
("enable_zone", {}, "async_enable_zone"),
("start_program", {}, "async_start_program"),
(
"start_zone",
{
vol.Required(CONF_ZONE_ID): cv.positive_int,
vol.Optional(
CONF_ZONE_RUN_TIME, default=DEFAULT_ZONE_RUN
): cv.positive_int,
): cv.positive_int
},
"async_start_zone",
),
("stop_all", {}, "async_stop_all"),
(
"stop_program",
{vol.Required(CONF_PROGRAM_ID): cv.positive_int},
"async_stop_program",
),
("stop_zone", {vol.Required(CONF_ZONE_ID): cv.positive_int}, "async_stop_zone"),
("unpause_watering", {}, "async_unpause_watering"),
("stop_program", {}, "async_stop_program"),
("stop_zone", {}, "async_stop_zone"),
):
platform.async_register_entity_service(service_name, schema, method)
@ -187,9 +161,7 @@ async def async_setup_entry(
controller,
entry,
RainMachineSwitchDescription(
key=f"RainMachineProgram_{uid}",
name=program["name"],
uid=uid,
key=f"RainMachineProgram_{uid}", name=program["name"], uid=uid
),
)
for uid, program in programs_coordinator.data.items()
@ -201,9 +173,7 @@ async def async_setup_entry(
controller,
entry,
RainMachineSwitchDescription(
key=f"RainMachineZone_{uid}",
name=zone["name"],
uid=uid,
key=f"RainMachineZone_{uid}", name=zone["name"], uid=uid
),
)
for uid, zone in zones_coordinator.data.items()
@ -267,60 +237,37 @@ class RainMachineSwitch(RainMachineEntity, SwitchEntity):
async_update_programs_and_zones(self.hass, self._entry)
)
async def async_disable_program(self, *, program_id: int) -> None:
async def async_disable_program(self) -> None:
"""Disable a program."""
await self._controller.programs.disable(program_id)
await async_update_programs_and_zones(self.hass, self._entry)
raise NotImplementedError("Service not implemented for this entity")
async def async_disable_zone(self, *, zone_id: int) -> None:
async def async_disable_zone(self) -> None:
"""Disable a zone."""
await self._controller.zones.disable(zone_id)
await async_update_programs_and_zones(self.hass, self._entry)
raise NotImplementedError("Service not implemented for this entity")
async def async_enable_program(self, *, program_id: int) -> None:
async def async_enable_program(self) -> None:
"""Enable a program."""
await self._controller.programs.enable(program_id)
await async_update_programs_and_zones(self.hass, self._entry)
raise NotImplementedError("Service not implemented for this entity")
async def async_enable_zone(self, *, zone_id: int) -> None:
async def async_enable_zone(self) -> None:
"""Enable a zone."""
await self._controller.zones.enable(zone_id)
await async_update_programs_and_zones(self.hass, self._entry)
raise NotImplementedError("Service not implemented for this entity")
async def async_pause_watering(self, *, seconds: int) -> None:
"""Pause watering for a set number of seconds."""
await self._controller.watering.pause_all(seconds)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_start_program(self) -> None:
"""Start a program."""
raise NotImplementedError("Service not implemented for this entity")
async def async_start_program(self, *, program_id: int) -> None:
"""Start a particular program."""
await self._controller.programs.start(program_id)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_start_zone(self, *, zone_run_time: int) -> None:
"""Start a zone."""
raise NotImplementedError("Service not implemented for this entity")
async def async_start_zone(self, *, zone_id: int, zone_run_time: int) -> None:
"""Start a particular zone for a certain amount of time."""
await self._controller.zones.start(zone_id, zone_run_time)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_stop_all(self) -> None:
"""Stop all watering."""
await self._controller.watering.stop_all()
await async_update_programs_and_zones(self.hass, self._entry)
async def async_stop_program(self, *, program_id: int) -> None:
async def async_stop_program(self) -> None:
"""Stop a program."""
await self._controller.programs.stop(program_id)
await async_update_programs_and_zones(self.hass, self._entry)
raise NotImplementedError("Service not implemented for this entity")
async def async_stop_zone(self, *, zone_id: int) -> None:
async def async_stop_zone(self) -> None:
"""Stop a zone."""
await self._controller.zones.stop(zone_id)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_unpause_watering(self) -> None:
"""Unpause watering."""
await self._controller.watering.unpause_all()
await async_update_programs_and_zones(self.hass, self._entry)
raise NotImplementedError("Service not implemented for this entity")
@callback
def update_from_latest_data(self) -> None:
@ -337,6 +284,24 @@ class RainMachineProgram(RainMachineSwitch):
"""Return a list of active zones associated with this program."""
return [z for z in self._data["wateringTimes"] if z["active"]]
async def async_disable_program(self) -> None:
"""Disable a program."""
await self._controller.programs.disable(self.entity_description.uid)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_enable_program(self) -> None:
"""Enable a program."""
await self._controller.programs.enable(self.entity_description.uid)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_start_program(self) -> None:
"""Start a program."""
await self.async_turn_on()
async def async_stop_program(self) -> None:
"""Stop a program."""
await self.async_turn_off()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the program off."""
await self._async_run_switch_coroutine(
@ -381,6 +346,25 @@ class RainMachineProgram(RainMachineSwitch):
class RainMachineZone(RainMachineSwitch):
"""A RainMachine zone."""
async def async_disable_zone(self) -> None:
"""Disable a zone."""
await self._controller.zones.disable(self.entity_description.uid)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_enable_zone(self) -> None:
"""Enable a zone."""
await self._controller.zones.enable(self.entity_description.uid)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_start_zone(self, *, zone_run_time: int) -> None:
"""Start a particular zone for a certain amount of time."""
await self._controller.zones.start(self.entity_description.uid, zone_run_time)
await async_update_programs_and_zones(self.hass, self._entry)
async def async_stop_zone(self) -> None:
"""Stop a zone."""
await self.async_turn_off()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off."""
await self._async_run_switch_coroutine(