Merge pull request #1243 from MartinHjelmare/refactor-mysensors-light

Refactor mysensors light
This commit is contained in:
Paulus Schoutsen 2016-02-17 20:27:52 -08:00
commit 70f05f30d5
6 changed files with 261 additions and 109 deletions

View File

@ -1,17 +1,17 @@
""" """
homeassistant.components.light.mysensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors lights. Support for MySensors lights.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mysensors.html https://home-assistant.io/components/light.mysensors/
""" """
import logging import logging
from homeassistant.components.light import ( from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR) Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR)
from homeassistant.util.color import (
rgb_hex_to_rgb_list)
from homeassistant.const import ( from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_BATTERY_LEVEL,
STATE_ON, STATE_OFF) STATE_ON, STATE_OFF)
@ -20,6 +20,8 @@ import homeassistant.components.mysensors as mysensors
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_RGB_WHITE = 'rgb_white' ATTR_RGB_WHITE = 'rgb_white'
ATTR_VALUE = 'value'
ATTR_VALUE_TYPE = 'value_type'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -38,6 +40,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pres.S_LIGHT: [set_req.V_LIGHT], pres.S_LIGHT: [set_req.V_LIGHT],
pres.S_DIMMER: [set_req.V_DIMMER], pres.S_DIMMER: [set_req.V_DIMMER],
} }
device_class_map = {
pres.S_LIGHT: MySensorsLightPlain,
pres.S_DIMMER: MySensorsLightDimmer,
}
if float(gateway.version) >= 1.5: if float(gateway.version) >= 1.5:
# Add V_RGBW when rgb_white is implemented in the frontend # Add V_RGBW when rgb_white is implemented in the frontend
map_sv_types.update({ map_sv_types.update({
@ -45,10 +51,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
}) })
map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS) map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS)
map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE) map_sv_types[pres.S_DIMMER].append(set_req.V_PERCENTAGE)
device_class_map.update({
pres.S_RGB_LIGHT: MySensorsLightRGB,
})
devices = {} devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory( gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsLight)) map_sv_types, devices, add_devices, device_class_map))
class MySensorsLight(Light): class MySensorsLight(Light):
@ -66,8 +74,8 @@ class MySensorsLight(Light):
self.battery_level = 0 self.battery_level = 0
self._values = {} self._values = {}
self._state = None self._state = None
self._rgb = None
self._brightness = None self._brightness = None
self._rgb = None
self._white = None self._white = None
@property @property
@ -108,119 +116,246 @@ class MySensorsLight(Light):
device_attr[self.gateway.const.SetReq(value_type).name] = value device_attr[self.gateway.const.SetReq(value_type).name] = value
return device_attr return device_attr
@property
def available(self):
"""Return True if entity is available."""
return self.value_type in self._values
@property @property
def is_on(self): def is_on(self):
"""True if device is on.""" """True if device is on."""
return self._state return self._state
def turn_on(self, **kwargs): def _turn_on_light(self):
"""Turn the device on.""" """Turn on light child device."""
set_req = self.gateway.const.SetReq set_req = self.gateway.const.SetReq
rgb = self._rgb
brightness = self._brightness
white = self._white
if set_req.V_LIGHT in self._values and not self._state: if not self._state and set_req.V_LIGHT in self._values:
self.gateway.set_child_value( self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_LIGHT, 1) self.node_id, self.child_id, set_req.V_LIGHT, 1)
if ATTR_BRIGHTNESS in kwargs and set_req.V_DIMMER in self._values and \ if self.gateway.optimistic:
# optimistically assume that light has changed state
self._state = True
self.update_ha_state()
def _turn_on_dimmer(self, **kwargs):
"""Turn on dimmer child device."""
set_req = self.gateway.const.SetReq
brightness = self._brightness
if ATTR_BRIGHTNESS in kwargs and \
kwargs[ATTR_BRIGHTNESS] != self._brightness: kwargs[ATTR_BRIGHTNESS] != self._brightness:
brightness = kwargs[ATTR_BRIGHTNESS] brightness = kwargs[ATTR_BRIGHTNESS]
percent = round(100 * brightness / 255) percent = round(100 * brightness / 255)
self.gateway.set_child_value( self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DIMMER, percent) self.node_id, self.child_id, set_req.V_DIMMER, percent)
if float(self.gateway.version) >= 1.5: if self.gateway.optimistic:
# optimistically assume that light has changed state
self._brightness = brightness
self.update_ha_state()
def _turn_on_rgb_and_w(self, hex_template, **kwargs):
"""Turn on RGB or RGBW child device."""
rgb = self._rgb
white = self._white
if ATTR_RGB_WHITE in kwargs and \ if ATTR_RGB_WHITE in kwargs and \
self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \
kwargs[ATTR_RGB_WHITE] != self._white: kwargs[ATTR_RGB_WHITE] != self._white:
white = kwargs[ATTR_RGB_WHITE] white = kwargs[ATTR_RGB_WHITE]
if ATTR_RGB_COLOR in kwargs and \ if ATTR_RGB_COLOR in kwargs and \
self.value_type in (set_req.V_RGB, set_req.V_RGBW) and \
kwargs[ATTR_RGB_COLOR] != self._rgb: kwargs[ATTR_RGB_COLOR] != self._rgb:
rgb = kwargs[ATTR_RGB_COLOR] rgb = kwargs[ATTR_RGB_COLOR]
if set_req.V_RGBW == self.value_type: if white is not None and hex_template == '%02x%02x%02x%02x':
hex_template = '%02x%02x%02x%02x' rgb.append(white)
color_list = rgb.append(white) hex_color = hex_template % tuple(rgb)
if set_req.V_RGB == self.value_type:
hex_template = '%02x%02x%02x'
color_list = rgb
hex_color = hex_template % tuple(color_list)
self.gateway.set_child_value( self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, hex_color) self.node_id, self.child_id, self.value_type, hex_color)
if self.gateway.optimistic: if self.gateway.optimistic:
# optimistically assume that light has changed state # optimistically assume that light has changed state
self._state = True
self._rgb = rgb self._rgb = rgb
self._brightness = brightness
self._white = white self._white = white
self.update_ha_state() self.update_ha_state()
def turn_off(self, **kwargs): def _turn_off_light(self, value_type=None, value=None):
"""Turn the device off.""" """Turn off light child device."""
set_req = self.gateway.const.SetReq set_req = self.gateway.const.SetReq
v_type = set_req.V_LIGHT value_type = (
value = 0 set_req.V_LIGHT
if set_req.V_LIGHT in self._values: if set_req.V_LIGHT in self._values else value_type)
self._values[set_req.V_LIGHT] = STATE_OFF value = 0 if set_req.V_LIGHT in self._values else value
elif set_req.V_DIMMER in self._values: return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value}
v_type = set_req.V_DIMMER
elif float(self.gateway.version) >= 1.5:
if set_req.V_RGB in self._values:
v_type = set_req.V_RGB
value = '000000'
elif set_req.V_RGBW in self._values:
v_type = set_req.V_RGBW
value = '00000000'
self.gateway.set_child_value(
self.node_id, self.child_id, v_type, value)
def _turn_off_dimmer(self, value_type=None, value=None):
"""Turn off dimmer child device."""
set_req = self.gateway.const.SetReq
value_type = (
set_req.V_DIMMER
if set_req.V_DIMMER in self._values else value_type)
value = 0 if set_req.V_DIMMER in self._values else value
return {ATTR_VALUE_TYPE: value_type, ATTR_VALUE: value}
def _turn_off_rgb_or_w(self, value_type=None, value=None):
"""Turn off RGB or RGBW child device."""
if float(self.gateway.version) >= 1.5:
set_req = self.gateway.const.SetReq
if self.value_type == set_req.V_RGB:
value = '000000'
elif self.value_type == set_req.V_RGBW:
value = '00000000'
return {ATTR_VALUE_TYPE: self.value_type, ATTR_VALUE: value}
def _turn_off_main(self, value_type=None, value=None):
"""Turn the device off."""
if value_type is None or value is None:
_LOGGER.warning(
'%s: value_type %s, value = %s, '
'None is not valid argument when setting child value'
'', self._name, value_type, value)
return
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic: if self.gateway.optimistic:
# optimistically assume that light has changed state # optimistically assume that light has changed state
self._state = False self._state = False
self.update_ha_state() self.update_ha_state()
@property def _update_light(self):
def available(self): """Update the controller with values from light child."""
"""Return True if entity is available.""" value_type = self.gateway.const.SetReq.V_LIGHT
return self.value_type in self._values if value_type in self._values:
def update(self):
"""Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
set_req = self.gateway.const.SetReq
self.battery_level = node.battery_level
for value_type, value in child.values.items():
_LOGGER.debug(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == set_req.V_LIGHT:
self._values[value_type] = ( self._values[value_type] = (
STATE_ON if int(value) == 1 else STATE_OFF) STATE_ON if int(self._values[value_type]) == 1 else STATE_OFF)
self._state = self._values[value_type] == STATE_ON self._state = self._values[value_type] == STATE_ON
else:
self._values[value_type] = value def _update_dimmer(self):
if value_type == set_req.V_DIMMER: """Update the controller with values from dimmer child."""
self._brightness = round( set_req = self.gateway.const.SetReq
255 * int(self._values[value_type]) / 100) value_type = set_req.V_DIMMER
if value_type in self._values:
self._brightness = round(255 * int(self._values[value_type]) / 100)
if self._brightness == 0: if self._brightness == 0:
self._state = False self._state = False
if set_req.V_LIGHT not in self._values: if set_req.V_LIGHT not in self._values:
self._state = self._brightness > 0 self._state = self._brightness > 0
if float(self.gateway.version) >= 1.5 and \
value_type in (set_req.V_RGB, set_req.V_RGBW): def _update_rgb_or_w(self):
# convert hex color string to rgb(w) integer list """Update the controller with values from RGB or RGBW child."""
color_list = [int(value[i:i + len(value) // 3], 16) set_req = self.gateway.const.SetReq
for i in range(0, value = self._values[self.value_type]
len(value), color_list = rgb_hex_to_rgb_list(value)
len(value) // 3)] if set_req.V_LIGHT not in self._values and \
set_req.V_DIMMER not in self._values:
self._state = max(color_list) > 0
if len(color_list) > 3: if len(color_list) > 3:
self._white = color_list.pop() self._white = color_list.pop()
self._rgb = color_list self._rgb = color_list
if set_req.V_LIGHT not in self._values or \
set_req.V_DIMMER not in self._values: def _update_main(self):
self._state = max(color_list) > 0 """Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
self.battery_level = node.battery_level
for value_type, value in child.values.items():
_LOGGER.debug(
'%s: value_type %s, value = %s', self._name, value_type, value)
self._values[value_type] = value
class MySensorsLightPlain(MySensorsLight):
"""Light child class to MySensorsLight."""
def turn_on(self, **kwargs):
"""Turn the device on."""
self._turn_on_light()
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_light()
self._turn_off_main(value_type=ret[
ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
self._update_light()
class MySensorsLightDimmer(MySensorsLight):
"""Dimmer child class to MySensorsLight."""
def turn_on(self, **kwargs):
"""Turn the device on."""
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_dimmer()
ret = self._turn_off_light(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
self._turn_off_main(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
self._update_light()
self._update_dimmer()
class MySensorsLightRGB(MySensorsLight):
"""RGB child class to MySensorsLight."""
def turn_on(self, **kwargs):
"""Turn the device on."""
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
self._turn_on_rgb_and_w('%02x%02x%02x', **kwargs)
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_rgb_or_w()
ret = self._turn_off_dimmer(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
ret = self._turn_off_light(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
self._turn_off_main(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
self._update_light()
self._update_dimmer()
self._update_rgb_or_w()
class MySensorsLightRGBW(MySensorsLight):
"""RGBW child class to MySensorsLight."""
def turn_on(self, **kwargs):
"""Turn the device on."""
self._turn_on_light()
self._turn_on_dimmer(**kwargs)
self._turn_on_rgb_and_w('%02x%02x%02x%02x', **kwargs)
def turn_off(self, **kwargs):
"""Turn the device off."""
ret = self._turn_off_rgb_or_w()
ret = self._turn_off_dimmer(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
ret = self._turn_off_light(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
self._turn_off_main(
value_type=ret[ATTR_VALUE_TYPE], value=ret[ATTR_VALUE])
def update(self):
"""Update the controller with the latest value from a sensor."""
self._update_main()
self._update_light()
self._update_dimmer()
self._update_rgb_or_w()

View File

@ -146,7 +146,11 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
continue continue
name = '{} {}.{}'.format( name = '{} {}.{}'.format(
gateway.sensors[node_id].sketch_name, node_id, child.id) gateway.sensors[node_id].sketch_name, node_id, child.id)
devices[key] = entity_class( if isinstance(entity_class, dict):
device_class = entity_class[child.type]
else:
device_class = entity_class
devices[key] = device_class(
gateway, node_id, child.id, name, value_type) gateway, node_id, child.id, name, value_type)
_LOGGER.info('Adding new devices: %s', devices[key]) _LOGGER.info('Adding new devices: %s', devices[key])

View File

@ -1,7 +1,4 @@
""" """
homeassistant.components.sensor.mysensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors sensors. Support for MySensors sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at

View File

@ -1,11 +1,8 @@
""" """
homeassistant.components.switch.mysensors.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors switches. Support for MySensors switches.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors.html https://home-assistant.io/components/switch.mysensors/
""" """
import logging import logging

View File

@ -1,8 +1,4 @@
""" """Color util methods."""
homeassistant.util.color
~~~~~~~~~~~~~~~~~~~~~~~~
Color util methods.
"""
# Taken from: http://www.cse.unr.edu/~quiroz/inc/colortransforms.py # Taken from: http://www.cse.unr.edu/~quiroz/inc/colortransforms.py
@ -50,9 +46,7 @@ def color_RGB_to_xy(R, G, B):
# Copyright (c) 2014 Benjamin Knight / MIT License. # Copyright (c) 2014 Benjamin Knight / MIT License.
# pylint: disable=bad-builtin # pylint: disable=bad-builtin
def color_xy_brightness_to_RGB(vX, vY, brightness): def color_xy_brightness_to_RGB(vX, vY, brightness):
''' """Convert from XYZ to RGB."""
Convert from XYZ to RGB.
'''
brightness /= 255. brightness /= 255.
if brightness == 0: if brightness == 0:
return (0, 0, 0) return (0, 0, 0)
@ -88,3 +82,11 @@ def color_xy_brightness_to_RGB(vX, vY, brightness):
r, g, b = map(lambda x: int(x * 255), [r, g, b]) r, g, b = map(lambda x: int(x * 255), [r, g, b])
return (r, g, b) return (r, g, b)
def rgb_hex_to_rgb_list(hex_string):
"""Return an RGB color value list from a hex color string."""
return [int(hex_string[i:i + len(hex_string) // 3], 16)
for i in range(0,
len(hex_string),
len(hex_string) // 3)]

View File

@ -1,14 +1,11 @@
""" """Tests Home Assistant color util methods."""
tests.util.test_color
~~~~~~~~~~~~~~~~~~~~~
Tests Home Assistant color util methods.
"""
import unittest import unittest
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
class TestColorUtil(unittest.TestCase): class TestColorUtil(unittest.TestCase):
"""Test color util methods."""
# pylint: disable=invalid-name # pylint: disable=invalid-name
def test_color_RGB_to_xy(self): def test_color_RGB_to_xy(self):
"""Test color_RGB_to_xy.""" """Test color_RGB_to_xy."""
@ -40,3 +37,23 @@ class TestColorUtil(unittest.TestCase):
self.assertEqual((0, 83, 255), self.assertEqual((0, 83, 255),
color_util.color_xy_brightness_to_RGB(0, 0, 255)) color_util.color_xy_brightness_to_RGB(0, 0, 255))
def test_rgb_hex_to_rgb_list(self):
"""Test rgb_hex_to_rgb_list."""
self.assertEqual([255, 255, 255],
color_util.rgb_hex_to_rgb_list('ffffff'))
self.assertEqual([0, 0, 0],
color_util.rgb_hex_to_rgb_list('000000'))
self.assertEqual([255, 255, 255, 255],
color_util.rgb_hex_to_rgb_list('ffffffff'))
self.assertEqual([0, 0, 0, 0],
color_util.rgb_hex_to_rgb_list('00000000'))
self.assertEqual([51, 153, 255],
color_util.rgb_hex_to_rgb_list('3399ff'))
self.assertEqual([51, 153, 255, 0],
color_util.rgb_hex_to_rgb_list('3399ff00'))