From 40c32d68b9de00a451906942c03494f26a2cf3ee Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 25 Apr 2018 22:05:02 -0600 Subject: [PATCH 01/13] Starting to add attributes --- .../components/switch/rainmachine.py | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index 8306b3233305ca..f61da626e37ec0 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -1,4 +1,9 @@ -"""Implements a RainMachine sprinkler controller for Home Assistant.""" +""" +This component provides support for RainMachine programs and zones. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/switch.rainmachine/ +""" from logging import getLogger @@ -13,11 +18,34 @@ _LOGGER = getLogger(__name__) +ATTR_CS_ON = 'cs_on' ATTR_CYCLES = 'cycles' +ATTR_DELAY = 'delay' +ATTR_DELAY_ON = 'delay_on' +ATTR_FREQUENCY = 'frequency' +ATTR_SOAK = 'soak' +ATTR_START_TIME = 'start_time' +ATTR_STATUS = 'status' ATTR_TOTAL_DURATION = 'total_duration' DEFAULT_ZONE_RUN = 60 * 10 +DAYS = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday' +] + +PROGRAM_STATUS_MAP = { + 0: 'Not Running', + 1: 'Running', + 2: 'Queued' +} + def setup_platform(hass, config, add_devices, discovery_info=None): """Set this component up under its platform.""" @@ -90,6 +118,25 @@ def rainmachine_entity_id(self) -> int: class RainMachineProgram(RainMachineEntity): """A RainMachine program.""" + def __init__(self, client, device_mac, program_json): + """Initialize.""" + super().__init__(client, device_mac, program_json) + + frequency = self.calculate_running_days( + self._entity_json['frequency']['type'], + self._entity_json['frequency']['param']) + + self._attrs.update({ + ATTR_CS_ON: self._entity_json['cs_on'], + ATTR_CYCLES: self._entity_json['cycles'], + ATTR_DELAY: self._entity_json['delay'], + ATTR_DELAY_ON: self._entity_json['delay_on'], + ATTR_FREQUENCY: frequency, + ATTR_SOAK: self._entity_json['soak'], + ATTR_START_TIME: self._entity_json['startTime'], + ATTR_STATUS: PROGRAM_STATUS_MAP[self._entity_json['status']] + }) + @property def is_on(self) -> bool: """Return whether the program is running.""" @@ -106,6 +153,26 @@ def unique_id(self) -> str: return '{0}_program_{1}'.format( self.device_mac.replace(':', ''), self.rainmachine_entity_id) + @staticmethod + def calculate_running_days(freq_type: int, freq_param: str) -> str: + """Calculates running days from an RM string ("0010001100").""" + if freq_type == 0: + return 'Daily' + + if freq_type == 1: + return 'Every {0} Days'.format(freq_param) + + if freq_type == 2: + return ', '.join([ + DAYS[idx] for idx, val in enumerate(freq_param[2:-1][::-1]) + if val == '1' + ]) + + if freq_type == 4: + return '{0} Days'.format('Odd' if freq_param == '1' else 'Even') + + return None + def turn_off(self, **kwargs) -> None: """Turn the program off.""" import regenmaschine.exceptions as exceptions From 4c85a0001dcc39007a829de09a47881b546e5593 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 26 Apr 2018 15:19:26 -0600 Subject: [PATCH 02/13] All attributes added to programs --- .../components/switch/rainmachine.py | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index f61da626e37ec0..f6d210f6eccedf 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -23,10 +23,17 @@ ATTR_DELAY = 'delay' ATTR_DELAY_ON = 'delay_on' ATTR_FREQUENCY = 'frequency' +ATTR_IGNORE_WEATHER = 'ignoring_weather_data' +ATTR_NEXT_RUN = 'next_run' +ATTR_SIMULATION_EXPIRED = 'simulation_expired' ATTR_SOAK = 'soak' ATTR_START_TIME = 'start_time' ATTR_STATUS = 'status' ATTR_TOTAL_DURATION = 'total_duration' +ATTR_USE_WATERSENSE = 'using_watersense' +ATTR_WATER_SKIP = 'watering_percentage_skip_amount' +ATTR_YEARLY_RECURRING = 'yearly_recurring' +ATTR_ZONES = 'zones' DEFAULT_ZONE_RUN = 60 * 10 @@ -122,16 +129,12 @@ def __init__(self, client, device_mac, program_json): """Initialize.""" super().__init__(client, device_mac, program_json) - frequency = self.calculate_running_days( - self._entity_json['frequency']['type'], - self._entity_json['frequency']['param']) - self._attrs.update({ ATTR_CS_ON: self._entity_json['cs_on'], ATTR_CYCLES: self._entity_json['cycles'], ATTR_DELAY: self._entity_json['delay'], ATTR_DELAY_ON: self._entity_json['delay_on'], - ATTR_FREQUENCY: frequency, + ATTR_FREQUENCY: self._calculate_running_days(), ATTR_SOAK: self._entity_json['soak'], ATTR_START_TIME: self._entity_json['startTime'], ATTR_STATUS: PROGRAM_STATUS_MAP[self._entity_json['status']] @@ -153,9 +156,15 @@ def unique_id(self) -> str: return '{0}_program_{1}'.format( self.device_mac.replace(':', ''), self.rainmachine_entity_id) - @staticmethod - def calculate_running_days(freq_type: int, freq_param: str) -> str: - """Calculates running days from an RM string ("0010001100").""" + @property + def zones(self) -> list: + """Return a list of active zones associated with this program.""" + return [z for z in self._entity_json['wateringTimes'] if z['active']] + + def _calculate_running_days(self) -> str: + """Calculate running days from an RM string ("0010001100").""" + freq_type = self._entity_json['frequency']['type'] + freq_param = self._entity_json['frequency']['param'] if freq_type == 0: return 'Daily' @@ -205,6 +214,28 @@ def update(self) -> None: try: self._entity_json = self._client.programs.get( self.rainmachine_entity_id) + + self._attrs.update({ + ATTR_CS_ON: self._entity_json.get('cs_on'), + ATTR_CYCLES: self._entity_json.get('cycles'), + ATTR_DELAY: self._entity_json.get('delay'), + ATTR_DELAY_ON: self._entity_json.get('delay_on'), + ATTR_FREQUENCY: self._calculate_running_days(), + ATTR_IGNORE_WEATHER: + self._entity_json.get('ignoreInternetWeather'), + ATTR_NEXT_RUN: self._entity_json.get('nextRun'), + ATTR_SIMULATION_EXPIRED: + self._entity_json.get('simulationExpired'), + ATTR_SOAK: self._entity_json.get('soak'), + ATTR_START_TIME: self._entity_json.get('startTime'), + ATTR_STATUS: + PROGRAM_STATUS_MAP[self._entity_json.get('status')], + ATTR_USE_WATERSENSE: self._entity_json.get('useWaterSense'), + ATTR_WATER_SKIP: self._entity_json.get('freq_modified'), + ATTR_YEARLY_RECURRING: + self._entity_json.get('yearlyRecurring'), + ATTR_ZONES: ', '.join(z['name'] for z in self.zones) + }) except exceptions.HTTPError as exc_info: _LOGGER.error('Unable to update info for program "%s"', self.unique_id) From 9706fb4d2467e02f73161fe19a85ee9e5b3488b9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 26 Apr 2018 15:29:56 -0600 Subject: [PATCH 03/13] Basic zone attributes in place --- .../components/switch/rainmachine.py | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index f6d210f6eccedf..0c928896a39f43 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -34,6 +34,14 @@ ATTR_WATER_SKIP = 'watering_percentage_skip_amount' ATTR_YEARLY_RECURRING = 'yearly_recurring' ATTR_ZONES = 'zones' +ATTR_USER_DURATION = 'user_duration' +ATTR_MACHINE_DURATION = 'machine_duration' +ATTR_SECONDS_REMAINING = 'seconds_remaining' +ATTR_CURRENT_CYCLE = 'current_cycle' +ATTR_NO_CYCLES = 'number_of_cycles' +ATTR_RESTRICTIONS = 'restrictions' +ATTR_VEGETATION_TYPE = 'vegetation_type' +ATTR_MASTER_VALVE = 'master_valve' DEFAULT_ZONE_RUN = 60 * 10 @@ -53,6 +61,19 @@ 2: 'Queued' } +VEGETATION_MAP = { + 0: 'Not Set', + 1: 'Not Set', + 2: 'Grass', + 3: 'Fruit Trees', + 4: 'Flowers', + 5: 'Vegetables', + 6: 'Citrus', + 7: 'Bushes', + 8: 'Xeriscape', + 99: 'Other' +} + def setup_platform(hass, config, add_devices, discovery_info=None): """Set this component up under its platform.""" @@ -94,6 +115,7 @@ def __init__(self, client, device_mac, entity_json): self._api_type = 'remote' if client.auth.using_remote_api else 'local' self._client = client self._entity_json = entity_json + self._properties_json = {} self.device_mac = device_mac @@ -184,32 +206,28 @@ def _calculate_running_days(self) -> str: def turn_off(self, **kwargs) -> None: """Turn the program off.""" - import regenmaschine.exceptions as exceptions + from regenmaschine.exceptions import HTTPError try: self._client.programs.stop(self.rainmachine_entity_id) - except exceptions.BrokenAPICall: - _LOGGER.error('programs.stop currently broken in remote API') - except exceptions.HTTPError as exc_info: + except HTTPError as exc_info: _LOGGER.error('Unable to turn off program "%s"', self.unique_id) _LOGGER.debug(exc_info) def turn_on(self, **kwargs) -> None: """Turn the program on.""" - import regenmaschine.exceptions as exceptions + from regenmaschine.exceptions import HTTPError try: self._client.programs.start(self.rainmachine_entity_id) - except exceptions.BrokenAPICall: - _LOGGER.error('programs.start currently broken in remote API') - except exceptions.HTTPError as exc_info: + except HTTPError as exc_info: _LOGGER.error('Unable to turn on program "%s"', self.unique_id) _LOGGER.debug(exc_info) @Throttle(MIN_SCAN_TIME, MIN_SCAN_TIME_FORCED) def update(self) -> None: """Update info for the program.""" - import regenmaschine.exceptions as exceptions + from regenmaschine.exceptions import HTTPError try: self._entity_json = self._client.programs.get( @@ -236,7 +254,7 @@ def update(self) -> None: self._entity_json.get('yearlyRecurring'), ATTR_ZONES: ', '.join(z['name'] for z in self.zones) }) - except exceptions.HTTPError as exc_info: + except HTTPError as exc_info: _LOGGER.error('Unable to update info for program "%s"', self.unique_id) _LOGGER.debug(exc_info) @@ -273,34 +291,47 @@ def unique_id(self) -> str: def turn_off(self, **kwargs) -> None: """Turn the zone off.""" - import regenmaschine.exceptions as exceptions + from regenmaschine.exceptions import HTTPError try: self._client.zones.stop(self.rainmachine_entity_id) - except exceptions.HTTPError as exc_info: + except HTTPError as exc_info: _LOGGER.error('Unable to turn off zone "%s"', self.unique_id) _LOGGER.debug(exc_info) def turn_on(self, **kwargs) -> None: """Turn the zone on.""" - import regenmaschine.exceptions as exceptions + from regenmaschine.exceptions import HTTPError try: self._client.zones.start(self.rainmachine_entity_id, self._run_time) - except exceptions.HTTPError as exc_info: + except HTTPError as exc_info: _LOGGER.error('Unable to turn on zone "%s"', self.unique_id) _LOGGER.debug(exc_info) @Throttle(MIN_SCAN_TIME, MIN_SCAN_TIME_FORCED) def update(self) -> None: """Update info for the zone.""" - import regenmaschine.exceptions as exceptions + from regenmaschine.exceptions import HTTPError try: self._entity_json = self._client.zones.get( self.rainmachine_entity_id) - except exceptions.HTTPError as exc_info: + + self._attrs.update({ + ATTR_USER_DURATION: self._entity_json.get('userDuration'), + ATTR_MACHINE_DURATION: + self._entity_json.get('machineDuration'), + ATTR_SECONDS_REMAINING: self._entity_json.get('remaining'), + ATTR_CURRENT_CYCLE: self._entity_json.get('cycle'), + ATTR_NO_CYCLES: self._entity_json.get('noOfCycles'), + ATTR_RESTRICTIONS: self._entity_json.get('restriction'), + ATTR_VEGETATION_TYPE: + VEGETATION_MAP[self._entity_json.get('type')], + ATTR_MASTER_VALVE: self._entity_json.get('master'), + }) + except HTTPError as exc_info: _LOGGER.error('Unable to update info for zone "%s"', self.unique_id) _LOGGER.debug(exc_info) From a4a8fb977ecb0e93219bbbb3693101f57613c1a8 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 26 Apr 2018 15:56:14 -0600 Subject: [PATCH 04/13] Added advanced properties for zones --- .../components/switch/rainmachine.py | 132 +++++++++++++++--- 1 file changed, 115 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index 0c928896a39f43..d58229960a8314 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -18,30 +18,48 @@ _LOGGER = getLogger(__name__) +ATTR_AREA = 'area' ATTR_CS_ON = 'cs_on' +ATTR_CURRENT_CYCLE = 'current_cycle' +ATTR_CURRENT_FIELD_CAPACITY = 'current_field_capacity' ATTR_CYCLES = 'cycles' ATTR_DELAY = 'delay' ATTR_DELAY_ON = 'delay_on' +ATTR_EFFICIENCY = 'sprinkler_head_efficiency' +ATTR_FIELD_CAPACITY = 'field_capacity' +ATTR_FLOW_RATE = 'flow_rate' ATTR_FREQUENCY = 'frequency' ATTR_IGNORE_WEATHER = 'ignoring_weather_data' +ATTR_INTAKE_RATE = 'soil_intake_rate' +ATTR_MACHINE_DURATION = 'machine_duration' +ATTR_MASTER_VALVE = 'master_valve' +ATTR_MAX_DEPLETION = 'max_allowed_depletion' ATTR_NEXT_RUN = 'next_run' +ATTR_NO_CYCLES = 'number_of_cycles' +ATTR_PERM_WILTING = 'permanent_wilting_point' +ATTR_PRECIP_RATE = 'sprinkler_head_precipitation_rate' +ATTR_REFERENCE_TIME = 'suggested_summer_watering_seconds' +ATTR_RESTRICTIONS = 'restrictions' +ATTR_ROOT_DEPTH = 'average_root_depth' +ATTR_SAVINGS = 'savings' +ATTR_SECONDS_REMAINING = 'seconds_remaining' ATTR_SIMULATION_EXPIRED = 'simulation_expired' +ATTR_SLOPE = 'slope' ATTR_SOAK = 'soak' +ATTR_SOIL_TYPE = 'soil_type' +ATTR_SPRINKLER_TYPE = 'sprinkler_head_type' ATTR_START_TIME = 'start_time' ATTR_STATUS = 'status' +ATTR_SUN_EXPOSURE = 'sun_exposure' +ATTR_SURFACE_ACCUM = 'soil_surface_accumulation' +ATTR_TALL = 'is_tall' ATTR_TOTAL_DURATION = 'total_duration' +ATTR_USER_DURATION = 'user_duration' ATTR_USE_WATERSENSE = 'using_watersense' +ATTR_VEGETATION_TYPE = 'vegetation_type' ATTR_WATER_SKIP = 'watering_percentage_skip_amount' ATTR_YEARLY_RECURRING = 'yearly_recurring' ATTR_ZONES = 'zones' -ATTR_USER_DURATION = 'user_duration' -ATTR_MACHINE_DURATION = 'machine_duration' -ATTR_SECONDS_REMAINING = 'seconds_remaining' -ATTR_CURRENT_CYCLE = 'current_cycle' -ATTR_NO_CYCLES = 'number_of_cycles' -ATTR_RESTRICTIONS = 'restrictions' -ATTR_VEGETATION_TYPE = 'vegetation_type' -ATTR_MASTER_VALVE = 'master_valve' DEFAULT_ZONE_RUN = 60 * 10 @@ -61,6 +79,46 @@ 2: 'Queued' } +SOIL_TYPE_MAP = { + 0: 'Not Set', + 1: 'Clay Loam', + 2: 'Silty Clay', + 3: 'Clay', + 4: 'Loam', + 5: 'Sandy Loam', + 6: 'Loamy Sand', + 7: 'Sand', + 8: 'Sandy Clay', + 9: 'Silt Loam', + 10: 'Silt', + 99: 'Other' +} + +SLOPE_TYPE_MAP = { + 0: 'Not Set', + 1: 'Flat', + 2: 'Moderate', + 3: 'High', + 4: 'Very High', + 99: 'Other' +} + +SPRINKLER_TYPE_MAP = { + 0: 'Not Set', + 1: 'Popup Spray', + 2: 'Rotors', + 3: 'Surface Drip', + 4: 'Bubblers', + 99: 'Other' +} + +SUN_EXPOSURE_MAP = { + 0: 'Not Set', + 1: 'Full Sun', + 2: 'Partial Shade', + 3: 'Full Shade' +} + VEGETATION_MAP = { 0: 'Not Set', 1: 'Not Set', @@ -115,7 +173,6 @@ def __init__(self, client, device_mac, entity_json): self._api_type = 'remote' if client.auth.using_remote_api else 'local' self._client = client self._entity_json = entity_json - self._properties_json = {} self.device_mac = device_mac @@ -267,11 +324,8 @@ def __init__(self, client, device_mac, zone_json, zone_run_time): """Initialize a RainMachine zone.""" super().__init__(client, device_mac, zone_json) + self._properties_json = {} self._run_time = zone_run_time - self._attrs.update({ - ATTR_CYCLES: self._entity_json.get('noOfCycles'), - ATTR_TOTAL_DURATION: self._entity_json.get('userDuration') - }) @property def is_on(self) -> bool: @@ -319,17 +373,61 @@ def update(self) -> None: self._entity_json = self._client.zones.get( self.rainmachine_entity_id) + self._properties_json = self._client.zones.get( + self.rainmachine_entity_id, properties=True) + self._attrs.update({ - ATTR_USER_DURATION: self._entity_json.get('userDuration'), + ATTR_AREA: self._properties_json.get('waterSense').get('area'), + ATTR_CURRENT_CYCLE: self._entity_json.get('cycle'), + ATTR_CURRENT_FIELD_CAPACITY: + self._properties_json.get( + 'waterSense').get('currentFieldCapacity'), + ATTR_EFFICIENCY: + self._properties_json.get( + 'waterSense').get('appEfficiency'), + ATTR_FIELD_CAPACITY: + self._properties_json.get( + 'waterSense').get('fieldCapacity'), + ATTR_FLOW_RATE: + self._properties_json.get('waterSense').get('flowRate'), + ATTR_INTAKE_RATE: + self._properties_json.get( + 'waterSense').get('soilIntakeRate'), ATTR_MACHINE_DURATION: self._entity_json.get('machineDuration'), - ATTR_SECONDS_REMAINING: self._entity_json.get('remaining'), - ATTR_CURRENT_CYCLE: self._entity_json.get('cycle'), + ATTR_MASTER_VALVE: self._entity_json.get('master'), + ATTR_MAX_DEPLETION: + self._properties_json.get( + 'waterSense').get('maxAllowedDepletion'), ATTR_NO_CYCLES: self._entity_json.get('noOfCycles'), + ATTR_PERM_WILTING: + self._properties_json.get('waterSense').get('permWilting'), + ATTR_PRECIP_RATE: + self._properties_json.get( + 'waterSense').get('precipitationRate'), + ATTR_REFERENCE_TIME: + self._properties_json.get( + 'waterSense').get('referenceTime'), ATTR_RESTRICTIONS: self._entity_json.get('restriction'), + ATTR_ROOT_DEPTH: + self._properties_json.get('waterSense').get('rootDepth'), + ATTR_SAVINGS: self._properties_json.get('savings'), + ATTR_SECONDS_REMAINING: self._entity_json.get('remaining'), + ATTR_SLOPE: SLOPE_TYPE_MAP[self._properties_json.get('slope')], + ATTR_SOIL_TYPE: + SOIL_TYPE_MAP[self._properties_json.get('sun')], + ATTR_SPRINKLER_TYPE: + SPRINKLER_TYPE_MAP[self._properties_json.get('group_id')], + ATTR_SUN_EXPOSURE: + SUN_EXPOSURE_MAP[self._properties_json.get('sun')], + ATTR_SURFACE_ACCUM: + self._properties_json.get( + 'waterSense').get('allowedSurfaceAcc'), + ATTR_TALL: + self._properties_json.get('waterSense').get('isTallPlant'), + ATTR_USER_DURATION: self._entity_json.get('userDuration'), ATTR_VEGETATION_TYPE: VEGETATION_MAP[self._entity_json.get('type')], - ATTR_MASTER_VALVE: self._entity_json.get('master'), }) except HTTPError as exc_info: _LOGGER.error('Unable to update info for zone "%s"', From 2588a070e2fcc3b74a0b155d39817514ffef3c17 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 1 May 2018 14:10:44 -0600 Subject: [PATCH 05/13] Working to move common logic into component + dispatcher --- homeassistant/components/rainmachine.py | 47 ++++- .../components/switch/rainmachine.py | 173 +++++++----------- 2 files changed, 110 insertions(+), 110 deletions(-) diff --git a/homeassistant/components/rainmachine.py b/homeassistant/components/rainmachine.py index 99cec53c2ed1d4..498de941e2588c 100644 --- a/homeassistant/components/rainmachine.py +++ b/homeassistant/components/rainmachine.py @@ -5,13 +5,14 @@ https://home-assistant.io/components/rainmachine/ """ import logging -from datetime import timedelta import voluptuous as vol -from homeassistant.helpers import config_validation as cv, discovery from homeassistant.const import ( - CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_SWITCHES) + 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'] @@ -26,11 +27,11 @@ 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 -MIN_SCAN_TIME = timedelta(seconds=1) -MIN_SCAN_TIME_FORCED = timedelta(milliseconds=100) +PROGRAM_UPDATE_TOPIC = '{0}_program_update'.format(DOMAIN) SWITCH_SCHEMA = vol.Schema({ vol.Optional(CONF_ZONE_RUN_TIME): @@ -68,8 +69,7 @@ def setup(hass, config): auth = Authenticator.create_local( ip_address, password, port=port, https=ssl) client = Client(auth) - mac = client.provision.wifi()['macAddress'] - hass.data[DATA_RAINMACHINE] = (client, mac) + hass.data[DATA_RAINMACHINE] = client except (HTTPError, ConnectTimeout, UnboundLocalError) as exc_info: _LOGGER.error('An error occurred: %s', str(exc_info)) hass.components.persistent_notification.create( @@ -87,3 +87,36 @@ def setup(hass, config): _LOGGER.debug('Setup complete') return True + + +class RainMachineEntity(Entity): + """Define a generic RainMachine entity.""" + + def __init__(self, + device_mac, + rainmachine_type, + rainmachine_entity_id, + icon=DEFAULT_ICON): + """Initialize.""" + self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} + self._icon = icon + self._device_mac = device_mac + self._rainmachine_type = rainmachine_type + self._rainmachine_entity_id = rainmachine_entity_id + + @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._device_mac.replace(':', ''), self._rainmachine_type, + self._rainmachine_entity_id) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index d58229960a8314..3a6a0f2959e739 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -8,11 +8,12 @@ from logging import getLogger from homeassistant.components.rainmachine import ( - CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, DEFAULT_ATTRIBUTION, MIN_SCAN_TIME, - MIN_SCAN_TIME_FORCED) + CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, PROGRAM_UPDATE_TOPIC, + RainMachineEntity) from homeassistant.components.switch import SwitchDevice -from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.util import Throttle +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, dispatcher_send) DEPENDENCIES = ['rainmachine'] @@ -142,7 +143,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): zone_run_time = discovery_info.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN) - client, device_mac = hass.data.get(DATA_RAINMACHINE) + client = hass.data.get(DATA_RAINMACHINE) entities = [] for program in client.programs.all().get('programs', {}): @@ -150,100 +151,62 @@ def setup_platform(hass, config, add_devices, discovery_info=None): continue _LOGGER.debug('Adding program: %s', program) - entities.append( - RainMachineProgram(client, device_mac, program)) + entities.append(RainMachineProgram(client, program)) for zone in client.zones.all().get('zones', {}): if not zone.get('active'): continue _LOGGER.debug('Adding zone: %s', zone) - entities.append( - RainMachineZone(client, device_mac, zone, - zone_run_time)) + entities.append(RainMachineZone(client, zone, zone_run_time)) add_devices(entities, True) -class RainMachineEntity(SwitchDevice): +class RainMachineSwitch(RainMachineEntity, SwitchDevice): """A class to represent a generic RainMachine entity.""" - def __init__(self, client, device_mac, entity_json): + def __init__(self, client, rainmachine_type, entity_attrs): """Initialize a generic RainMachine entity.""" - self._api_type = 'remote' if client.auth.using_remote_api else 'local' self._client = client - self._entity_json = entity_json + self._entity_attrs = entity_attrs + self._type = rainmachine_type - self.device_mac = device_mac - - self._attrs = { - ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION - } - - @property - def device_state_attributes(self) -> dict: - """Return the state attributes.""" - return self._attrs - - @property - def icon(self) -> str: - """Return the icon.""" - return 'mdi:water' + super().__init__(self._client.provision.wifi()['macAddress'], + rainmachine_type, entity_attrs.get('uid')) @property def is_enabled(self) -> bool: """Return whether the entity is enabled.""" - return self._entity_json.get('active') - - @property - def rainmachine_entity_id(self) -> int: - """Return the RainMachine ID for this entity.""" - return self._entity_json.get('uid') + return self._entity_attrs.get('active') -class RainMachineProgram(RainMachineEntity): +class RainMachineProgram(RainMachineSwitch): """A RainMachine program.""" - def __init__(self, client, device_mac, program_json): + def __init__(self, client, program_json): """Initialize.""" - super().__init__(client, device_mac, program_json) - - self._attrs.update({ - ATTR_CS_ON: self._entity_json['cs_on'], - ATTR_CYCLES: self._entity_json['cycles'], - ATTR_DELAY: self._entity_json['delay'], - ATTR_DELAY_ON: self._entity_json['delay_on'], - ATTR_FREQUENCY: self._calculate_running_days(), - ATTR_SOAK: self._entity_json['soak'], - ATTR_START_TIME: self._entity_json['startTime'], - ATTR_STATUS: PROGRAM_STATUS_MAP[self._entity_json['status']] - }) + super().__init__(client, 'program', program_json) @property def is_on(self) -> bool: """Return whether the program is running.""" - return bool(self._entity_json.get('status')) + return bool(self._entity_attrs.get('status')) @property def name(self) -> str: """Return the name of the program.""" - return 'Program: {0}'.format(self._entity_json.get('name')) - - @property - def unique_id(self) -> str: - """Return a unique, HASS-friendly identifier for this entity.""" - return '{0}_program_{1}'.format( - self.device_mac.replace(':', ''), self.rainmachine_entity_id) + return 'Program: {0}'.format(self._entity_attrs.get('name')) @property def zones(self) -> list: """Return a list of active zones associated with this program.""" - return [z for z in self._entity_json['wateringTimes'] if z['active']] + return [z for z in self._entity_attrs['wateringTimes'] if z['active']] def _calculate_running_days(self) -> str: """Calculate running days from an RM string ("0010001100").""" - freq_type = self._entity_json['frequency']['type'] - freq_param = self._entity_json['frequency']['param'] + freq_type = self._entity_attrs['frequency']['type'] + freq_param = self._entity_attrs['frequency']['param'] if freq_type == 0: return 'Daily' @@ -266,7 +229,8 @@ def turn_off(self, **kwargs) -> None: from regenmaschine.exceptions import HTTPError try: - self._client.programs.stop(self.rainmachine_entity_id) + self._client.programs.stop(self._rainmachine_entity_id) + dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC) except HTTPError as exc_info: _LOGGER.error('Unable to turn off program "%s"', self.unique_id) _LOGGER.debug(exc_info) @@ -276,39 +240,39 @@ def turn_on(self, **kwargs) -> None: from regenmaschine.exceptions import HTTPError try: - self._client.programs.start(self.rainmachine_entity_id) + self._client.programs.start(self._rainmachine_entity_id) + dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC) except HTTPError as exc_info: _LOGGER.error('Unable to turn on program "%s"', self.unique_id) _LOGGER.debug(exc_info) - @Throttle(MIN_SCAN_TIME, MIN_SCAN_TIME_FORCED) def update(self) -> None: """Update info for the program.""" from regenmaschine.exceptions import HTTPError try: - self._entity_json = self._client.programs.get( - self.rainmachine_entity_id) + self._entity_attrs = self._client.programs.get( + self._rainmachine_entity_id) self._attrs.update({ - ATTR_CS_ON: self._entity_json.get('cs_on'), - ATTR_CYCLES: self._entity_json.get('cycles'), - ATTR_DELAY: self._entity_json.get('delay'), - ATTR_DELAY_ON: self._entity_json.get('delay_on'), + ATTR_CS_ON: self._entity_attrs.get('cs_on'), + ATTR_CYCLES: self._entity_attrs.get('cycles'), + ATTR_DELAY: self._entity_attrs.get('delay'), + ATTR_DELAY_ON: self._entity_attrs.get('delay_on'), ATTR_FREQUENCY: self._calculate_running_days(), ATTR_IGNORE_WEATHER: - self._entity_json.get('ignoreInternetWeather'), - ATTR_NEXT_RUN: self._entity_json.get('nextRun'), + self._entity_attrs.get('ignoreInternetWeather'), + ATTR_NEXT_RUN: self._entity_attrs.get('nextRun'), ATTR_SIMULATION_EXPIRED: - self._entity_json.get('simulationExpired'), - ATTR_SOAK: self._entity_json.get('soak'), - ATTR_START_TIME: self._entity_json.get('startTime'), + self._entity_attrs.get('simulationExpired'), + ATTR_SOAK: self._entity_attrs.get('soak'), + ATTR_START_TIME: self._entity_attrs.get('startTime'), ATTR_STATUS: - PROGRAM_STATUS_MAP[self._entity_json.get('status')], - ATTR_USE_WATERSENSE: self._entity_json.get('useWaterSense'), - ATTR_WATER_SKIP: self._entity_json.get('freq_modified'), + PROGRAM_STATUS_MAP[self._entity_attrs.get('status')], + ATTR_USE_WATERSENSE: self._entity_attrs.get('useWaterSense'), + ATTR_WATER_SKIP: self._entity_attrs.get('freq_modified'), ATTR_YEARLY_RECURRING: - self._entity_json.get('yearlyRecurring'), + self._entity_attrs.get('yearlyRecurring'), ATTR_ZONES: ', '.join(z['name'] for z in self.zones) }) except HTTPError as exc_info: @@ -317,38 +281,42 @@ def update(self) -> None: _LOGGER.debug(exc_info) -class RainMachineZone(RainMachineEntity): +class RainMachineZone(RainMachineSwitch): """A RainMachine zone.""" - def __init__(self, client, device_mac, zone_json, - zone_run_time): + def __init__(self, client, zone_json, zone_run_time): """Initialize a RainMachine zone.""" - super().__init__(client, device_mac, zone_json) + super().__init__(client, 'zone', zone_json) + self._properties_json = {} self._run_time = zone_run_time @property def is_on(self) -> bool: """Return whether the zone is running.""" - return bool(self._entity_json.get('state')) + return bool(self._entity_attrs.get('state')) @property def name(self) -> str: """Return the name of the zone.""" - return 'Zone: {0}'.format(self._entity_json.get('name')) + return 'Zone: {0}'.format(self._entity_attrs.get('name')) - @property - def unique_id(self) -> str: - """Return a unique, HASS-friendly identifier for this entity.""" - return '{0}_zone_{1}'.format( - self.device_mac.replace(':', ''), self.rainmachine_entity_id) + @callback + def _program_updated(self): + """Update state, trigger updates.""" + self.schedule_update_ha_state(True) + + async def async_added_to_hass(self): + """Register callbacks.""" + async_dispatcher_connect(self.hass, PROGRAM_UPDATE_TOPIC, + self._program_updated) def turn_off(self, **kwargs) -> None: """Turn the zone off.""" from regenmaschine.exceptions import HTTPError try: - self._client.zones.stop(self.rainmachine_entity_id) + self._client.zones.stop(self._rainmachine_entity_id) except HTTPError as exc_info: _LOGGER.error('Unable to turn off zone "%s"', self.unique_id) _LOGGER.debug(exc_info) @@ -358,27 +326,26 @@ def turn_on(self, **kwargs) -> None: from regenmaschine.exceptions import HTTPError try: - self._client.zones.start(self.rainmachine_entity_id, + self._client.zones.start(self._rainmachine_entity_id, self._run_time) except HTTPError as exc_info: _LOGGER.error('Unable to turn on zone "%s"', self.unique_id) _LOGGER.debug(exc_info) - @Throttle(MIN_SCAN_TIME, MIN_SCAN_TIME_FORCED) def update(self) -> None: """Update info for the zone.""" from regenmaschine.exceptions import HTTPError try: - self._entity_json = self._client.zones.get( - self.rainmachine_entity_id) + self._entity_attrs = self._client.zones.get( + self._rainmachine_entity_id) self._properties_json = self._client.zones.get( - self.rainmachine_entity_id, properties=True) + self._rainmachine_entity_id, properties=True) self._attrs.update({ ATTR_AREA: self._properties_json.get('waterSense').get('area'), - ATTR_CURRENT_CYCLE: self._entity_json.get('cycle'), + ATTR_CURRENT_CYCLE: self._entity_attrs.get('cycle'), ATTR_CURRENT_FIELD_CAPACITY: self._properties_json.get( 'waterSense').get('currentFieldCapacity'), @@ -394,12 +361,12 @@ def update(self) -> None: self._properties_json.get( 'waterSense').get('soilIntakeRate'), ATTR_MACHINE_DURATION: - self._entity_json.get('machineDuration'), - ATTR_MASTER_VALVE: self._entity_json.get('master'), + self._entity_attrs.get('machineDuration'), + ATTR_MASTER_VALVE: self._entity_attrs.get('master'), ATTR_MAX_DEPLETION: self._properties_json.get( 'waterSense').get('maxAllowedDepletion'), - ATTR_NO_CYCLES: self._entity_json.get('noOfCycles'), + ATTR_NO_CYCLES: self._entity_attrs.get('noOfCycles'), ATTR_PERM_WILTING: self._properties_json.get('waterSense').get('permWilting'), ATTR_PRECIP_RATE: @@ -408,11 +375,11 @@ def update(self) -> None: ATTR_REFERENCE_TIME: self._properties_json.get( 'waterSense').get('referenceTime'), - ATTR_RESTRICTIONS: self._entity_json.get('restriction'), + ATTR_RESTRICTIONS: self._entity_attrs.get('restriction'), ATTR_ROOT_DEPTH: self._properties_json.get('waterSense').get('rootDepth'), ATTR_SAVINGS: self._properties_json.get('savings'), - ATTR_SECONDS_REMAINING: self._entity_json.get('remaining'), + ATTR_SECONDS_REMAINING: self._entity_attrs.get('remaining'), ATTR_SLOPE: SLOPE_TYPE_MAP[self._properties_json.get('slope')], ATTR_SOIL_TYPE: SOIL_TYPE_MAP[self._properties_json.get('sun')], @@ -425,9 +392,9 @@ def update(self) -> None: 'waterSense').get('allowedSurfaceAcc'), ATTR_TALL: self._properties_json.get('waterSense').get('isTallPlant'), - ATTR_USER_DURATION: self._entity_json.get('userDuration'), + ATTR_USER_DURATION: self._entity_attrs.get('userDuration'), ATTR_VEGETATION_TYPE: - VEGETATION_MAP[self._entity_json.get('type')], + VEGETATION_MAP[self._entity_attrs.get('type')], }) except HTTPError as exc_info: _LOGGER.error('Unable to update info for zone "%s"', From fa1a45893b359dde143a2dbbb04024afe9b8e89a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 1 May 2018 14:26:38 -0600 Subject: [PATCH 06/13] We shouldn't calculate the MAC with every entity --- homeassistant/components/rainmachine.py | 5 +++-- .../components/switch/rainmachine.py | 20 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/rainmachine.py b/homeassistant/components/rainmachine.py index 498de941e2588c..9d45873c4209fd 100644 --- a/homeassistant/components/rainmachine.py +++ b/homeassistant/components/rainmachine.py @@ -93,14 +93,15 @@ class RainMachineEntity(Entity): """Define a generic RainMachine entity.""" def __init__(self, - device_mac, rainmachine_type, rainmachine_entity_id, icon=DEFAULT_ICON): """Initialize.""" + self._client = self.hass.data[DATA_RAINMACHINE] + self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._icon = icon - self._device_mac = device_mac + self._device_mac = self._client.provision.wifi()['macAddress'] self._rainmachine_type = rainmachine_type self._rainmachine_entity_id = rainmachine_entity_id diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index 3a6a0f2959e739..34933d62198fdd 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -143,7 +143,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): zone_run_time = discovery_info.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN) - client = hass.data.get(DATA_RAINMACHINE) + client = hass.data[DATA_RAINMACHINE] entities = [] for program in client.programs.all().get('programs', {}): @@ -151,14 +151,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): continue _LOGGER.debug('Adding program: %s', program) - entities.append(RainMachineProgram(client, program)) + entities.append(RainMachineProgram(program)) for zone in client.zones.all().get('zones', {}): if not zone.get('active'): continue _LOGGER.debug('Adding zone: %s', zone) - entities.append(RainMachineZone(client, zone, zone_run_time)) + entities.append(RainMachineZone(zone, zone_run_time)) add_devices(entities, True) @@ -166,14 +166,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class RainMachineSwitch(RainMachineEntity, SwitchDevice): """A class to represent a generic RainMachine entity.""" - def __init__(self, client, rainmachine_type, entity_attrs): + def __init__(self, rainmachine_type, entity_attrs): """Initialize a generic RainMachine entity.""" - self._client = client self._entity_attrs = entity_attrs self._type = rainmachine_type - super().__init__(self._client.provision.wifi()['macAddress'], - rainmachine_type, entity_attrs.get('uid')) + super().__init__(rainmachine_type, entity_attrs.get('uid')) @property def is_enabled(self) -> bool: @@ -184,9 +182,9 @@ def is_enabled(self) -> bool: class RainMachineProgram(RainMachineSwitch): """A RainMachine program.""" - def __init__(self, client, program_json): + def __init__(self, program_json): """Initialize.""" - super().__init__(client, 'program', program_json) + super().__init__('program', program_json) @property def is_on(self) -> bool: @@ -284,9 +282,9 @@ def update(self) -> None: class RainMachineZone(RainMachineSwitch): """A RainMachine zone.""" - def __init__(self, client, zone_json, zone_run_time): + def __init__(self, zone_json, zone_run_time): """Initialize a RainMachine zone.""" - super().__init__(client, 'zone', zone_json) + super().__init__('zone', zone_json) self._properties_json = {} self._run_time = zone_run_time From 4239e2afaaa0f609d0904d9753cc5c6b1dd443c8 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 1 May 2018 14:43:09 -0600 Subject: [PATCH 07/13] Small fixes --- homeassistant/components/rainmachine.py | 19 +++- .../components/switch/rainmachine.py | 98 +++++++++---------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/rainmachine.py b/homeassistant/components/rainmachine.py index 9d45873c4209fd..f2d5893d60bb95 100644 --- a/homeassistant/components/rainmachine.py +++ b/homeassistant/components/rainmachine.py @@ -69,7 +69,7 @@ def setup(hass, config): auth = Authenticator.create_local( ip_address, password, port=port, https=ssl) client = Client(auth) - hass.data[DATA_RAINMACHINE] = client + 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( @@ -89,21 +89,29 @@ def setup(hass, config): 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._client = self.hass.data[DATA_RAINMACHINE] - self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._icon = icon - self._device_mac = self._client.provision.wifi()['macAddress'] self._rainmachine_type = rainmachine_type self._rainmachine_entity_id = rainmachine_entity_id + self.rainmachine = rainmachine @property def device_state_attributes(self) -> dict: @@ -119,5 +127,6 @@ def icon(self) -> str: def unique_id(self) -> str: """Return a unique, HASS-friendly identifier for this entity.""" return '{0}_{1}_{2}'.format( - self._device_mac.replace(':', ''), self._rainmachine_type, + self.rainmachine.device_mac.replace( + ':', ''), self._rainmachine_type, self._rainmachine_entity_id) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index 34933d62198fdd..ce8f9827006c1f 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -143,22 +143,22 @@ def setup_platform(hass, config, add_devices, discovery_info=None): zone_run_time = discovery_info.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN) - client = hass.data[DATA_RAINMACHINE] + rainmachine = hass.data[DATA_RAINMACHINE] entities = [] - for program in client.programs.all().get('programs', {}): + for program in rainmachine.client.programs.all().get('programs', {}): if not program.get('active'): continue _LOGGER.debug('Adding program: %s', program) - entities.append(RainMachineProgram(program)) + entities.append(RainMachineProgram(rainmachine, program)) - for zone in client.zones.all().get('zones', {}): + for zone in rainmachine.client.zones.all().get('zones', {}): if not zone.get('active'): continue _LOGGER.debug('Adding zone: %s', zone) - entities.append(RainMachineZone(zone, zone_run_time)) + entities.append(RainMachineZone(rainmachine, zone, zone_run_time)) add_devices(entities, True) @@ -166,45 +166,45 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class RainMachineSwitch(RainMachineEntity, SwitchDevice): """A class to represent a generic RainMachine entity.""" - def __init__(self, rainmachine_type, entity_attrs): + def __init__(self, rainmachine, rainmachine_type, obj): """Initialize a generic RainMachine entity.""" - self._entity_attrs = entity_attrs + self._obj = obj self._type = rainmachine_type - super().__init__(rainmachine_type, entity_attrs.get('uid')) + super().__init__(rainmachine, rainmachine_type, obj.get('uid')) @property def is_enabled(self) -> bool: """Return whether the entity is enabled.""" - return self._entity_attrs.get('active') + return self._obj.get('active') class RainMachineProgram(RainMachineSwitch): """A RainMachine program.""" - def __init__(self, program_json): + def __init__(self, rainmachine, obj): """Initialize.""" - super().__init__('program', program_json) + super().__init__(rainmachine, 'program', obj) @property def is_on(self) -> bool: """Return whether the program is running.""" - return bool(self._entity_attrs.get('status')) + return bool(self._obj.get('status')) @property def name(self) -> str: """Return the name of the program.""" - return 'Program: {0}'.format(self._entity_attrs.get('name')) + return 'Program: {0}'.format(self._obj.get('name')) @property def zones(self) -> list: """Return a list of active zones associated with this program.""" - return [z for z in self._entity_attrs['wateringTimes'] if z['active']] + return [z for z in self._obj['wateringTimes'] if z['active']] def _calculate_running_days(self) -> str: """Calculate running days from an RM string ("0010001100").""" - freq_type = self._entity_attrs['frequency']['type'] - freq_param = self._entity_attrs['frequency']['param'] + freq_type = self._obj['frequency']['type'] + freq_param = self._obj['frequency']['param'] if freq_type == 0: return 'Daily' @@ -227,7 +227,7 @@ def turn_off(self, **kwargs) -> None: from regenmaschine.exceptions import HTTPError try: - self._client.programs.stop(self._rainmachine_entity_id) + self.rainmachine.client.programs.stop(self._rainmachine_entity_id) dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC) except HTTPError as exc_info: _LOGGER.error('Unable to turn off program "%s"', self.unique_id) @@ -238,7 +238,7 @@ def turn_on(self, **kwargs) -> None: from regenmaschine.exceptions import HTTPError try: - self._client.programs.start(self._rainmachine_entity_id) + self.rainmachine.client.programs.start(self._rainmachine_entity_id) dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC) except HTTPError as exc_info: _LOGGER.error('Unable to turn on program "%s"', self.unique_id) @@ -249,28 +249,28 @@ def update(self) -> None: from regenmaschine.exceptions import HTTPError try: - self._entity_attrs = self._client.programs.get( + self._obj = self.rainmachine.client.programs.get( self._rainmachine_entity_id) self._attrs.update({ - ATTR_CS_ON: self._entity_attrs.get('cs_on'), - ATTR_CYCLES: self._entity_attrs.get('cycles'), - ATTR_DELAY: self._entity_attrs.get('delay'), - ATTR_DELAY_ON: self._entity_attrs.get('delay_on'), + 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_FREQUENCY: self._calculate_running_days(), ATTR_IGNORE_WEATHER: - self._entity_attrs.get('ignoreInternetWeather'), - ATTR_NEXT_RUN: self._entity_attrs.get('nextRun'), + self._obj.get('ignoreInternetWeather'), + ATTR_NEXT_RUN: self._obj.get('nextRun'), ATTR_SIMULATION_EXPIRED: - self._entity_attrs.get('simulationExpired'), - ATTR_SOAK: self._entity_attrs.get('soak'), - ATTR_START_TIME: self._entity_attrs.get('startTime'), + self._obj.get('simulationExpired'), + ATTR_SOAK: self._obj.get('soak'), + ATTR_START_TIME: self._obj.get('startTime'), ATTR_STATUS: - PROGRAM_STATUS_MAP[self._entity_attrs.get('status')], - ATTR_USE_WATERSENSE: self._entity_attrs.get('useWaterSense'), - ATTR_WATER_SKIP: self._entity_attrs.get('freq_modified'), + PROGRAM_STATUS_MAP[self._obj.get('status')], + ATTR_USE_WATERSENSE: self._obj.get('useWaterSense'), + ATTR_WATER_SKIP: self._obj.get('freq_modified'), ATTR_YEARLY_RECURRING: - self._entity_attrs.get('yearlyRecurring'), + self._obj.get('yearlyRecurring'), ATTR_ZONES: ', '.join(z['name'] for z in self.zones) }) except HTTPError as exc_info: @@ -282,9 +282,9 @@ def update(self) -> None: class RainMachineZone(RainMachineSwitch): """A RainMachine zone.""" - def __init__(self, zone_json, zone_run_time): + def __init__(self, rainmachine, obj, zone_run_time): """Initialize a RainMachine zone.""" - super().__init__('zone', zone_json) + super().__init__(rainmachine, 'zone', obj) self._properties_json = {} self._run_time = zone_run_time @@ -292,12 +292,12 @@ def __init__(self, zone_json, zone_run_time): @property def is_on(self) -> bool: """Return whether the zone is running.""" - return bool(self._entity_attrs.get('state')) + return bool(self._obj.get('state')) @property def name(self) -> str: """Return the name of the zone.""" - return 'Zone: {0}'.format(self._entity_attrs.get('name')) + return 'Zone: {0}'.format(self._obj.get('name')) @callback def _program_updated(self): @@ -314,7 +314,7 @@ def turn_off(self, **kwargs) -> None: from regenmaschine.exceptions import HTTPError try: - self._client.zones.stop(self._rainmachine_entity_id) + self.rainmachine.client.zones.stop(self._rainmachine_entity_id) except HTTPError as exc_info: _LOGGER.error('Unable to turn off zone "%s"', self.unique_id) _LOGGER.debug(exc_info) @@ -324,8 +324,8 @@ def turn_on(self, **kwargs) -> None: from regenmaschine.exceptions import HTTPError try: - self._client.zones.start(self._rainmachine_entity_id, - self._run_time) + self.rainmachine.client.zones.start(self._rainmachine_entity_id, + self._run_time) except HTTPError as exc_info: _LOGGER.error('Unable to turn on zone "%s"', self.unique_id) _LOGGER.debug(exc_info) @@ -335,15 +335,15 @@ def update(self) -> None: from regenmaschine.exceptions import HTTPError try: - self._entity_attrs = self._client.zones.get( + self._obj = self.rainmachine.client.zones.get( self._rainmachine_entity_id) - self._properties_json = self._client.zones.get( + self._properties_json = self.rainmachine.client.zones.get( self._rainmachine_entity_id, properties=True) self._attrs.update({ ATTR_AREA: self._properties_json.get('waterSense').get('area'), - ATTR_CURRENT_CYCLE: self._entity_attrs.get('cycle'), + ATTR_CURRENT_CYCLE: self._obj.get('cycle'), ATTR_CURRENT_FIELD_CAPACITY: self._properties_json.get( 'waterSense').get('currentFieldCapacity'), @@ -359,12 +359,12 @@ def update(self) -> None: self._properties_json.get( 'waterSense').get('soilIntakeRate'), ATTR_MACHINE_DURATION: - self._entity_attrs.get('machineDuration'), - ATTR_MASTER_VALVE: self._entity_attrs.get('master'), + self._obj.get('machineDuration'), + ATTR_MASTER_VALVE: self._obj.get('master'), ATTR_MAX_DEPLETION: self._properties_json.get( 'waterSense').get('maxAllowedDepletion'), - ATTR_NO_CYCLES: self._entity_attrs.get('noOfCycles'), + ATTR_NO_CYCLES: self._obj.get('noOfCycles'), ATTR_PERM_WILTING: self._properties_json.get('waterSense').get('permWilting'), ATTR_PRECIP_RATE: @@ -373,11 +373,11 @@ def update(self) -> None: ATTR_REFERENCE_TIME: self._properties_json.get( 'waterSense').get('referenceTime'), - ATTR_RESTRICTIONS: self._entity_attrs.get('restriction'), + ATTR_RESTRICTIONS: self._obj.get('restriction'), ATTR_ROOT_DEPTH: self._properties_json.get('waterSense').get('rootDepth'), ATTR_SAVINGS: self._properties_json.get('savings'), - ATTR_SECONDS_REMAINING: self._entity_attrs.get('remaining'), + ATTR_SECONDS_REMAINING: self._obj.get('remaining'), ATTR_SLOPE: SLOPE_TYPE_MAP[self._properties_json.get('slope')], ATTR_SOIL_TYPE: SOIL_TYPE_MAP[self._properties_json.get('sun')], @@ -390,9 +390,9 @@ def update(self) -> None: 'waterSense').get('allowedSurfaceAcc'), ATTR_TALL: self._properties_json.get('waterSense').get('isTallPlant'), - ATTR_USER_DURATION: self._entity_attrs.get('userDuration'), + ATTR_USER_DURATION: self._obj.get('userDuration'), ATTR_VEGETATION_TYPE: - VEGETATION_MAP[self._entity_attrs.get('type')], + VEGETATION_MAP[self._obj.get('type')], }) except HTTPError as exc_info: _LOGGER.error('Unable to update info for zone "%s"', From a94b1e2f1c0060e81106245ce3302f48c9266988 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 1 May 2018 17:19:44 -0600 Subject: [PATCH 08/13] Small adjustments --- homeassistant/components/switch/rainmachine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index ce8f9827006c1f..ba7ed62c10bc5f 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -135,7 +135,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): - """Set this component up under its platform.""" + """Set up the RainMachine Switch platform.""" if discovery_info is None: return From 763d4adb0f35749d4c62204cd99febe279ca2494 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 5 May 2018 19:49:32 -0600 Subject: [PATCH 09/13] Owner-requested changes --- .../components/switch/rainmachine.py | 88 ------------------- 1 file changed, 88 deletions(-) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index ba7ed62c10bc5f..d8fbfcb62e95ad 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -22,44 +22,20 @@ ATTR_AREA = 'area' ATTR_CS_ON = 'cs_on' ATTR_CURRENT_CYCLE = 'current_cycle' -ATTR_CURRENT_FIELD_CAPACITY = 'current_field_capacity' ATTR_CYCLES = 'cycles' ATTR_DELAY = 'delay' ATTR_DELAY_ON = 'delay_on' -ATTR_EFFICIENCY = 'sprinkler_head_efficiency' ATTR_FIELD_CAPACITY = 'field_capacity' -ATTR_FLOW_RATE = 'flow_rate' -ATTR_FREQUENCY = 'frequency' -ATTR_IGNORE_WEATHER = 'ignoring_weather_data' -ATTR_INTAKE_RATE = 'soil_intake_rate' -ATTR_MACHINE_DURATION = 'machine_duration' -ATTR_MASTER_VALVE = 'master_valve' -ATTR_MAX_DEPLETION = 'max_allowed_depletion' -ATTR_NEXT_RUN = 'next_run' ATTR_NO_CYCLES = 'number_of_cycles' -ATTR_PERM_WILTING = 'permanent_wilting_point' ATTR_PRECIP_RATE = 'sprinkler_head_precipitation_rate' -ATTR_REFERENCE_TIME = 'suggested_summer_watering_seconds' ATTR_RESTRICTIONS = 'restrictions' -ATTR_ROOT_DEPTH = 'average_root_depth' -ATTR_SAVINGS = 'savings' -ATTR_SECONDS_REMAINING = 'seconds_remaining' -ATTR_SIMULATION_EXPIRED = 'simulation_expired' ATTR_SLOPE = 'slope' ATTR_SOAK = 'soak' ATTR_SOIL_TYPE = 'soil_type' ATTR_SPRINKLER_TYPE = 'sprinkler_head_type' -ATTR_START_TIME = 'start_time' ATTR_STATUS = 'status' ATTR_SUN_EXPOSURE = 'sun_exposure' -ATTR_SURFACE_ACCUM = 'soil_surface_accumulation' -ATTR_TALL = 'is_tall' -ATTR_TOTAL_DURATION = 'total_duration' -ATTR_USER_DURATION = 'user_duration' -ATTR_USE_WATERSENSE = 'using_watersense' ATTR_VEGETATION_TYPE = 'vegetation_type' -ATTR_WATER_SKIP = 'watering_percentage_skip_amount' -ATTR_YEARLY_RECURRING = 'yearly_recurring' ATTR_ZONES = 'zones' DEFAULT_ZONE_RUN = 60 * 10 @@ -201,27 +177,6 @@ def zones(self) -> list: """Return a list of active zones associated with this program.""" return [z for z in self._obj['wateringTimes'] if z['active']] - def _calculate_running_days(self) -> str: - """Calculate running days from an RM string ("0010001100").""" - freq_type = self._obj['frequency']['type'] - freq_param = self._obj['frequency']['param'] - if freq_type == 0: - return 'Daily' - - if freq_type == 1: - return 'Every {0} Days'.format(freq_param) - - if freq_type == 2: - return ', '.join([ - DAYS[idx] for idx, val in enumerate(freq_param[2:-1][::-1]) - if val == '1' - ]) - - if freq_type == 4: - return '{0} Days'.format('Odd' if freq_param == '1' else 'Even') - - return None - def turn_off(self, **kwargs) -> None: """Turn the program off.""" from regenmaschine.exceptions import HTTPError @@ -257,20 +212,9 @@ def update(self) -> None: ATTR_CYCLES: self._obj.get('cycles'), ATTR_DELAY: self._obj.get('delay'), ATTR_DELAY_ON: self._obj.get('delay_on'), - ATTR_FREQUENCY: self._calculate_running_days(), - ATTR_IGNORE_WEATHER: - self._obj.get('ignoreInternetWeather'), - ATTR_NEXT_RUN: self._obj.get('nextRun'), - ATTR_SIMULATION_EXPIRED: - self._obj.get('simulationExpired'), ATTR_SOAK: self._obj.get('soak'), - ATTR_START_TIME: self._obj.get('startTime'), ATTR_STATUS: PROGRAM_STATUS_MAP[self._obj.get('status')], - ATTR_USE_WATERSENSE: self._obj.get('useWaterSense'), - ATTR_WATER_SKIP: self._obj.get('freq_modified'), - ATTR_YEARLY_RECURRING: - self._obj.get('yearlyRecurring'), ATTR_ZONES: ', '.join(z['name'] for z in self.zones) }) except HTTPError as exc_info: @@ -344,40 +288,14 @@ def update(self) -> None: self._attrs.update({ ATTR_AREA: self._properties_json.get('waterSense').get('area'), ATTR_CURRENT_CYCLE: self._obj.get('cycle'), - ATTR_CURRENT_FIELD_CAPACITY: - self._properties_json.get( - 'waterSense').get('currentFieldCapacity'), - ATTR_EFFICIENCY: - self._properties_json.get( - 'waterSense').get('appEfficiency'), ATTR_FIELD_CAPACITY: self._properties_json.get( 'waterSense').get('fieldCapacity'), - ATTR_FLOW_RATE: - self._properties_json.get('waterSense').get('flowRate'), - ATTR_INTAKE_RATE: - self._properties_json.get( - 'waterSense').get('soilIntakeRate'), - ATTR_MACHINE_DURATION: - self._obj.get('machineDuration'), - ATTR_MASTER_VALVE: self._obj.get('master'), - ATTR_MAX_DEPLETION: - self._properties_json.get( - 'waterSense').get('maxAllowedDepletion'), ATTR_NO_CYCLES: self._obj.get('noOfCycles'), - ATTR_PERM_WILTING: - self._properties_json.get('waterSense').get('permWilting'), ATTR_PRECIP_RATE: self._properties_json.get( 'waterSense').get('precipitationRate'), - ATTR_REFERENCE_TIME: - self._properties_json.get( - 'waterSense').get('referenceTime'), ATTR_RESTRICTIONS: self._obj.get('restriction'), - ATTR_ROOT_DEPTH: - self._properties_json.get('waterSense').get('rootDepth'), - ATTR_SAVINGS: self._properties_json.get('savings'), - ATTR_SECONDS_REMAINING: self._obj.get('remaining'), ATTR_SLOPE: SLOPE_TYPE_MAP[self._properties_json.get('slope')], ATTR_SOIL_TYPE: SOIL_TYPE_MAP[self._properties_json.get('sun')], @@ -385,12 +303,6 @@ def update(self) -> None: SPRINKLER_TYPE_MAP[self._properties_json.get('group_id')], ATTR_SUN_EXPOSURE: SUN_EXPOSURE_MAP[self._properties_json.get('sun')], - ATTR_SURFACE_ACCUM: - self._properties_json.get( - 'waterSense').get('allowedSurfaceAcc'), - ATTR_TALL: - self._properties_json.get('waterSense').get('isTallPlant'), - ATTR_USER_DURATION: self._obj.get('userDuration'), ATTR_VEGETATION_TYPE: VEGETATION_MAP[self._obj.get('type')], }) From 515f5b926d21208236713f3975d1c28fd1d03f42 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 6 May 2018 17:59:13 -0600 Subject: [PATCH 10/13] Restart --- homeassistant/components/rainmachine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/rainmachine.py b/homeassistant/components/rainmachine.py index f2d5893d60bb95..bef2be0a66d0cc 100644 --- a/homeassistant/components/rainmachine.py +++ b/homeassistant/components/rainmachine.py @@ -130,3 +130,4 @@ def unique_id(self) -> str: self.rainmachine.device_mac.replace( ':', ''), self._rainmachine_type, self._rainmachine_entity_id) + From c5c066951331b6bf972845e58b3bd50d078275c9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 6 May 2018 17:59:23 -0600 Subject: [PATCH 11/13] Restart part 2 --- homeassistant/components/rainmachine.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/rainmachine.py b/homeassistant/components/rainmachine.py index bef2be0a66d0cc..f2d5893d60bb95 100644 --- a/homeassistant/components/rainmachine.py +++ b/homeassistant/components/rainmachine.py @@ -130,4 +130,3 @@ def unique_id(self) -> str: self.rainmachine.device_mac.replace( ':', ''), self._rainmachine_type, self._rainmachine_entity_id) - From 20e70c3150d530d7aadb2cfd33f7d7e033dfbece Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 6 May 2018 20:25:04 -0600 Subject: [PATCH 12/13] Added ID attribute to each switch --- homeassistant/components/switch/rainmachine.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index d8fbfcb62e95ad..24c75f6acc49c7 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -10,6 +10,7 @@ from homeassistant.components.rainmachine import ( CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, PROGRAM_UPDATE_TOPIC, RainMachineEntity) +from homeassistant.const import ATTR_ID from homeassistant.components.switch import SwitchDevice from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( @@ -208,6 +209,7 @@ def update(self) -> None: self._rainmachine_entity_id) self._attrs.update({ + 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'), @@ -286,6 +288,7 @@ def update(self) -> None: self._rainmachine_entity_id, properties=True) self._attrs.update({ + ATTR_ID: self._obj['uid'], ATTR_AREA: self._properties_json.get('waterSense').get('area'), ATTR_CURRENT_CYCLE: self._obj.get('cycle'), ATTR_FIELD_CAPACITY: From 3443cdc989a71b7090fe9faece3df2ef0d0afea2 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 8 May 2018 15:02:16 -0600 Subject: [PATCH 13/13] Collaborator-requested changes --- homeassistant/components/switch/rainmachine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py index 24c75f6acc49c7..beb00eeca441bf 100644 --- a/homeassistant/components/switch/rainmachine.py +++ b/homeassistant/components/switch/rainmachine.py @@ -248,7 +248,7 @@ def name(self) -> str: @callback def _program_updated(self): """Update state, trigger updates.""" - self.schedule_update_ha_state(True) + self.async_schedule_update_ha_state(True) async def async_added_to_hass(self): """Register callbacks."""