From 469d0619ba0bc777f7ace316aa7a7d1f3bfb9aa5 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 25 Oct 2015 21:48:01 +0100 Subject: [PATCH 01/82] mqtt light component --- homeassistant/components/light/mqtt.py | 152 +++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 homeassistant/components/light/mqtt.py diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py new file mode 100644 index 00000000000..2fc9fef7d50 --- /dev/null +++ b/homeassistant/components/light/mqtt.py @@ -0,0 +1,152 @@ +""" +homeassistant.components.light.mqtt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Allows to configure a MQTT light. +""" +import logging +import homeassistant.components.mqtt as mqtt +from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_XY_COLOR) + +import random + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = "MQTT Light" +DEFAULT_QOS = 0 +DEFAULT_PAYLOAD_ON = "on" +DEFAULT_PAYLOAD_OFF = "off" +DEFAULT_RGB = [ 255, 255, 255 ] +DEFAULT_RGB_PATTERN = "%d,%d,%d" +DEFAULT_BRIGHTNESS = 120 + +DEFAULT_STATE_TOPIC = "homeassistant/light/state" +DEFAULT_COMMAND_TOPIC = "homeassistant/light/switch" + +DEFAULT_STATE_BRIGHTNESS = "homeassistant/light/brightness/state" +DEFAULT_COMMAND_BRIGHTNESS = "homeassistant/light/brightness/set" + +DEFAULT_STATE_RGB = "homeassistant/light/rgb/state" +DEFAULT_COMMAND_RGB = "homeassistant/light/rgb/set" + +DEPENDENCIES = ['mqtt'] + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Add MQTT Light. """ + + if config.get('command_topic') is None: + _LOGGER.error("Missing required variable: command_topic") + return False + + add_devices_callback([MqttLight( + hass, + config.get('name', DEFAULT_NAME), + config.get('state_topic', DEFAULT_STATE_TOPIC), + config.get('command_topic', DEFAULT_COMMAND_TOPIC), + config.get('brightness_state_topic', DEFAULT_STATE_BRIGHTNESS), + config.get('brightness_command_topic', DEFAULT_COMMAND_BRIGHTNESS), + config.get('rgb_state_topic', DEFAULT_STATE_RGB), + config.get('rgb_command_topic', DEFAULT_COMMAND_RGB), + config.get('rgb', DEFAULT_RGB ), + config.get('qos', DEFAULT_QOS), + config.get('payload_on', DEFAULT_PAYLOAD_ON), + config.get('payload_off', DEFAULT_PAYLOAD_OFF), + config.get('brightness', DEFAULT_BRIGHTNESS))]) + +class MqttLight(Light): + """ Provides a demo switch. """ + def __init__(self, hass, name, state_topic, command_topic, brightness_state_topic, brightness_command_topic, rgb_state_topic, rgb_command_topic, rgb, qos, payload_on, payload_off, brightness): + self._name = name + self._hass = hass + self._state = False + self._command_topic = command_topic + self._state_topic = state_topic + self._brightness_state_topic = brightness_state_topic + self._brightness_command_topic = brightness_command_topic + self._rgb_state_topic = rgb_state_topic + self._rgb_command_topic = rgb_command_topic + self._qos = qos + self._payload_on = payload_on + self._payload_off = payload_off + self._rgb = rgb + self._brightness = brightness + self._xy = [[ 0.5, 0.5 ]] + + def message_received(topic, payload, qos): + """ A new MQTT message has been received. """ + if payload == self._payload_on: + self._state = True + self.update_ha_state() + elif payload == self._payload_off: + self._state = False + self.update_ha_state() + + def brightness_received(topic, payload, qos): + """ A new MQTT message has been received. """ + self._brightness = int(payload) + self.update_ha_state() + + def rgb_received(topic, payload, qos): + """ A new MQTT message has been received. """ + rgb = payload.split( "," ) + self._rgb = list(map(int, rgb)) + self.update_ha_state() + + # subscribe the state_topic + mqtt.subscribe(self._hass, self._state_topic, message_received, self._qos) + mqtt.subscribe(self._hass, self._brightness_state_topic, brightness_received, self._qos) + mqtt.subscribe(self._hass, self._rgb_state_topic, rgb_received, self._qos) + + @property + def should_poll(self): + """ No polling needed for a demo light. """ + return False + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + @property + def brightness(self): + """ Brightness of this light between 0..255. """ + return self._brightness + + @property + def rgb_color(self): + """ RGB color value. """ + return self._rgb + + @property + def color_xy(self): + """ RGB color value. """ + return self._xy + + @property + def is_on(self): + """ True if device is on. """ + return self._state + + def turn_on(self, **kwargs): + + if ATTR_RGB_COLOR in kwargs: + self._rgb = kwargs[ATTR_RGB_COLOR] + rgb = DEFAULT_RGB_PATTERN % tuple(self._rgb) + mqtt.publish(self._hass, self._rgb_command_topic, rgb, self._qos) + + if ATTR_BRIGHTNESS in kwargs: + self._brightness = kwargs[ATTR_BRIGHTNESS] + mqtt.publish(self._hass, self._brightness_command_topic, self._brightness, self._qos) + + if not self._state: + """ Turn the device on. """ + self._state = True + mqtt.publish(self._hass, self._command_topic, self._payload_on, self._qos) + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + self._state = False + mqtt.publish(self._hass, self._command_topic, self._payload_off,self._qos) + self.update_ha_state() + From 7cfce94dfbf095f4123e7f85cd119c3ebbe221b3 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 25 Oct 2015 22:58:07 +0100 Subject: [PATCH 02/82] pylint rework for light/mqtt --- homeassistant/components/light/mqtt.py | 75 +++++++++++++++----------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 2fc9fef7d50..4df0347d506 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -5,9 +5,8 @@ Allows to configure a MQTT light. """ import logging import homeassistant.components.mqtt as mqtt -from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_XY_COLOR) - -import random +from homeassistant.components.light import (Light, + ATTR_BRIGHTNESS, ATTR_RGB_COLOR) _LOGGER = logging.getLogger(__name__) @@ -15,7 +14,7 @@ DEFAULT_NAME = "MQTT Light" DEFAULT_QOS = 0 DEFAULT_PAYLOAD_ON = "on" DEFAULT_PAYLOAD_OFF = "off" -DEFAULT_RGB = [ 255, 255, 255 ] +DEFAULT_RGB = [255, 255, 255] DEFAULT_RGB_PATTERN = "%d,%d,%d" DEFAULT_BRIGHTNESS = 120 @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): config.get('brightness_command_topic', DEFAULT_COMMAND_BRIGHTNESS), config.get('rgb_state_topic', DEFAULT_STATE_RGB), config.get('rgb_command_topic', DEFAULT_COMMAND_RGB), - config.get('rgb', DEFAULT_RGB ), + config.get('rgb', DEFAULT_RGB), config.get('qos', DEFAULT_QOS), config.get('payload_on', DEFAULT_PAYLOAD_ON), config.get('payload_off', DEFAULT_PAYLOAD_OFF), @@ -55,47 +54,61 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class MqttLight(Light): """ Provides a demo switch. """ - def __init__(self, hass, name, state_topic, command_topic, brightness_state_topic, brightness_command_topic, rgb_state_topic, rgb_command_topic, rgb, qos, payload_on, payload_off, brightness): - self._name = name + + # pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-locals,bad-builtin + # Eight is reasonable in this case. + + def __init__(self, hass, name, + state_topic, command_topic, + brightness_state_topic, brightness_command_topic, + rgb_state_topic, rgb_command_topic, + rgb, qos, + payload_on, payload_off, + brightness): + self._hass = hass - self._state = False - self._command_topic = command_topic + self._name = name self._state_topic = state_topic + self._command_topic = command_topic self._brightness_state_topic = brightness_state_topic self._brightness_command_topic = brightness_command_topic self._rgb_state_topic = rgb_state_topic self._rgb_command_topic = rgb_command_topic + self._rgb = rgb self._qos = qos self._payload_on = payload_on self._payload_off = payload_off - self._rgb = rgb self._brightness = brightness - self._xy = [[ 0.5, 0.5 ]] - + self._xy = [[0.5, 0.5]] + self._state = False + def message_received(topic, payload, qos): """ A new MQTT message has been received. """ if payload == self._payload_on: self._state = True - self.update_ha_state() + self._hass.update_ha_state() elif payload == self._payload_off: self._state = False - self.update_ha_state() - + self._hass.update_ha_state() + def brightness_received(topic, payload, qos): """ A new MQTT message has been received. """ self._brightness = int(payload) - self.update_ha_state() - + self._hass.update_ha_state() + def rgb_received(topic, payload, qos): """ A new MQTT message has been received. """ - rgb = payload.split( "," ) + rgb = payload.split(",") self._rgb = list(map(int, rgb)) - self.update_ha_state() + self._hass.update_ha_state() # subscribe the state_topic - mqtt.subscribe(self._hass, self._state_topic, message_received, self._qos) - mqtt.subscribe(self._hass, self._brightness_state_topic, brightness_received, self._qos) - mqtt.subscribe(self._hass, self._rgb_state_topic, rgb_received, self._qos) + mqtt.subscribe(self._hass, self._state_topic, + message_received, self._qos) + mqtt.subscribe(self._hass, self._brightness_state_topic, + brightness_received, self._qos) + mqtt.subscribe(self._hass, self._rgb_state_topic, + rgb_received, self._qos) @property def should_poll(self): @@ -116,7 +129,7 @@ class MqttLight(Light): def rgb_color(self): """ RGB color value. """ return self._rgb - + @property def color_xy(self): """ RGB color value. """ @@ -128,6 +141,7 @@ class MqttLight(Light): return self._state def turn_on(self, **kwargs): + """ Turn the device on. """ if ATTR_RGB_COLOR in kwargs: self._rgb = kwargs[ATTR_RGB_COLOR] @@ -136,17 +150,18 @@ class MqttLight(Light): if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] - mqtt.publish(self._hass, self._brightness_command_topic, self._brightness, self._qos) + mqtt.publish(self._hass, self._brightness_command_topic, + self._brightness, self._qos) if not self._state: - """ Turn the device on. """ self._state = True - mqtt.publish(self._hass, self._command_topic, self._payload_on, self._qos) - self.update_ha_state() + mqtt.publish(self._hass, self._command_topic, + self._payload_on, self._qos) + self._hass.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ self._state = False - mqtt.publish(self._hass, self._command_topic, self._payload_off,self._qos) - self.update_ha_state() - + mqtt.publish(self._hass, self._command_topic, + self._payload_off, self._qos) + self._hass.update_ha_state() From 538f8545f7954be296e001f9698312b809861342 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 25 Oct 2015 23:04:43 +0100 Subject: [PATCH 03/82] fix a bug after the pylint rework --- homeassistant/components/light/mqtt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 4df0347d506..290ea6813ee 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -86,21 +86,21 @@ class MqttLight(Light): """ A new MQTT message has been received. """ if payload == self._payload_on: self._state = True - self._hass.update_ha_state() + self.update_ha_state() elif payload == self._payload_off: self._state = False - self._hass.update_ha_state() + self.update_ha_state() def brightness_received(topic, payload, qos): """ A new MQTT message has been received. """ self._brightness = int(payload) - self._hass.update_ha_state() + self.update_ha_state() def rgb_received(topic, payload, qos): """ A new MQTT message has been received. """ rgb = payload.split(",") self._rgb = list(map(int, rgb)) - self._hass.update_ha_state() + self.update_ha_state() # subscribe the state_topic mqtt.subscribe(self._hass, self._state_topic, @@ -157,11 +157,11 @@ class MqttLight(Light): self._state = True mqtt.publish(self._hass, self._command_topic, self._payload_on, self._qos) - self._hass.update_ha_state() + self.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ self._state = False mqtt.publish(self._hass, self._command_topic, self._payload_off, self._qos) - self._hass.update_ha_state() + self.update_ha_state() From a8c2cc4c33094daa3062bf28ce431c090d9a3440 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 25 Oct 2015 23:38:24 +0100 Subject: [PATCH 04/82] rework for flake8 errors done --- homeassistant/components/light/mqtt.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 290ea6813ee..078a9a4075f 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -30,6 +30,8 @@ DEFAULT_COMMAND_RGB = "homeassistant/light/rgb/set" DEPENDENCIES = ['mqtt'] # pylint: disable=unused-argument + + def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Add MQTT Light. """ @@ -52,10 +54,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): config.get('payload_off', DEFAULT_PAYLOAD_OFF), config.get('brightness', DEFAULT_BRIGHTNESS))]) + class MqttLight(Light): """ Provides a demo switch. """ - # pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-locals,bad-builtin + # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-arguments,too-many-locals,bad-builtin # Eight is reasonable in this case. def __init__(self, hass, name, From b66e4f1e155801bb4d3b93b1eed2723c4fdaf7e2 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 26 Oct 2015 15:05:01 +0100 Subject: [PATCH 05/82] two different demo lights on without RGB and one with RGB support. and code cleanup more pylint aligned --- homeassistant/components/light/mqtt.py | 236 ++++++++++++++++--------- 1 file changed, 151 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 078a9a4075f..ff78ecb3ca8 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -2,6 +2,33 @@ homeassistant.components.light.mqtt ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows to configure a MQTT light. + +config for RGB Version with brightness: + +light: + platform: mqtt + name: "Office Light RGB" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + brightness_state_topic: "office/rgb1/brightness/status" + brightness_command_topic: "office/rgb1/brightness/set" + rgb_state_topic: "office/rgb1/rgb/status" + rgb_command_topic: "office/rgb1/rgb/set" + qos: 0 + payload_on: "on" + payload_off: "off" + +config without RGB: + +light: + platform: mqtt + name: "Office Light" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + qos: 0 + payload_on: "on" + payload_off: "off" + """ import logging import homeassistant.components.mqtt as mqtt @@ -17,15 +44,7 @@ DEFAULT_PAYLOAD_OFF = "off" DEFAULT_RGB = [255, 255, 255] DEFAULT_RGB_PATTERN = "%d,%d,%d" DEFAULT_BRIGHTNESS = 120 - -DEFAULT_STATE_TOPIC = "homeassistant/light/state" -DEFAULT_COMMAND_TOPIC = "homeassistant/light/switch" - -DEFAULT_STATE_BRIGHTNESS = "homeassistant/light/brightness/state" -DEFAULT_COMMAND_BRIGHTNESS = "homeassistant/light/brightness/set" - -DEFAULT_STATE_RGB = "homeassistant/light/rgb/state" -DEFAULT_COMMAND_RGB = "homeassistant/light/rgb/set" +DEFAULT_OPTIMISTIC = False DEPENDENCIES = ['mqtt'] @@ -39,80 +58,70 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): _LOGGER.error("Missing required variable: command_topic") return False - add_devices_callback([MqttLight( - hass, - config.get('name', DEFAULT_NAME), - config.get('state_topic', DEFAULT_STATE_TOPIC), - config.get('command_topic', DEFAULT_COMMAND_TOPIC), - config.get('brightness_state_topic', DEFAULT_STATE_BRIGHTNESS), - config.get('brightness_command_topic', DEFAULT_COMMAND_BRIGHTNESS), - config.get('rgb_state_topic', DEFAULT_STATE_RGB), - config.get('rgb_command_topic', DEFAULT_COMMAND_RGB), - config.get('rgb', DEFAULT_RGB), - config.get('qos', DEFAULT_QOS), - config.get('payload_on', DEFAULT_PAYLOAD_ON), - config.get('payload_off', DEFAULT_PAYLOAD_OFF), - config.get('brightness', DEFAULT_BRIGHTNESS))]) + if config.get('rgb_command_topic') is not None: + add_devices_callback([MqttLightRGB( + hass, + config.get('name', DEFAULT_NAME), + {"state_topic": config.get('state_topic'), + "command_topic": config.get('command_topic'), + "brightness_state_topic": config.get('brightness_state_topic'), + "brightness_command_topic": + config.get('brightness_command_topic'), + "rgb_state_topic": config.get('rgb_state_topic'), + "rgb_command_topic": config.get('rgb_command_topic')}, + config.get('rgb', DEFAULT_RGB), + config.get('qos', DEFAULT_QOS), + {"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), + "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, + config.get('brightness', DEFAULT_BRIGHTNESS), + config.get('optimistic', DEFAULT_OPTIMISTIC))]) + + else: + add_devices_callback([MqttLight( + hass, + config.get('name', DEFAULT_NAME), + {"state_topic": config.get('state_topic'), + "command_topic": config.get('command_topic')}, + config.get('qos', DEFAULT_QOS), + {"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), + "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, + config.get('optimistic', DEFAULT_OPTIMISTIC))]) class MqttLight(Light): - """ Provides a demo switch. """ - - # pylint: disable=too-many-instance-attributes - # pylint: disable=too-many-arguments,too-many-locals,bad-builtin - # Eight is reasonable in this case. + """ Provides a demo light. """ + # pylint: disable=too-many-arguments def __init__(self, hass, name, - state_topic, command_topic, - brightness_state_topic, brightness_command_topic, - rgb_state_topic, rgb_command_topic, - rgb, qos, - payload_on, payload_off, - brightness): + topic, + qos, + payload, + optimistic): self._hass = hass self._name = name - self._state_topic = state_topic - self._command_topic = command_topic - self._brightness_state_topic = brightness_state_topic - self._brightness_command_topic = brightness_command_topic - self._rgb_state_topic = rgb_state_topic - self._rgb_command_topic = rgb_command_topic - self._rgb = rgb + self._topic = topic self._qos = qos - self._payload_on = payload_on - self._payload_off = payload_off - self._brightness = brightness - self._xy = [[0.5, 0.5]] + self._payload = payload + self._optimistic = optimistic self._state = False def message_received(topic, payload, qos): """ A new MQTT message has been received. """ - if payload == self._payload_on: + if payload == self._payload["on"]: self._state = True - self.update_ha_state() - elif payload == self._payload_off: + elif payload == self._payload["off"]: self._state = False - self.update_ha_state() - def brightness_received(topic, payload, qos): - """ A new MQTT message has been received. """ - self._brightness = int(payload) self.update_ha_state() - def rgb_received(topic, payload, qos): - """ A new MQTT message has been received. """ - rgb = payload.split(",") - self._rgb = list(map(int, rgb)) - self.update_ha_state() - - # subscribe the state_topic - mqtt.subscribe(self._hass, self._state_topic, - message_received, self._qos) - mqtt.subscribe(self._hass, self._brightness_state_topic, - brightness_received, self._qos) - mqtt.subscribe(self._hass, self._rgb_state_topic, - rgb_received, self._qos) + if self._topic["state_topic"] is None: + # force optimistic mode + self._optimistic = True + else: + # subscribe the state_topic + mqtt.subscribe(self._hass, self._topic["state_topic"], + message_received, self._qos) @property def should_poll(self): @@ -124,6 +133,68 @@ class MqttLight(Light): """ Returns the name of the device if any. """ return self._name + @property + def is_on(self): + """ True if device is on. """ + return self._state + + def turn_on(self, **kwargs): + """ Turn the device on. """ + + mqtt.publish(self._hass, self._topic["command_topic"], + self._payload["on"], self._qos) + + if self._optimistic: + # optimistically assume that switch has changed state + self._state = True + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + mqtt.publish(self._hass, self._topic["command_topic"], + self._payload["off"], self._qos) + + if self._optimistic: + # optimistically assume that switch has changed state + self._state = False + self.update_ha_state() + + +class MqttLightRGB(MqttLight): + """ Provides a demo RGB light. """ + + # pylint: disable=too-many-arguments + def __init__(self, hass, name, + topic, + rgb, qos, + payload, + brightness, optimistic): + + super().__init__(hass, name, topic, qos, + payload, optimistic) + + self._rgb = rgb + self._brightness = brightness + self._xy = [[0.5, 0.5]] + + def brightness_received(topic, payload, qos): + """ A new MQTT message has been received. """ + self._brightness = int(payload) + self.update_ha_state() + + def rgb_received(topic, payload, qos): + """ A new MQTT message has been received. """ + self._rgb = [int(val) for val in payload.split(',')] + self.update_ha_state() + + if self._topic["brightness_state_topic"] is not None: + mqtt.subscribe(self._hass, self._topic["brightness_state_topic"], + brightness_received, self._qos) + + if self._topic["rgb_state_topic"] is not None: + mqtt.subscribe(self._hass, self._topic["rgb_state_topic"], + rgb_received, self._qos) + @property def brightness(self): """ Brightness of this light between 0..255. """ @@ -139,33 +210,28 @@ class MqttLight(Light): """ RGB color value. """ return self._xy - @property - def is_on(self): - """ True if device is on. """ - return self._state - def turn_on(self, **kwargs): """ Turn the device on. """ - if ATTR_RGB_COLOR in kwargs: + if ATTR_RGB_COLOR in kwargs and \ + self._topic["rgb_command_topic"] is not None: + self._rgb = kwargs[ATTR_RGB_COLOR] rgb = DEFAULT_RGB_PATTERN % tuple(self._rgb) - mqtt.publish(self._hass, self._rgb_command_topic, rgb, self._qos) + mqtt.publish(self._hass, self._topic["rgb_command_topic"], + rgb, self._qos) + + if ATTR_BRIGHTNESS in kwargs and \ + self._topic["brightness_command_topic"] is not None: - if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] - mqtt.publish(self._hass, self._brightness_command_topic, + mqtt.publish(self._hass, self._topic["brightness_command_topic"], self._brightness, self._qos) - if not self._state: - self._state = True - mqtt.publish(self._hass, self._command_topic, - self._payload_on, self._qos) - self.update_ha_state() + mqtt.publish(self._hass, self._topic["command_topic"], + self._payload["on"], self._qos) - def turn_off(self, **kwargs): - """ Turn the device off. """ - self._state = False - mqtt.publish(self._hass, self._command_topic, - self._payload_off, self._qos) - self.update_ha_state() + if self._optimistic: + # optimistically assume that switch has changed state + self._state = True + self.update_ha_state() From 4fcd27e9050051371c9efa7f7850dbd486267c99 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 27 Oct 2015 16:52:43 +0100 Subject: [PATCH 06/82] light/mqtt to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 28b4b926eab..bef52d57248 100644 --- a/.coveragerc +++ b/.coveragerc @@ -48,6 +48,7 @@ omit = homeassistant/components/downloader.py homeassistant/components/keyboard.py homeassistant/components/light/hue.py + homeassistant/components/light/mqtt.py homeassistant/components/light/limitlessled.py homeassistant/components/light/blinksticklight.py homeassistant/components/light/hyperion.py From 31826ab263212fa6821b933899dad9c9f96aba44 Mon Sep 17 00:00:00 2001 From: hexxter Date: Sat, 31 Oct 2015 19:26:03 +0100 Subject: [PATCH 07/82] redesigned mqtt light an first steps with the unittest system --- homeassistant/components/light/mqtt.py | 187 +++++++++++-------------- tests/components/light/test_mqtt.py | 130 +++++++++++++++++ 2 files changed, 209 insertions(+), 108 deletions(-) create mode 100644 tests/components/light/test_mqtt.py diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index ff78ecb3ca8..4e9b58e272e 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -31,6 +31,8 @@ light: """ import logging + +import homeassistant.util.color as color_util import homeassistant.components.mqtt as mqtt from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR) @@ -41,9 +43,7 @@ DEFAULT_NAME = "MQTT Light" DEFAULT_QOS = 0 DEFAULT_PAYLOAD_ON = "on" DEFAULT_PAYLOAD_OFF = "off" -DEFAULT_RGB = [255, 255, 255] DEFAULT_RGB_PATTERN = "%d,%d,%d" -DEFAULT_BRIGHTNESS = 120 DEFAULT_OPTIMISTIC = False DEPENDENCIES = ['mqtt'] @@ -58,53 +58,46 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): _LOGGER.error("Missing required variable: command_topic") return False - if config.get('rgb_command_topic') is not None: - add_devices_callback([MqttLightRGB( - hass, - config.get('name', DEFAULT_NAME), - {"state_topic": config.get('state_topic'), - "command_topic": config.get('command_topic'), - "brightness_state_topic": config.get('brightness_state_topic'), - "brightness_command_topic": + add_devices_callback([MqttLight( + hass, + config.get('name', DEFAULT_NAME), + {"state_topic": config.get('state_topic'), + "command_topic": config.get('command_topic'), + "brightness_state_topic": config.get('brightness_state_topic'), + "brightness_command_topic": config.get('brightness_command_topic'), - "rgb_state_topic": config.get('rgb_state_topic'), - "rgb_command_topic": config.get('rgb_command_topic')}, - config.get('rgb', DEFAULT_RGB), - config.get('qos', DEFAULT_QOS), - {"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), - "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, - config.get('brightness', DEFAULT_BRIGHTNESS), - config.get('optimistic', DEFAULT_OPTIMISTIC))]) + "rgb_state_topic": config.get('rgb_state_topic'), + "rgb_command_topic": config.get('rgb_command_topic')}, + config.get('rgb', [255, 255, 255]), + config.get('qos', DEFAULT_QOS), + {"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), + "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, + config.get('brightness'), + config.get('optimistic', DEFAULT_OPTIMISTIC))]) - else: - add_devices_callback([MqttLight( - hass, - config.get('name', DEFAULT_NAME), - {"state_topic": config.get('state_topic'), - "command_topic": config.get('command_topic')}, - config.get('qos', DEFAULT_QOS), - {"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), - "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, - config.get('optimistic', DEFAULT_OPTIMISTIC))]) +# pylint: disable=too-many-instance-attributes class MqttLight(Light): - """ Provides a demo light. """ + """ Provides a demo Mqtt light. """ # pylint: disable=too-many-arguments def __init__(self, hass, name, topic, - qos, + rgb, qos, payload, - optimistic): + brightness, optimistic): self._hass = hass self._name = name self._topic = topic + self._rgb = rgb self._qos = qos self._payload = payload + self._brightness = brightness self._optimistic = optimistic self._state = False + self._xy = None def message_received(topic, payload, qos): """ A new MQTT message has been received. """ @@ -123,6 +116,48 @@ class MqttLight(Light): mqtt.subscribe(self._hass, self._topic["state_topic"], message_received, self._qos) + def brightness_received(topic, payload, qos): + """ A new MQTT message has been received. """ + self._brightness = int(payload) + self.update_ha_state() + + def rgb_received(topic, payload, qos): + """ A new MQTT message has been received. """ + self._rgb = [int(val) for val in payload.split(',')] + self._xy = color_util.color_RGB_to_xy(int(self._rgb[0]), + int(self._rgb[1]), + int(self._rgb[2])) + self.update_ha_state() + + if self._topic["brightness_state_topic"] is not None: + mqtt.subscribe(self._hass, self._topic["brightness_state_topic"], + brightness_received, self._qos) + self._brightness = 255 + else: + self._brightness = None + + if self._topic["rgb_state_topic"] is not None: + mqtt.subscribe(self._hass, self._topic["rgb_state_topic"], + rgb_received, self._qos) + self._xy = [0, 0] + else: + self._xy = None + + @property + def brightness(self): + """ Brightness of this light between 0..255. """ + return self._brightness + + @property + def rgb_color(self): + """ RGB color value. """ + return self._rgb + + @property + def color_xy(self): + """ RGB color value. """ + return self._xy + @property def should_poll(self): """ No polling needed for a demo light. """ @@ -141,6 +176,19 @@ class MqttLight(Light): def turn_on(self, **kwargs): """ Turn the device on. """ + if ATTR_RGB_COLOR in kwargs and \ + self._topic["rgb_command_topic"] is not None: + self._rgb = kwargs[ATTR_RGB_COLOR] + rgb = DEFAULT_RGB_PATTERN % tuple(self._rgb) + mqtt.publish(self._hass, self._topic["rgb_command_topic"], + rgb, self._qos) + + if ATTR_BRIGHTNESS in kwargs and \ + self._topic["brightness_command_topic"] is not None: + self._brightness = kwargs[ATTR_BRIGHTNESS] + mqtt.publish(self._hass, self._topic["brightness_command_topic"], + self._brightness, self._qos) + mqtt.publish(self._hass, self._topic["command_topic"], self._payload["on"], self._qos) @@ -158,80 +206,3 @@ class MqttLight(Light): # optimistically assume that switch has changed state self._state = False self.update_ha_state() - - -class MqttLightRGB(MqttLight): - """ Provides a demo RGB light. """ - - # pylint: disable=too-many-arguments - def __init__(self, hass, name, - topic, - rgb, qos, - payload, - brightness, optimistic): - - super().__init__(hass, name, topic, qos, - payload, optimistic) - - self._rgb = rgb - self._brightness = brightness - self._xy = [[0.5, 0.5]] - - def brightness_received(topic, payload, qos): - """ A new MQTT message has been received. """ - self._brightness = int(payload) - self.update_ha_state() - - def rgb_received(topic, payload, qos): - """ A new MQTT message has been received. """ - self._rgb = [int(val) for val in payload.split(',')] - self.update_ha_state() - - if self._topic["brightness_state_topic"] is not None: - mqtt.subscribe(self._hass, self._topic["brightness_state_topic"], - brightness_received, self._qos) - - if self._topic["rgb_state_topic"] is not None: - mqtt.subscribe(self._hass, self._topic["rgb_state_topic"], - rgb_received, self._qos) - - @property - def brightness(self): - """ Brightness of this light between 0..255. """ - return self._brightness - - @property - def rgb_color(self): - """ RGB color value. """ - return self._rgb - - @property - def color_xy(self): - """ RGB color value. """ - return self._xy - - def turn_on(self, **kwargs): - """ Turn the device on. """ - - if ATTR_RGB_COLOR in kwargs and \ - self._topic["rgb_command_topic"] is not None: - - self._rgb = kwargs[ATTR_RGB_COLOR] - rgb = DEFAULT_RGB_PATTERN % tuple(self._rgb) - mqtt.publish(self._hass, self._topic["rgb_command_topic"], - rgb, self._qos) - - if ATTR_BRIGHTNESS in kwargs and \ - self._topic["brightness_command_topic"] is not None: - - self._brightness = kwargs[ATTR_BRIGHTNESS] - mqtt.publish(self._hass, self._topic["brightness_command_topic"], - self._brightness, self._qos) - - mqtt.publish(self._hass, self._topic["command_topic"], - self._payload["on"], self._qos) - - if self._optimistic: - # optimistically assume that switch has changed state - self._state = True - self.update_ha_state() diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py new file mode 100644 index 00000000000..28c0e75e256 --- /dev/null +++ b/tests/components/light/test_mqtt.py @@ -0,0 +1,130 @@ +""" +tests.components.light.test_mqtt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests mqtt light. + +config for RGB Version with brightness: + +light: + platform: mqtt + name: "Office Light RGB" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + brightness_state_topic: "office/rgb1/brightness/status" + brightness_command_topic: "office/rgb1/brightness/set" + rgb_state_topic: "office/rgb1/rgb/status" + rgb_command_topic: "office/rgb1/rgb/set" + qos: 0 + payload_on: "on" + payload_off: "off" + +config without RGB: + +light: + platform: mqtt + name: "Office Light" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + brightness_state_topic: "office/rgb1/brightness/status" + brightness_command_topic: "office/rgb1/brightness/set" + qos: 0 + payload_on: "on" + payload_off: "off" + +config without RGB and brightness: + +light: + platform: mqtt + name: "Office Light" + state_topic: "office/rgb1/light/status" + command_topic: "office/rgb1/light/switch" + qos: 0 + payload_on: "on" + payload_off: "off" +""" +import unittest + +from homeassistant.const import STATE_ON, STATE_OFF +import homeassistant.core as ha +import homeassistant.components.light as light +from tests.common import mock_mqtt_component, fire_mqtt_message + + +class TestLightMQTT(unittest.TestCase): + """ Test the MQTT light. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + self.mock_publish = mock_mqtt_component(self.hass) + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_controlling_state_via_topic(self): + self.assertTrue(light.setup(self.hass, { + 'switch': { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_light_rgb/status', + 'command_topic': 'test_light_rgb/set', + 'brightness_state_topic': 'test_light_rgb/brightness/status', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_state_topic': 'test_light_rgb/rgb/status', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'qos': 0, + 'payload_on': 'on', + 'payload_off': 'off' + } + })) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) + + fire_mqtt_message(self.hass, 'test', 'on') + self.hass.pool.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_ON, state.state) + + fire_mqtt_message(self.hass, 'test', 'off') + self.hass.pool.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) + + def test_sending_mqtt_commands_and_optimistic(self): + self.assertTrue(light.setup(self.hass, { + 'switch': { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'brightness_state_topic': 'test_light_rgb/brightness/status', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_state_topic': 'test_light_rgb/rgb/status', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'qos': 2, + 'payload_on': 'on', + 'payload_off': 'off' + } + })) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) + + switch.turn_on(self.hass, 'light.test') + self.hass.pool.block_till_done() + + self.assertEqual(('test_light_rgb/set', 'on', 2), + self.mock_publish.mock_calls[-1][1]) + state = self.hass.states.get('light.test') + self.assertEqual(STATE_ON, state.state) + + switch.turn_off(self.hass, 'light.test') + self.hass.pool.block_till_done() + + self.assertEqual(('test_light_rgb/set', 'off', 2), + self.mock_publish.mock_calls[-1][1]) + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) From 168eb8e5a2b3ae12dc90774374684684463ac951 Mon Sep 17 00:00:00 2001 From: hexxter Date: Mon, 2 Nov 2015 17:02:34 +0100 Subject: [PATCH 08/82] mqtt light test is working more test should be written --- tests/components/light/__init__.py | 0 tests/components/light/test_mqtt.py | 14 +++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 tests/components/light/__init__.py diff --git a/tests/components/light/__init__.py b/tests/components/light/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 28c0e75e256..98c3615aeba 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -64,7 +64,7 @@ class TestLightMQTT(unittest.TestCase): def test_controlling_state_via_topic(self): self.assertTrue(light.setup(self.hass, { - 'switch': { + 'light': { 'platform': 'mqtt', 'name': 'test', 'state_topic': 'test_light_rgb/status', @@ -82,21 +82,21 @@ class TestLightMQTT(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) - fire_mqtt_message(self.hass, 'test', 'on') + fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') self.hass.pool.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) - fire_mqtt_message(self.hass, 'test', 'off') + fire_mqtt_message(self.hass, 'test_light_rgb/status', 'off') self.hass.pool.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) - + def test_sending_mqtt_commands_and_optimistic(self): self.assertTrue(light.setup(self.hass, { - 'switch': { + 'light': { 'platform': 'mqtt', 'name': 'test', 'command_topic': 'test_light_rgb/set', @@ -113,7 +113,7 @@ class TestLightMQTT(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) - switch.turn_on(self.hass, 'light.test') + light.turn_on(self.hass, 'light.test') self.hass.pool.block_till_done() self.assertEqual(('test_light_rgb/set', 'on', 2), @@ -121,7 +121,7 @@ class TestLightMQTT(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) - switch.turn_off(self.hass, 'light.test') + light.turn_off(self.hass, 'light.test') self.hass.pool.block_till_done() self.assertEqual(('test_light_rgb/set', 'off', 2), From 186f68cce30a1f0200b632aa21ee2d75269ad7a1 Mon Sep 17 00:00:00 2001 From: hexxter Date: Mon, 2 Nov 2015 20:16:36 +0100 Subject: [PATCH 09/82] not working mqtt light unittest --- tests/components/light/test_mqtt.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 98c3615aeba..0d97b61441c 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -93,6 +93,13 @@ class TestLightMQTT(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) + + fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', '100') + self.hass.pool.block_till_done() + + light_state = self.hass.states.get('light.test') + self.assertEqual(100, + light_state.attributes) def test_sending_mqtt_commands_and_optimistic(self): self.assertTrue(light.setup(self.hass, { From 19649390d3ce09e98346ec67b17539fb0cca70ed Mon Sep 17 00:00:00 2001 From: Arthur Andersen Date: Sat, 7 Nov 2015 15:52:36 +0100 Subject: [PATCH 10/82] [Zwave] Add binary switch component --- homeassistant/components/switch/__init__.py | 6 +- homeassistant/components/switch/zwave.py | 80 +++++++++++++++++++++ homeassistant/components/zwave.py | 8 +++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/switch/zwave.py diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index ac4ae533596..9a0abb4ce7a 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -16,7 +16,8 @@ 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, verisure +from homeassistant.components import ( + group, discovery, wink, isy994, verisure, zwave) DOMAIN = 'switch' DEPENDENCIES = [] @@ -38,7 +39,8 @@ DISCOVERY_PLATFORMS = { discovery.SERVICE_WEMO: 'wemo', wink.DISCOVER_SWITCHES: 'wink', isy994.DISCOVER_SWITCHES: 'isy994', - verisure.DISCOVER_SWITCHES: 'verisure' + verisure.DISCOVER_SWITCHES: 'verisure', + zwave.DISCOVER_SWITCHES: 'zwave', } PROP_TO_ATTR = { diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py new file mode 100644 index 00000000000..12368dc0c22 --- /dev/null +++ b/homeassistant/components/switch/zwave.py @@ -0,0 +1,80 @@ +""" +homeassistant.components.switch.demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Demo platform that has two fake switches. +""" +# pylint: disable=import-error +from openzwave.network import ZWaveNetwork +from pydispatch import dispatcher + +import homeassistant.components.zwave as zwave + +from homeassistant.components.switch import SwitchDevice + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Find and return demo switches. """ + if discovery_info is None: + return + + node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]] + value = node.values[discovery_info[zwave.ATTR_VALUE_ID]] + + if value.command_class != zwave.COMMAND_CLASS_SWITCH_BINARY: + return + if value.type != zwave.TYPE_BOOL: + return + if value.genre != zwave.GENRE_USER: + return + + value.set_change_verified(False) + add_devices([ZwaveSwitch(value)]) + + +class ZwaveSwitch(SwitchDevice): + """ Provides a zwave switch. """ + def __init__(self, value): + self._value = value + self._node = value.node + + self._state = value.data + + dispatcher.connect( + self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED) + + def _value_changed(self, value): + """ Called when a value has changed on the network. """ + if self._value.value_id == value.value_id: + self._state = value.data + self.update_ha_state() + + @property + def should_poll(self): + """ No polling needed for a demo switch. """ + return False + + @property + def name(self): + """ Returns the name of the device if any. """ + name = self._node.name or "{}".format(self._node.product_name) + + return "{}".format(name or self._value.label) + + @property + def is_on(self): + """ True if device is on. """ + return self._state + + def turn_on(self, **kwargs): + """ Turn the device on. """ + if self._node.set_switch(self._value.value_id, True): + self._state = True + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + if self._node.set_switch(self._value.value_id, False): + self._state = False + self.update_ha_state() diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index e5b321c989d..b7a8ddce1f4 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -22,9 +22,12 @@ DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick" CONF_DEBUG = "debug" DISCOVER_SENSORS = "zwave.sensors" +DISCOVER_SWITCHES = "zwave.switch" DISCOVER_LIGHTS = "zwave.light" COMMAND_CLASS_SWITCH_MULTILEVEL = 38 + +COMMAND_CLASS_SWITCH_BINARY = 37 COMMAND_CLASS_SENSOR_BINARY = 48 COMMAND_CLASS_SENSOR_MULTILEVEL = 49 COMMAND_CLASS_BATTERY = 128 @@ -49,6 +52,11 @@ DISCOVERY_COMPONENTS = [ [COMMAND_CLASS_SWITCH_MULTILEVEL], TYPE_BYTE, GENRE_USER), + ('switch', + DISCOVER_SWITCHES, + [COMMAND_CLASS_SWITCH_BINARY], + TYPE_BOOL, + GENRE_USER), ] ATTR_NODE_ID = "node_id" From 877926cfee1df9cacdb84be3477d742042e424fe Mon Sep 17 00:00:00 2001 From: Arthur Andersen Date: Tue, 10 Nov 2015 20:04:25 +0100 Subject: [PATCH 11/82] [Zwave] Fix docstring --- homeassistant/components/switch/zwave.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py index 12368dc0c22..cc022df18c9 100644 --- a/homeassistant/components/switch/zwave.py +++ b/homeassistant/components/switch/zwave.py @@ -1,8 +1,8 @@ """ -homeassistant.components.switch.demo +homeassistant.components.switch.zwave ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Demo platform that has two fake switches. +Zwave platform that handles simple binary switches. """ # pylint: disable=import-error from openzwave.network import ZWaveNetwork From 8f12b997f8c61ab21fb1536c3668c8ed40b4e73e Mon Sep 17 00:00:00 2001 From: hexxter Date: Wed, 11 Nov 2015 12:32:24 +0100 Subject: [PATCH 12/82] more unittests --- tests/components/light/test_mqtt.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 0d97b61441c..d1678f8cee1 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -44,6 +44,7 @@ light: payload_off: "off" """ import unittest +import homeassistant.util.color as color_util from homeassistant.const import STATE_ON, STATE_OFF import homeassistant.core as ha @@ -93,13 +94,25 @@ class TestLightMQTT(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) + + fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') + self.hass.pool.block_till_done() fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', '100') self.hass.pool.block_till_done() light_state = self.hass.states.get('light.test') self.assertEqual(100, - light_state.attributes) + light_state.attributes['brightness']) + self.assertEqual([0, 0], + light_state.attributes['xy_color']) + xy = color_util.color_RGB_to_xy(125,125,125) + fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', '125,125,125') + self.hass.pool.block_till_done() + + light_state = self.hass.states.get('light.test') + self.assertEqual(xy, + light_state.attributes['xy_color']) def test_sending_mqtt_commands_and_optimistic(self): self.assertTrue(light.setup(self.hass, { From 0c52b143ae22f79e57f1c3a6b98d1276b7ba401a Mon Sep 17 00:00:00 2001 From: hexxter Date: Wed, 11 Nov 2015 12:38:10 +0100 Subject: [PATCH 13/82] now saved --- tests/components/light/test_mqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index d1678f8cee1..cb7b0a97f8e 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -106,7 +106,9 @@ class TestLightMQTT(unittest.TestCase): light_state.attributes['brightness']) self.assertEqual([0, 0], light_state.attributes['xy_color']) - xy = color_util.color_RGB_to_xy(125,125,125) + + xy = color_util.color_RGB_to_xy(125,125,125) + fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', '125,125,125') self.hass.pool.block_till_done() From 90063ea7f887fff9a3cd5d63c16346d1e17cb001 Mon Sep 17 00:00:00 2001 From: hexxter Date: Wed, 11 Nov 2015 12:44:59 +0100 Subject: [PATCH 14/82] check the default value only checkable local. I removed it. --- tests/components/light/test_mqtt.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index cb7b0a97f8e..f67d1cb15a0 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -104,8 +104,6 @@ class TestLightMQTT(unittest.TestCase): light_state = self.hass.states.get('light.test') self.assertEqual(100, light_state.attributes['brightness']) - self.assertEqual([0, 0], - light_state.attributes['xy_color']) xy = color_util.color_RGB_to_xy(125,125,125) From 698e30bd2b167f72de24b439b518e8dc05108ec9 Mon Sep 17 00:00:00 2001 From: hexxter Date: Wed, 11 Nov 2015 20:40:21 +0100 Subject: [PATCH 15/82] more self.hass.pool.block_till_done() --- tests/components/light/test_mqtt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index f67d1cb15a0..3570889c94b 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -102,6 +102,7 @@ class TestLightMQTT(unittest.TestCase): self.hass.pool.block_till_done() light_state = self.hass.states.get('light.test') + self.hass.pool.block_till_done() self.assertEqual(100, light_state.attributes['brightness']) @@ -111,6 +112,7 @@ class TestLightMQTT(unittest.TestCase): self.hass.pool.block_till_done() light_state = self.hass.states.get('light.test') + self.hass.pool.block_till_done() self.assertEqual(xy, light_state.attributes['xy_color']) From 329d63ac118f4f4975df1a8048161da73180784c Mon Sep 17 00:00:00 2001 From: hexxter Date: Wed, 11 Nov 2015 20:52:41 +0100 Subject: [PATCH 16/82] next online unittest test ;) --- tests/components/light/test_mqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 3570889c94b..dc87c90e896 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -108,11 +108,13 @@ class TestLightMQTT(unittest.TestCase): xy = color_util.color_RGB_to_xy(125,125,125) + fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') + self.hass.pool.block_till_done() + fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', '125,125,125') self.hass.pool.block_till_done() light_state = self.hass.states.get('light.test') - self.hass.pool.block_till_done() self.assertEqual(xy, light_state.attributes['xy_color']) From 2812fae721f4c71aa93d91e3c51c3933f5401b17 Mon Sep 17 00:00:00 2001 From: Daren Lord Date: Wed, 11 Nov 2015 16:21:42 -0700 Subject: [PATCH 17/82] Fixing bug when connecting to squeezebox and it is a float --- homeassistant/components/media_player/squeezebox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 86e6addf44a..d3139d52c01 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -173,7 +173,7 @@ class SqueezeBoxDevice(MediaPlayerDevice): def volume_level(self): """ Volume level of the media player (0..1). """ if 'mixer volume' in self._status: - return int(self._status['mixer volume']) / 100.0 + return int(float(self._status['mixer volume'])) / 100.0 @property def is_volume_muted(self): From b652dd47cd63ed15868db3d7dc117eb179cb8c2e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 12 Nov 2015 18:04:48 +0100 Subject: [PATCH 18/82] Update file header and docstrings --- homeassistant/components/logger.py | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/logger.py b/homeassistant/components/logger.py index 62396243316..a6dafa56005 100644 --- a/homeassistant/components/logger.py +++ b/homeassistant/components/logger.py @@ -1,30 +1,10 @@ """ homeassistant.components.logger -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Component that will help guide the user taking its first steps. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Component that will help set the level of logging for components. For more details about this component, please refer to the documentation at https://home-assistant.io/components/logger/ - -Sample configuration - -# By default log all messages and ignore log event lowest than critical -# for custom omponents -logger: - default: info - logs: - homeassistant.components.device_tracker: critical - homeassistant.components.camera: critical - -# By default ignore all messages lowest than critical and log event -# for custom components -logger: - default: critical - logs: - homeassistant.components: info - homeassistant.components.rfxtrx: debug - homeassistant.components.device_tracker: critical - homeassistant.components.camera: critical """ import logging from collections import OrderedDict @@ -48,7 +28,7 @@ LOGGER_LOGS = 'logs' class HomeAssistantLogFilter(logging.Filter): - """A Home Assistant log filter""" + """ A log filter. """ # pylint: disable=no-init,too-few-public-methods def __init__(self, logfilter): @@ -58,7 +38,7 @@ class HomeAssistantLogFilter(logging.Filter): def filter(self, record): - # Log with filterd severity + # Log with filtered severity if LOGGER_LOGS in self.logfilter: for filtername in self.logfilter[LOGGER_LOGS]: logseverity = self.logfilter[LOGGER_LOGS][filtername] @@ -82,7 +62,7 @@ def setup(hass, config=None): config.get(DOMAIN)[LOGGER_DEFAULT].upper() ] - # Compute logseverity for components + # Compute log severity for components if LOGGER_LOGS in config.get(DOMAIN): for key, value in config.get(DOMAIN)[LOGGER_LOGS].items(): config.get(DOMAIN)[LOGGER_LOGS][key] = LOGSEVERITY[value.upper()] From 158d9e27ff1b903e7ea4e5e7cc6370133a760fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Thu, 12 Nov 2015 20:10:25 +0100 Subject: [PATCH 19/82] more robust and more logging --- .../components/device_tracker/asuswrt.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 5284d45835b..c23828168fb 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -158,13 +158,16 @@ class AsusWrtDeviceScanner(object): for lease in leases_result: match = _LEASES_REGEX.search(lease.decode('utf-8')) + if not match: + _LOGGER.warning("Could not parse lease row: %s", lease) + continue + # For leases where the client doesn't set a hostname, ensure # it is blank and not '*', which breaks the entity_id down # the line - if match: - host = match.group('host') - if host == '*': - host = '' + host = match.group('host') + if host == '*': + host = '' devices[match.group('ip')] = { 'host': host, @@ -175,6 +178,9 @@ class AsusWrtDeviceScanner(object): for neighbor in neighbors: match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8')) - if match and match.group('ip') in devices: + if not match: + _LOGGER.warning("Could not parse neighbor row: %s", neighbor) + continue + if match.group('ip') in devices: devices[match.group('ip')]['status'] = match.group('status') return devices From 41d0f95d9ad459aff30c8bc8040434579dfa22e8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 12 Nov 2015 23:03:56 -0800 Subject: [PATCH 20/82] Move core light test to correct dir --- tests/components/{test_light.py => light/test_init.py} | 0 tests/components/light/test_mqtt.py | 9 +++++---- 2 files changed, 5 insertions(+), 4 deletions(-) rename tests/components/{test_light.py => light/test_init.py} (100%) diff --git a/tests/components/test_light.py b/tests/components/light/test_init.py similarity index 100% rename from tests/components/test_light.py rename to tests/components/light/test_init.py diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index dc87c90e896..f4f64c68acc 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -94,7 +94,7 @@ class TestLightMQTT(unittest.TestCase): state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) - + fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') self.hass.pool.block_till_done() @@ -106,18 +106,19 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual(100, light_state.attributes['brightness']) - xy = color_util.color_RGB_to_xy(125,125,125) + xy = color_util.color_RGB_to_xy(125, 125, 125) fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') self.hass.pool.block_till_done() - fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', '125,125,125') + fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', + '125,125,125') self.hass.pool.block_till_done() light_state = self.hass.states.get('light.test') self.assertEqual(xy, light_state.attributes['xy_color']) - + def test_sending_mqtt_commands_and_optimistic(self): self.assertTrue(light.setup(self.hass, { 'light': { From 7ebda9c3c64aaaa773a3a7d37cbd56393e6577c4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 12 Nov 2015 23:08:26 -0800 Subject: [PATCH 21/82] Fix MQTT light test --- tests/components/light/test_mqtt.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index f4f64c68acc..39c81ee0a04 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -106,8 +106,6 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual(100, light_state.attributes['brightness']) - xy = color_util.color_RGB_to_xy(125, 125, 125) - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') self.hass.pool.block_till_done() @@ -116,8 +114,8 @@ class TestLightMQTT(unittest.TestCase): self.hass.pool.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual(xy, - light_state.attributes['xy_color']) + self.assertEqual([125, 125, 125], + light_state.attributes.get('rgb_color')) def test_sending_mqtt_commands_and_optimistic(self): self.assertTrue(light.setup(self.hass, { From d993f4014e66674a8d8d22ce818f882b1bbe4832 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 13 Nov 2015 08:29:54 +0100 Subject: [PATCH 22/82] Add link to docs --- homeassistant/components/light/mqtt.py | 38 +++++-------------------- homeassistant/components/light/zwave.py | 13 ++++++--- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 4e9b58e272e..f65c99e43e3 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -1,34 +1,10 @@ """ homeassistant.components.light.mqtt -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows to configure a MQTT light. -config for RGB Version with brightness: - -light: - platform: mqtt - name: "Office Light RGB" - state_topic: "office/rgb1/light/status" - command_topic: "office/rgb1/light/switch" - brightness_state_topic: "office/rgb1/brightness/status" - brightness_command_topic: "office/rgb1/brightness/set" - rgb_state_topic: "office/rgb1/rgb/status" - rgb_command_topic: "office/rgb1/rgb/set" - qos: 0 - payload_on: "on" - payload_off: "off" - -config without RGB: - -light: - platform: mqtt - name: "Office Light" - state_topic: "office/rgb1/light/status" - command_topic: "office/rgb1/light/switch" - qos: 0 - payload_on: "on" - payload_off: "off" - +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.mqtt/ """ import logging @@ -79,7 +55,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class MqttLight(Light): - """ Provides a demo Mqtt light. """ + """ Provides a MQTT light. """ # pylint: disable=too-many-arguments def __init__(self, hass, name, @@ -112,12 +88,12 @@ class MqttLight(Light): # force optimistic mode self._optimistic = True else: - # subscribe the state_topic + # Subscribe the state_topic mqtt.subscribe(self._hass, self._topic["state_topic"], message_received, self._qos) def brightness_received(topic, payload, qos): - """ A new MQTT message has been received. """ + """ A new MQTT message for the brightness has been received. """ self._brightness = int(payload) self.update_ha_state() @@ -160,7 +136,7 @@ class MqttLight(Light): @property def should_poll(self): - """ No polling needed for a demo light. """ + """ No polling needed for a MQTT light. """ return False @property diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 10e75125e12..f1cd6f57fc0 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -1,7 +1,10 @@ """ homeassistant.components.light.zwave ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Z-Wave lights. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.zwave/ """ # pylint: disable=import-error from openzwave.network import ZWaveNetwork @@ -15,7 +18,7 @@ from threading import Timer def setup_platform(hass, config, add_devices, discovery_info=None): - """ Find and add zwave lights. """ + """ Find and add Z-Wave lights. """ if discovery_info is None: return @@ -34,8 +37,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def brightness_state(value): - """ Returns the brightness and state according to the current - data of given value. """ + """ + Returns the brightness and state according to the current data of given + value. + """ if value.data > 0: return (value.data / 99) * 255, STATE_ON else: @@ -43,7 +48,7 @@ def brightness_state(value): class ZwaveDimmer(Light): - """ Provides a zwave dimmer. """ + """ Provides a Z-Wave dimmer. """ # pylint: disable=too-many-arguments def __init__(self, value): self._value = value From 85e0db6ade537e2f910d3e4274edc414ebf3109a Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Fri, 13 Nov 2015 13:55:22 -0500 Subject: [PATCH 23/82] add exception handling to generic camera requests function. --- homeassistant/components/camera/__init__.py | 30 ++++++++++++--------- homeassistant/components/camera/generic.py | 16 ++++++++--- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 01c6c1b6e03..c44f56ed9f5 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -87,7 +87,10 @@ def setup(hass, config): if camera: response = camera.camera_image() - handler.wfile.write(response) + if response is not None: + handler.wfile.write(response) + else: + handler.send_response(HTTP_NOT_FOUND) else: handler.send_response(HTTP_NOT_FOUND) @@ -129,20 +132,21 @@ def setup(hass, config): while True: img_bytes = camera.camera_image() + if img_bytes is not None: + headers_str = '\r\n'.join(( + 'Content-length: {}'.format(len(img_bytes)), + 'Content-type: image/jpeg', + )) + '\r\n\r\n' - headers_str = '\r\n'.join(( - 'Content-length: {}'.format(len(img_bytes)), - 'Content-type: image/jpeg', - )) + '\r\n\r\n' - - handler.request.sendall( - bytes(headers_str, 'utf-8') + - img_bytes + - bytes('\r\n', 'utf-8')) - - handler.request.sendall( - bytes('--jpgboundary\r\n', 'utf-8')) + handler.request.sendall( + bytes(headers_str, 'utf-8') + + img_bytes + + bytes('\r\n', 'utf-8')) + handler.request.sendall( + bytes('--jpgboundary\r\n', 'utf-8')) + else: + break except (requests.RequestException, IOError): camera.is_streaming = False camera.update_ha_state() diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index 55fa4ec913f..b8be51292bf 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -42,11 +42,19 @@ class GenericCamera(Camera): def camera_image(self): """ Return a still image reponse from the camera. """ if self._username and self._password: - response = requests.get( - self._still_image_url, - auth=HTTPBasicAuth(self._username, self._password)) + try: + response = requests.get( + self._still_image_url, + auth=HTTPBasicAuth(self._username, self._password)) + except requests.exceptions.RequestException as error: + _LOGGER.error('Error getting camera image: %s', error) + return None else: - response = requests.get(self._still_image_url) + try: + response = requests.get(self._still_image_url) + except requests.exceptions.RequestException as error: + _LOGGER.error('Error getting camera image: %s', error) + return None return response.content From d68a4b52f12fc1027ea6564bcaa5dab5334e6414 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Fri, 13 Nov 2015 14:32:47 -0500 Subject: [PATCH 24/82] Remove rgb color if it's not an rgb bulb. --- homeassistant/components/light/mqtt.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index f65c99e43e3..a2bae742b6a 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): config.get('brightness_command_topic'), "rgb_state_topic": config.get('rgb_state_topic'), "rgb_command_topic": config.get('rgb_command_topic')}, - config.get('rgb', [255, 255, 255]), + config.get('rgb', [-1, -1, -1]), config.get('qos', DEFAULT_QOS), {"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, @@ -67,7 +67,10 @@ class MqttLight(Light): self._hass = hass self._name = name self._topic = topic - self._rgb = rgb + if rgb != [-1, -1, -1]: + self._rgb = rgb + else: + self._rgb = None self._qos = qos self._payload = payload self._brightness = brightness From 776324807ee364452c5c79b040324939807b3b01 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Fri, 13 Nov 2015 14:58:49 -0500 Subject: [PATCH 25/82] last PR was dumb. this fix is better. --- homeassistant/components/light/mqtt.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index a2bae742b6a..13537859a0d 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): config.get('brightness_command_topic'), "rgb_state_topic": config.get('rgb_state_topic'), "rgb_command_topic": config.get('rgb_command_topic')}, - config.get('rgb', [-1, -1, -1]), + config.get('rgb', None), config.get('qos', DEFAULT_QOS), {"on": config.get('payload_on', DEFAULT_PAYLOAD_ON), "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)}, @@ -67,10 +67,7 @@ class MqttLight(Light): self._hass = hass self._name = name self._topic = topic - if rgb != [-1, -1, -1]: - self._rgb = rgb - else: - self._rgb = None + self._rgb = rgb self._qos = qos self._payload = payload self._brightness = brightness From 646618a25e44c98a8bba0c7cceb9dd074698adc1 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 14 Nov 2015 15:23:20 +0100 Subject: [PATCH 26/82] Improve error messages, use constants, and fix docstrings --- homeassistant/components/sensor/glances.py | 84 ++++++++++++---------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/sensor/glances.py index 1ac01e90a22..d651b477932 100644 --- a/homeassistant/components/sensor/glances.py +++ b/homeassistant/components/sensor/glances.py @@ -12,11 +12,14 @@ from datetime import timedelta from homeassistant.util import Throttle from homeassistant.helpers.entity import Entity +from homeassistant.const import STATE_UNKNOWN _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Glances Sensor' _RESOURCE = '/api/2/all' +CONF_HOST = 'host' +CONF_PORT = '61208' +CONF_RESOURCES = 'resources' SENSOR_TYPES = { 'disk_use_percent': ['Disk Use', '%'], 'disk_use': ['Disk Use', 'GiB'], @@ -43,13 +46,15 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) def setup_platform(hass, config, add_devices, discovery_info=None): """ Setup the Glances sensor. """ - if not config.get('host'): - _LOGGER.error('"host:" is missing your configuration') - return False - - host = config.get('host') - port = config.get('port', 61208) + host = config.get(CONF_HOST) + port = config.get('port', CONF_PORT) url = 'http://{}:{}{}'.format(host, port, _RESOURCE) + var_conf = config.get(CONF_RESOURCES) + + if None in (host, var_conf): + _LOGGER.error('Not all required config keys present: %s', + ', '.join((CONF_HOST, CONF_RESOURCES))) + return False try: response = requests.get(url, timeout=10) @@ -57,18 +62,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('Response status is "%s"', response.status_code) return False except requests.exceptions.MissingSchema: - _LOGGER.error('Missing resource or schema in configuration. ' - 'Please heck our details in the configuration file.') + _LOGGER.error("Missing resource or schema in configuration. " + "Please check the details in the configuration file.") return False except requests.exceptions.ConnectionError: - _LOGGER.error('No route to resource/endpoint. ' - 'Please check the details in the configuration file.') + _LOGGER.error("No route to resource/endpoint: '%s'. " + "Please check the details in the configuration file.", + url) return False rest = GlancesData(url) dev = [] - for resource in config['resources']: + for resource in var_conf: if resource not in SENSOR_TYPES: _LOGGER.error('Sensor type: "%s" does not exist', resource) else: @@ -78,13 +84,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class GlancesSensor(Entity): - """ Implements a REST sensor. """ + """ Implements a Glances sensor. """ def __init__(self, rest, sensor_type): self.rest = rest self._name = SENSOR_TYPES[sensor_type][0] self.type = sensor_type - self._state = None + self._state = STATE_UNKNOWN self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] self.update() @@ -98,46 +104,45 @@ class GlancesSensor(Entity): """ Unit the value is expressed in. """ return self._unit_of_measurement + # pylint: disable=too-many-branches, too-many-return-statements @property def state(self): - """ Returns the state of the device. """ - return self._state - - # pylint: disable=too-many-branches - def update(self): - """ Gets the latest data from REST API and updates the state. """ - self.rest.update() + """ Returns the state of the resources. """ value = self.rest.data if value is not None: if self.type == 'disk_use_percent': - self._state = value['fs'][0]['percent'] + return value['fs'][0]['percent'] elif self.type == 'disk_use': - self._state = round(value['fs'][0]['used'] / 1024**3, 1) + return round(value['fs'][0]['used'] / 1024**3, 1) elif self.type == 'disk_free': - self._state = round(value['fs'][0]['free'] / 1024**3, 1) + return round(value['fs'][0]['free'] / 1024**3, 1) elif self.type == 'memory_use_percent': - self._state = value['mem']['percent'] + return value['mem']['percent'] elif self.type == 'memory_use': - self._state = round(value['mem']['used'] / 1024**2, 1) + return round(value['mem']['used'] / 1024**2, 1) elif self.type == 'memory_free': - self._state = round(value['mem']['free'] / 1024**2, 1) + return round(value['mem']['free'] / 1024**2, 1) elif self.type == 'swap_use_percent': - self._state = value['memswap']['percent'] + return value['memswap']['percent'] elif self.type == 'swap_use': - self._state = round(value['memswap']['used'] / 1024**3, 1) + return round(value['memswap']['used'] / 1024**3, 1) elif self.type == 'swap_free': - self._state = round(value['memswap']['free'] / 1024**3, 1) + return round(value['memswap']['free'] / 1024**3, 1) elif self.type == 'processor_load': - self._state = value['load']['min15'] + return value['load']['min15'] elif self.type == 'process_running': - self._state = value['processcount']['running'] + return value['processcount']['running'] elif self.type == 'process_total': - self._state = value['processcount']['total'] + return value['processcount']['total'] elif self.type == 'process_thread': - self._state = value['processcount']['thread'] + return value['processcount']['thread'] elif self.type == 'process_sleeping': - self._state = value['processcount']['sleeping'] + return value['processcount']['sleeping'] + + def update(self): + """ Gets the latest data from REST API. """ + self.rest.update() # pylint: disable=too-few-public-methods @@ -145,15 +150,16 @@ class GlancesData(object): """ Class for handling the data retrieval. """ def __init__(self, resource): - self.resource = resource + self._resource = resource self.data = dict() @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from REST service. """ + """ Gets the latest data from teh Glances REST API. """ try: - response = requests.get(self.resource, timeout=10) + response = requests.get(self._resource, timeout=10) self.data = response.json() except requests.exceptions.ConnectionError: - _LOGGER.error("No route to host/endpoint.") + _LOGGER.error("No route to host/endpoint '%s'. Is device offline?", + self._resource) self.data = None From 5275ca9ce75af94c76ae4575c6cc71b75f473e79 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 14 Nov 2015 15:25:52 +0100 Subject: [PATCH 27/82] Fix typo --- homeassistant/components/sensor/glances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/sensor/glances.py index d651b477932..176081336df 100644 --- a/homeassistant/components/sensor/glances.py +++ b/homeassistant/components/sensor/glances.py @@ -155,7 +155,7 @@ class GlancesData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """ Gets the latest data from teh Glances REST API. """ + """ Gets the latest data from the Glances REST API. """ try: response = requests.get(self._resource, timeout=10) self.data = response.json() From 9acb341b9607dd1dd404378a324fdcd10b6e2452 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Sat, 14 Nov 2015 10:51:07 -0500 Subject: [PATCH 28/82] remove break --- homeassistant/components/camera/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index c44f56ed9f5..77a8eb83ce1 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -132,7 +132,9 @@ def setup(hass, config): while True: img_bytes = camera.camera_image() - if img_bytes is not None: + if img_bytes is None: + continue + else: headers_str = '\r\n'.join(( 'Content-length: {}'.format(len(img_bytes)), 'Content-type: image/jpeg', @@ -145,8 +147,7 @@ def setup(hass, config): handler.request.sendall( bytes('--jpgboundary\r\n', 'utf-8')) - else: - break + except (requests.RequestException, IOError): camera.is_streaming = False camera.update_ha_state() From cf8e23adbc3fe477468a3370096ef88dea3f6f2b Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sat, 14 Nov 2015 14:14:02 -0500 Subject: [PATCH 29/82] s20 switch support --- .coveragerc | 1 + homeassistant/components/switch/s20.py | 73 ++++++++++++++++++++++++++ requirements_all.txt | 3 ++ 3 files changed, 77 insertions(+) create mode 100644 homeassistant/components/switch/s20.py diff --git a/.coveragerc b/.coveragerc index 5202822b22f..c23238788a4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -99,6 +99,7 @@ omit = homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/rest.py homeassistant/components/switch/rpi_gpio.py + homeassistant/components/switch/s20.py homeassistant/components/switch/transmission.py homeassistant/components/switch/wemo.py homeassistant/components/thermostat/honeywell.py diff --git a/homeassistant/components/switch/s20.py b/homeassistant/components/switch/s20.py new file mode 100644 index 00000000000..62b36721182 --- /dev/null +++ b/homeassistant/components/switch/s20.py @@ -0,0 +1,73 @@ +""" +homeassistant.components.switch.s20 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Orvibo S20 Wifi Smart Switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.s20/ +""" +import logging + +from homeassistant.components.switch import SwitchDevice + +from orvibo.s20 import S20, S20Exception + +DEFAULT_NAME = "Orbivo S20 Switch" +REQUIREMENTS = ['orbivo==1.0.0'] +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return S20 switches. """ + if config.get('host') is None: + _LOGGER.error("Missing required variable: host") + return + try: + s20 = S20(config.get('host')) + add_devices_callback([S20Switch(config.get('name', DEFAULT_NAME), + s20)]) + except S20Exception as exception: + _LOGGER.error(exception) + + +class S20Switch(SwitchDevice): + """ Represents an S20 switch. """ + def __init__(self, name, s20): + self._name = name + self._s20 = s20 + + @property + def should_poll(self): + """ No polling needed. """ + return False + + @property + def name(self): + """ The name of the switch. """ + return self._name + + @property + def is_on(self): + """ True if device is on. """ + try: + return self._s20.on + except S20Exception as exception: + _LOGGER.error(exception) + return False + + def turn_on(self, **kwargs): + """ Turn the device on. """ + try: + self._s20.on = True + except S20Exception as exception: + _LOGGER.error(exception) + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + try: + self._s20.on = False + except S20Exception as exception: + _LOGGER.error(exception) + self.update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index 571708ab873..7cb34b22c36 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -156,3 +156,6 @@ evohomeclient==0.2.3 # Pushetta (notify.pushetta) pushetta==1.0.15 + +# Orbivo S10 +orbivo==1.0.0 From 57ec58e255788bbf825b646d1f43748cd14a7141 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sat, 14 Nov 2015 14:19:47 -0500 Subject: [PATCH 30/82] fixed requirements --- requirements_all.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_all.txt b/requirements_all.txt index 7cb34b22c36..907c79823bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -157,5 +157,5 @@ evohomeclient==0.2.3 # Pushetta (notify.pushetta) pushetta==1.0.15 -# Orbivo S10 -orbivo==1.0.0 +# Orvibo S10 +orvibo==1.0.0 From 70fef3c5b55ad2f28d6f42807fba95a91dbc2768 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sat, 14 Nov 2015 14:25:53 -0500 Subject: [PATCH 31/82] fixed names --- homeassistant/components/switch/s20.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/s20.py b/homeassistant/components/switch/s20.py index 62b36721182..cefee79201c 100644 --- a/homeassistant/components/switch/s20.py +++ b/homeassistant/components/switch/s20.py @@ -12,8 +12,8 @@ from homeassistant.components.switch import SwitchDevice from orvibo.s20 import S20, S20Exception -DEFAULT_NAME = "Orbivo S20 Switch" -REQUIREMENTS = ['orbivo==1.0.0'] +DEFAULT_NAME = "Orvibo S20 Switch" +REQUIREMENTS = ['orvibo==1.0.0'] _LOGGER = logging.getLogger(__name__) From aabcad59d8d0c47d4261b518fa3c9f46594b9ec5 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sat, 14 Nov 2015 15:05:22 -0500 Subject: [PATCH 32/82] rename platform to orvibo --- .coveragerc | 2 +- homeassistant/components/switch/{s20.py => orvibo.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename homeassistant/components/switch/{s20.py => orvibo.py} (98%) diff --git a/.coveragerc b/.coveragerc index c23238788a4..f19e37d00a1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -84,6 +84,7 @@ omit = homeassistant/components/sensor/glances.py homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py + homeassistant/components/switch/orvibo.py homeassistant/components/sensor/rest.py homeassistant/components/sensor/rpi_gpio.py homeassistant/components/sensor/sabnzbd.py @@ -99,7 +100,6 @@ omit = homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/rest.py homeassistant/components/switch/rpi_gpio.py - homeassistant/components/switch/s20.py homeassistant/components/switch/transmission.py homeassistant/components/switch/wemo.py homeassistant/components/thermostat/honeywell.py diff --git a/homeassistant/components/switch/s20.py b/homeassistant/components/switch/orvibo.py similarity index 98% rename from homeassistant/components/switch/s20.py rename to homeassistant/components/switch/orvibo.py index cefee79201c..909f1e89ce5 100644 --- a/homeassistant/components/switch/s20.py +++ b/homeassistant/components/switch/orvibo.py @@ -1,5 +1,5 @@ """ -homeassistant.components.switch.s20 +homeassistant.components.switch.orvibo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Orvibo S20 Wifi Smart Switches. From df264f2ec00dc84a0a7ca637c568a3273f55fd03 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Sat, 14 Nov 2015 15:49:39 -0500 Subject: [PATCH 33/82] remove unnecessary else --- homeassistant/components/camera/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 77a8eb83ce1..ff5198b7ab1 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -134,19 +134,18 @@ def setup(hass, config): img_bytes = camera.camera_image() if img_bytes is None: continue - else: - headers_str = '\r\n'.join(( - 'Content-length: {}'.format(len(img_bytes)), - 'Content-type: image/jpeg', - )) + '\r\n\r\n' + headers_str = '\r\n'.join(( + 'Content-length: {}'.format(len(img_bytes)), + 'Content-type: image/jpeg', + )) + '\r\n\r\n' - handler.request.sendall( - bytes(headers_str, 'utf-8') + - img_bytes + - bytes('\r\n', 'utf-8')) + handler.request.sendall( + bytes(headers_str, 'utf-8') + + img_bytes + + bytes('\r\n', 'utf-8')) - handler.request.sendall( - bytes('--jpgboundary\r\n', 'utf-8')) + handler.request.sendall( + bytes('--jpgboundary\r\n', 'utf-8')) except (requests.RequestException, IOError): camera.is_streaming = False From 86b9ae95669d511ee6669d263e43b1d5ead5906a Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sat, 14 Nov 2015 16:14:25 -0500 Subject: [PATCH 34/82] addressed comments --- homeassistant/components/switch/orvibo.py | 32 +++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/switch/orvibo.py b/homeassistant/components/switch/orvibo.py index 909f1e89ce5..f53b131eabf 100644 --- a/homeassistant/components/switch/orvibo.py +++ b/homeassistant/components/switch/orvibo.py @@ -27,8 +27,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): s20 = S20(config.get('host')) add_devices_callback([S20Switch(config.get('name', DEFAULT_NAME), s20)]) - except S20Exception as exception: - _LOGGER.error(exception) + except S20Exception: + _LOGGER.exception("S20 couldn't be initialized") class S20Switch(SwitchDevice): @@ -36,11 +36,12 @@ class S20Switch(SwitchDevice): def __init__(self, name, s20): self._name = name self._s20 = s20 + self._state = False @property def should_poll(self): - """ No polling needed. """ - return False + """ Poll. """ + return True @property def name(self): @@ -50,24 +51,27 @@ class S20Switch(SwitchDevice): @property def is_on(self): """ True if device is on. """ + return self._state + + def update(self): + """ Update device state. """ try: - return self._s20.on - except S20Exception as exception: - _LOGGER.error(exception) - return False + self._state = self._s20.on + except S20Exception: + _LOGGER.exception("Error while fetching S20 state") def turn_on(self, **kwargs): """ Turn the device on. """ try: self._s20.on = True - except S20Exception as exception: - _LOGGER.error(exception) - self.update_ha_state() + self._state = True + except S20Exception: + _LOGGER.exception("Error while turning on S20") def turn_off(self, **kwargs): """ Turn the device off. """ try: self._s20.on = False - except S20Exception as exception: - _LOGGER.error(exception) - self.update_ha_state() + self._state = False + except S20Exception: + _LOGGER.exception("Error while turning off S20") From 56c5d345a4cdb4d27fde34835ad42b354075e396 Mon Sep 17 00:00:00 2001 From: Arthur Andersen Date: Tue, 10 Nov 2015 20:04:47 +0100 Subject: [PATCH 35/82] [Zwave] Update HA state on value change --- homeassistant/components/switch/zwave.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py index cc022df18c9..7d86605c646 100644 --- a/homeassistant/components/switch/zwave.py +++ b/homeassistant/components/switch/zwave.py @@ -69,12 +69,8 @@ class ZwaveSwitch(SwitchDevice): def turn_on(self, **kwargs): """ Turn the device on. """ - if self._node.set_switch(self._value.value_id, True): - self._state = True - self.update_ha_state() + self._node.set_switch(self._value.value_id, True) def turn_off(self, **kwargs): """ Turn the device off. """ - if self._node.set_switch(self._value.value_id, False): - self._state = False - self.update_ha_state() + self._node.set_switch(self._value.value_id, False) From e2c530b85dcc743b3a134ba890c16715a46a0049 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 14 Nov 2015 15:36:27 -0800 Subject: [PATCH 36/82] Script: new attribute if can cancel --- homeassistant/components/script.py | 7 ++++++- tests/components/test_script.py | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 3feaff3f903..2b18a5143fd 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -37,6 +37,7 @@ CONF_EVENT_DATA = "event_data" CONF_DELAY = "delay" ATTR_LAST_ACTION = 'last_action' +ATTR_CAN_CANCEL = 'can_cancel' _LOGGER = logging.getLogger(__name__) @@ -113,6 +114,8 @@ class Script(ToggleEntity): self._cur = -1 self._last_action = None self._listener = None + self._can_cancel = not any(CONF_DELAY in action for action + in self.sequence) @property def should_poll(self): @@ -126,7 +129,9 @@ class Script(ToggleEntity): @property def state_attributes(self): """ Returns the state attributes. """ - attrs = {} + attrs = { + ATTR_CAN_CANCEL: self._can_cancel + } if self._last_action: attrs[ATTR_LAST_ACTION] = self._last_action diff --git a/tests/components/test_script.py b/tests/components/test_script.py index e4abed18ec9..50cfba55ec5 100644 --- a/tests/components/test_script.py +++ b/tests/components/test_script.py @@ -88,6 +88,8 @@ class TestScript(unittest.TestCase): self.assertEqual(1, len(calls)) self.assertEqual('world', calls[0].data.get('hello')) + self.assertEqual( + True, self.hass.states.get(ENTITY_ID).attributes.get('can_cancel')) def test_calling_service_old(self): calls = [] @@ -172,6 +174,9 @@ class TestScript(unittest.TestCase): self.hass.pool.block_till_done() self.assertTrue(script.is_on(self.hass, ENTITY_ID)) + self.assertEqual( + False, + self.hass.states.get(ENTITY_ID).attributes.get('can_cancel')) self.assertEqual( event, From 88f3a5a50ad18ba0c70d749d9f9d8b4daafdfac1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 15 Nov 2015 00:51:12 -0800 Subject: [PATCH 37/82] Update to new version frontend --- homeassistant/components/frontend/version.py | 2 +- .../components/frontend/www_static/frontend.html | 15 +++++++++------ .../frontend/www_static/home-assistant-polymer | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 924e46ae814..1240aeac93b 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "75532015507fd544f46081ec0eeb5004" +VERSION = "44fc40d6c32d1f5ff94c590d17f9cc2e" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 5ae7ce0b002..7dd95eaba3e 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3784,6 +3784,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN }