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 Coveralls
pull/14685/head
Aaron Bach 2018-05-29 13:02:16 -06:00 committed by Martin Hjelmare
parent 4105429639
commit 084b3287ab
9 changed files with 490 additions and 176 deletions

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -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)

View File

@ -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