Skip to content

Commit

Permalink
add custom services : start/stop watering, enable/disable controller
Browse files Browse the repository at this point in the history
  • Loading branch information
kcofoni committed May 14, 2023
1 parent 292e4c6 commit c79bf1c
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 38 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,12 @@ Options may be changed related to polling refresh interval of sensors and contro
**IMPORTANT: to be effective, each time options have been changed the related device must be reloaded.**

## Running
No dedicated card has been implemented yet but perhaps there will be user contributions in this direction. In the meantime can be displayed in a very classical layout as follows:

![start watering](images/running.png "Dashboard")

The Netro Watering entities may be integrated into automations. The following integration custom services are available:
- **start watering** and **stop watering** services - to be applied to any controller or zone
- **enable** and **disable** services - to be applied to any controller

![call service](images/service_call.png "Developer Tools")
4 changes: 4 additions & 0 deletions custom_components/netro_watering/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@
CONF_API_URL = "netro_api_url"
CONF_DELAY_BEFORE_REFRESH = "delay_before_refresh"
CONF_DEFAULT_WATERING_DELAY = "default_watering_delay"

ATTR_WATERING_DURATION = "duration"
ATTR_WATERING_DELAY = "delay"
ATTR_WATERING_START_TIME = "start_time"
20 changes: 16 additions & 4 deletions custom_components/netro_watering/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,19 @@ def __init__(
self.serial_number = serial_number + "_" + str(ith) # virtual serial number
self.parent_controller = controller

async def start_watering(self, duration: int, delay: int) -> None:
async def start_watering(
self, duration: int, delay: int, start_time: datetime.time
) -> None:
"""Start watering for the current zone for given duration in minutes."""
await self.parent_controller.hass.async_add_executor_job(
netro_water,
self.parent_controller.serial_number,
duration,
[str(self.ith)],
delay,
start_time.strftime("%Y-%m-%d %H:%M")
if start_time is not None
else None,
)

async def stop_watering(self) -> None:
Expand Down Expand Up @@ -634,10 +639,17 @@ async def disable(self):
)
return res

async def start_watering(self, duration: int, delay: int) -> None:
async def start_watering(
self, duration: int, delay: int, start_time: datetime.time
) -> None:
"""Start watering for the current zone for given duration in minutes."""
await self.hass.async_add_executor_job(
netro_water, self.serial_number, duration, None, delay
netro_water,
self.serial_number,
duration,
None,
delay,
start_time.strftime("%Y-%m-%d %H:%M") if start_time is not None else None,
)

async def stop_watering(self) -> None:
Expand All @@ -646,4 +658,4 @@ async def stop_watering(self) -> None:

def __str__(self) -> str:
"""Convert to string, for logging in particular."""
return f'sensor coordinator "{self.name}" ({NETRO_PIXIE_CONTROLLER_MODEL if hasattr(self, NETRO_CONTROLLER_BATTERY_LEVEL) else NETRO_SPRITE_CONTROLLER_MODEL})'
return f'controller coordinator "{self.name}" ({NETRO_PIXIE_CONTROLLER_MODEL if hasattr(self, NETRO_CONTROLLER_BATTERY_LEVEL) else NETRO_SPRITE_CONTROLLER_MODEL})'
2 changes: 1 addition & 1 deletion custom_components/netro_watering/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"requirements": ["requests==2.28.2", "validators==0.20.0"],
"ssdp": [],
"zeroconf": [],
"version": "0.9.2"
"version": "0.9.3"
}
42 changes: 39 additions & 3 deletions custom_components/netro_watering/services.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,53 @@
start_watering:
name: Start watering
description: Start the watering for a given duration and a delay before starting
description: Start watering for a specified duration, delay before start, or start time and date
target:
entity:
integration: netro-watering
integration: netro_watering
domain: switch
fields:
duration:
name: Duration
description: Duration for this sprinkler to be turned on
description: Duration of activation of this sprinkler
required: true
selector:
number:
min: 1
max: 90
unit_of_measurement: "minutes"
delay:
name: Delay
description: Waiting time before starting watering
required: false
selector:
number:
min: 0
max: 90
unit_of_measurement: "minutes"
start_time:
name: Start time
description: Watering start date and time
required: false
selector:
datetime:
stop_watering:
name: Stop watering
description: Stop watering all zones at once or a specific zone
target:
entity:
integration: netro_watering
domain: switch
enable:
name: Enable
description: Enable the selected controller
target:
entity:
integration: netro_watering
domain: switch
disable:
name: Disable
description: Disable the selected controller
target:
entity:
integration: netro_watering
domain: switch
135 changes: 111 additions & 24 deletions custom_components/netro_watering/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import asyncio
from dataclasses import dataclass
from enum import IntFlag
import logging
from typing import Any

