From fdd19577503574a65f1e534de1d5b890db3a458d Mon Sep 17 00:00:00 2001 From: Colin O'Dell Date: Wed, 8 Mar 2017 02:20:30 -0500 Subject: [PATCH] Allow configurable conditions for Pi-Hole sensor (#6465) * Allow configurable conditions for Pi-Hole sensor * Include all three conditions by default * Share Pi-Hole API data across all sensors; eliminate redundant API calls --- homeassistant/components/sensor/pi_hole.py | 98 +++++++++++++++------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/sensor/pi_hole.py b/homeassistant/components/sensor/pi_hole.py index a9a4c11e67f..31335acc466 100644 --- a/homeassistant/components/sensor/pi_hole.py +++ b/homeassistant/components/sensor/pi_hole.py @@ -6,15 +6,16 @@ https://home-assistant.io/components/sensor.pi_hole/ """ import logging import json +from datetime import timedelta import voluptuous as vol from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.components.sensor.rest import RestData from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_SSL, CONF_VERIFY_SSL) + CONF_NAME, CONF_HOST, CONF_SSL, CONF_VERIFY_SSL, CONF_MONITORED_CONDITIONS) import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) _ENDPOINT = '/admin/api.php' @@ -29,11 +30,24 @@ DEFAULT_NAME = 'Pi-Hole' DEFAULT_SSL = False DEFAULT_VERIFY_SSL = True +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) + +MONITORED_CONDITIONS = { + 'dns_queries_today': ['DNS Queries Today', + None, 'mdi:network-question'], + 'ads_blocked_today': ['Ads Blocked Today', + None, 'mdi:close-octagon-outline'], + 'ads_percentage_today': ['Ads Percentage Blocked Today', + '%', 'mdi:close-octagon-outline'], +} + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + vol.Optional(CONF_MONITORED_CONDITIONS, default=MONITORED_CONDITIONS): + vol.All(cv.ensure_list, [vol.In(MONITORED_CONDITIONS)]), }) @@ -41,66 +55,90 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Pi-Hole sensor.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) - method = 'GET' - payload = None - auth = None - headers = None - verify_ssl = config.get(CONF_VERIFY_SSL) use_ssl = config.get(CONF_SSL) + verify_ssl = config.get(CONF_VERIFY_SSL) - if use_ssl: - uri_scheme = 'https://' - else: - uri_scheme = 'http://' + api = PiHoleAPI(host, use_ssl, verify_ssl) - resource = "{}{}{}".format(uri_scheme, host, _ENDPOINT) - - rest = RestData(method, resource, auth, headers, payload, verify_ssl) - rest.update() - - if rest.data is None: + if api.data is None: _LOGGER.error("Unable to fetch data from Pi-Hole") return False - add_devices([PiHoleSensor(hass, rest, name)]) + sensors = [PiHoleSensor(hass, api, name, condition) + for condition in config[CONF_MONITORED_CONDITIONS]] + + add_devices(sensors) class PiHoleSensor(Entity): """Representation of a Pi-Hole sensor.""" - def __init__(self, hass, rest, name): + def __init__(self, hass, api, name, variable): """Initialize a Pi-Hole sensor.""" self._hass = hass - self.rest = rest + self._api = api self._name = name - self._state = False - self.update() + self._var_id = variable + + variable_info = MONITORED_CONDITIONS[variable] + self._var_name = variable_info[0] + self._var_units = variable_info[1] + self._var_icon = variable_info[2] @property def name(self): """Return the name of the sensor.""" - return self._name + return "{} {}".format(self._name, self._var_name) + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._var_icon + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._var_units # pylint: disable=no-member @property def state(self): """Return the state of the device.""" - return self._state.get('ads_blocked_today') + return self._api.data[self._var_id] # pylint: disable=no-member @property def device_state_attributes(self): """Return the state attributes of the Pi-Hole.""" return { - ATTR_BLOCKED_DOMAINS: self._state.get('domains_being_blocked'), - ATTR_PERCENTAGE_TODAY: self._state.get('ads_percentage_today'), - ATTR_QUERIES_TODAY: self._state.get('dns_queries_today'), + ATTR_BLOCKED_DOMAINS: self._api.data['domains_being_blocked'], } def update(self): - """Get the latest data from the Pi-Hole API and updates the state.""" + """Get the latest data from the Pi-Hole API.""" + self._api.update() + + +class PiHoleAPI(object): + """Get the latest data and update the states.""" + + def __init__(self, host, use_ssl, verify_ssl): + """Initialize the data object.""" + from homeassistant.components.sensor.rest import RestData + + uri_scheme = 'https://' if use_ssl else 'http://' + resource = "{}{}{}".format(uri_scheme, host, _ENDPOINT) + + self._rest = RestData('GET', resource, None, None, None, verify_ssl) + self.data = None + + self.update() + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from the Pi-Hole.""" try: - self.rest.update() - self._state = json.loads(self.rest.data) + self._rest.update() + self.data = json.loads(self._rest.data) except TypeError: _LOGGER.error("Unable to fetch data from Pi-Hole")