From 6eba7c4ff334094eccf9c91e4d2c1f94ebae302a Mon Sep 17 00:00:00 2001 From: kbickar Date: Fri, 2 Nov 2018 05:13:14 -0400 Subject: [PATCH] Add binary sensors for sense energy monitor (#17645) * Added error handling for sense API timeouts * Moved imports in function * Moved imports to more appropriate function * Change exception to custom package version * Updated sense_energy library to 0.4.2 * Added binary sensors for individual devices * Whitespace updates * Split into component, sensors, binary sensors * Fixed whitespace * Fixed whitespace * Moved time constant into sensor file * Regenerated requirements * Fixed whitespace * Updated component dependency * Fixed whitespace * Code cleanup * High and low target temps are also supported if target is supported * Revert "High and low target temps are also supported if target is supported" This reverts commit 66b33dc2b81ce81a84553fff327575a0e36d3c8d. * Added all sense components to .coveragerc * Added check authentication exception * binary/sensor platforms loaded in setup * Changed to add all detected devices * Changed to add all sensors on setup * Code cleanup * Whitespace * Whitespace * Added sense as requirement for platform * pylint fixes * Whitespace * Switched requirement to dependency * Made non-class function * Whitespace * Removed unneeded checks * Increased API delay to 60 seconds * Added guard clause for discovery_info * Tidy code * Whitespace --- .coveragerc | 4 +- .../components/binary_sensor/sense.py | 116 ++++++++++++++++++ homeassistant/components/sense.py | 53 ++++++++ homeassistant/components/sensor/sense.py | 60 +++------ requirements_all.txt | 4 +- 5 files changed, 194 insertions(+), 43 deletions(-) create mode 100644 homeassistant/components/binary_sensor/sense.py create mode 100644 homeassistant/components/sense.py diff --git a/.coveragerc b/.coveragerc index e63ac6daec9..54927f74c45 100644 --- a/.coveragerc +++ b/.coveragerc @@ -289,6 +289,9 @@ omit = homeassistant/components/scsgate.py homeassistant/components/*/scsgate.py + homeassistant/components/sense.py + homeassistant/components/*/sense.py + homeassistant/components/simplisafe/__init__.py homeassistant/components/*/simplisafe.py @@ -760,7 +763,6 @@ omit = homeassistant/components/sensor/ripple.py homeassistant/components/sensor/rtorrent.py homeassistant/components/sensor/scrape.py - homeassistant/components/sensor/sense.py homeassistant/components/sensor/sensehat.py homeassistant/components/sensor/serial_pm.py homeassistant/components/sensor/serial.py diff --git a/homeassistant/components/binary_sensor/sense.py b/homeassistant/components/binary_sensor/sense.py new file mode 100644 index 00000000000..8c5ddda0383 --- /dev/null +++ b/homeassistant/components/binary_sensor/sense.py @@ -0,0 +1,116 @@ +""" +Support for monitoring a Sense energy sensor device. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.sense/ +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.sense import SENSE_DATA + +DEPENDENCIES = ['sense'] + +_LOGGER = logging.getLogger(__name__) + +BIN_SENSOR_CLASS = 'power' +MDI_ICONS = {'ac': 'air-conditioner', + 'aquarium': 'fish', + 'car': 'car-electric', + 'computer': 'desktop-classic', + 'cup': 'coffee', + 'dehumidifier': 'water-off', + 'dishes': 'dishwasher', + 'drill': 'toolbox', + 'fan': 'fan', + 'freezer': 'fridge-top', + 'fridge': 'fridge-bottom', + 'game': 'gamepad-variant', + 'garage': 'garage', + 'grill': 'stove', + 'heat': 'fire', + 'heater': 'radiatior', + 'humidifier': 'water', + 'kettle': 'kettle', + 'leafblower': 'leaf', + 'lightbulb': 'lightbulb', + 'media_console': 'set-top-box', + 'modem': 'router-wireless', + 'outlet': 'power-socket-us', + 'papershredder': 'shredder', + 'printer': 'printer', + 'pump': 'water-pump', + 'settings': 'settings', + 'skillet': 'pot', + 'smartcamera': 'webcam', + 'socket': 'power-plug', + 'sound': 'speaker', + 'stove': 'stove', + 'trash': 'trash-can', + 'tv': 'television', + 'vacuum': 'robot-vacuum', + 'washer': 'washing-machine'} + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Sense sensor.""" + if discovery_info is None: + return + + data = hass.data[SENSE_DATA] + + sense_devices = data.get_discovered_device_data() + devices = [SenseDevice(data, device) for device in sense_devices] + add_entities(devices) + + +def sense_to_mdi(sense_icon): + """Convert sense icon to mdi icon.""" + return 'mdi:' + MDI_ICONS.get(sense_icon, 'power-plug') + + +class SenseDevice(BinarySensorDevice): + """Implementation of a Sense energy device binary sensor.""" + + def __init__(self, data, device): + """Initialize the sensor.""" + self._name = device['name'] + self._id = device['id'] + self._icon = sense_to_mdi(device['icon']) + self._data = data + self._state = False + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + return self._state + + @property + def name(self): + """Return the name of the binary sensor.""" + return self._name + + @property + def unique_id(self): + """Return the id of the binary sensor.""" + return self._id + + @property + def icon(self): + """Return the icon of the binary sensor.""" + return self._icon + + @property + def device_class(self): + """Return the device class of the binary sensor.""" + return BIN_SENSOR_CLASS + + def update(self): + """Retrieve latest state.""" + from sense_energy.sense_api import SenseAPITimeoutException + try: + self._data.get_realtime() + except SenseAPITimeoutException: + _LOGGER.error("Timeout retrieving data") + return + self._state = self._name in self._data.active_devices diff --git a/homeassistant/components/sense.py b/homeassistant/components/sense.py new file mode 100644 index 00000000000..3792e10e761 --- /dev/null +++ b/homeassistant/components/sense.py @@ -0,0 +1,53 @@ +""" +Support for monitoring a Sense energy sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sense/ +""" +import logging + +import voluptuous as vol + +from homeassistant.helpers.discovery import load_platform +from homeassistant.const import (CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['sense_energy==0.5.1'] + +_LOGGER = logging.getLogger(__name__) + +SENSE_DATA = 'sense_data' + +DOMAIN = 'sense' + +ACTIVE_UPDATE_RATE = 60 +DEFAULT_TIMEOUT = 5 + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_EMAIL): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, DEFAULT_TIMEOUT): cv.positive_int, + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up the Sense sensor.""" + from sense_energy import Senseable, SenseAuthenticationException + + username = config[DOMAIN][CONF_EMAIL] + password = config[DOMAIN][CONF_PASSWORD] + + timeout = config[DOMAIN][CONF_TIMEOUT] + try: + hass.data[SENSE_DATA] = Senseable(api_timeout=timeout, + wss_timeout=timeout) + hass.data[SENSE_DATA].authenticate(username, password) + hass.data[SENSE_DATA].rate_limit = ACTIVE_UPDATE_RATE + except SenseAuthenticationException: + _LOGGER.error("Could not authenticate with sense server") + return False + load_platform(hass, 'sensor', DOMAIN, {}, config) + load_platform(hass, 'binary_sensor', DOMAIN, {}, config) + return True diff --git a/homeassistant/components/sensor/sense.py b/homeassistant/components/sensor/sense.py index dd9d66f58f1..b494257beb7 100644 --- a/homeassistant/components/sensor/sense.py +++ b/homeassistant/components/sensor/sense.py @@ -5,24 +5,20 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.sense/ """ import logging + from datetime import timedelta -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_EMAIL, CONF_PASSWORD, - CONF_MONITORED_CONDITIONS) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv +from homeassistant.components.sense import SENSE_DATA -REQUIREMENTS = ['sense_energy==0.4.2'] +DEPENDENCIES = ['sense'] _LOGGER = logging.getLogger(__name__) -ACTIVE_NAME = "Energy" -PRODUCTION_NAME = "Production" -CONSUMPTION_NAME = "Usage" +ACTIVE_NAME = 'Energy' +PRODUCTION_NAME = 'Production' +CONSUMPTION_NAME = 'Usage' ACTIVE_TYPE = 'active' @@ -46,55 +42,39 @@ SENSOR_TYPES = {'active': SensorConfig(ACTIVE_NAME, ACTIVE_TYPE), # Production/consumption variants SENSOR_VARIANTS = [PRODUCTION_NAME.lower(), CONSUMPTION_NAME.lower()] -# Valid sensors for configuration -VALID_SENSORS = ['%s_%s' % (typ, var) - for typ in SENSOR_TYPES - for var in SENSOR_VARIANTS] - ICON = 'mdi:flash' MIN_TIME_BETWEEN_DAILY_UPDATES = timedelta(seconds=300) -MIN_TIME_BETWEEN_ACTIVE_UPDATES = timedelta(seconds=60) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_EMAIL): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_MONITORED_CONDITIONS): - vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(VALID_SENSORS)]), -}) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Sense sensor.""" - from sense_energy import Senseable + if discovery_info is None: + return - username = config.get(CONF_EMAIL) - password = config.get(CONF_PASSWORD) - - data = Senseable(username, password) + data = hass.data[SENSE_DATA] @Throttle(MIN_TIME_BETWEEN_DAILY_UPDATES) def update_trends(): """Update the daily power usage.""" data.update_trend_data() - @Throttle(MIN_TIME_BETWEEN_ACTIVE_UPDATES) def update_active(): """Update the active power usage.""" data.get_realtime() devices = [] - for sensor in config.get(CONF_MONITORED_CONDITIONS): - config_name, prod = sensor.rsplit('_', 1) - name = SENSOR_TYPES[config_name].name - sensor_type = SENSOR_TYPES[config_name].sensor_type - is_production = prod == PRODUCTION_NAME.lower() - if sensor_type == ACTIVE_TYPE: - update_call = update_active - else: - update_call = update_trends - devices.append(Sense(data, name, sensor_type, - is_production, update_call)) + for typ in SENSOR_TYPES.values(): + for var in SENSOR_VARIANTS: + name = typ.name + sensor_type = typ.sensor_type + is_production = var == PRODUCTION_NAME.lower() + if sensor_type == ACTIVE_TYPE: + update_call = update_active + else: + update_call = update_trends + devices.append(Sense(data, name, sensor_type, + is_production, update_call)) add_entities(devices) diff --git a/requirements_all.txt b/requirements_all.txt index 9f88916d245..2b080a5751d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1348,8 +1348,8 @@ sendgrid==5.6.0 # homeassistant.components.sensor.sensehat sense-hat==2.2.0 -# homeassistant.components.sensor.sense -sense_energy==0.4.2 +# homeassistant.components.sense +sense_energy==0.5.1 # homeassistant.components.media_player.aquostv sharp_aquos_rc==0.3.2