diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 5cbd07d0e59..90317cdf90a 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -6,7 +6,7 @@ Component to interface with various sensors that can be monitored. import logging from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.components import wink, zwave, isy994 +from homeassistant.components import wink, zwave, isy994, verisure DOMAIN = 'sensor' DEPENDENCIES = [] @@ -18,7 +18,8 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' DISCOVERY_PLATFORMS = { wink.DISCOVER_SENSORS: 'wink', zwave.DISCOVER_SENSORS: 'zwave', - isy994.DISCOVER_SENSORS: 'isy994' + isy994.DISCOVER_SENSORS: 'isy994', + verisure.DISCOVER_SENSORS: 'verisure' } diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py new file mode 100644 index 00000000000..163d608d7d2 --- /dev/null +++ b/homeassistant/components/sensor/verisure.py @@ -0,0 +1,83 @@ +""" +homeassistant.components.sensor.verisure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Interfaces with Verisure sensors. +""" +import logging + +import homeassistant.components.verisure as verisure + +from homeassistant.helpers.entity import Entity +from homeassistant.const import TEMP_CELCIUS + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Verisure platform. """ + + if not verisure.MY_PAGES: + _LOGGER.error('A connection has not been made to Verisure mypages.') + return False + + sensors = [] + + sensors.extend([ + VerisureClimateDevice(value) + for value in verisure.get_climate_status().values() + ]) + + sensors.extend([ + VerisureAlarmDevice(value) + for value in verisure.get_alarm_status().values() + ]) + + add_devices(sensors) + + +class VerisureClimateDevice(Entity): + """ represents a Verisure climate sensor within home assistant. """ + + def __init__(self, climate_status): + self._id = climate_status.id + self._device = verisure.MY_PAGES.DEVICE_CLIMATE + + @property + def name(self): + """ Returns the name of the device. """ + return verisure.STATUS[self._device][self._id].location + + @property + def state(self): + """ Returns the state of the device. """ + # remove ° character + return verisure.STATUS[self._device][self._id].temperature[:-1] + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity """ + return TEMP_CELCIUS # can verisure report in fahrenheit? + + def update(self): + verisure.update() + + +class VerisureAlarmDevice(Entity): + """ represents a Verisure alarm status within home assistant. """ + + def __init__(self, alarm_status): + self._id = alarm_status.id + self._device = verisure.MY_PAGES.DEVICE_ALARM + + @property + def name(self): + """ Returns the name of the device. """ + return 'Alarm {}'.format(self._id) + + @property + def state(self): + """ Returns the state of the device. """ + return verisure.STATUS[self._device][self._id].label + + def update(self): + verisure.update() diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 1d6f0b79d52..424d4505d39 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) -from homeassistant.components import group, discovery, wink, isy994 +from homeassistant.components import group, discovery, wink, isy994, verisure DOMAIN = 'switch' DEPENDENCIES = [] @@ -32,6 +32,7 @@ DISCOVERY_PLATFORMS = { discovery.SERVICE_WEMO: 'wemo', wink.DISCOVER_SWITCHES: 'wink', isy994.DISCOVER_SWITCHES: 'isy994', + verisure.DISCOVER_SWITCHES: 'verisure' } PROP_TO_ATTR = { diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py new file mode 100644 index 00000000000..f71ca7550a7 --- /dev/null +++ b/homeassistant/components/switch/verisure.py @@ -0,0 +1,70 @@ +""" +homeassistant.components.switch.verisure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Verisure Smartplugs + +Configuration: + +switch: + platform: verisure + +Variables: + +""" +import logging + +import homeassistant.components.verisure as verisure +from homeassistant.components.switch import SwitchDevice + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + if not verisure.MY_PAGES: + _LOGGER.error('A connection has not been made to Verisure mypages.') + return False + + switches = [] + + switches.extend([ + VerisureSmartplug(value) + for value in verisure.get_smartplug_status().values() + ]) + + add_devices(switches) + + +class VerisureSmartplug(SwitchDevice): + """ Represents a Verisure smartplug. """ + def __init__(self, smartplug_status): + self._id = smartplug_status.id + self.status_on = verisure.MY_PAGES.SMARTPLUG_ON + self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF + + @property + def name(self): + """ Get the name (location) of the smartplug. """ + return verisure.get_smartplug_status()[self._id].location + + @property + def is_on(self): + """ Returns True if on """ + plug_status = verisure.get_smartplug_status()[self._id].status + return plug_status == self.status_on + + def turn_on(self): + """ Set smartplug status on """ + verisure.MY_PAGES.set_smartplug_status( + self._id, + self.status_on) + + def turn_off(self): + """ Set smartplug status off """ + verisure.MY_PAGES.set_smartplug_status( + self._id, + self.status_off) + + def update(self): + verisure.update() diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py new file mode 100644 index 00000000000..18a7701a3ab --- /dev/null +++ b/homeassistant/components/verisure.py @@ -0,0 +1,128 @@ +""" +components.verisure +~~~~~~~~~~~~~~~~~~ +""" +import logging +from datetime import timedelta + +from homeassistant import bootstrap +from homeassistant.loader import get_component + +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle +from homeassistant.const import ( + EVENT_PLATFORM_DISCOVERED, + ATTR_SERVICE, ATTR_DISCOVERED, + CONF_USERNAME, CONF_PASSWORD) + + +DOMAIN = "verisure" +DISCOVER_SENSORS = 'verisure.sensors' +DISCOVER_SWITCHES = 'verisure.switches' + +DEPENDENCIES = [] +REQUIREMENTS = [ + 'https://github.com/persandstrom/python-verisure/archive/master.zip' + ] + +_LOGGER = logging.getLogger(__name__) + +MY_PAGES = None +STATUS = {} + +VERISURE_LOGIN_ERROR = None +VERISURE_ERROR = None + +# if wrong password was given don't try again +WRONG_PASSWORD_GIVEN = False + +MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=5) + + +def setup(hass, config): + """ Setup the Verisure component. """ + + if not validate_config(config, + {DOMAIN: [CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return False + + from verisure import MyPages, LoginError, Error + + STATUS[MyPages.DEVICE_ALARM] = {} + STATUS[MyPages.DEVICE_CLIMATE] = {} + STATUS[MyPages.DEVICE_SMARTPLUG] = {} + + global MY_PAGES + MY_PAGES = MyPages( + config[DOMAIN][CONF_USERNAME], + config[DOMAIN][CONF_PASSWORD]) + global VERISURE_LOGIN_ERROR, VERISURE_ERROR + VERISURE_LOGIN_ERROR = LoginError + VERISURE_ERROR = Error + + try: + MY_PAGES.login() + except (ConnectionError, Error) as ex: + _LOGGER.error('Could not log in to verisure mypages, %s', ex) + return False + + update() + + # Load components for the devices in the ISY controller that we support + for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), + ('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 + + +def get_alarm_status(): + ''' return a list of status overviews for alarm components ''' + return STATUS[MY_PAGES.DEVICE_ALARM] + + +def get_climate_status(): + ''' return a list of status overviews for alarm components ''' + return STATUS[MY_PAGES.DEVICE_CLIMATE] + + +def get_smartplug_status(): + ''' return a list of status overviews for alarm components ''' + return STATUS[MY_PAGES.DEVICE_SMARTPLUG] + + +def reconnect(): + ''' reconnect to verisure mypages ''' + try: + MY_PAGES.login() + except VERISURE_LOGIN_ERROR as ex: + _LOGGER.error("Could not login to Verisure mypages, %s", ex) + global WRONG_PASSWORD_GIVEN + WRONG_PASSWORD_GIVEN = True + except (ConnectionError, VERISURE_ERROR) as ex: + _LOGGER.error("Could not login to Verisure mypages, %s", ex) + + +@Throttle(MIN_TIME_BETWEEN_REQUESTS) +def update(): + ''' Updates the status of verisure components ''' + if WRONG_PASSWORD_GIVEN: + # Is there any way to inform user? + return + + try: + for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_ALARM): + STATUS[MY_PAGES.DEVICE_ALARM][overview.id] = overview + for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_CLIMATE): + STATUS[MY_PAGES.DEVICE_CLIMATE][overview.id] = overview + for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_SMARTPLUG): + STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview + except ConnectionError as ex: + _LOGGER.error('Caught connection error %s, tries to reconnect', ex) + reconnect() diff --git a/requirements.txt b/requirements.txt index 445a2fe3657..24027be2d3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -110,3 +110,5 @@ paho-mqtt>=1.1 # PyModbus (modbus) https://github.com/bashwork/pymodbus/archive/python3.zip#pymodbus>=1.2.0 +# Verisure +https://github.com/persandstrom/python-verisure/archive/master.zip