Add sensors and services to RainMachine (#14326)
* Starting to add attributes * All attributes added to programs * Basic zone attributes in place * Added advanced properties for zones * We shouldn't calculate the MAC with every entity * Small fixes * Basic framework for push in play * I THINK IT'S WORKING * Some state cleanup * Restart * Restart part 2 * Added stub for service schema * Update * Added services * Small service description update * Lint * Updated CODEOWNERS * Moving to async methods * Fixed coverage test * Lint * Removed unnecessary hass reference * Lint * Lint * Round 1 of Owner-requested changes * Round 2 of Owner-requested changes * Round 3 of Owner-requested changes * Round 4 (final for now) of Owner-requested changes * Hound * Updated package requirements * Lint * Collaborator-requested changes * Collaborator-requested changes * More small tweaks * One more small tweak * Bumping Travis and Coverallspull/14685/head
parent
4105429639
commit
084b3287ab
|
@ -219,7 +219,7 @@ omit =
|
||||||
homeassistant/components/raincloud.py
|
homeassistant/components/raincloud.py
|
||||||
homeassistant/components/*/raincloud.py
|
homeassistant/components/*/raincloud.py
|
||||||
|
|
||||||
homeassistant/components/rainmachine.py
|
homeassistant/components/rainmachine/*
|
||||||
homeassistant/components/*/rainmachine.py
|
homeassistant/components/*/rainmachine.py
|
||||||
|
|
||||||
homeassistant/components/raspihats.py
|
homeassistant/components/raspihats.py
|
||||||
|
|
|
@ -78,7 +78,6 @@ homeassistant/components/sensor/sytadin.py @gautric
|
||||||
homeassistant/components/sensor/tibber.py @danielhiversen
|
homeassistant/components/sensor/tibber.py @danielhiversen
|
||||||
homeassistant/components/sensor/upnp.py @dgomes
|
homeassistant/components/sensor/upnp.py @dgomes
|
||||||
homeassistant/components/sensor/waqi.py @andrey-git
|
homeassistant/components/sensor/waqi.py @andrey-git
|
||||||
homeassistant/components/switch/rainmachine.py @bachya
|
|
||||||
homeassistant/components/switch/tplink.py @rytilahti
|
homeassistant/components/switch/tplink.py @rytilahti
|
||||||
homeassistant/components/vacuum/roomba.py @pschmitt
|
homeassistant/components/vacuum/roomba.py @pschmitt
|
||||||
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
||||||
|
@ -100,6 +99,8 @@ homeassistant/components/matrix.py @tinloaf
|
||||||
homeassistant/components/*/matrix.py @tinloaf
|
homeassistant/components/*/matrix.py @tinloaf
|
||||||
homeassistant/components/qwikswitch.py @kellerza
|
homeassistant/components/qwikswitch.py @kellerza
|
||||||
homeassistant/components/*/qwikswitch.py @kellerza
|
homeassistant/components/*/qwikswitch.py @kellerza
|
||||||
|
homeassistant/components/rainmachine/* @bachya
|
||||||
|
homeassistant/components/*/rainmachine.py @bachya
|
||||||
homeassistant/components/*/rfxtrx.py @danielhiversen
|
homeassistant/components/*/rfxtrx.py @danielhiversen
|
||||||
homeassistant/components/tahoma.py @philklei
|
homeassistant/components/tahoma.py @philklei
|
||||||
homeassistant/components/*/tahoma.py @philklei
|
homeassistant/components/*/tahoma.py @philklei
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
"""
|
||||||
|
This platform provides binary sensors for key RainMachine data.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/binary_sensor.rainmachine/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
from homeassistant.components.rainmachine import (
|
||||||
|
BINARY_SENSORS, DATA_RAINMACHINE, DATA_UPDATE_TOPIC, TYPE_FREEZE,
|
||||||
|
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
|
||||||
|
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
|
||||||
|
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
|
DEPENDENCIES = ['rainmachine']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Set up the RainMachine Switch platform."""
|
||||||
|
if discovery_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
rainmachine = hass.data[DATA_RAINMACHINE]
|
||||||
|
|
||||||
|
binary_sensors = []
|
||||||
|
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||||
|
name, icon = BINARY_SENSORS[sensor_type]
|
||||||
|
binary_sensors.append(
|
||||||
|
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
|
||||||
|
|
||||||
|
add_devices(binary_sensors, True)
|
||||||
|
|
||||||
|
|
||||||
|
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
|
||||||
|
"""A sensor implementation for raincloud device."""
|
||||||
|
|
||||||
|
def __init__(self, rainmachine, sensor_type, name, icon):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(rainmachine)
|
||||||
|
|
||||||
|
self._icon = icon
|
||||||
|
self._name = name
|
||||||
|
self._sensor_type = sensor_type
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self) -> str:
|
||||||
|
"""Return the icon."""
|
||||||
|
return self._icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return the status of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Disable polling."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||||
|
return '{0}_{1}'.format(
|
||||||
|
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_data(self):
|
||||||
|
"""Update the state."""
|
||||||
|
self.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks."""
|
||||||
|
async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC,
|
||||||
|
self.update_data)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update the state."""
|
||||||
|
if self._sensor_type == TYPE_FREEZE:
|
||||||
|
self._state = self.rainmachine.restrictions['current']['freeze']
|
||||||
|
elif self._sensor_type == TYPE_FREEZE_PROTECTION:
|
||||||
|
self._state = self.rainmachine.restrictions['global'][
|
||||||
|
'freezeProtectEnabled']
|
||||||
|
elif self._sensor_type == TYPE_HOT_DAYS:
|
||||||
|
self._state = self.rainmachine.restrictions['global'][
|
||||||
|
'hotDaysExtraWatering']
|
||||||
|
elif self._sensor_type == TYPE_HOURLY:
|
||||||
|
self._state = self.rainmachine.restrictions['current']['hourly']
|
||||||
|
elif self._sensor_type == TYPE_MONTH:
|
||||||
|
self._state = self.rainmachine.restrictions['current']['month']
|
||||||
|
elif self._sensor_type == TYPE_RAINDELAY:
|
||||||
|
self._state = self.rainmachine.restrictions['current']['rainDelay']
|
||||||
|
elif self._sensor_type == TYPE_RAINSENSOR:
|
||||||
|
self._state = self.rainmachine.restrictions['current'][
|
||||||
|
'rainSensor']
|
||||||
|
elif self._sensor_type == TYPE_WEEKDAY:
|
||||||
|
self._state = self.rainmachine.restrictions['current']['weekDay']
|
|
@ -1,132 +0,0 @@
|
||||||
"""
|
|
||||||
This component provides support for RainMachine sprinkler controllers.
|
|
||||||
|
|
||||||
For more details about this component, please refer to the documentation at
|
|
||||||
https://home-assistant.io/components/rainmachine/
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
|
||||||
ATTR_ATTRIBUTION, CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SSL,
|
|
||||||
CONF_SWITCHES)
|
|
||||||
from homeassistant.helpers import config_validation as cv, discovery
|
|
||||||
from homeassistant.helpers.entity import Entity
|
|
||||||
|
|
||||||
REQUIREMENTS = ['regenmaschine==0.4.1']
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DATA_RAINMACHINE = 'data_rainmachine'
|
|
||||||
DOMAIN = 'rainmachine'
|
|
||||||
|
|
||||||
NOTIFICATION_ID = 'rainmachine_notification'
|
|
||||||
NOTIFICATION_TITLE = 'RainMachine Component Setup'
|
|
||||||
|
|
||||||
CONF_ZONE_RUN_TIME = 'zone_run_time'
|
|
||||||
|
|
||||||
DEFAULT_ATTRIBUTION = 'Data provided by Green Electronics LLC'
|
|
||||||
DEFAULT_ICON = 'mdi:water'
|
|
||||||
DEFAULT_PORT = 8080
|
|
||||||
DEFAULT_SSL = True
|
|
||||||
|
|
||||||
PROGRAM_UPDATE_TOPIC = '{0}_program_update'.format(DOMAIN)
|
|
||||||
|
|
||||||
SWITCH_SCHEMA = vol.Schema({
|
|
||||||
vol.Optional(CONF_ZONE_RUN_TIME):
|
|
||||||
cv.positive_int
|
|
||||||
})
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
DOMAIN: vol.Schema({
|
|
||||||
vol.Required(CONF_IP_ADDRESS): cv.string,
|
|
||||||
vol.Required(CONF_PASSWORD): cv.string,
|
|
||||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
||||||
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
|
||||||
vol.Optional(CONF_SWITCHES): SWITCH_SCHEMA,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
extra=vol.ALLOW_EXTRA)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
|
||||||
"""Set up the RainMachine component."""
|
|
||||||
from regenmaschine import Authenticator, Client
|
|
||||||
from regenmaschine.exceptions import HTTPError
|
|
||||||
from requests.exceptions import ConnectTimeout
|
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
|
||||||
ip_address = conf[CONF_IP_ADDRESS]
|
|
||||||
password = conf[CONF_PASSWORD]
|
|
||||||
port = conf[CONF_PORT]
|
|
||||||
ssl = conf[CONF_SSL]
|
|
||||||
|
|
||||||
_LOGGER.debug('Setting up RainMachine client')
|
|
||||||
|
|
||||||
try:
|
|
||||||
auth = Authenticator.create_local(
|
|
||||||
ip_address, password, port=port, https=ssl)
|
|
||||||
client = Client(auth)
|
|
||||||
hass.data[DATA_RAINMACHINE] = RainMachine(client)
|
|
||||||
except (HTTPError, ConnectTimeout, UnboundLocalError) as exc_info:
|
|
||||||
_LOGGER.error('An error occurred: %s', str(exc_info))
|
|
||||||
hass.components.persistent_notification.create(
|
|
||||||
'Error: {0}<br />'
|
|
||||||
'You will need to restart hass after fixing.'
|
|
||||||
''.format(exc_info),
|
|
||||||
title=NOTIFICATION_TITLE,
|
|
||||||
notification_id=NOTIFICATION_ID)
|
|
||||||
return False
|
|
||||||
|
|
||||||
_LOGGER.debug('Setting up switch platform')
|
|
||||||
switch_config = conf.get(CONF_SWITCHES, {})
|
|
||||||
discovery.load_platform(hass, 'switch', DOMAIN, switch_config, config)
|
|
||||||
|
|
||||||
_LOGGER.debug('Setup complete')
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class RainMachine(object):
|
|
||||||
"""Define a generic RainMachine object."""
|
|
||||||
|
|
||||||
def __init__(self, client):
|
|
||||||
"""Initialize."""
|
|
||||||
self.client = client
|
|
||||||
self.device_mac = self.client.provision.wifi()['macAddress']
|
|
||||||
|
|
||||||
|
|
||||||
class RainMachineEntity(Entity):
|
|
||||||
"""Define a generic RainMachine entity."""
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
rainmachine,
|
|
||||||
rainmachine_type,
|
|
||||||
rainmachine_entity_id,
|
|
||||||
icon=DEFAULT_ICON):
|
|
||||||
"""Initialize."""
|
|
||||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
|
||||||
self._icon = icon
|
|
||||||
self._rainmachine_type = rainmachine_type
|
|
||||||
self._rainmachine_entity_id = rainmachine_entity_id
|
|
||||||
self.rainmachine = rainmachine
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self) -> dict:
|
|
||||||
"""Return the state attributes."""
|
|
||||||
return self._attrs
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self) -> str:
|
|
||||||
"""Return the icon."""
|
|
||||||
return self._icon
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self) -> str:
|
|
||||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
|
||||||
return '{0}_{1}_{2}'.format(
|
|
||||||
self.rainmachine.device_mac.replace(
|
|
||||||
':', ''), self._rainmachine_type,
|
|
||||||
self._rainmachine_entity_id)
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
"""
|
||||||
|
Support for RainMachine devices.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/rainmachine/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ATTRIBUTION, CONF_BINARY_SENSORS, CONF_IP_ADDRESS, CONF_PASSWORD,
|
||||||
|
CONF_PORT, CONF_SENSORS, CONF_SSL, CONF_MONITORED_CONDITIONS,
|
||||||
|
CONF_SWITCHES)
|
||||||
|
from homeassistant.helpers import config_validation as cv, discovery
|
||||||
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.event import track_time_interval
|
||||||
|
|
||||||
|
REQUIREMENTS = ['regenmaschine==0.4.2']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DATA_RAINMACHINE = 'data_rainmachine'
|
||||||
|
DOMAIN = 'rainmachine'
|
||||||
|
|
||||||
|
NOTIFICATION_ID = 'rainmachine_notification'
|
||||||
|
NOTIFICATION_TITLE = 'RainMachine Component Setup'
|
||||||
|
|
||||||
|
DATA_UPDATE_TOPIC = '{0}_data_update'.format(DOMAIN)
|
||||||
|
PROGRAM_UPDATE_TOPIC = '{0}_program_update'.format(DOMAIN)
|
||||||
|
|
||||||
|
CONF_PROGRAM_ID = 'program_id'
|
||||||
|
CONF_ZONE_ID = 'zone_id'
|
||||||
|
CONF_ZONE_RUN_TIME = 'zone_run_time'
|
||||||
|
|
||||||
|
DEFAULT_ATTRIBUTION = 'Data provided by Green Electronics LLC'
|
||||||
|
DEFAULT_ICON = 'mdi:water'
|
||||||
|
DEFAULT_PORT = 8080
|
||||||
|
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
DEFAULT_SSL = True
|
||||||
|
DEFAULT_ZONE_RUN = 60 * 10
|
||||||
|
|
||||||
|
TYPE_FREEZE = 'freeze'
|
||||||
|
TYPE_FREEZE_PROTECTION = 'freeze_protection'
|
||||||
|
TYPE_FREEZE_TEMP = 'freeze_protect_temp'
|
||||||
|
TYPE_HOT_DAYS = 'extra_water_on_hot_days'
|
||||||
|
TYPE_HOURLY = 'hourly'
|
||||||
|
TYPE_MONTH = 'month'
|
||||||
|
TYPE_RAINDELAY = 'raindelay'
|
||||||
|
TYPE_RAINSENSOR = 'rainsensor'
|
||||||
|
TYPE_WEEKDAY = 'weekday'
|
||||||
|
|
||||||
|
BINARY_SENSORS = {
|
||||||
|
TYPE_FREEZE: ('Freeze Restrictions', 'mdi:cancel'),
|
||||||
|
TYPE_FREEZE_PROTECTION: ('Freeze Protection', 'mdi:weather-snowy'),
|
||||||
|
TYPE_HOT_DAYS: ('Extra Water on Hot Days', 'mdi:thermometer-lines'),
|
||||||
|
TYPE_HOURLY: ('Hourly Restrictions', 'mdi:cancel'),
|
||||||
|
TYPE_MONTH: ('Month Restrictions', 'mdi:cancel'),
|
||||||
|
TYPE_RAINDELAY: ('Rain Delay Restrictions', 'mdi:cancel'),
|
||||||
|
TYPE_RAINSENSOR: ('Rain Sensor Restrictions', 'mdi:cancel'),
|
||||||
|
TYPE_WEEKDAY: ('Weekday Restrictions', 'mdi:cancel'),
|
||||||
|
}
|
||||||
|
|
||||||
|
SENSORS = {
|
||||||
|
TYPE_FREEZE_TEMP: ('Freeze Protect Temperature', 'mdi:thermometer', '°C'),
|
||||||
|
}
|
||||||
|
|
||||||
|
BINARY_SENSOR_SCHEMA = vol.Schema({
|
||||||
|
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)):
|
||||||
|
vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)])
|
||||||
|
})
|
||||||
|
|
||||||
|
SENSOR_SCHEMA = vol.Schema({
|
||||||
|
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)):
|
||||||
|
vol.All(cv.ensure_list, [vol.In(SENSORS)])
|
||||||
|
})
|
||||||
|
|
||||||
|
SERVICE_START_PROGRAM_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_PROGRAM_ID): cv.positive_int,
|
||||||
|
})
|
||||||
|
|
||||||
|
SERVICE_START_ZONE_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_ZONE_ID): cv.positive_int,
|
||||||
|
vol.Optional(CONF_ZONE_RUN_TIME, default=DEFAULT_ZONE_RUN):
|
||||||
|
cv.positive_int,
|
||||||
|
})
|
||||||
|
|
||||||
|
SERVICE_STOP_PROGRAM_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_PROGRAM_ID): cv.positive_int,
|
||||||
|
})
|
||||||
|
|
||||||
|
SERVICE_STOP_ZONE_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_ZONE_ID): cv.positive_int,
|
||||||
|
})
|
||||||
|
|
||||||
|
SWITCH_SCHEMA = vol.Schema({vol.Optional(CONF_ZONE_RUN_TIME): cv.positive_int})
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
DOMAIN:
|
||||||
|
vol.Schema({
|
||||||
|
vol.Required(CONF_IP_ADDRESS): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
||||||
|
vol.Optional(CONF_BINARY_SENSORS, default={}):
|
||||||
|
BINARY_SENSOR_SCHEMA,
|
||||||
|
vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
|
||||||
|
vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Set up the RainMachine component."""
|
||||||
|
from regenmaschine import Authenticator, Client
|
||||||
|
from regenmaschine.exceptions import RainMachineError
|
||||||
|
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
ip_address = conf[CONF_IP_ADDRESS]
|
||||||
|
password = conf[CONF_PASSWORD]
|
||||||
|
port = conf[CONF_PORT]
|
||||||
|
ssl = conf[CONF_SSL]
|
||||||
|
|
||||||
|
try:
|
||||||
|
auth = Authenticator.create_local(
|
||||||
|
ip_address, password, port=port, https=ssl)
|
||||||
|
rainmachine = RainMachine(hass, Client(auth))
|
||||||
|
rainmachine.update()
|
||||||
|
hass.data[DATA_RAINMACHINE] = rainmachine
|
||||||
|
except RainMachineError as exc:
|
||||||
|
_LOGGER.error('An error occurred: %s', str(exc))
|
||||||
|
hass.components.persistent_notification.create(
|
||||||
|
'Error: {0}<br />'
|
||||||
|
'You will need to restart hass after fixing.'
|
||||||
|
''.format(exc),
|
||||||
|
title=NOTIFICATION_TITLE,
|
||||||
|
notification_id=NOTIFICATION_ID)
|
||||||
|
return False
|
||||||
|
|
||||||
|
for component, schema in [
|
||||||
|
('binary_sensor', conf[CONF_BINARY_SENSORS]),
|
||||||
|
('sensor', conf[CONF_SENSORS]),
|
||||||
|
('switch', conf[CONF_SWITCHES]),
|
||||||
|
]:
|
||||||
|
discovery.load_platform(hass, component, DOMAIN, schema, config)
|
||||||
|
|
||||||
|
def refresh(event_time):
|
||||||
|
"""Refresh RainMachine data."""
|
||||||
|
_LOGGER.debug('Updating RainMachine data')
|
||||||
|
hass.data[DATA_RAINMACHINE].update()
|
||||||
|
dispatcher_send(hass, DATA_UPDATE_TOPIC)
|
||||||
|
|
||||||
|
track_time_interval(hass, refresh, DEFAULT_SCAN_INTERVAL)
|
||||||
|
|
||||||
|
def start_program(service):
|
||||||
|
"""Start a particular program."""
|
||||||
|
rainmachine.client.programs.start(service.data[CONF_PROGRAM_ID])
|
||||||
|
|
||||||
|
def start_zone(service):
|
||||||
|
"""Start a particular zone for a certain amount of time."""
|
||||||
|
rainmachine.client.zones.start(service.data[CONF_ZONE_ID],
|
||||||
|
service.data[CONF_ZONE_RUN_TIME])
|
||||||
|
|
||||||
|
def stop_all(service):
|
||||||
|
"""Stop all watering."""
|
||||||
|
rainmachine.client.watering.stop_all()
|
||||||
|
|
||||||
|
def stop_program(service):
|
||||||
|
"""Stop a program."""
|
||||||
|
rainmachine.client.programs.stop(service.data[CONF_PROGRAM_ID])
|
||||||
|
|
||||||
|
def stop_zone(service):
|
||||||
|
"""Stop a zone."""
|
||||||
|
rainmachine.client.zones.stop(service.data[CONF_ZONE_ID])
|
||||||
|
|
||||||
|
for service, method, schema in [
|
||||||
|
('start_program', start_program, SERVICE_START_PROGRAM_SCHEMA),
|
||||||
|
('start_zone', start_zone, SERVICE_START_ZONE_SCHEMA),
|
||||||
|
('stop_all', stop_all, {}),
|
||||||
|
('stop_program', stop_program, SERVICE_STOP_PROGRAM_SCHEMA),
|
||||||
|
('stop_zone', stop_zone, SERVICE_STOP_ZONE_SCHEMA)
|
||||||
|
]:
|
||||||
|
hass.services.register(DOMAIN, service, method, schema=schema)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class RainMachine(object):
|
||||||
|
"""Define a generic RainMachine object."""
|
||||||
|
|
||||||
|
def __init__(self, hass, client):
|
||||||
|
"""Initialize."""
|
||||||
|
self.client = client
|
||||||
|
self.device_mac = self.client.provision.wifi()['macAddress']
|
||||||
|
self.restrictions = {}
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update sensor/binary sensor data."""
|
||||||
|
self.restrictions.update({
|
||||||
|
'current': self.client.restrictions.current(),
|
||||||
|
'global': self.client.restrictions.universal()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class RainMachineEntity(Entity):
|
||||||
|
"""Define a generic RainMachine entity."""
|
||||||
|
|
||||||
|
def __init__(self, rainmachine):
|
||||||
|
"""Initialize."""
|
||||||
|
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||||
|
self._name = None
|
||||||
|
self.rainmachine = rainmachine
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self) -> dict:
|
||||||
|
"""Return the state attributes."""
|
||||||
|
return self._attrs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return self._name
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Describes the format for available RainMachine services
|
||||||
|
|
||||||
|
---
|
||||||
|
start_program:
|
||||||
|
description: Start a program.
|
||||||
|
fields:
|
||||||
|
program_id:
|
||||||
|
description: The program to start.
|
||||||
|
example: 3
|
||||||
|
start_zone:
|
||||||
|
description: Start a zone for a set number of seconds.
|
||||||
|
fields:
|
||||||
|
zone_id:
|
||||||
|
description: The zone to start.
|
||||||
|
example: 3
|
||||||
|
zone_run_time:
|
||||||
|
description: The number of seconds to run the zone.
|
||||||
|
example: 120
|
||||||
|
stop_all:
|
||||||
|
description: Stop all watering activities.
|
||||||
|
stop_program:
|
||||||
|
description: Stop a program.
|
||||||
|
fields:
|
||||||
|
program_id:
|
||||||
|
description: The program to stop.
|
||||||
|
example: 3
|
||||||
|
stop_zone:
|
||||||
|
description: Stop a zone.
|
||||||
|
fields:
|
||||||
|
zone_id:
|
||||||
|
description: The zone to stop.
|
||||||
|
example: 3
|
|
@ -0,0 +1,88 @@
|
||||||
|
"""
|
||||||
|
This platform provides support for sensor data from RainMachine.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.rainmachine/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.rainmachine import (
|
||||||
|
DATA_RAINMACHINE, DATA_UPDATE_TOPIC, SENSORS, RainMachineEntity)
|
||||||
|
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
|
DEPENDENCIES = ['rainmachine']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Set up the RainMachine Switch platform."""
|
||||||
|
if discovery_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
rainmachine = hass.data[DATA_RAINMACHINE]
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||||
|
name, icon, unit = SENSORS[sensor_type]
|
||||||
|
sensors.append(
|
||||||
|
RainMachineSensor(rainmachine, sensor_type, name, icon, unit))
|
||||||
|
|
||||||
|
add_devices(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
|
class RainMachineSensor(RainMachineEntity):
|
||||||
|
"""A sensor implementation for raincloud device."""
|
||||||
|
|
||||||
|
def __init__(self, rainmachine, sensor_type, name, icon, unit):
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(rainmachine)
|
||||||
|
|
||||||
|
self._icon = icon
|
||||||
|
self._name = name
|
||||||
|
self._sensor_type = sensor_type
|
||||||
|
self._state = None
|
||||||
|
self._unit = unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self) -> str:
|
||||||
|
"""Return the icon."""
|
||||||
|
return self._icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Disable polling."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> str:
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||||
|
return '{0}_{1}'.format(
|
||||||
|
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit the value is expressed in."""
|
||||||
|
return self._unit
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_data(self):
|
||||||
|
"""Update the state."""
|
||||||
|
self.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks."""
|
||||||
|
async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC,
|
||||||
|
self.update_data)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update the sensor's state."""
|
||||||
|
self._state = self.rainmachine.restrictions['global'][
|
||||||
|
'freezeProtectTemp']
|
|
@ -4,12 +4,11 @@ This component provides support for RainMachine programs and zones.
|
||||||
For more details about this component, please refer to the documentation at
|
For more details about this component, please refer to the documentation at
|
||||||
https://home-assistant.io/components/switch.rainmachine/
|
https://home-assistant.io/components/switch.rainmachine/
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from homeassistant.components.rainmachine import (
|
from homeassistant.components.rainmachine import (
|
||||||
CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, PROGRAM_UPDATE_TOPIC,
|
CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, DEFAULT_ZONE_RUN,
|
||||||
RainMachineEntity)
|
PROGRAM_UPDATE_TOPIC, RainMachineEntity)
|
||||||
from homeassistant.const import ATTR_ID
|
from homeassistant.const import ATTR_ID
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
@ -18,7 +17,7 @@ from homeassistant.helpers.dispatcher import (
|
||||||
|
|
||||||
DEPENDENCIES = ['rainmachine']
|
DEPENDENCIES = ['rainmachine']
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_AREA = 'area'
|
ATTR_AREA = 'area'
|
||||||
ATTR_CS_ON = 'cs_on'
|
ATTR_CS_ON = 'cs_on'
|
||||||
|
@ -39,8 +38,6 @@ ATTR_SUN_EXPOSURE = 'sun_exposure'
|
||||||
ATTR_VEGETATION_TYPE = 'vegetation_type'
|
ATTR_VEGETATION_TYPE = 'vegetation_type'
|
||||||
ATTR_ZONES = 'zones'
|
ATTR_ZONES = 'zones'
|
||||||
|
|
||||||
DEFAULT_ZONE_RUN = 60 * 10
|
|
||||||
|
|
||||||
DAYS = [
|
DAYS = [
|
||||||
'Monday',
|
'Monday',
|
||||||
'Tuesday',
|
'Tuesday',
|
||||||
|
@ -141,26 +138,41 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
|
||||||
|
|
||||||
class RainMachineSwitch(RainMachineEntity, SwitchDevice):
|
class RainMachineSwitch(RainMachineEntity, SwitchDevice):
|
||||||
"""A class to represent a generic RainMachine entity."""
|
"""A class to represent a generic RainMachine switch."""
|
||||||
|
|
||||||
def __init__(self, rainmachine, rainmachine_type, obj):
|
def __init__(self, rainmachine, switch_type, obj):
|
||||||
"""Initialize a generic RainMachine entity."""
|
"""Initialize a generic RainMachine switch."""
|
||||||
|
super().__init__(rainmachine)
|
||||||
|
|
||||||
|
self._name = obj['name']
|
||||||
self._obj = obj
|
self._obj = obj
|
||||||
self._type = rainmachine_type
|
self._rainmachine_entity_id = obj['uid']
|
||||||
|
self._switch_type = switch_type
|
||||||
|
|
||||||
super().__init__(rainmachine, rainmachine_type, obj.get('uid'))
|
@property
|
||||||
|
def icon(self) -> str:
|
||||||
|
"""Return the icon."""
|
||||||
|
return 'mdi:water'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
"""Return whether the entity is enabled."""
|
"""Return whether the entity is enabled."""
|
||||||
return self._obj.get('active')
|
return self._obj.get('active')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||||
|
return '{0}_{1}_{2}'.format(
|
||||||
|
self.rainmachine.device_mac.replace(':', ''),
|
||||||
|
self._switch_type,
|
||||||
|
self._rainmachine_entity_id)
|
||||||
|
|
||||||
|
|
||||||
class RainMachineProgram(RainMachineSwitch):
|
class RainMachineProgram(RainMachineSwitch):
|
||||||
"""A RainMachine program."""
|
"""A RainMachine program."""
|
||||||
|
|
||||||
def __init__(self, rainmachine, obj):
|
def __init__(self, rainmachine, obj):
|
||||||
"""Initialize."""
|
"""Initialize a generic RainMachine switch."""
|
||||||
super().__init__(rainmachine, 'program', obj)
|
super().__init__(rainmachine, 'program', obj)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -168,11 +180,6 @@ class RainMachineProgram(RainMachineSwitch):
|
||||||
"""Return whether the program is running."""
|
"""Return whether the program is running."""
|
||||||
return bool(self._obj.get('status'))
|
return bool(self._obj.get('status'))
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of the program."""
|
|
||||||
return 'Program: {0}'.format(self._obj.get('name'))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def zones(self) -> list:
|
def zones(self) -> list:
|
||||||
"""Return a list of active zones associated with this program."""
|
"""Return a list of active zones associated with this program."""
|
||||||
|
@ -180,29 +187,29 @@ class RainMachineProgram(RainMachineSwitch):
|
||||||
|
|
||||||
def turn_off(self, **kwargs) -> None:
|
def turn_off(self, **kwargs) -> None:
|
||||||
"""Turn the program off."""
|
"""Turn the program off."""
|
||||||
from regenmaschine.exceptions import HTTPError
|
from regenmaschine.exceptions import RainMachineError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.rainmachine.client.programs.stop(self._rainmachine_entity_id)
|
self.rainmachine.client.programs.stop(self._rainmachine_entity_id)
|
||||||
dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
||||||
except HTTPError as exc_info:
|
except RainMachineError as exc_info:
|
||||||
_LOGGER.error('Unable to turn off program "%s"', self.unique_id)
|
_LOGGER.error('Unable to turn off program "%s"', self.unique_id)
|
||||||
_LOGGER.debug(exc_info)
|
_LOGGER.debug(exc_info)
|
||||||
|
|
||||||
def turn_on(self, **kwargs) -> None:
|
def turn_on(self, **kwargs) -> None:
|
||||||
"""Turn the program on."""
|
"""Turn the program on."""
|
||||||
from regenmaschine.exceptions import HTTPError
|
from regenmaschine.exceptions import RainMachineError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.rainmachine.client.programs.start(self._rainmachine_entity_id)
|
self.rainmachine.client.programs.start(self._rainmachine_entity_id)
|
||||||
dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
||||||
except HTTPError as exc_info:
|
except RainMachineError as exc_info:
|
||||||
_LOGGER.error('Unable to turn on program "%s"', self.unique_id)
|
_LOGGER.error('Unable to turn on program "%s"', self.unique_id)
|
||||||
_LOGGER.debug(exc_info)
|
_LOGGER.debug(exc_info)
|
||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
"""Update info for the program."""
|
"""Update info for the program."""
|
||||||
from regenmaschine.exceptions import HTTPError
|
from regenmaschine.exceptions import RainMachineError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._obj = self.rainmachine.client.programs.get(
|
self._obj = self.rainmachine.client.programs.get(
|
||||||
|
@ -210,16 +217,11 @@ class RainMachineProgram(RainMachineSwitch):
|
||||||
|
|
||||||
self._attrs.update({
|
self._attrs.update({
|
||||||
ATTR_ID: self._obj['uid'],
|
ATTR_ID: self._obj['uid'],
|
||||||
ATTR_CS_ON: self._obj.get('cs_on'),
|
|
||||||
ATTR_CYCLES: self._obj.get('cycles'),
|
|
||||||
ATTR_DELAY: self._obj.get('delay'),
|
|
||||||
ATTR_DELAY_ON: self._obj.get('delay_on'),
|
|
||||||
ATTR_SOAK: self._obj.get('soak'),
|
ATTR_SOAK: self._obj.get('soak'),
|
||||||
ATTR_STATUS:
|
ATTR_STATUS: PROGRAM_STATUS_MAP[self._obj.get('status')],
|
||||||
PROGRAM_STATUS_MAP[self._obj.get('status')],
|
|
||||||
ATTR_ZONES: ', '.join(z['name'] for z in self.zones)
|
ATTR_ZONES: ', '.join(z['name'] for z in self.zones)
|
||||||
})
|
})
|
||||||
except HTTPError as exc_info:
|
except RainMachineError as exc_info:
|
||||||
_LOGGER.error('Unable to update info for program "%s"',
|
_LOGGER.error('Unable to update info for program "%s"',
|
||||||
self.unique_id)
|
self.unique_id)
|
||||||
_LOGGER.debug(exc_info)
|
_LOGGER.debug(exc_info)
|
||||||
|
@ -240,11 +242,6 @@ class RainMachineZone(RainMachineSwitch):
|
||||||
"""Return whether the zone is running."""
|
"""Return whether the zone is running."""
|
||||||
return bool(self._obj.get('state'))
|
return bool(self._obj.get('state'))
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of the zone."""
|
|
||||||
return 'Zone: {0}'.format(self._obj.get('name'))
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _program_updated(self):
|
def _program_updated(self):
|
||||||
"""Update state, trigger updates."""
|
"""Update state, trigger updates."""
|
||||||
|
@ -257,28 +254,28 @@ class RainMachineZone(RainMachineSwitch):
|
||||||
|
|
||||||
def turn_off(self, **kwargs) -> None:
|
def turn_off(self, **kwargs) -> None:
|
||||||
"""Turn the zone off."""
|
"""Turn the zone off."""
|
||||||
from regenmaschine.exceptions import HTTPError
|
from regenmaschine.exceptions import RainMachineError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.rainmachine.client.zones.stop(self._rainmachine_entity_id)
|
self.rainmachine.client.zones.stop(self._rainmachine_entity_id)
|
||||||
except HTTPError as exc_info:
|
except RainMachineError as exc_info:
|
||||||
_LOGGER.error('Unable to turn off zone "%s"', self.unique_id)
|
_LOGGER.error('Unable to turn off zone "%s"', self.unique_id)
|
||||||
_LOGGER.debug(exc_info)
|
_LOGGER.debug(exc_info)
|
||||||
|
|
||||||
def turn_on(self, **kwargs) -> None:
|
def turn_on(self, **kwargs) -> None:
|
||||||
"""Turn the zone on."""
|
"""Turn the zone on."""
|
||||||
from regenmaschine.exceptions import HTTPError
|
from regenmaschine.exceptions import RainMachineError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.rainmachine.client.zones.start(self._rainmachine_entity_id,
|
self.rainmachine.client.zones.start(self._rainmachine_entity_id,
|
||||||
self._run_time)
|
self._run_time)
|
||||||
except HTTPError as exc_info:
|
except RainMachineError as exc_info:
|
||||||
_LOGGER.error('Unable to turn on zone "%s"', self.unique_id)
|
_LOGGER.error('Unable to turn on zone "%s"', self.unique_id)
|
||||||
_LOGGER.debug(exc_info)
|
_LOGGER.debug(exc_info)
|
||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
"""Update info for the zone."""
|
"""Update info for the zone."""
|
||||||
from regenmaschine.exceptions import HTTPError
|
from regenmaschine.exceptions import RainMachineError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._obj = self.rainmachine.client.zones.get(
|
self._obj = self.rainmachine.client.zones.get(
|
||||||
|
@ -309,7 +306,7 @@ class RainMachineZone(RainMachineSwitch):
|
||||||
ATTR_VEGETATION_TYPE:
|
ATTR_VEGETATION_TYPE:
|
||||||
VEGETATION_MAP[self._obj.get('type')],
|
VEGETATION_MAP[self._obj.get('type')],
|
||||||
})
|
})
|
||||||
except HTTPError as exc_info:
|
except RainMachineError as exc_info:
|
||||||
_LOGGER.error('Unable to update info for zone "%s"',
|
_LOGGER.error('Unable to update info for zone "%s"',
|
||||||
self.unique_id)
|
self.unique_id)
|
||||||
_LOGGER.debug(exc_info)
|
_LOGGER.debug(exc_info)
|
||||||
|
|
|
@ -1147,7 +1147,7 @@ raincloudy==0.0.4
|
||||||
# raspihats==2.2.3
|
# raspihats==2.2.3
|
||||||
|
|
||||||
# homeassistant.components.rainmachine
|
# homeassistant.components.rainmachine
|
||||||
regenmaschine==0.4.1
|
regenmaschine==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.python_script
|
# homeassistant.components.python_script
|
||||||
restrictedpython==4.0b4
|
restrictedpython==4.0b4
|
||||||
|
|
Loading…
Reference in New Issue