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 reviewpull/57349/head
parent
eba1d7d16a
commit
0364405595
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue