diff --git a/.coveragerc b/.coveragerc index 63eb4ea9be1..602721d5143 100644 --- a/.coveragerc +++ b/.coveragerc @@ -32,6 +32,7 @@ omit = homeassistant/components/tellduslive.py homeassistant/components/*/tellduslive.py + homeassistant/components/vera.py homeassistant/components/*/vera.py homeassistant/components/ecobee.py diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index fe47a91abb1..777974905b8 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -9,7 +9,8 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.components import (bloomsky, mysensors, zwave, wemo, wink) +from homeassistant.components import ( + bloomsky, mysensors, zwave, vera, wemo, wink) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -37,6 +38,7 @@ DISCOVERY_PLATFORMS = { bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', zwave.DISCOVER_BINARY_SENSORS: 'zwave', + vera.DISCOVER_BINARY_SENSORS: 'vera', wemo.DISCOVER_BINARY_SENSORS: 'wemo', wink.DISCOVER_BINARY_SENSORS: 'wink' } diff --git a/homeassistant/components/binary_sensor/vera.py b/homeassistant/components/binary_sensor/vera.py new file mode 100644 index 00000000000..3f92503dcbf --- /dev/null +++ b/homeassistant/components/binary_sensor/vera.py @@ -0,0 +1,69 @@ +""" +Support for Vera binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.vera/ +""" +import logging + +import homeassistant.util.dt as dt_util +from homeassistant.const import ( + ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED) +from homeassistant.components.binary_sensor import ( + BinarySensorDevice) +from homeassistant.components.vera import ( + VeraDevice, VERA_DEVICES, VERA_CONTROLLER) + +DEPENDENCIES = ['vera'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Perform the setup for Vera controller devices.""" + add_devices_callback( + VeraBinarySensor(device, VERA_CONTROLLER) + for device in VERA_DEVICES['binary_sensor']) + + +class VeraBinarySensor(VeraDevice, BinarySensorDevice): + """Representation of a Vera Binary Sensor.""" + + def __init__(self, vera_device, controller): + """Initialize the binary_sensor.""" + self._state = False + VeraDevice.__init__(self, vera_device, controller) + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attr = {} + if self.vera_device.has_battery: + attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%' + + if self.vera_device.is_armable: + armed = self.vera_device.is_armed + attr[ATTR_ARMED] = 'True' if armed else 'False' + + if self.vera_device.is_trippable: + last_tripped = self.vera_device.last_trip + if last_tripped is not None: + utc_time = dt_util.utc_from_timestamp(int(last_tripped)) + attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( + utc_time) + else: + attr[ATTR_LAST_TRIP_TIME] = None + tripped = self.vera_device.is_tripped + attr[ATTR_TRIPPED] = 'True' if tripped else 'False' + + attr['Vera Device Id'] = self.vera_device.vera_device_id + return attr + + @property + def is_on(self): + """Return true if sensor is on.""" + return self._state + + def update(self): + """Get the latest data and update the state.""" + self._state = self.vera_device.is_tripped diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 159cd19a96a..a80154bc16e 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -10,7 +10,7 @@ import csv from homeassistant.components import ( group, discovery, wemo, wink, isy994, - zwave, insteon_hub, mysensors, tellstick) + zwave, insteon_hub, mysensors, tellstick, vera) from homeassistant.config import load_yaml_config_file from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, @@ -66,6 +66,7 @@ DISCOVERY_PLATFORMS = { zwave.DISCOVER_LIGHTS: 'zwave', mysensors.DISCOVER_LIGHTS: 'mysensors', tellstick.DISCOVER_LIGHTS: 'tellstick', + vera.DISCOVER_LIGHTS: 'vera', } PROP_TO_ATTR = { diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index 99e60b1921e..689e3a21818 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -6,15 +6,15 @@ https://home-assistant.io/components/light.vera/ """ import logging -from requests.exceptions import RequestException - import homeassistant.util.dt as dt_util from homeassistant.components.light import ATTR_BRIGHTNESS, Light from homeassistant.const import ( ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, - EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) + STATE_OFF, STATE_ON) +from homeassistant.components.vera import ( + VeraDevice, VERA_DEVICES, VERA_CONTROLLER) -REQUIREMENTS = ['pyvera==0.2.8'] +DEPENDENCIES = ['vera'] _LOGGER = logging.getLogger(__name__) @@ -22,74 +22,17 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Setup Vera lights.""" - import pyvera as veraApi - - base_url = config.get('vera_controller_url') - if not base_url: - _LOGGER.error( - "The required parameter 'vera_controller_url'" - " was not found in config" - ) - return False - - device_data = config.get('device_data', {}) - - vera_controller, created = veraApi.init_controller(base_url) - - if created: - def stop_subscription(event): - """Shutdown Vera subscriptions and subscription thread on exit.""" - _LOGGER.info("Shutting down subscriptions.") - vera_controller.stop() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription) - - devices = [] - try: - devices = vera_controller.get_devices([ - 'Switch', - 'On/Off Switch', - 'Dimmable Switch']) - except RequestException: - # There was a network related error connecting to the vera controller. - _LOGGER.exception("Error communicating with Vera API") - return False - - lights = [] - for device in devices: - extra_data = device_data.get(device.device_id, {}) - exclude = extra_data.get('exclude', False) - - if exclude is not True: - lights.append(VeraLight(device, vera_controller, extra_data)) - - add_devices_callback(lights) + add_devices_callback( + VeraLight(device, VERA_CONTROLLER) for device in VERA_DEVICES['light']) -class VeraLight(Light): +class VeraLight(VeraDevice, Light): """Representation of a Vera Light, including dimmable.""" - def __init__(self, vera_device, controller, extra_data=None): + def __init__(self, vera_device, controller): """Initialize the light.""" - self.vera_device = vera_device - self.extra_data = extra_data - self.controller = controller - if self.extra_data and self.extra_data.get('name'): - self._name = self.extra_data.get('name') - else: - self._name = self.vera_device.name - self._state = STATE_OFF - - self.controller.register(vera_device, self._update_callback) - self.update() - - def _update_callback(self, _device): - self.update_ha_state(True) - - @property - def name(self): - """Return the name of the light.""" - return self._name + self._state = False + VeraDevice.__init__(self, vera_device, controller) @property def brightness(self): @@ -137,20 +80,13 @@ class VeraLight(Light): attr[ATTR_TRIPPED] = 'True' if tripped else 'False' attr['Vera Device Id'] = self.vera_device.vera_device_id - - @property - def should_poll(self): - """No polling needed.""" - return False + return attr @property def is_on(self): """Return true if device is on.""" - return self._state == STATE_ON + return self._state def update(self): """Called by the vera device callback to update state.""" - if self.vera_device.is_switched_on(): - self._state = STATE_ON - else: - self._state = STATE_OFF + self._state = self.vera_device.is_switched_on() diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 67bcfb6f701..48dee4e169b 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -8,7 +8,8 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.components import ( - wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, bloomsky) + wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, + bloomsky, vera) DOMAIN = 'sensor' SCAN_INTERVAL = 30 @@ -25,6 +26,7 @@ DISCOVERY_PLATFORMS = { ecobee.DISCOVER_SENSORS: 'ecobee', tellduslive.DISCOVER_SENSORS: 'tellduslive', mysensors.DISCOVER_SENSORS: 'mysensors', + vera.DISCOVER_SENSORS: 'vera', } diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index 118035b8285..7cf7fed71ee 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -6,109 +6,40 @@ https://home-assistant.io/components/sensor.vera/ """ import logging -from requests.exceptions import RequestException - import homeassistant.util.dt as dt_util from homeassistant.const import ( ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, - EVENT_HOMEASSISTANT_STOP, TEMP_CELCIUS, TEMP_FAHRENHEIT) + TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity +from homeassistant.components.vera import ( + VeraDevice, VERA_DEVICES, VERA_CONTROLLER) -REQUIREMENTS = ['pyvera==0.2.8'] +DEPENDENCIES = ['vera'] _LOGGER = logging.getLogger(__name__) -# pylint: disable=unused-argument -def get_devices(hass, config): - """Setup the Vera Sensors.""" - import pyvera as veraApi - - base_url = config.get('vera_controller_url') - if not base_url: - _LOGGER.error( - "The required parameter 'vera_controller_url'" - " was not found in config" - ) - return False - - device_data = config.get('device_data', {}) - - vera_controller, created = veraApi.init_controller(base_url) - - if created: - def stop_subscription(event): - """Shutdown Vera subscriptions and subscription thread on exit.""" - _LOGGER.info("Shutting down subscriptions.") - vera_controller.stop() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription) - - categories = ['Temperature Sensor', - 'Light Sensor', - 'Humidity Sensor', - 'Sensor'] - devices = [] - try: - devices = vera_controller.get_devices(categories) - except RequestException: - # There was a network related error connecting to the vera controller. - _LOGGER.exception("Error communicating with Vera API") - return False - - vera_sensors = [] - for device in devices: - extra_data = device_data.get(device.device_id, {}) - exclude = extra_data.get('exclude', False) - - if exclude is not True: - vera_sensors.append( - VeraSensor(device, vera_controller, extra_data)) - - return vera_sensors - - -def setup_platform(hass, config, add_devices, discovery_info=None): +def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Perform the setup for Vera controller devices.""" - add_devices(get_devices(hass, config)) + add_devices_callback( + VeraSensor(device, VERA_CONTROLLER) + for device in VERA_DEVICES['sensor']) -class VeraSensor(Entity): +class VeraSensor(VeraDevice, Entity): """Representation of a Vera Sensor.""" - def __init__(self, vera_device, controller, extra_data=None): + def __init__(self, vera_device, controller): """Initialize the sensor.""" - self.vera_device = vera_device - self.controller = controller - self.extra_data = extra_data - if self.extra_data and self.extra_data.get('name'): - self._name = self.extra_data.get('name') - else: - self._name = self.vera_device.name - self.current_value = '' + self.current_value = None self._temperature_units = None - - self.controller.register(vera_device, self._update_callback) - self.update() - - def _update_callback(self, _device): - """Called by the vera device callback to update state.""" - self.update_ha_state(True) - - def __str__(self): - """String representation of sensor.""" - return "%s %s %s" % (self.name, self.vera_device.device_id, self.state) + VeraDevice.__init__(self, vera_device, controller) @property def state(self): """Return the name of the sensor.""" return self.current_value - @property - def name(self): - """Return the mame of the sensor.""" - return self._name - @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" @@ -144,11 +75,6 @@ class VeraSensor(Entity): attr['Vera Device Id'] = self.vera_device.vera_device_id return attr - @property - def should_poll(self): - """No polling needed.""" - return False - def update(self): """Update the state.""" if self.vera_device.category == "Temperature Sensor": diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 1797fc2af8a..ac19a1a7639 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -17,7 +17,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID) from homeassistant.components import ( group, wemo, wink, isy994, verisure, - zwave, tellduslive, tellstick, mysensors) + zwave, tellduslive, tellstick, mysensors, vera) DOMAIN = 'switch' SCAN_INTERVAL = 30 @@ -42,6 +42,7 @@ DISCOVERY_PLATFORMS = { tellduslive.DISCOVER_SWITCHES: 'tellduslive', mysensors.DISCOVER_SWITCHES: 'mysensors', tellstick.DISCOVER_SWITCHES: 'tellstick', + vera.DISCOVER_SWITCHES: 'vera', } PROP_TO_ATTR = { diff --git a/homeassistant/components/switch/vera.py b/homeassistant/components/switch/vera.py index c5e73faae44..55d4b77b554 100644 --- a/homeassistant/components/switch/vera.py +++ b/homeassistant/components/switch/vera.py @@ -6,94 +6,33 @@ https://home-assistant.io/components/switch.vera/ """ import logging -from requests.exceptions import RequestException - import homeassistant.util.dt as dt_util from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED, - EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON) + STATE_OFF, STATE_ON) +from homeassistant.components.vera import ( + VeraDevice, VERA_DEVICES, VERA_CONTROLLER) -REQUIREMENTS = ['pyvera==0.2.8'] +DEPENDENCIES = ['vera'] _LOGGER = logging.getLogger(__name__) -# pylint: disable=unused-argument -def get_devices(hass, config): +def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Find and return Vera switches.""" - import pyvera as veraApi - - base_url = config.get('vera_controller_url') - if not base_url: - _LOGGER.error( - "The required parameter 'vera_controller_url'" - " was not found in config" - ) - return False - - device_data = config.get('device_data', {}) - - vera_controller, created = veraApi.init_controller(base_url) - - if created: - def stop_subscription(event): - """Shutdown Vera subscriptions and subscription thread on exit.""" - _LOGGER.info("Shutting down subscriptions.") - vera_controller.stop() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription) - - devices = [] - try: - devices = vera_controller.get_devices([ - 'Switch', 'Armable Sensor', 'On/Off Switch']) - except RequestException: - # There was a network related error connecting to the vera controller. - _LOGGER.exception("Error communicating with Vera API") - return False - - vera_switches = [] - for device in devices: - extra_data = device_data.get(device.device_id, {}) - exclude = extra_data.get('exclude', False) - - if exclude is not True: - vera_switches.append( - VeraSwitch(device, vera_controller, extra_data)) - - return vera_switches + add_devices_callback( + VeraSwitch(device, VERA_CONTROLLER) for + device in VERA_DEVICES['switch']) -def setup_platform(hass, config, add_devices, discovery_info=None): - """Find and return Vera lights.""" - add_devices(get_devices(hass, config)) - - -class VeraSwitch(SwitchDevice): +class VeraSwitch(VeraDevice, SwitchDevice): """Representation of a Vera Switch.""" - def __init__(self, vera_device, controller, extra_data=None): + def __init__(self, vera_device, controller): """Initialize the Vera device.""" - self.vera_device = vera_device - self.extra_data = extra_data - self.controller = controller - if self.extra_data and self.extra_data.get('name'): - self._name = self.extra_data.get('name') - else: - self._name = self.vera_device.name - self._state = STATE_OFF - - self.controller.register(vera_device, self._update_callback) - self.update() - - def _update_callback(self, _device): - self.update_ha_state(True) - - @property - def name(self): - """Return the mame of the switch.""" - return self._name + self._state = False + VeraDevice.__init__(self, vera_device, controller) @property def device_state_attributes(self): @@ -134,19 +73,11 @@ class VeraSwitch(SwitchDevice): self._state = STATE_OFF self.update_ha_state() - @property - def should_poll(self): - """No polling needed.""" - return False - @property def is_on(self): """Return true if device is on.""" - return self._state == STATE_ON + return self._state def update(self): """Called by the vera device callback to update state.""" - if self.vera_device.is_switched_on(): - self._state = STATE_ON - else: - self._state = STATE_OFF + self._state = self.vera_device.is_switched_on() diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera.py new file mode 100644 index 00000000000..ed008a9da75 --- /dev/null +++ b/homeassistant/components/vera.py @@ -0,0 +1,142 @@ +""" +Support for Vera devices. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/vera/ +""" +import logging + +from collections import defaultdict +from requests.exceptions import RequestException + +from homeassistant import bootstrap +from homeassistant.const import ( + ATTR_SERVICE, ATTR_DISCOVERED, + EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED) +from homeassistant.helpers.entity import Entity +from homeassistant.loader import get_component + +REQUIREMENTS = ['pyvera==0.2.8'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'vera' + +VERA_CONTROLLER = None + +CONF_EXCLUDE = 'exclude' +CONF_LIGHTS = 'lights' + +BINARY_SENSOR = 'binary_sensor' +SENSOR = 'sensor' +LIGHT = 'light' +SWITCH = 'switch' + +DEVICE_CATEGORIES = { + 'Sensor': BINARY_SENSOR, + 'Temperature Sensor': SENSOR, + 'Light Sensor': SENSOR, + 'Humidity Sensor': SENSOR, + 'Dimmable Switch': LIGHT, + 'Switch': SWITCH, + 'Armable Sensor': SWITCH, + 'On/Off Switch': SWITCH, + # 'Window Covering': NOT SUPPORTED YET +} + +DISCOVER_BINARY_SENSORS = 'vera.binary_sensors' +DISCOVER_SENSORS = 'vera.sensors' +DISCOVER_LIGHTS = 'vera.lights' +DISCOVER_SWITCHES = 'vera.switchs' + +VERA_DEVICES = defaultdict(list) + + +# pylint: disable=unused-argument, too-many-function-args +def setup(hass, base_config): + """Common setup for Vera devices.""" + global VERA_CONTROLLER + import pyvera as veraApi + + config = base_config.get(DOMAIN) + base_url = config.get('vera_controller_url') + if not base_url: + _LOGGER.error( + "The required parameter 'vera_controller_url'" + " was not found in config" + ) + return False + + VERA_CONTROLLER, _ = veraApi.init_controller(base_url) + + def stop_subscription(event): + """Shutdown Vera subscriptions and subscription thread on exit.""" + _LOGGER.info("Shutting down subscriptions.") + VERA_CONTROLLER.stop() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription) + + try: + all_devices = VERA_CONTROLLER.get_devices( + list(DEVICE_CATEGORIES.keys())) + except RequestException: + # There was a network related error connecting to the vera controller. + _LOGGER.exception("Error communicating with Vera API") + return False + + exclude = config.get(CONF_EXCLUDE, []) + if not isinstance(exclude, list): + _LOGGER.error("'exclude' must be a list of device_ids") + return False + + lights_ids = config.get(CONF_LIGHTS, []) + if not isinstance(lights_ids, list): + _LOGGER.error("'lights' must be a list of device_ids") + return False + + for device in all_devices: + if device.device_id in exclude: + continue + dev_type = DEVICE_CATEGORIES.get(device.category) + if dev_type is None: + continue + if dev_type == SWITCH and device.device_id in lights_ids: + dev_type = LIGHT + VERA_DEVICES[dev_type].append(device) + + for comp_name, discovery in (((BINARY_SENSOR, DISCOVER_BINARY_SENSORS), + (SENSOR, DISCOVER_SENSORS), + (LIGHT, DISCOVER_LIGHTS), + (SWITCH, DISCOVER_SWITCHES))): + component = get_component(comp_name) + bootstrap.setup_component(hass, component.DOMAIN, config) + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, + {ATTR_SERVICE: discovery, + ATTR_DISCOVERED: {}}) + return True + + +class VeraDevice(Entity): + """Representation of a Vera devicetity.""" + + def __init__(self, vera_device, controller): + """Initialize the device.""" + self.vera_device = vera_device + self.controller = controller + self._name = self.vera_device.name + + self.controller.register(vera_device, self._update_callback) + self.update() + + def _update_callback(self, _device): + self.update_ha_state(True) + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def should_poll(self): + """No polling needed.""" + return False diff --git a/requirements_all.txt b/requirements_all.txt index a3f6a33bc27..a8ba7751145 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,9 +218,7 @@ python-wink==0.6.2 # homeassistant.components.keyboard pyuserinput==0.1.9 -# homeassistant.components.light.vera -# homeassistant.components.sensor.vera -# homeassistant.components.switch.vera +# homeassistant.components.vera pyvera==0.2.8 # homeassistant.components.wemo