diff --git a/homeassistant/components/apcupsd.py b/homeassistant/components/apcupsd.py index 8c4a964d330..8abb06b54ea 100644 --- a/homeassistant/components/apcupsd.py +++ b/homeassistant/components/apcupsd.py @@ -5,6 +5,9 @@ Sets up and provides access to the status output of APCUPSd via its Network Information Server (NIS). """ import logging +from datetime import timedelta + +from homeassistant.util import Throttle DOMAIN = "apcupsd" @@ -21,31 +24,59 @@ KEY_STATUS = "STATUS" VALUE_ONLINE = "ONLINE" -GET_STATUS = None +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) + +DATA = None _LOGGER = logging.getLogger(__name__) def setup(hass, config): """ Use config values to set up a function enabling status retrieval. """ - global GET_STATUS - from apcaccess import status + global DATA host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST) port = config[DOMAIN].get(CONF_PORT, DEFAULT_PORT) - def get_status(): - """ Get the status from APCUPSd and parse it into a dict. """ - return status.parse(status.get(host=host, port=port)) - - GET_STATUS = get_status + DATA = APCUPSdData(host, port) # It doesn't really matter why we're not able to get the status, just that # we can't. # pylint: disable=broad-except try: - GET_STATUS() + DATA.update(no_throttle=True) except Exception: _LOGGER.exception("Failure while testing APCUPSd status retrieval.") return False return True + + +class APCUPSdData(object): + """ + Stores the data retrieved from APCUPSd for each entity to use, acts as the + single point responsible for fetching updates from the server. + """ + def __init__(self, host, port): + from apcaccess import status + self._host = host + self._port = port + self._status = None + self._get = status.get + self._parse = status.parse + + @property + def status(self): + """ Get latest update if throttle allows. Return status. """ + self.update() + return self._status + + def _get_status(self): + """ Get the status from APCUPSd and parse it into a dict. """ + return self._parse(self._get(host=self._host, port=self._port)) + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self, **kwargs): + """ + Fetch the latest status from APCUPSd and store it in self._status. + """ + self._status = self._get_status() diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/binary_sensor/apcupsd.py index a6eaa7f7ea9..8524b06e4ba 100644 --- a/homeassistant/components/binary_sensor/apcupsd.py +++ b/homeassistant/components/binary_sensor/apcupsd.py @@ -3,7 +3,6 @@ homeassistant.components.binary_sensor.apcupsd ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a binary sensor to track online status of a UPS. """ -from homeassistant.core import JobPriority from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components import apcupsd @@ -15,17 +14,16 @@ DEFAULT_NAME = "UPS Online Status" def setup_platform(hass, config, add_entities, discovery_info=None): """ Instantiate an OnlineStatus binary sensor entity and add it to HA. """ - add_entities((OnlineStatus(hass, config),)) + add_entities((OnlineStatus(config, apcupsd.DATA),)) class OnlineStatus(BinarySensorDevice): """ Binary sensor to represent UPS online status. """ - def __init__(self, hass, config): + def __init__(self, config, data): self._config = config + self._data = data self._state = None - # Get initial state - hass.pool.add_job( - JobPriority.EVENT_STATE, (self.update_ha_state, True)) + self.update() @property def name(self): @@ -39,8 +37,7 @@ class OnlineStatus(BinarySensorDevice): def update(self): """ - Get the latest status report from APCUPSd and establish whether the - UPS is online. + Get the status report from APCUPSd (or cache) and set this entity's + state. """ - status = apcupsd.GET_STATUS() - self._state = status[apcupsd.KEY_STATUS] + self._state = self._data.status[apcupsd.KEY_STATUS] diff --git a/homeassistant/components/sensor/apcupsd.py b/homeassistant/components/sensor/apcupsd.py index 99d61d74384..d72dc4ae4b9 100644 --- a/homeassistant/components/sensor/apcupsd.py +++ b/homeassistant/components/sensor/apcupsd.py @@ -5,7 +5,6 @@ Provides a sensor to track various status aspects of a UPS. """ import logging -from homeassistant.core import JobPriority from homeassistant.const import TEMP_CELCIUS from homeassistant.helpers.entity import Entity from homeassistant.components import apcupsd @@ -32,28 +31,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error( "You must include a '%s' when configuring an APCUPSd sensor.", apcupsd.CONF_TYPE) - return + return False typ = typ.upper() - # Get a status reading from APCUPSd and check whether the user provided - # 'type' is present in the output. If we're not able to check, then assume - # the user knows what they're doing. - # pylint: disable=broad-except - status = None - try: - status = apcupsd.GET_STATUS() - if typ not in status: - _LOGGER.error( - "Specified '%s' of '%s' does not appear in the APCUPSd status " - "output.", apcupsd.CONF_TYPE, typ) - return - except Exception as exc: - _LOGGER.warning( - "Unable to fetch initial value from ACPUPSd to check that '%s' is " - "a supported '%s': %s", typ, apcupsd.CONF_TYPE, exc) - unit = SPECIFIC_UNITS.get(typ) + if typ not in apcupsd.DATA.status: + _LOGGER.error( + "Specified '%s' of '%s' does not appear in the APCUPSd status " + "output.", apcupsd.CONF_TYPE, typ) + return False + add_entities(( - Sensor(hass, config, unit=unit, initial_status=status), + Sensor(config, apcupsd.DATA, unit=SPECIFIC_UNITS.get(typ)), )) @@ -72,16 +60,12 @@ def infer_unit(value): class Sensor(Entity): """ Generic sensor entity for APCUPSd status values. """ - def __init__(self, hass, config, unit=None, initial_status=None): + def __init__(self, config, data, unit=None): self._config = config self._unit = unit - self._state = None + self._data = data self._inferred_unit = None - if initial_status is None: - hass.pool.add_job( - JobPriority.EVENT_STATE, (self.update_ha_state, True)) - else: - self._update_from_status(initial_status) + self.update() @property def name(self): @@ -99,9 +83,5 @@ class Sensor(Entity): def update(self): """ Get the latest status and use it to update our sensor state. """ - self._update_from_status(apcupsd.GET_STATUS()) - - def _update_from_status(self, status): - """ Set state and infer unit from status. """ key = self._config[apcupsd.CONF_TYPE].upper() - self._state, self._inferred_unit = infer_unit(status[key]) + self._state, self._inferred_unit = infer_unit(self._data.status[key])