mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Centralize rainbird config and add binary sensor platform (#26393)
* Update pyrainbird to version 0.2.0 to fix zone number issue: - home-assistant/home-assistant/issues/24519 - jbarrancos/pyrainbird/issues/5 - https://community.home-assistant.io/t/rainbird-zone-switches-5-8-dont-correspond/104705 * requirements_all.txt regenerated * code formatting * pyrainbird version 0.3.0 * zone id * rainsensor return state * updating rainsensor * new version of pyrainbird * binary sensor state * quiet in check format * is_on instead of state for binary_sensor * no unit of measurement for binary sensor * no monitored conditions config * get keys of dict directly * removed redundant update of state * simplified switch * right states for switch * raindelay sensor * raindelay sensor * binary sensor state * binary sensor state * reorganized imports * doc on public method * reformatted * add irrigation service to rain bird, which allows you to set the duration * rebased on konikvranik and solved some feedback * add irrigation service to rain bird * sensor types to constants * synchronized register service * patform discovery * binary sensor as wrapper to sensor * version 0.4.0 * new config approach * sensors cleanup * bypass if no zones found * platform schema removed * Change config schema to list of controllers some small code improvements as suggested in CR: - dictionary acces by [] - just return instead of return False - import order - no optional parameter name * some small code improvements as suggested in CR: - supported platforms in constant - just return instead of return False - removed unused constant * No single controller configuration Co-Authored-By: Martin Hjelmare <marhje52@kth.se> * pyrainbird 0.4.1 * individual switch configuration * imports order * generate default name out of entity * trigger time required for controller * incorporated CR remarks: - constant fo rzones - removed SCAN_INTERVAL - detection of success on initialization - removed underscore - refactored if/else - empty line on end of file - hass as first parameter * import of library on top * refactored * Update homeassistant/components/rainbird/__init__.py Co-Authored-By: Martin Hjelmare <marhje52@kth.se> * validate time and set defaults * set defaults on right place * pylint bypass * iterate over values * codeowner * reverted changes: * irrigation time just as positive integer. Making it complex does make sense * zone edfaults fullfiled at runtime. There is no information about available zones in configuration time. * codeowners updated * accept timedelta in irrigation time * simplified time calculation * call total_seconds * irrigation time as seconds. * simplified schema
This commit is contained in:
parent
82b77c2d29
commit
3efdf29dfa
@ -226,6 +226,7 @@ homeassistant/components/qld_bushfire/* @exxamalte
|
||||
homeassistant/components/qnap/* @colinodell
|
||||
homeassistant/components/quantum_gateway/* @cisasteelersfan
|
||||
homeassistant/components/qwikswitch/* @kellerza
|
||||
homeassistant/components/rainbird/* @konikvranik
|
||||
homeassistant/components/raincloud/* @vanstinator
|
||||
homeassistant/components/rainforest_eagle/* @gtdiehl
|
||||
homeassistant/components/rainmachine/* @bachya
|
||||
|
@ -1,42 +1,91 @@
|
||||
"""Support for Rain Bird Irrigation system LNK WiFi Module."""
|
||||
import logging
|
||||
|
||||
from pyrainbird import RainbirdController
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import binary_sensor, sensor, switch
|
||||
from homeassistant.const import (
|
||||
CONF_FRIENDLY_NAME,
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_TRIGGER_TIME,
|
||||
)
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD
|
||||
|
||||
CONF_ZONES = "zones"
|
||||
|
||||
SUPPORTED_PLATFORMS = [switch.DOMAIN, sensor.DOMAIN, binary_sensor.DOMAIN]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
RAINBIRD_CONTROLLER = "controller"
|
||||
DATA_RAINBIRD = "rainbird"
|
||||
DOMAIN = "rainbird"
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
SENSOR_TYPE_RAINDELAY = "raindelay"
|
||||
SENSOR_TYPE_RAINSENSOR = "rainsensor"
|
||||
# sensor_type [ description, unit, icon ]
|
||||
SENSOR_TYPES = {
|
||||
SENSOR_TYPE_RAINSENSOR: ["Rainsensor", None, "mdi:water"],
|
||||
SENSOR_TYPE_RAINDELAY: ["Raindelay", None, "mdi:water-off"],
|
||||
}
|
||||
|
||||
TRIGGER_TIME_SCHEMA = vol.All(
|
||||
cv.time_period, cv.positive_timedelta, lambda td: (td.total_seconds() // 60)
|
||||
)
|
||||
|
||||
ZONE_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PASSWORD): cv.string}
|
||||
)
|
||||
},
|
||||
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(CONF_TRIGGER_TIME): TRIGGER_TIME_SCHEMA,
|
||||
}
|
||||
)
|
||||
CONTROLLER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_TRIGGER_TIME): TRIGGER_TIME_SCHEMA,
|
||||
vol.Optional(CONF_ZONES): vol.Schema({cv.positive_int: ZONE_SCHEMA}),
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{DOMAIN: vol.Schema(vol.All(cv.ensure_list, [CONTROLLER_SCHEMA]))},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Rain Bird component."""
|
||||
conf = config[DOMAIN]
|
||||
server = conf.get(CONF_HOST)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
from pyrainbird import RainbirdController
|
||||
hass.data[DATA_RAINBIRD] = []
|
||||
success = False
|
||||
for controller_config in config[DOMAIN]:
|
||||
success = success or _setup_controller(hass, controller_config, config)
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def _setup_controller(hass, controller_config, config):
|
||||
"""Set up a controller."""
|
||||
server = controller_config[CONF_HOST]
|
||||
password = controller_config[CONF_PASSWORD]
|
||||
controller = RainbirdController(server, password)
|
||||
|
||||
_LOGGER.debug("Rain Bird Controller set to: %s", server)
|
||||
|
||||
initial_status = controller.currentIrrigation()
|
||||
if initial_status and initial_status["type"] != "CurrentStationsActiveResponse":
|
||||
_LOGGER.error("Error getting state. Possible configuration issues")
|
||||
position = len(hass.data[DATA_RAINBIRD])
|
||||
try:
|
||||
controller.get_serial_number()
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
_LOGGER.error("Unable to setup controller: %s", exc)
|
||||
return False
|
||||
|
||||
hass.data[DATA_RAINBIRD] = controller
|
||||
hass.data[DATA_RAINBIRD].append(controller)
|
||||
_LOGGER.debug("Rain Bird Controller %d set to: %s", position, server)
|
||||
for platform in SUPPORTED_PLATFORMS:
|
||||
discovery.load_platform(
|
||||
hass,
|
||||
platform,
|
||||
DOMAIN,
|
||||
{RAINBIRD_CONTROLLER: position, **controller_config},
|
||||
config,
|
||||
)
|
||||
return True
|
||||
|
64
homeassistant/components/rainbird/binary_sensor.py
Normal file
64
homeassistant/components/rainbird/binary_sensor.py
Normal file
@ -0,0 +1,64 @@
|
||||
"""Support for Rain Bird Irrigation system LNK WiFi Module."""
|
||||
import logging
|
||||
|
||||
from pyrainbird import RainbirdController
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
|
||||
from . import (
|
||||
DATA_RAINBIRD,
|
||||
RAINBIRD_CONTROLLER,
|
||||
SENSOR_TYPE_RAINDELAY,
|
||||
SENSOR_TYPE_RAINSENSOR,
|
||||
SENSOR_TYPES,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up a Rain Bird sensor."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
controller = hass.data[DATA_RAINBIRD][discovery_info[RAINBIRD_CONTROLLER]]
|
||||
add_entities(
|
||||
[RainBirdSensor(controller, sensor_type) for sensor_type in SENSOR_TYPES], True
|
||||
)
|
||||
|
||||
|
||||
class RainBirdSensor(BinarySensorDevice):
|
||||
"""A sensor implementation for Rain Bird device."""
|
||||
|
||||
def __init__(self, controller: RainbirdController, sensor_type):
|
||||
"""Initialize the Rain Bird sensor."""
|
||||
self._sensor_type = sensor_type
|
||||
self._controller = controller
|
||||
self._name = SENSOR_TYPES[self._sensor_type][0]
|
||||
self._icon = SENSOR_TYPES[self._sensor_type][2]
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return None if self._state is None else bool(self._state)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data and updates the states."""
|
||||
_LOGGER.debug("Updating sensor: %s", self._name)
|
||||
state = None
|
||||
if self._sensor_type == SENSOR_TYPE_RAINSENSOR:
|
||||
state = self._controller.get_rain_sensor_state()
|
||||
elif self._sensor_type == SENSOR_TYPE_RAINDELAY:
|
||||
state = self._controller.get_rain_delay()
|
||||
self._state = None if state is None else bool(state)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
return self._icon
|
@ -3,8 +3,10 @@
|
||||
"name": "Rainbird",
|
||||
"documentation": "https://www.home-assistant.io/components/rainbird",
|
||||
"requirements": [
|
||||
"pyrainbird==0.2.1"
|
||||
"pyrainbird==0.4.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
"codeowners": [
|
||||
"@konikvranik"
|
||||
]
|
||||
}
|
||||
|
@ -1,44 +1,37 @@
|
||||
"""Support for Rain Bird Irrigation system LNK WiFi Module."""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from pyrainbird import RainbirdController
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import DATA_RAINBIRD
|
||||
from . import (
|
||||
DATA_RAINBIRD,
|
||||
RAINBIRD_CONTROLLER,
|
||||
SENSOR_TYPE_RAINDELAY,
|
||||
SENSOR_TYPE_RAINSENSOR,
|
||||
SENSOR_TYPES,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# sensor_type [ description, unit, icon ]
|
||||
SENSOR_TYPES = {"rainsensor": ["Rainsensor", None, "mdi:water"]}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(
|
||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up a Rain Bird sensor."""
|
||||
controller = hass.data[DATA_RAINBIRD]
|
||||
|
||||
sensors = []
|
||||
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
|
||||
sensors.append(RainBirdSensor(controller, sensor_type))
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
add_entities(sensors, True)
|
||||
controller = hass.data[DATA_RAINBIRD][discovery_info[RAINBIRD_CONTROLLER]]
|
||||
add_entities(
|
||||
[RainBirdSensor(controller, sensor_type) for sensor_type in SENSOR_TYPES], True
|
||||
)
|
||||
|
||||
|
||||
class RainBirdSensor(Entity):
|
||||
"""A sensor implementation for Rain Bird device."""
|
||||
|
||||
def __init__(self, controller, sensor_type):
|
||||
def __init__(self, controller: RainbirdController, sensor_type):
|
||||
"""Initialize the Rain Bird sensor."""
|
||||
self._sensor_type = sensor_type
|
||||
self._controller = controller
|
||||
@ -55,12 +48,10 @@ class RainBirdSensor(Entity):
|
||||
def update(self):
|
||||
"""Get the latest data and updates the states."""
|
||||
_LOGGER.debug("Updating sensor: %s", self._name)
|
||||
if self._sensor_type == "rainsensor":
|
||||
result = self._controller.currentRainSensorState()
|
||||
if result and result["type"] == "CurrentRainSensorStateResponse":
|
||||
self._state = result["sensorState"]
|
||||
else:
|
||||
self._state = None
|
||||
if self._sensor_type == SENSOR_TYPE_RAINSENSOR:
|
||||
self._state = self._controller.get_rain_sensor_state()
|
||||
elif self._sensor_type == SENSOR_TYPE_RAINDELAY:
|
||||
self._state = self._controller.get_rain_delay()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
9
homeassistant/components/rainbird/services.yaml
Normal file
9
homeassistant/components/rainbird/services.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
start_irrigation:
|
||||
description: Start the irrigation
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of a single irrigation to turn on
|
||||
example: 'switch.sprinkler_1'
|
||||
duration:
|
||||
description: Duration for this sprinkler to be turned on
|
||||
example: 1
|
@ -2,61 +2,85 @@
|
||||
|
||||
import logging
|
||||
|
||||
from pyrainbird import AvailableStations, RainbirdController
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
|
||||
from homeassistant.const import (
|
||||
CONF_FRIENDLY_NAME,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_SWITCHES,
|
||||
CONF_TRIGGER_TIME,
|
||||
CONF_ZONE,
|
||||
)
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_FRIENDLY_NAME, CONF_TRIGGER_TIME
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from . import DATA_RAINBIRD
|
||||
from . import CONF_ZONES, DATA_RAINBIRD, DOMAIN, RAINBIRD_CONTROLLER
|
||||
|
||||
DOMAIN = "rainbird"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
ATTR_DURATION = "duration"
|
||||
|
||||
SERVICE_START_IRRIGATION = "start_irrigation"
|
||||
|
||||
SERVICE_SCHEMA_IRRIGATION = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_SWITCHES, default={}): vol.Schema(
|
||||
{
|
||||
cv.string: {
|
||||
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
||||
vol.Required(CONF_ZONE): cv.string,
|
||||
vol.Required(CONF_TRIGGER_TIME): cv.string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL): cv.string,
|
||||
}
|
||||
}
|
||||
)
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(ATTR_DURATION): vol.All(vol.Coerce(float), vol.Range(min=0)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up Rain Bird switches over a Rain Bird controller."""
|
||||
controller = hass.data[DATA_RAINBIRD]
|
||||
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
controller: RainbirdController = hass.data[DATA_RAINBIRD][
|
||||
discovery_info[RAINBIRD_CONTROLLER]
|
||||
]
|
||||
available_stations: AvailableStations = controller.get_available_stations()
|
||||
if not (available_stations and available_stations.stations):
|
||||
return
|
||||
devices = []
|
||||
for dev_id, switch in config.get(CONF_SWITCHES).items():
|
||||
devices.append(RainBirdSwitch(controller, switch, dev_id))
|
||||
for zone in range(1, available_stations.stations.count + 1):
|
||||
if available_stations.stations.active(zone):
|
||||
zone_config = discovery_info.get(CONF_ZONES, {}).get(zone, {})
|
||||
time = zone_config.get(CONF_TRIGGER_TIME, discovery_info[CONF_TRIGGER_TIME])
|
||||
name = zone_config.get(CONF_FRIENDLY_NAME)
|
||||
devices.append(
|
||||
RainBirdSwitch(
|
||||
controller,
|
||||
zone,
|
||||
time,
|
||||
name if name else "Sprinkler {}".format(zone),
|
||||
)
|
||||
)
|
||||
|
||||
add_entities(devices, True)
|
||||
|
||||
def start_irrigation(service):
|
||||
entity_id = service.data[ATTR_ENTITY_ID]
|
||||
duration = service.data[ATTR_DURATION]
|
||||
|
||||
for device in devices:
|
||||
if device.entity_id == entity_id:
|
||||
device.turn_on(duration=duration)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN,
|
||||
SERVICE_START_IRRIGATION,
|
||||
start_irrigation,
|
||||
schema=SERVICE_SCHEMA_IRRIGATION,
|
||||
)
|
||||
|
||||
|
||||
class RainBirdSwitch(SwitchDevice):
|
||||
"""Representation of a Rain Bird switch."""
|
||||
|
||||
def __init__(self, rb, dev, dev_id):
|
||||
def __init__(self, controller: RainbirdController, zone, time, name):
|
||||
"""Initialize a Rain Bird Switch Device."""
|
||||
self._rainbird = rb
|
||||
self._devid = dev_id
|
||||
self._zone = int(dev.get(CONF_ZONE))
|
||||
self._name = dev.get(CONF_FRIENDLY_NAME, f"Sprinkler {self._zone}")
|
||||
self._rainbird = controller
|
||||
self._zone = zone
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._duration = dev.get(CONF_TRIGGER_TIME)
|
||||
self._attributes = {"duration": self._duration, "zone": self._zone}
|
||||
self._duration = time
|
||||
self._attributes = {ATTR_DURATION: self._duration, "zone": self._zone}
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
@ -70,27 +94,20 @@ class RainBirdSwitch(SwitchDevice):
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
response = self._rainbird.startIrrigation(int(self._zone), int(self._duration))
|
||||
if response and response["type"] == "AcknowledgeResponse":
|
||||
if self._rainbird.irrigate_zone(
|
||||
int(self._zone),
|
||||
int(kwargs[ATTR_DURATION] if ATTR_DURATION in kwargs else self._duration),
|
||||
):
|
||||
self._state = True
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the switch off."""
|
||||
response = self._rainbird.stopIrrigation()
|
||||
if response and response["type"] == "AcknowledgeResponse":
|
||||
if self._rainbird.stop_irrigation():
|
||||
self._state = False
|
||||
|
||||
def get_device_status(self):
|
||||
"""Get the status of the switch from Rain Bird Controller."""
|
||||
response = self._rainbird.currentIrrigation()
|
||||
if response is None:
|
||||
return None
|
||||
if isinstance(response, dict) and "sprinklers" in response:
|
||||
return response["sprinklers"][self._zone]
|
||||
|
||||
def update(self):
|
||||
"""Update switch status."""
|
||||
self._state = self.get_device_status()
|
||||
self._state = self._rainbird.get_zone_state(self._zone)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
@ -1396,7 +1396,7 @@ pyqwikswitch==0.93
|
||||
pyrail==0.0.3
|
||||
|
||||
# homeassistant.components.rainbird
|
||||
pyrainbird==0.2.1
|
||||
pyrainbird==0.4.1
|
||||
|
||||
# homeassistant.components.recswitch
|
||||
pyrecswitch==1.0.2
|
||||
|
Loading…
x
Reference in New Issue
Block a user