From 86dad0c0457ca9155d8ec9150e1d46f58201ade8 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Wed, 6 Apr 2016 13:51:26 -0700 Subject: [PATCH 01/12] Add ZeroConf support to http.py --- homeassistant/components/http.py | 36 ++++++++++++++++++++++++++++---- requirements_all.txt | 3 +++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 00b35dda8c9..18bac7c46fd 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -7,6 +7,7 @@ https://home-assistant.io/developers/api/ import gzip import json import logging +import socket import ssl import threading import time @@ -27,7 +28,9 @@ from homeassistant.const import ( HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES, HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY, HTTP_METHOD_NOT_ALLOWED, HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY, - SERVER_PORT) + SERVER_PORT, __version__) + +REQUIREMENTS = ["zeroconf==0.17.5"] DOMAIN = "http" @@ -108,6 +111,10 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): # We will lazy init this one if needed self.event_forwarder = None + from zeroconf import Zeroconf + + self.zeroconf = Zeroconf() + if development: _LOGGER.info("running http in development mode") @@ -122,14 +129,35 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): def stop_http(event): """Stop the HTTP server.""" self.shutdown() + self.zeroconf.unregister_all_services() self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http) protocol = 'https' if self.use_ssl else 'http' - _LOGGER.info( - "Starting web interface at %s://%s:%d", - protocol, self.server_address[0], self.server_address[1]) + base_url = "{}://{}:{}".format(protocol, util.get_local_ip(), + self.server_address[1]) + + zeroconf_type = "_home-assistant._tcp.local." + zeroconf_name = "{}.{}".format(self.hass.config.location_name, + zeroconf_type) + + ip_address = socket.inet_aton(util.get_local_ip()) + + has_device_tracker = ("device_tracker" in self.hass.config.components) + + params = {"version": __version__, "base_url": base_url, + "device_tracker_component": has_device_tracker, + "needs_password": (self.api_password != "")} + + from zeroconf import ServiceInfo + + info = ServiceInfo(zeroconf_type, zeroconf_name, ip_address, + self.server_address[1], 0, 0, params) + + self.zeroconf.register_service(info) + + _LOGGER.info("Starting web interface at %s", base_url) # 31-1-2015: Refactored frontend/api components out of this component # To prevent stuff from breaking, load the two extracted components diff --git a/requirements_all.txt b/requirements_all.txt index c0b011e869d..56829f67f97 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -313,3 +313,6 @@ xbee-helper==0.0.6 # homeassistant.components.sensor.yr xmltodict + +# homeassistant.components.http +zeroconf==0.17.5 From 27aabd961cca59828efbfdba0fc49ff65ccf79d8 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Wed, 6 Apr 2016 13:51:43 -0700 Subject: [PATCH 02/12] Remove unnecessary variable --- homeassistant/components/http.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 18bac7c46fd..99f745adcb2 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -142,8 +142,6 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): zeroconf_name = "{}.{}".format(self.hass.config.location_name, zeroconf_type) - ip_address = socket.inet_aton(util.get_local_ip()) - has_device_tracker = ("device_tracker" in self.hass.config.components) params = {"version": __version__, "base_url": base_url, @@ -152,7 +150,8 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): from zeroconf import ServiceInfo - info = ServiceInfo(zeroconf_type, zeroconf_name, ip_address, + info = ServiceInfo(zeroconf_type, zeroconf_name, + socket.inet_aton(util.get_local_ip()), self.server_address[1], 0, 0, params) self.zeroconf.register_service(info) From c33c2c01d29c611fe1c8b58fbe85d00f9dd2e422 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 15:34:04 -0700 Subject: [PATCH 03/12] Break Zeroconf into its own component --- homeassistant/components/http.py | 37 +++----------------- homeassistant/components/zeroconf.py | 50 ++++++++++++++++++++++++++++ requirements_all.txt | 2 +- 3 files changed, 56 insertions(+), 33 deletions(-) create mode 100644 homeassistant/components/zeroconf.py diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 99f745adcb2..03d18170b3b 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -30,8 +30,6 @@ from homeassistant.const import ( HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY, SERVER_PORT, __version__) -REQUIREMENTS = ["zeroconf==0.17.5"] - DOMAIN = "http" CONF_API_PASSWORD = "api_password" @@ -106,15 +104,14 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.development = development self.paths = [] self.sessions = SessionStore() - self.use_ssl = ssl_certificate is not None + self.protocol = 'https' if ssl_certificate is not None else 'http' + self.base_url = "{}://{}:{}".format(self.protocol, + util.get_local_ip(), + self.server_address[1]) # We will lazy init this one if needed self.event_forwarder = None - from zeroconf import Zeroconf - - self.zeroconf = Zeroconf() - if development: _LOGGER.info("running http in development mode") @@ -129,34 +126,10 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): def stop_http(event): """Stop the HTTP server.""" self.shutdown() - self.zeroconf.unregister_all_services() self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http) - protocol = 'https' if self.use_ssl else 'http' - - base_url = "{}://{}:{}".format(protocol, util.get_local_ip(), - self.server_address[1]) - - zeroconf_type = "_home-assistant._tcp.local." - zeroconf_name = "{}.{}".format(self.hass.config.location_name, - zeroconf_type) - - has_device_tracker = ("device_tracker" in self.hass.config.components) - - params = {"version": __version__, "base_url": base_url, - "device_tracker_component": has_device_tracker, - "needs_password": (self.api_password != "")} - - from zeroconf import ServiceInfo - - info = ServiceInfo(zeroconf_type, zeroconf_name, - socket.inet_aton(util.get_local_ip()), - self.server_address[1], 0, 0, params) - - self.zeroconf.register_service(info) - - _LOGGER.info("Starting web interface at %s", base_url) + _LOGGER.info("Starting web interface at %s", self.base_url) # 31-1-2015: Refactored frontend/api components out of this component # To prevent stuff from breaking, load the two extracted components diff --git a/homeassistant/components/zeroconf.py b/homeassistant/components/zeroconf.py new file mode 100644 index 00000000000..a9e773fae6d --- /dev/null +++ b/homeassistant/components/zeroconf.py @@ -0,0 +1,50 @@ +""" +This module exposes Home Assistant via Zeroconf, also sometimes known as +Bonjour, Rendezvous, Avahi or Multicast DNS (mDNS). + +For more details about Zeroconf, please refer to the documentation at +https://home-assistant.io/components/zeroconf/ +""" +import logging +import socket + +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, __version__) + +import homeassistant.util as util + +REQUIREMENTS = ["zeroconf==0.17.5"] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "zeroconf" + +ZEROCONF_TYPE = "_home-assistant._tcp.local." + +DEPENDENCIES = ["http", "api"] + +def setup(hass, config): + + from zeroconf import Zeroconf, ServiceInfo + + zeroconf = Zeroconf() + + zeroconf_name = "{}.{}".format(hass.config.location_name, + ZEROCONF_TYPE) + + params = {"version": __version__, "base_url": hass.http.base_url, + "has_password": (hass.http.api_password != "")} + + info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name, + socket.inet_aton(util.get_local_ip()), + hass.http.server_address[1], 0, 0, params) + + zeroconf.register_service(info) + + def stop_zeroconf(event): + """Stop Zeroconf.""" + zeroconf.unregister_all_services() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf) + + return True diff --git a/requirements_all.txt b/requirements_all.txt index 56829f67f97..720c49a47c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -314,5 +314,5 @@ xbee-helper==0.0.6 # homeassistant.components.sensor.yr xmltodict -# homeassistant.components.http +# homeassistant.components.zeroconf zeroconf==0.17.5 From beac69ad175fd730d8d7054fedd1a6c45c4f34ff Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 16:02:07 -0700 Subject: [PATCH 04/12] Final clean up, flake8, pylint, change a variable name, remove unnecessary imports --- homeassistant/components/http.py | 9 ++++-- homeassistant/components/zeroconf.py | 45 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 03d18170b3b..bacdf5b15a0 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -7,7 +7,6 @@ https://home-assistant.io/developers/api/ import gzip import json import logging -import socket import ssl import threading import time @@ -28,7 +27,7 @@ from homeassistant.const import ( HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES, HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY, HTTP_METHOD_NOT_ALLOWED, HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY, - SERVER_PORT, __version__) + SERVER_PORT) DOMAIN = "http" @@ -105,8 +104,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.paths = [] self.sessions = SessionStore() self.protocol = 'https' if ssl_certificate is not None else 'http' + if server_address[0] == '0.0.0.0': + self.routable_address = util.get_local_ip() + else: + self.routable_address = server_address[0] self.base_url = "{}://{}:{}".format(self.protocol, - util.get_local_ip(), + self.routable_address, self.server_address[1]) # We will lazy init this one if needed diff --git a/homeassistant/components/zeroconf.py b/homeassistant/components/zeroconf.py index a9e773fae6d..cd472734f86 100644 --- a/homeassistant/components/zeroconf.py +++ b/homeassistant/components/zeroconf.py @@ -1,6 +1,7 @@ """ -This module exposes Home Assistant via Zeroconf, also sometimes known as -Bonjour, Rendezvous, Avahi or Multicast DNS (mDNS). +This module exposes Home Assistant via Zeroconf. + +Zeroconf is also known as Bonjour, Avahi or Multicast DNS (mDNS). For more details about Zeroconf, please refer to the documentation at https://home-assistant.io/components/zeroconf/ @@ -8,10 +9,7 @@ https://home-assistant.io/components/zeroconf/ import logging import socket -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, __version__) - -import homeassistant.util as util +from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, __version__) REQUIREMENTS = ["zeroconf==0.17.5"] @@ -21,30 +19,31 @@ DOMAIN = "zeroconf" ZEROCONF_TYPE = "_home-assistant._tcp.local." -DEPENDENCIES = ["http", "api"] +DEPENDENCIES = ["http"] + def setup(hass, config): + """Set up Zeroconf and make Home Assistant discoverable.""" + from zeroconf import Zeroconf, ServiceInfo - from zeroconf import Zeroconf, ServiceInfo + zeroconf = Zeroconf() - zeroconf = Zeroconf() + zeroconf_name = "{}.{}".format(hass.config.location_name, + ZEROCONF_TYPE) - zeroconf_name = "{}.{}".format(hass.config.location_name, - ZEROCONF_TYPE) + params = {"version": __version__, "base_url": hass.http.base_url, + "needs_auth": (hass.http.api_password != "")} - params = {"version": __version__, "base_url": hass.http.base_url, - "has_password": (hass.http.api_password != "")} + info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name, + socket.inet_aton(hass.http.routable_address), + hass.http.server_address[1], 0, 0, params) - info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name, - socket.inet_aton(util.get_local_ip()), - hass.http.server_address[1], 0, 0, params) + zeroconf.register_service(info) - zeroconf.register_service(info) + def stop_zeroconf(event): + """Stop Zeroconf.""" + zeroconf.unregister_service(info) - def stop_zeroconf(event): - """Stop Zeroconf.""" - zeroconf.unregister_all_services() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf) - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf) - - return True + return True From e70338dfe14e54179757e2b225b417b717d4c4c0 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 16:03:40 -0700 Subject: [PATCH 05/12] Block zeroconf from tests --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index 2f6e7a9adf8..9e54fdccecc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -65,6 +65,8 @@ omit = homeassistant/components/scsgate.py homeassistant/components/*/scsgate.py + homeassistant/components/zeroconf.py + homeassistant/components/binary_sensor/arest.py homeassistant/components/binary_sensor/rest.py homeassistant/components/browser.py From 085d90ed6758a81de05dc96769b0b489befe2c5e Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 16:08:00 -0700 Subject: [PATCH 06/12] Revert all http.py changes --- homeassistant/components/http.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index bacdf5b15a0..00b35dda8c9 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -103,14 +103,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.development = development self.paths = [] self.sessions = SessionStore() - self.protocol = 'https' if ssl_certificate is not None else 'http' - if server_address[0] == '0.0.0.0': - self.routable_address = util.get_local_ip() - else: - self.routable_address = server_address[0] - self.base_url = "{}://{}:{}".format(self.protocol, - self.routable_address, - self.server_address[1]) + self.use_ssl = ssl_certificate is not None # We will lazy init this one if needed self.event_forwarder = None @@ -132,7 +125,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http) - _LOGGER.info("Starting web interface at %s", self.base_url) + protocol = 'https' if self.use_ssl else 'http' + + _LOGGER.info( + "Starting web interface at %s://%s:%d", + protocol, self.server_address[0], self.server_address[1]) # 31-1-2015: Refactored frontend/api components out of this component # To prevent stuff from breaking, load the two extracted components From e1ffdcc5f12be623633e2abab2041fcb574173ea Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 16:09:52 -0700 Subject: [PATCH 07/12] Use hass.config.api instead of hass.http --- homeassistant/components/zeroconf.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zeroconf.py b/homeassistant/components/zeroconf.py index cd472734f86..4c5c21f08dd 100644 --- a/homeassistant/components/zeroconf.py +++ b/homeassistant/components/zeroconf.py @@ -13,14 +13,14 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, __version__) REQUIREMENTS = ["zeroconf==0.17.5"] +DEPENDENCIES = ["api"] + _LOGGER = logging.getLogger(__name__) DOMAIN = "zeroconf" ZEROCONF_TYPE = "_home-assistant._tcp.local." -DEPENDENCIES = ["http"] - def setup(hass, config): """Set up Zeroconf and make Home Assistant discoverable.""" @@ -31,12 +31,12 @@ def setup(hass, config): zeroconf_name = "{}.{}".format(hass.config.location_name, ZEROCONF_TYPE) - params = {"version": __version__, "base_url": hass.http.base_url, - "needs_auth": (hass.http.api_password != "")} + params = {"version": __version__, "base_url": hass.config.api.base_url, + "needs_auth": (hass.config.api.api_password != "")} info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name, - socket.inet_aton(hass.http.routable_address), - hass.http.server_address[1], 0, 0, params) + socket.inet_aton(hass.config.api.host), + hass.config.api.port, 0, 0, params) zeroconf.register_service(info) From a862e994c71fee34e9365d7a9cc4f449f2b9850d Mon Sep 17 00:00:00 2001 From: Dennis Karpienski Date: Mon, 11 Apr 2016 01:59:53 +0200 Subject: [PATCH 08/12] Thinkingcleaner support (#1784) * added first implementation of thinking cleaner * fix lock release * fixed tox flaws * updated coveragerc * fixed lock * changed update lock * fixed codestyle --- .coveragerc | 1 + .../components/sensor/thinkingcleaner.py | 114 +++++++++++++++ .../components/switch/thinkingcleaner.py | 131 ++++++++++++++++++ requirements_all.txt | 4 + 4 files changed, 250 insertions(+) create mode 100644 homeassistant/components/sensor/thinkingcleaner.py create mode 100644 homeassistant/components/switch/thinkingcleaner.py diff --git a/.coveragerc b/.coveragerc index 2f6e7a9adf8..f3dd3d9115e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -173,6 +173,7 @@ omit = homeassistant/components/thermostat/homematic.py homeassistant/components/thermostat/proliphix.py homeassistant/components/thermostat/radiotherm.py + homeassistant/components/*/thinkingcleaner.py [report] diff --git a/homeassistant/components/sensor/thinkingcleaner.py b/homeassistant/components/sensor/thinkingcleaner.py new file mode 100644 index 00000000000..1ba8593650e --- /dev/null +++ b/homeassistant/components/sensor/thinkingcleaner.py @@ -0,0 +1,114 @@ +"""Support for ThinkingCleaner.""" +import logging +from datetime import timedelta + +import homeassistant.util as util +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['https://github.com/TheRealLink/pythinkingcleaner' + '/archive/v0.0.2.zip' + '#pythinkingcleaner==0.0.2'] + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) + +SENSOR_TYPES = { + 'battery': ['Battery', '%', 'mdi:battery'], + 'state': ['State', None, None], + 'capacity': ['Capacity', None, None], +} + +STATES = { + 'st_base': 'On homebase: Not Charging', + 'st_base_recon': 'On homebase: Reconditioning Charging', + 'st_base_full': 'On homebase: Full Charging', + 'st_base_trickle': 'On homebase: Trickle Charging', + 'st_base_wait': 'On homebase: Waiting', + 'st_plug': 'Plugged in: Not Charging', + 'st_plug_recon': 'Plugged in: Reconditioning Charging', + 'st_plug_full': 'Plugged in: Full Charging', + 'st_plug_trickle': 'Plugged in: Trickle Charging', + 'st_plug_wait': 'Plugged in: Waiting', + 'st_stopped': 'Stopped', + 'st_clean': 'Cleaning', + 'st_cleanstop': 'Stopped with cleaning', + 'st_clean_spot': 'Spot cleaning', + 'st_clean_max': 'Max cleaning', + 'st_delayed': 'Delayed cleaning will start soon', + 'st_dock': 'Searching Homebase', + 'st_pickup': 'Roomba picked up', + 'st_remote': 'Remote control driving', + 'st_wait': 'Waiting for command', + 'st_off': 'Off', + 'st_error': 'Error', + 'st_locate': 'Find me!', + 'st_unknown': 'Unknown state', +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ThinkingCleaner platform.""" + from pythinkingcleaner import Discovery + + discovery = Discovery() + devices = discovery.discover() + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + def update_devices(): + """Update all devices.""" + for device_object in devices: + device_object.update() + + dev = [] + for device in devices: + for type_name in SENSOR_TYPES.keys(): + dev.append(ThinkingCleanerSensor(device, type_name, + update_devices)) + + add_devices(dev) + + +class ThinkingCleanerSensor(Entity): + """ThinkingCleaner Sensor.""" + + def __init__(self, tc_object, sensor_type, update_devices): + """Initialize the ThinkingCleaner.""" + self.type = sensor_type + + self._tc_object = tc_object + self._update_devices = update_devices + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._tc_object.name + ' ' + SENSOR_TYPES[self.type][0] + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return SENSOR_TYPES[self.type][2] + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + def update(self): + """Update the sensor.""" + self._update_devices() + + if self.type == 'battery': + self._state = self._tc_object.battery + elif self.type == 'state': + self._state = STATES[self._tc_object.status] + elif self.type == 'capacity': + self._state = self._tc_object.capacity diff --git a/homeassistant/components/switch/thinkingcleaner.py b/homeassistant/components/switch/thinkingcleaner.py new file mode 100644 index 00000000000..3bc4484db38 --- /dev/null +++ b/homeassistant/components/switch/thinkingcleaner.py @@ -0,0 +1,131 @@ +"""Support for ThinkingCleaner.""" +import time +import logging +from datetime import timedelta + +import homeassistant.util as util + +from homeassistant.const import (STATE_ON, STATE_OFF) +from homeassistant.helpers.entity import ToggleEntity + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['https://github.com/TheRealLink/pythinkingcleaner' + '/archive/v0.0.2.zip' + '#pythinkingcleaner==0.0.2'] + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) + +MIN_TIME_TO_WAIT = timedelta(seconds=5) +MIN_TIME_TO_LOCK_UPDATE = 5 + +SWITCH_TYPES = { + 'clean': ['Clean', None, None], + 'dock': ['Dock', None, None], + 'find': ['Find', None, None], +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the ThinkingCleaner platform.""" + from pythinkingcleaner import Discovery + + discovery = Discovery() + devices = discovery.discover() + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + def update_devices(): + """Update all devices.""" + for device_object in devices: + device_object.update() + + dev = [] + for device in devices: + for type_name in SWITCH_TYPES.keys(): + dev.append(ThinkingCleanerSwitch(device, type_name, + update_devices)) + + add_devices(dev) + + +class ThinkingCleanerSwitch(ToggleEntity): + """ThinkingCleaner Switch (dock, clean, find me).""" + + def __init__(self, tc_object, switch_type, update_devices): + """Initialize the ThinkingCleaner.""" + self.type = switch_type + + self._update_devices = update_devices + self._tc_object = tc_object + self._state = \ + self._tc_object.is_cleaning if switch_type == 'clean' else False + self.lock = False + self.last_lock_time = None + self.graceful_state = False + + def lock_update(self): + """Lock the update since TC clean takes some time to update.""" + if self.is_update_locked(): + return + self.lock = True + self.last_lock_time = time.time() + + def reset_update_lock(self): + """Reset the update lock.""" + self.lock = False + self.last_lock_time = None + + def set_graceful_lock(self, state): + """Set the graceful state.""" + self.graceful_state = state + self.reset_update_lock() + self.lock_update() + + def is_update_locked(self): + """Check if the update method is locked.""" + if self.last_lock_time is None: + return False + + if time.time() - self.last_lock_time >= MIN_TIME_TO_LOCK_UPDATE: + self.last_lock_time = None + return False + + return True + + @property + def name(self): + """Return the name of the sensor.""" + return self._tc_object.name + ' ' + SWITCH_TYPES[self.type][0] + + @property + def is_on(self): + """Return true if device is on.""" + if self.type == 'clean': + return self.graceful_state \ + if self.is_update_locked() else self._tc_object.is_cleaning + + return False + + def turn_on(self, **kwargs): + """Turn the device on.""" + if self.type == 'clean': + self.set_graceful_lock(True) + self._tc_object.start_cleaning() + elif self.type == 'dock': + self._tc_object.dock() + elif self.type == 'find': + self._tc_object.find_me() + + def turn_off(self, **kwargs): + """Turn the device off.""" + if self.type == 'clean': + self.set_graceful_lock(False) + self._tc_object.stop_cleaning() + + def update(self): + """Update the switch state (Only for clean).""" + if self.type == 'clean' and not self.is_update_locked(): + self._tc_object.update() + self._state = STATE_ON \ + if self._tc_object.is_cleaning else STATE_OFF diff --git a/requirements_all.txt b/requirements_all.txt index c1c678f4f44..eaf37715e45 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -76,6 +76,10 @@ https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a # homeassistant.components.switch.dlink https://github.com/LinuxChristian/pyW215/archive/v0.1.1.zip#pyW215==0.1.1 +# homeassistant.components.sensor.thinkingcleaner +# homeassistant.components.switch.thinkingcleaner +https://github.com/TheRealLink/pythinkingcleaner/archive/v0.0.2.zip#pythinkingcleaner==0.0.2 + # homeassistant.components.alarm_control_panel.alarmdotcom https://github.com/Xorso/pyalarmdotcom/archive/0.1.1.zip#pyalarmdotcom==0.1.1 From 197388a9b2bbf762ef07e66f089b719ac6a1c0dc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 10 Apr 2016 17:35:10 -0700 Subject: [PATCH 09/12] Prevent device tracker error --- homeassistant/components/device_tracker/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 1866e972e28..beb9e4a7214 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -94,7 +94,7 @@ def setup(hass, config): yaml_path = hass.config.path(YAML_DEVICES) conf = config.get(DOMAIN, {}) - if isinstance(conf, list): + if isinstance(conf, list) and len(conf) > 0: conf = conf[0] consider_home = timedelta( seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int, From 1de45ebe8bbc30b7203123630ba4a23ef7f419bf Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 17:59:21 -0700 Subject: [PATCH 10/12] Fix api_password conditional and close zeroconf when we shut down --- homeassistant/components/zeroconf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zeroconf.py b/homeassistant/components/zeroconf.py index 4c5c21f08dd..bda6dcb153b 100644 --- a/homeassistant/components/zeroconf.py +++ b/homeassistant/components/zeroconf.py @@ -32,7 +32,7 @@ def setup(hass, config): ZEROCONF_TYPE) params = {"version": __version__, "base_url": hass.config.api.base_url, - "needs_auth": (hass.config.api.api_password != "")} + "needs_auth": (hass.config.api.api_password is not None)} info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name, socket.inet_aton(hass.config.api.host), @@ -43,6 +43,7 @@ def setup(hass, config): def stop_zeroconf(event): """Stop Zeroconf.""" zeroconf.unregister_service(info) + zeroconf.close() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf) From eca1631f1bed5f23d9c6ed471015c32319c99e5d Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 18:05:30 -0700 Subject: [PATCH 11/12] Update netdisco dependency to 0.6.3 --- homeassistant/components/discovery.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index f03fdf35929..6f301e549d1 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -15,7 +15,7 @@ from homeassistant.const import ( EVENT_PLATFORM_DISCOVERED) DOMAIN = "discovery" -REQUIREMENTS = ['netdisco==0.6.2'] +REQUIREMENTS = ['netdisco==0.6.3'] SCAN_INTERVAL = 300 # seconds diff --git a/requirements_all.txt b/requirements_all.txt index 7a1e619780c..5fb0646b426 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -132,7 +132,7 @@ messagebird==1.1.1 mficlient==0.3.0 # homeassistant.components.discovery -netdisco==0.6.2 +netdisco==0.6.3 # homeassistant.components.sensor.neurio_energy neurio==0.2.10 From a3959d5e01112e486f4db84fe073df91e6e38ba3 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 10 Apr 2016 18:10:31 -0700 Subject: [PATCH 12/12] Update netdisco dependency to 0.6.4 (deja vu all over again!) --- homeassistant/components/discovery.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 6f301e549d1..900d826e61a 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -15,7 +15,7 @@ from homeassistant.const import ( EVENT_PLATFORM_DISCOVERED) DOMAIN = "discovery" -REQUIREMENTS = ['netdisco==0.6.3'] +REQUIREMENTS = ['netdisco==0.6.4'] SCAN_INTERVAL = 300 # seconds diff --git a/requirements_all.txt b/requirements_all.txt index 5fb0646b426..c9035936fc2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -132,7 +132,7 @@ messagebird==1.1.1 mficlient==0.3.0 # homeassistant.components.discovery -netdisco==0.6.3 +netdisco==0.6.4 # homeassistant.components.sensor.neurio_energy neurio==0.2.10