From 717b2dd8ecfe80de74b7b567ab6912952f4e041c Mon Sep 17 00:00:00 2001 From: Juggels Date: Wed, 10 May 2017 20:11:56 +0200 Subject: [PATCH 1/5] Redesign monitored variables Allow generating specific sensors without the need for template sensors --- homeassistant/components/sensor/hp_ilo.py | 62 ++++++++++++++++------- homeassistant/const.py | 1 + requirements_all.txt | 3 ++ 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py index 338a6e7aff5b2d..84b690c4229343 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/sensor/hp_ilo.py @@ -8,21 +8,25 @@ from datetime import timedelta import voluptuous as vol +import jsonpath_rw from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_NAME, - CONF_MONITORED_VARIABLES, STATE_ON, STATE_OFF) + CONF_MONITORED_VARIABLES, CONF_PATH, CONF_SENSOR_TYPE, + CONF_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-hpilo==3.9'] +REQUIREMENTS = ['python-hpilo==3.9', 'jsonpath_rw==1.4.0'] _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'HP ILO' DEFAULT_PORT = 443 +DEFAULT_SENSOR_PATH = '$' +DEFAULT_UNIT_OF_MEASUREMENT = None MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) @@ -45,8 +49,15 @@ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_MONITORED_VARIABLES, default=['server_name']): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Optional(CONF_MONITORED_VARIABLES, default=[]): + vol.All(cv.ensure_list, [vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SENSOR_TYPE): + vol.All(cv.string, vol.In(SENSOR_TYPES)), + vol.Optional(CONF_UNIT_OF_MEASUREMENT, + default=DEFAULT_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_PATH, default=DEFAULT_SENSOR_PATH): cv.string + })]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, }) @@ -60,7 +71,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): login = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) monitored_variables = config.get(CONF_MONITORED_VARIABLES) - name = config.get(CONF_NAME) # Create a data fetcher to support all of the configured sensors. Then make # the first call to init the data and confirm we can connect. @@ -72,10 +82,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Initialize and add all of the sensors. devices = [] - for ilo_type in monitored_variables: + for monitored_variable in monitored_variables: new_device = HpIloSensor( - hp_ilo_data=hp_ilo_data, sensor_type=SENSOR_TYPES.get(ilo_type), - client_name=name) + hp_ilo_data=hp_ilo_data, + sensor_name='{} {}'.format( + config.get(CONF_NAME), monitored_variable[CONF_NAME]), + sensor_type=monitored_variable[CONF_SENSOR_TYPE], + sensor_path=monitored_variable[CONF_PATH], + unit_of_measurement=monitored_variable[CONF_UNIT_OF_MEASUREMENT]) devices.append(new_device) add_devices(devices) @@ -84,11 +98,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class HpIloSensor(Entity): """Representation of a HP ILO sensor.""" - def __init__(self, hp_ilo_data, sensor_type, client_name): + def __init__(self, hp_ilo_data, sensor_type, sensor_name, sensor_path, + unit_of_measurement): """Initialize the sensor.""" - self._name = '{} {}'.format(client_name, sensor_type[0]) - self._ilo_function = sensor_type[1] - self.client_name = client_name + self._name = sensor_name + self._unit_of_measurement = unit_of_measurement + self._ilo_function = SENSOR_TYPES[sensor_type][1] + self.sensor_path = sensor_path self.hp_ilo_data = hp_ilo_data self._state = None @@ -103,6 +119,11 @@ def name(self): """Return the name of the sensor.""" return self._name + @property + def unit_of_measurement(self): + """Return the unit of measurement of the sensor.""" + return self._unit_of_measurement + @property def state(self): """Return the state of the sensor.""" @@ -121,14 +142,17 @@ def update(self): self.hp_ilo_data.update() ilo_data = getattr(self.hp_ilo_data.data, self._ilo_function)() - # Store the data received from the ILO API - if isinstance(ilo_data, dict): - self._data = ilo_data - else: - self._data = {'value': ilo_data} + if self.sensor_path is not DEFAULT_SENSOR_PATH: + try: + ilo_data = jsonpath_rw.parse(self.sensor_path).find( + ilo_data)[0].value + except IndexError: + _LOGGER.warning( + "No result found in ILO data for jsonpath '%s'", + self.sensor_path) # If the data received is an integer or string, store it as - # the sensor state + # the sensor state, otherwise store the data in the sensor attributes if isinstance(ilo_data, (str, bytes)): states = [STATE_ON, STATE_OFF] try: @@ -138,6 +162,8 @@ def update(self): self._state = ilo_data elif isinstance(ilo_data, (int, float)): self._state = ilo_data + else: + self._data = {'ilo_data': ilo_data} class HpIloData(object): diff --git a/homeassistant/const.py b/homeassistant/const.py index 5b2367db718199..cee16051fd373a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -137,6 +137,7 @@ CONF_SCAN_INTERVAL = 'scan_interval' CONF_SENDER = 'sender' CONF_SENSOR_CLASS = 'sensor_class' +CONF_SENSOR_TYPE = 'sensor_type' CONF_SENSORS = 'sensors' CONF_SSL = 'ssl' CONF_STATE = 'state' diff --git a/requirements_all.txt b/requirements_all.txt index 55678e55e87ee6..80e78c80d6a4aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -328,6 +328,9 @@ insteonlocal==0.48 # homeassistant.components.insteon_plm insteonplm==0.7.4 +# homeassistant.components.sensor.hp_ilo +jsonpath_rw==1.4.0 + # homeassistant.components.media_player.kodi # homeassistant.components.notify.kodi jsonrpc-async==0.6 From c62d86170db7e9f4b962601436b98fbb0610262e Mon Sep 17 00:00:00 2001 From: Juggels Date: Wed, 10 May 2017 21:47:41 +0200 Subject: [PATCH 2/5] Import 3rd party library inside update method --- homeassistant/components/sensor/hp_ilo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py index 84b690c4229343..1cd9bbc5e5d1eb 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/sensor/hp_ilo.py @@ -8,7 +8,6 @@ from datetime import timedelta import voluptuous as vol -import jsonpath_rw from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_NAME, @@ -136,6 +135,8 @@ def device_state_attributes(self): def update(self): """Get the latest data from HP ILO and updates the states.""" + import jsonpath_rw + # Call the API for new data. Each sensor will re-trigger this # same exact call, but that's fine. Results should be cached for # a short period of time to prevent hitting API limits. From 485079c38fd5d7468fb565410b0035986baf8970 Mon Sep 17 00:00:00 2001 From: Juggels Date: Sat, 13 May 2017 15:18:43 +0200 Subject: [PATCH 3/5] Remove jsonpath_rw dependency --- homeassistant/components/sensor/hp_ilo.py | 58 +++++++++++++---------- requirements_all.txt | 3 -- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py index 1cd9bbc5e5d1eb..f04e9bffc0980c 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/sensor/hp_ilo.py @@ -11,21 +11,20 @@ from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_NAME, - CONF_MONITORED_VARIABLES, CONF_PATH, CONF_SENSOR_TYPE, + CONF_MONITORED_VARIABLES, CONF_VALUE_TEMPLATE, CONF_SENSOR_TYPE, CONF_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity +from homeassistant.helpers.template import Template from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-hpilo==3.9', 'jsonpath_rw==1.4.0'] +REQUIREMENTS = ['python-hpilo==3.9'] _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'HP ILO' DEFAULT_PORT = 443 -DEFAULT_SENSOR_PATH = '$' -DEFAULT_UNIT_OF_MEASUREMENT = None MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) @@ -53,9 +52,8 @@ vol.Required(CONF_NAME): cv.string, vol.Required(CONF_SENSOR_TYPE): vol.All(cv.string, vol.In(SENSOR_TYPES)), - vol.Optional(CONF_UNIT_OF_MEASUREMENT, - default=DEFAULT_UNIT_OF_MEASUREMENT): cv.string, - vol.Optional(CONF_PATH, default=DEFAULT_SENSOR_PATH): cv.string + vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=None): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE, default=None): cv.template })]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, @@ -83,11 +81,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices = [] for monitored_variable in monitored_variables: new_device = HpIloSensor( + hass=hass, hp_ilo_data=hp_ilo_data, sensor_name='{} {}'.format( config.get(CONF_NAME), monitored_variable[CONF_NAME]), sensor_type=monitored_variable[CONF_SENSOR_TYPE], - sensor_path=monitored_variable[CONF_PATH], + sensor_value_template=monitored_variable[CONF_VALUE_TEMPLATE], unit_of_measurement=monitored_variable[CONF_UNIT_OF_MEASUREMENT]) devices.append(new_device) @@ -97,17 +96,30 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class HpIloSensor(Entity): """Representation of a HP ILO sensor.""" - def __init__(self, hp_ilo_data, sensor_type, sensor_name, sensor_path, - unit_of_measurement): + def __init__(self, hass, hp_ilo_data, sensor_type, sensor_name, + sensor_value_template, unit_of_measurement): """Initialize the sensor.""" + self._hass = hass self._name = sensor_name self._unit_of_measurement = unit_of_measurement self._ilo_function = SENSOR_TYPES[sensor_type][1] - self.sensor_path = sensor_path self.hp_ilo_data = hp_ilo_data + if sensor_value_template is not None: + # Make sure the template always returns valid json + if not sensor_value_template.template.endswith('| tojson() }}'): + sensor_value_template = Template( + '{}{}'.format( + sensor_value_template.template[:-len('}}')], + '| tojson() }}') + ) + + sensor_value_template.hass = hass + + self._sensor_value_template = sensor_value_template + self._state = None - self._data = None + self._state_attributes = None self.update() @@ -131,11 +143,11 @@ def state(self): @property def device_state_attributes(self): """Return the state attributes.""" - return self._data + return self._state_attributes def update(self): """Get the latest data from HP ILO and updates the states.""" - import jsonpath_rw + import json # Call the API for new data. Each sensor will re-trigger this # same exact call, but that's fine. Results should be cached for @@ -143,14 +155,11 @@ def update(self): self.hp_ilo_data.update() ilo_data = getattr(self.hp_ilo_data.data, self._ilo_function)() - if self.sensor_path is not DEFAULT_SENSOR_PATH: - try: - ilo_data = jsonpath_rw.parse(self.sensor_path).find( - ilo_data)[0].value - except IndexError: - _LOGGER.warning( - "No result found in ILO data for jsonpath '%s'", - self.sensor_path) + if self._sensor_value_template is not None: + # Get the template result and convert back to a Python object + ilo_data = json.loads(self._sensor_value_template.render(ilo_data)) + + self._state_attributes = {'ilo_data': json.dumps(ilo_data)} # If the data received is an integer or string, store it as # the sensor state, otherwise store the data in the sensor attributes @@ -161,10 +170,11 @@ def update(self): self._state = states[index_element] except ValueError: self._state = ilo_data + + self._state_attributes = None elif isinstance(ilo_data, (int, float)): self._state = ilo_data - else: - self._data = {'ilo_data': ilo_data} + self._state_attributes = None class HpIloData(object): diff --git a/requirements_all.txt b/requirements_all.txt index 80e78c80d6a4aa..55678e55e87ee6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -328,9 +328,6 @@ insteonlocal==0.48 # homeassistant.components.insteon_plm insteonplm==0.7.4 -# homeassistant.components.sensor.hp_ilo -jsonpath_rw==1.4.0 - # homeassistant.components.media_player.kodi # homeassistant.components.notify.kodi jsonrpc-async==0.6 From 0befcf0f90b41da5fd3647377f41e7f6c1cdc851 Mon Sep 17 00:00:00 2001 From: Juggels Date: Mon, 15 May 2017 12:09:11 +0200 Subject: [PATCH 4/5] Do not interfere with value_template or ilo_data output Do not interfere with value_template or ilo_data output, this is now the responsibility of the user and should be handled in `configuration.yaml` Fix UnusedImportStatement Fix newline after function docstring --- homeassistant/components/sensor/hp_ilo.py | 33 ++++------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py index f04e9bffc0980c..0fdd89a3c99970 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/sensor/hp_ilo.py @@ -12,10 +12,9 @@ from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_NAME, CONF_MONITORED_VARIABLES, CONF_VALUE_TEMPLATE, CONF_SENSOR_TYPE, - CONF_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF) + CONF_UNIT_OF_MEASUREMENT) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity -from homeassistant.helpers.template import Template from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -106,16 +105,7 @@ def __init__(self, hass, hp_ilo_data, sensor_type, sensor_name, self.hp_ilo_data = hp_ilo_data if sensor_value_template is not None: - # Make sure the template always returns valid json - if not sensor_value_template.template.endswith('| tojson() }}'): - sensor_value_template = Template( - '{}{}'.format( - sensor_value_template.template[:-len('}}')], - '| tojson() }}') - ) - sensor_value_template.hass = hass - self._sensor_value_template = sensor_value_template self._state = None @@ -147,8 +137,6 @@ def device_state_attributes(self): def update(self): """Get the latest data from HP ILO and updates the states.""" - import json - # Call the API for new data. Each sensor will re-trigger this # same exact call, but that's fine. Results should be cached for # a short period of time to prevent hitting API limits. @@ -156,25 +144,14 @@ def update(self): ilo_data = getattr(self.hp_ilo_data.data, self._ilo_function)() if self._sensor_value_template is not None: - # Get the template result and convert back to a Python object - ilo_data = json.loads(self._sensor_value_template.render(ilo_data)) - - self._state_attributes = {'ilo_data': json.dumps(ilo_data)} + ilo_data = self._sensor_value_template.render(ilo_data=ilo_data) # If the data received is an integer or string, store it as # the sensor state, otherwise store the data in the sensor attributes - if isinstance(ilo_data, (str, bytes)): - states = [STATE_ON, STATE_OFF] - try: - index_element = states.index(str(ilo_data).lower()) - self._state = states[index_element] - except ValueError: - self._state = ilo_data - - self._state_attributes = None - elif isinstance(ilo_data, (int, float)): + if isinstance(ilo_data, (str, bytes, int, float)): self._state = ilo_data - self._state_attributes = None + else: + self._state_attributes = {'ilo_data': ilo_data} class HpIloData(object): From b9c95dca3c9923a1e4ac116412315b469ee629a1 Mon Sep 17 00:00:00 2001 From: Juggels Date: Wed, 17 May 2017 11:45:59 +0200 Subject: [PATCH 5/5] Always output results to state --- homeassistant/components/sensor/hp_ilo.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py index 0fdd89a3c99970..2e578c64cd2bc2 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/sensor/hp_ilo.py @@ -146,12 +146,7 @@ def update(self): if self._sensor_value_template is not None: ilo_data = self._sensor_value_template.render(ilo_data=ilo_data) - # If the data received is an integer or string, store it as - # the sensor state, otherwise store the data in the sensor attributes - if isinstance(ilo_data, (str, bytes, int, float)): - self._state = ilo_data - else: - self._state_attributes = {'ilo_data': ilo_data} + self._state = ilo_data class HpIloData(object):