Expand All @@ -15,11 +16,15 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
import homeassistant.util.dt as dt_util

from .const import (
ATTR_WATERING_DELAY,
ATTR_WATERING_DURATION,
ATTR_WATERING_START_TIME,
CONF_DEFAULT_WATERING_DELAY,
CONF_DELAY_BEFORE_REFRESH,
CONF_DEVICE_TYPE,
Expand All @@ -38,8 +43,22 @@
# mypy: disable-error-code="arg-type"

SERVICE_START_WATERING = "start_watering"
SERVICE_STOP_WATERING = "stop_watering"
SERVICE_ENABLE = "enable"
SERVICE_DISABLE = "disable"

SERVICE_SCHEMA_WATERING = {vol.Required("duration"): cv.positive_int}
SERVICE_SCHEMA_WATERING = {
vol.Required(ATTR_WATERING_DURATION): cv.positive_int,
vol.Optional(ATTR_WATERING_DELAY): cv.positive_int,
vol.Optional(ATTR_WATERING_START_TIME): cv.datetime,
}


class NetroSwitchEntityFeature(IntFlag):
"""Supported features of the Netro Watering switch."""

ENABLE_DISABLE = 1
START_STOP_WATERING = 2


@dataclass
Expand All @@ -63,6 +82,7 @@ class NetroSwitchEntityDescription(SwitchEntityDescription, NetroRequiredKeysMix
translation_key="watering",
netro_on_name="start_watering",
netro_off_name="stop_watering",
icon="mdi:sprinkler",
)

# description of the start/stop watering switch
Expand Down Expand Up @@ -111,6 +131,8 @@ async def async_setup_entry(
CONF_DELAY_BEFORE_REFRESH
]

_LOGGER.info("Adding switch entities")

# enable/disable controller switch
async_add_entities(
[
Expand Down Expand Up @@ -149,13 +171,39 @@ async def async_setup_entry(
]
)

platform = entity_platform.async_get_current_platform()

_LOGGER.info("Adding custom service : %s", SERVICE_START_WATERING)
platform.async_register_entity_service(
SERVICE_START_WATERING,
SERVICE_SCHEMA_WATERING,
"async_turn_on",
[NetroSwitchEntityFeature.START_STOP_WATERING],
)

_LOGGER.info("Adding custom service : %s", SERVICE_STOP_WATERING)
platform.async_register_entity_service(
SERVICE_STOP_WATERING,
{},
"async_turn_off",
[NetroSwitchEntityFeature.START_STOP_WATERING],
)

_LOGGER.info("Adding custom service : %s", SERVICE_ENABLE)
platform.async_register_entity_service(
SERVICE_ENABLE,
{},
"async_turn_on",
[NetroSwitchEntityFeature.ENABLE_DISABLE],
)

# platform = entity_platform.async_get_current_platform()
# platform.async_register_entity_service(
# SERVICE_ENABLE_CONTROLLER,
# {},
# "async_turn_on",
# )
_LOGGER.info("Adding custom service : %s", SERVICE_DISABLE)
platform.async_register_entity_service(
SERVICE_DISABLE,
{},
"async_turn_off",
[NetroSwitchEntityFeature.ENABLE_DISABLE],
)


class ControllerEnablingSwitch(
Expand All @@ -165,6 +213,7 @@ class ControllerEnablingSwitch(

_attr_has_entity_name = True
_attr_assumed_state = False
_attr_supported_features = NetroSwitchEntityFeature.ENABLE_DISABLE

def __init__(
self,
Expand Down Expand Up @@ -201,6 +250,7 @@ class ZoneWateringSwitch(

_attr_has_entity_name = True
_attr_assumed_state = True
_attr_supported_features = NetroSwitchEntityFeature.START_STOP_WATERING
entity_description: NetroSwitchEntityDescription

def __init__(
Expand All @@ -225,25 +275,46 @@ def __init__(
)
self._attr_device_info = coordinator.active_zones[zone_id].device_info

@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return state attributes."""
return {"zone": self._zone_id}

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
start_time = kwargs.get(ATTR_WATERING_START_TIME, None)
await getattr(
self.coordinator.active_zones[self._zone_id],
self.entity_description.netro_on_name,
)(self._duration_minutes, self._delay_minutes)
if self._delay_minutes == 0:
)(
int(duration := kwargs.get(ATTR_WATERING_DURATION, self._duration_minutes)),
int(delay := kwargs.get(ATTR_WATERING_DELAY, self._delay_minutes)),
dt_util.as_utc(start_time) if start_time is not None else None,
)
if delay == 0 and start_time is None:
_LOGGER.info(
'Watering of zone "%s" has been started right now for %s minutes',
self.coordinator.active_zones[self._zone_id].name,
self._duration_minutes,
)
else:
_LOGGER.info(
"Watering of zone %s will start in %s minutes and will last %s minutes",
self.coordinator.active_zones[self._zone_id].name,
self._delay_minutes,
self._duration_minutes,
)
if (
start_time is not None
): # start_time is higher priority than delay if both provided
_LOGGER.info(
"Watering of zone %s will start on %s and will last %s minutes",
self.coordinator.active_zones[self._zone_id].name,
start_time,
duration,
)
else: # delay is necessarily not 0
_LOGGER.info(
"Watering of zone %s will start in %s minutes and will last %s minutes",
self.coordinator.active_zones[self._zone_id].name,
delay,
duration,
)

_LOGGER.info(
"Waiting for %s seconds before refreshing info (time it takes for Netro to return the status)",
self._before_refresh_seconds,
Expand Down Expand Up @@ -278,6 +349,7 @@ class ControllerWateringSwitch(

_attr_has_entity_name = True
_attr_assumed_state = True
_attr_supported_features = NetroSwitchEntityFeature.START_STOP_WATERING
entity_description: NetroSwitchEntityDescription

def __init__(
Expand All @@ -300,21 +372,36 @@ def __init__(

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
start_time = kwargs.get(ATTR_WATERING_START_TIME, None)
await getattr(
self.coordinator,
self.entity_description.netro_on_name,
)(self._duration_minutes, self._delay_minutes)
if self._delay_minutes == 0:
)(
int(duration := kwargs.get(ATTR_WATERING_DURATION, self._duration_minutes)),
int(delay := kwargs.get(ATTR_WATERING_DELAY, self._delay_minutes)),
dt_util.as_utc(start_time) if start_time is not None else None,
)
if delay == 0 and start_time is None:
_LOGGER.info(
"Watering of all zones has been started right now for %s minutes for each zone",
self._duration_minutes,
duration,
)
else:
_LOGGER.info(
"Watering of all zones will start in %s minutes and will last %s minutes for each zone",
self._delay_minutes,
self._duration_minutes,
)
if (
start_time is not None
): # start_time is higher priority than delay if both provided
_LOGGER.info(
"Watering of all zones will start on %s and will last %s minutes for each zone",
start_time,
duration,
)
else: # delay is necessarily not 0
_LOGGER.info(
"Watering of all zones will start in %s minutes and will last %s minutes for each zone",
delay,
duration,
)

_LOGGER.info(
"Waiting for %s seconds before refreshing info (time it takes for Netro to return the status)",
self._before_refresh_seconds,
Expand Down
Loading

0 comments on commit c79bf1c

Please sign in to comment.