mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Initial Support for Zwave color bulbs (#2376)
* Initial Support for Zwave color bulbs * Revert name override for ZwaveColorLight
This commit is contained in:
parent
3afc566be1
commit
dc75b28b90
@ -4,12 +4,31 @@ Support for Z-Wave 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.zwave/
|
https://home-assistant.io/components/light.zwave/
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
# Because we do not compile openzwave on CI
|
# Because we do not compile openzwave on CI
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
from threading import Timer
|
from threading import Timer
|
||||||
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light
|
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
|
||||||
|
ATTR_RGB_COLOR, DOMAIN, Light
|
||||||
from homeassistant.components import zwave
|
from homeassistant.components import zwave
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
|
from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \
|
||||||
|
color_temperature_mired_to_kelvin, color_temperature_to_rgb
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
COLOR_CHANNEL_WARM_WHITE = 0x01
|
||||||
|
COLOR_CHANNEL_COLD_WHITE = 0x02
|
||||||
|
COLOR_CHANNEL_RED = 0x04
|
||||||
|
COLOR_CHANNEL_GREEN = 0x08
|
||||||
|
COLOR_CHANNEL_BLUE = 0x10
|
||||||
|
|
||||||
|
# Generate midpoint color temperatures for bulbs that have limited
|
||||||
|
# support for white light colors
|
||||||
|
TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN
|
||||||
|
TEMP_WARM_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 * 2 + HASS_COLOR_MIN
|
||||||
|
TEMP_COLD_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 + HASS_COLOR_MIN
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
@ -28,7 +47,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
value.set_change_verified(False)
|
value.set_change_verified(False)
|
||||||
add_devices([ZwaveDimmer(value)])
|
|
||||||
|
if node.has_command_class(zwave.COMMAND_CLASS_COLOR):
|
||||||
|
try:
|
||||||
|
add_devices([ZwaveColorLight(value)])
|
||||||
|
except ValueError as exception:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Error initializing as color bulb: %s "
|
||||||
|
"Initializing as standard dimmer.", exception)
|
||||||
|
add_devices([ZwaveDimmer(value)])
|
||||||
|
else:
|
||||||
|
add_devices([ZwaveDimmer(value)])
|
||||||
|
|
||||||
|
|
||||||
def brightness_state(value):
|
def brightness_state(value):
|
||||||
@ -49,8 +78,9 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||||
|
self._brightness = None
|
||||||
self._brightness, self._state = brightness_state(value)
|
self._state = None
|
||||||
|
self.update_properties()
|
||||||
|
|
||||||
# Used for value change event handling
|
# Used for value change event handling
|
||||||
self._refreshing = False
|
self._refreshing = False
|
||||||
@ -59,6 +89,11 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
dispatcher.connect(
|
dispatcher.connect(
|
||||||
self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
||||||
|
|
||||||
|
def update_properties(self):
|
||||||
|
"""Update internal properties based on zwave values."""
|
||||||
|
# Brightness
|
||||||
|
self._brightness, self._state = brightness_state(self._value)
|
||||||
|
|
||||||
def _value_changed(self, value):
|
def _value_changed(self, value):
|
||||||
"""Called when a value has changed on the network."""
|
"""Called when a value has changed on the network."""
|
||||||
if self._value.value_id != value.value_id:
|
if self._value.value_id != value.value_id:
|
||||||
@ -66,7 +101,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
|
|
||||||
if self._refreshing:
|
if self._refreshing:
|
||||||
self._refreshing = False
|
self._refreshing = False
|
||||||
self._brightness, self._state = brightness_state(value)
|
self.update_properties()
|
||||||
else:
|
else:
|
||||||
def _refresh_value():
|
def _refresh_value():
|
||||||
"""Used timer callback for delayed value refresh."""
|
"""Used timer callback for delayed value refresh."""
|
||||||
@ -107,3 +142,168 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
|
|||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
if self._value.node.set_dimmer(self._value.value_id, 0):
|
if self._value.node.set_dimmer(self._value.value_id, 0):
|
||||||
self._state = STATE_OFF
|
self._state = STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
def ct_to_rgb(temp):
|
||||||
|
"""Convert color temperature (mireds) to RGB."""
|
||||||
|
colorlist = list(
|
||||||
|
color_temperature_to_rgb(color_temperature_mired_to_kelvin(temp)))
|
||||||
|
return [int(val) for val in colorlist]
|
||||||
|
|
||||||
|
|
||||||
|
class ZwaveColorLight(ZwaveDimmer):
|
||||||
|
"""Representation of a Z-Wave color changing light."""
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
"""Initialize the light."""
|
||||||
|
self._value_color = None
|
||||||
|
self._value_color_channels = None
|
||||||
|
self._color_channels = None
|
||||||
|
self._rgb = None
|
||||||
|
self._ct = None
|
||||||
|
|
||||||
|
# Here we attempt to find a zwave color value with the same instance
|
||||||
|
# id as the dimmer value. Currently zwave nodes that change colors
|
||||||
|
# only include one dimmer and one color command, but this will
|
||||||
|
# hopefully provide some forward compatibility for new devices that
|
||||||
|
# have multiple color changing elements.
|
||||||
|
for value_color in value.node.get_rgbbulbs().values():
|
||||||
|
if value.instance == value_color.instance:
|
||||||
|
self._value_color = value_color
|
||||||
|
|
||||||
|
if self._value_color is None:
|
||||||
|
raise ValueError("No matching color command found.")
|
||||||
|
|
||||||
|
for value_color_channels in value.node.get_values(
|
||||||
|
class_id=zwave.COMMAND_CLASS_COLOR, genre='System',
|
||||||
|
type="Int").values():
|
||||||
|
self._value_color_channels = value_color_channels
|
||||||
|
|
||||||
|
if self._value_color_channels is None:
|
||||||
|
raise ValueError("Color Channels not found.")
|
||||||
|
|
||||||
|
super().__init__(value)
|
||||||
|
|
||||||
|
def update_properties(self):
|
||||||
|
"""Update internal properties based on zwave values."""
|
||||||
|
super().update_properties()
|
||||||
|
|
||||||
|
# Color Channels
|
||||||
|
self._color_channels = self._value_color_channels.data
|
||||||
|
|
||||||
|
# Color Data String
|
||||||
|
data = self._value_color.data
|
||||||
|
|
||||||
|
# RGB is always present in the openzwave color data string.
|
||||||
|
self._rgb = [
|
||||||
|
int(data[1:3], 16),
|
||||||
|
int(data[3:5], 16),
|
||||||
|
int(data[5:7], 16)]
|
||||||
|
|
||||||
|
# Parse remaining color channels. Openzwave appends white channels
|
||||||
|
# that are present.
|
||||||
|
index = 7
|
||||||
|
|
||||||
|
# Warm white
|
||||||
|
if self._color_channels & COLOR_CHANNEL_WARM_WHITE:
|
||||||
|
warm_white = int(data[index:index+2], 16)
|
||||||
|
index += 2
|
||||||
|
else:
|
||||||
|
warm_white = 0
|
||||||
|
|
||||||
|
# Cold white
|
||||||
|
if self._color_channels & COLOR_CHANNEL_COLD_WHITE:
|
||||||
|
cold_white = int(data[index:index+2], 16)
|
||||||
|
index += 2
|
||||||
|
else:
|
||||||
|
cold_white = 0
|
||||||
|
|
||||||
|
# Color temperature. With two white channels, only two color
|
||||||
|
# temperatures are supported for the bulb. The channel values
|
||||||
|
# indicate brightness for warm/cold color temperature.
|
||||||
|
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
|
||||||
|
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
|
||||||
|
if warm_white > 0:
|
||||||
|
self._ct = TEMP_WARM_HASS
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
elif cold_white > 0:
|
||||||
|
self._ct = TEMP_COLD_HASS
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
else:
|
||||||
|
# RGB color is being used. Just report midpoint.
|
||||||
|
self._ct = TEMP_MID_HASS
|
||||||
|
|
||||||
|
# If only warm white is reported 0-255 is color temperature.
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
|
||||||
|
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
|
||||||
|
warm_white / 255)
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
|
||||||
|
# If only cold white is reported 0-255 is negative color temperature.
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
|
||||||
|
self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * (
|
||||||
|
(255 - cold_white) / 255)
|
||||||
|
self._rgb = ct_to_rgb(self._ct)
|
||||||
|
|
||||||
|
# If no rgb channels supported, report None.
|
||||||
|
if not (self._color_channels & COLOR_CHANNEL_RED or
|
||||||
|
self._color_channels & COLOR_CHANNEL_GREEN or
|
||||||
|
self._color_channels & COLOR_CHANNEL_BLUE):
|
||||||
|
self._rgb = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rgb_color(self):
|
||||||
|
"""Return the rgb color."""
|
||||||
|
return self._rgb
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_temp(self):
|
||||||
|
"""Return the color temperature."""
|
||||||
|
return self._ct
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
"""Turn the device on."""
|
||||||
|
rgbw = None
|
||||||
|
|
||||||
|
if ATTR_COLOR_TEMP in kwargs:
|
||||||
|
# With two white channels, only two color temperatures are
|
||||||
|
# supported for the bulb.
|
||||||
|
if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and
|
||||||
|
self._color_channels & COLOR_CHANNEL_COLD_WHITE):
|
||||||
|
if kwargs[ATTR_COLOR_TEMP] > TEMP_MID_HASS:
|
||||||
|
self._ct = TEMP_WARM_HASS
|
||||||
|
rgbw = b'#000000FF00'
|
||||||
|
else:
|
||||||
|
self._ct = TEMP_COLD_HASS
|
||||||
|
rgbw = b'#00000000FF'
|
||||||
|
|
||||||
|
# If only warm white is reported 0-255 is color temperature
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
|
||||||
|
rgbw = b'#000000'
|
||||||
|
temp = (
|
||||||
|
(kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
|
||||||
|
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
|
||||||
|
rgbw += format(int(temp)).encode('utf-8')
|
||||||
|
|
||||||
|
# If only cold white is reported 0-255 is negative color temp
|
||||||
|
elif self._color_channels & COLOR_CHANNEL_COLD_WHITE:
|
||||||
|
rgbw = b'#000000'
|
||||||
|
temp = (
|
||||||
|
255 - (kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) /
|
||||||
|
(HASS_COLOR_MAX - HASS_COLOR_MIN) * 255)
|
||||||
|
rgbw += format(int(temp)).encode('utf-8')
|
||||||
|
|
||||||
|
elif ATTR_RGB_COLOR in kwargs:
|
||||||
|
self._rgb = kwargs[ATTR_RGB_COLOR]
|
||||||
|
|
||||||
|
rgbw = b'#'
|
||||||
|
for colorval in self._rgb:
|
||||||
|
rgbw += format(colorval, '02x').encode('utf-8')
|
||||||
|
rgbw += b'0000'
|
||||||
|
|
||||||
|
if rgbw is None:
|
||||||
|
_LOGGER.warning("rgbw string was not generated for turn_on")
|
||||||
|
else:
|
||||||
|
self._value_color.node.set_rgbw(self._value_color.value_id, rgbw)
|
||||||
|
|
||||||
|
super().turn_on(**kwargs)
|
||||||
|
@ -41,6 +41,7 @@ EVENT_SCENE_ACTIVATED = "zwave.scene_activated"
|
|||||||
|
|
||||||
COMMAND_CLASS_WHATEVER = None
|
COMMAND_CLASS_WHATEVER = None
|
||||||
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
||||||
|
COMMAND_CLASS_COLOR = 51
|
||||||
COMMAND_CLASS_METER = 50
|
COMMAND_CLASS_METER = 50
|
||||||
COMMAND_CLASS_ALARM = 113
|
COMMAND_CLASS_ALARM = 113
|
||||||
COMMAND_CLASS_SWITCH_BINARY = 37
|
COMMAND_CLASS_SWITCH_BINARY = 37
|
||||||
|
Loading…
x
Reference in New Issue
Block a user