From 9e16be31738187146b7fc11b822f36fbb607f781 Mon Sep 17 00:00:00 2001
From: Anders Melchiorsen <amelchio@nogoto.net>
Date: Sun, 11 Jun 2017 21:19:58 +0200
Subject: [PATCH] LIFX: clean up internal color conversions (#7964)

* Add color_util.color_hsv_to_RGB

* Use helper functions for LIFX conversions

The LIFX API uses 16 bits for saturation/brightness while HA uses 8 bits.
Using helper functions makes the conversion a bit nicer and less prone
to off-by-one issues.

The colorsys library uses 0.0-1.0 but we can avoid that by using the HA
color_util converters instead.
---
 .../components/light/lifx/__init__.py         | 45 ++++++++-----------
 homeassistant/util/color.py                   |  7 +++
 tests/util/test_color.py                      | 17 +++++++
 3 files changed, 42 insertions(+), 27 deletions(-)

diff --git a/homeassistant/components/light/lifx/__init__.py b/homeassistant/components/light/lifx/__init__.py
index 1a0e8d7d9ed..3bd53c425b9 100644
--- a/homeassistant/components/light/lifx/__init__.py
+++ b/homeassistant/components/light/lifx/__init__.py
@@ -4,7 +4,6 @@ Support for the LIFX platform that implements lights.
 For more details about this platform, please refer to the documentation at
 https://home-assistant.io/components/light.lifx/
 """
-import colorsys
 import logging
 import asyncio
 import sys
@@ -24,8 +23,6 @@ from homeassistant.components.light import (
     SUPPORT_XY_COLOR, SUPPORT_TRANSITION, SUPPORT_EFFECT,
     preprocess_turn_on_alternatives)
 from homeassistant.config import load_yaml_config_file
-from homeassistant.util.color import (
-    color_temperature_mired_to_kelvin, color_temperature_kelvin_to_mired)
 from homeassistant import util
 from homeassistant.core import callback
 from homeassistant.helpers.event import async_track_point_in_utc_time
@@ -51,9 +48,6 @@ SERVICE_LIFX_SET_STATE = 'lifx_set_state'
 ATTR_HSBK = 'hsbk'
 ATTR_POWER = 'power'
 
-BYTE_MAX = 255
-SHORT_MAX = 65535
-
 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
     vol.Optional(CONF_SERVER, default='0.0.0.0'): cv.string,
 })
@@ -200,15 +194,14 @@ class AwaitAioLIFX:
         return self.message
 
 
-def convert_rgb_to_hsv(rgb):
-    """Convert Home Assistant RGB values to HSV values."""
-    red, green, blue = [_ / BYTE_MAX for _ in rgb]
+def convert_8_to_16(value):
+    """Scale an 8 bit level into 16 bits."""
+    return (value << 8) | value
 
-    hue, saturation, brightness = colorsys.rgb_to_hsv(red, green, blue)
 
-    return [int(hue * SHORT_MAX),
-            int(saturation * SHORT_MAX),
-            int(brightness * SHORT_MAX)]
+def convert_16_to_8(value):
+    """Scale a 16 bit level into 8 bits."""
+    return value >> 8
 
 
 class LIFXLight(Light):
@@ -260,14 +253,14 @@ class LIFXLight(Light):
     @property
     def brightness(self):
         """Return the brightness of this light between 0..255."""
-        brightness = int(self._bri / (BYTE_MAX + 1))
+        brightness = convert_16_to_8(self._bri)
         _LOGGER.debug("brightness: %d", brightness)
         return brightness
 
     @property
     def color_temp(self):
         """Return the color temperature."""
-        temperature = color_temperature_kelvin_to_mired(self._kel)
+        temperature = color_util.color_temperature_kelvin_to_mired(self._kel)
 
         _LOGGER.debug("color_temp: %d", temperature)
         return temperature
@@ -280,7 +273,7 @@ class LIFXLight(Light):
             kelvin = 6500
         else:
             kelvin = 9000
-        return math.floor(color_temperature_kelvin_to_mired(kelvin))
+        return math.floor(color_util.color_temperature_kelvin_to_mired(kelvin))
 
     @property
     def max_mireds(self):
@@ -290,7 +283,7 @@ class LIFXLight(Light):
             kelvin = 2700
         else:
             kelvin = 2500
-        return math.ceil(color_temperature_kelvin_to_mired(kelvin))
+        return math.ceil(color_util.color_temperature_kelvin_to_mired(kelvin))
 
     @property
     def is_on(self):
@@ -446,7 +439,9 @@ class LIFXLight(Light):
 
         if ATTR_RGB_COLOR in kwargs:
             hue, saturation, brightness = \
-                convert_rgb_to_hsv(kwargs[ATTR_RGB_COLOR])
+                color_util.color_RGB_to_hsv(*kwargs[ATTR_RGB_COLOR])
+            saturation = convert_8_to_16(saturation)
+            brightness = convert_8_to_16(brightness)
             changed_color = True
         else:
             hue = self._hue
@@ -455,12 +450,12 @@ class LIFXLight(Light):
 
         if ATTR_XY_COLOR in kwargs:
             hue, saturation = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR])
-            saturation = saturation * (BYTE_MAX + 1)
+            saturation = convert_8_to_16(saturation)
             changed_color = True
 
         # When color or temperature is set, use a default value for the other
         if ATTR_COLOR_TEMP in kwargs:
-            kelvin = int(color_temperature_mired_to_kelvin(
+            kelvin = int(color_util.color_temperature_mired_to_kelvin(
                 kwargs[ATTR_COLOR_TEMP]))
             if not changed_color:
                 saturation = 0
@@ -472,7 +467,7 @@ class LIFXLight(Light):
                 kelvin = self._kel
 
         if ATTR_BRIGHTNESS in kwargs:
-            brightness = kwargs[ATTR_BRIGHTNESS] * (BYTE_MAX + 1)
+            brightness = convert_8_to_16(kwargs[ATTR_BRIGHTNESS])
             changed_color = True
         else:
             brightness = self._bri
@@ -491,12 +486,8 @@ class LIFXLight(Light):
         self._bri = bri
         self._kel = kel
 
-        red, green, blue = colorsys.hsv_to_rgb(
-            hue / SHORT_MAX, sat / SHORT_MAX, bri / SHORT_MAX)
-
-        red = int(red * BYTE_MAX)
-        green = int(green * BYTE_MAX)
-        blue = int(blue * BYTE_MAX)
+        red, green, blue = color_util.color_hsv_to_RGB(
+            hue, convert_16_to_8(sat), convert_16_to_8(bri))
 
         _LOGGER.debug("set_color: %d %d %d %d [%d %d %d]",
                       hue, sat, bri, kel, red, green, blue)
diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py
index 396b8a63601..d76816cfbb8 100644
--- a/homeassistant/util/color.py
+++ b/homeassistant/util/color.py
@@ -264,6 +264,13 @@ def color_RGB_to_hsv(iR: int, iG: int, iB: int) -> Tuple[int, int, int]:
     return (int(fHSV[0]*65536), int(fHSV[1]*255), int(fHSV[2]*255))
 
 
+# pylint: disable=invalid-sequence-index
+def color_hsv_to_RGB(iH: int, iS: int, iV: int) -> Tuple[int, int, int]:
+    """Convert an hsv color into its rgb representation."""
+    fRGB = colorsys.hsv_to_rgb(iH/65536, iS/255, iV/255)
+    return (int(fRGB[0]*255), int(fRGB[1]*255), int(fRGB[2]*255))
+
+
 # pylint: disable=invalid-sequence-index
 def color_xy_to_hs(vX: float, vY: float) -> Tuple[int, int]:
     """Convert an xy color to its hs representation."""
diff --git a/tests/util/test_color.py b/tests/util/test_color.py
index 43b5904a895..dfb2cd0733c 100644
--- a/tests/util/test_color.py
+++ b/tests/util/test_color.py
@@ -56,6 +56,23 @@ class TestColorUtil(unittest.TestCase):
         self.assertEqual((0, 255, 255),
                          color_util.color_RGB_to_hsv(255, 0, 0))
 
+    def test_color_hsv_to_RGB(self):
+        """Test color_RGB_to_hsv."""
+        self.assertEqual((0, 0, 0),
+                         color_util.color_hsv_to_RGB(0, 0, 0))
+
+        self.assertEqual((255, 255, 255),
+                         color_util.color_hsv_to_RGB(0, 0, 255))
+
+        self.assertEqual((0, 0, 255),
+                         color_util.color_hsv_to_RGB(43690, 255, 255))
+
+        self.assertEqual((0, 255, 0),
+                         color_util.color_hsv_to_RGB(21845, 255, 255))
+
+        self.assertEqual((255, 0, 0),
+                         color_util.color_hsv_to_RGB(0, 255, 255))
+
     def test_color_xy_to_hs(self):
         """Test color_xy_to_hs."""
         self.assertEqual((8609, 255),