Przeglądaj źródła

Light: add support for named_color dp.

As an alternative to full RGB support through the rgbhsv dp, handle
lights that have just a limited range of named colors to choose from.

Previously some such lights were handled with separate select
entities, or misusing the light "effect".

This uses the color name list in homeassistant.util.color to map
the named colors to RGB, and matches user input from the color wheel
UI to the nearest available color.

Updated lights in the following configs to use this, and deprecated
any previous select entities:

- blitzwolf_bwsh2_humidifier (unit tests updated to cover the new feature)
- dynasty_btx_fireplace
- grafkit_filament_dryer
- modernflames_orion_fireplace
- tesla_s300_purifier
- touchstone_sideline_fireplace

Issue #2472
Jason Rumney 1 rok temu
rodzic
commit
02f4dbce3e

+ 2 - 2
custom_components/tuya_local/devices/README.md

@@ -658,8 +658,8 @@ Humidifer can also cover dehumidifiers (use class to specify which).
 - **switch** (optional, boolean): a dp to control the on/off state of the light
 - **brightness** (optional, number): a dp to control the dimmer if available.  If a range is provided, the value will be automatically scaled into the 0-255 range for HA, so there is no need to provide a scale. If there is a fixed list of mappings, the values should be between 0 (off) and 255 (full brightness). If there is no switch dp, a brightness of 0 will be sent to turn the light off.
 - **color_temp** (optional, number): a dp to control the color temperature if available. See `target_range` above for mapping Tuya's range into Kelvin.
-
-- **rgbhsv** (optional, hex): a dp to control the color of the light, using encoded RGB and HSV values. The `format` field names recognized for decoding this field are `r`, `g`, `b`, `h`, `s`, `v`.
+- **rgbhsv** (optional, hex): a dp to control the color of the light, using encoded RGB and HSV values. The `format` field names recognized for decoding this field are `r`, `g`, `b`, `h`, `s`, `v`. If both RGB and HSV values are supplied by the light, the HSV will be preferred. Either RGB values or HS values are required. If V is missing, the brightness dp is required.
+- **named_color** (optional, string): a dp to control the color of the light, using a list of named colors. This is mutually exclusive with the rgbhsv dp. The list of recognised colors is from the HA COLORS table at https://github.com/home-assistant/core/blob/dev/homeassistant/util/color.py
 - **color_mode** (optional, mapping of strings): a dp to control which mode to use if the light supports multiple modes.
     Special values: `white, color_temp, hs, xy, rgb, rgbw, rgbww`, others will be treated as effects,
 	Note: only white, color_temp and hs are currently supported, others listed above are reserved and may be implemented in future when the need arises.

+ 36 - 0
custom_components/tuya_local/devices/blitzwolf_bwsh2_humidifier.yaml

@@ -28,8 +28,44 @@ primary_entity:
         - dps_val: grade6
           value: 100
 secondary_entities:
+  - entity: light
+    dps:
+      - id: 6
+        type: string
+        name: switch
+        mapping:
+          - dps_val: close
+            value: false
+            # We need a value to set to turn on the light with no parameters.
+          - dps_val: colour
+            value: true
+          - value: true
+            hidden: true
+      - id: 6
+        type: string
+        name: named_color
+        mapping:
+          - dps_val: purple
+            value: purple
+          - dps_val: blue
+            value: blue
+          - dps_val: cyan
+            value: cyan
+          - dps_val: green
+            value: green
+          - dps_val: yellow
+            value: yellow
+          - dps_val: orange
+            value: orange
+          - dps_val: red
+            value: red
+          - dps_val: colour
+            value: white
+          - dps_val: close
+            value: black
   - entity: select
     name: Light
+    deprecated: light
     icon: "mdi:lightbulb"
     category: config
     dps:

+ 46 - 0
custom_components/tuya_local/devices/dynasty_btx_fireplace.yaml

@@ -153,7 +153,53 @@ secondary_entities:
             value: Medium
           - dps_val: "3"
             value: High
+  - entity: light
+    name: Embers
+    dps:
+      - id: 106
+        type: string
+        name: switch
+        mapping:
+          - dps_val: "0"
+            value: false
+          - dps_val: "1"
+            value: true
+          - value: true
+            hidden: true
+      - id: 106
+        type: string
+        name: named_color
+        mapping:
+          - dps_val: "0"
+            value: black
+          - dps_val: "1"
+            value: red
+          - dps_val: "2"
+            value: orange
+          - dps_val: "3"
+            value: yellow
+          - dps_val: "4"
+            value: green
+          - dps_val: "5"
+            value: teal
+          - dps_val: "6"
+            value: blue
+          - dps_val: "7"
+            value: lightblue
+          - dps_val: "8"
+            value: darkblue
+          - dps_val: "9"
+            value: powderblue
+          - dps_val: "10"
+            value: purple
+          - dps_val: "11"
+            value: pink
+          - dps_val: "12"
+            value: violet
+          - dps_val: "13"
+            value: white
   - entity: select
+    deprecated: light_embers
     name: Embers
     icon: "mdi:campfire"
     category: config

+ 67 - 11
custom_components/tuya_local/devices/gratkit_filament_dryer.yaml

@@ -59,8 +59,64 @@ secondary_entities:
         name: sensor
         unit: "%"
         class: measurement
+  - entity: light
+    icon: "mdi:led-strip-variant"
+    dps:
+      - id: 103
+        type: string
+        name: switch
+        mapping:
+          - dps_val: "0"
+            value: false
+          - dps_val: "4"
+            value: true
+          - value: true
+            hidden: true
+      - id: 103
+        type: string
+        name: named_color
+        mapping:
+          - dps_val: "0"
+            value: black
+          - dps_val: "1"
+            value: red
+          - dps_val: "2"
+            value: green
+          - dps_val: "3"
+            value: blue
+          - dps_val: "4"
+            value: white
+          - dps_val: "5"
+            value: yellow
+          - dps_val: "6"
+            value: cyan
+          - dps_val: "7"
+            value: purple
+          - dps_val: "8"
+            value: orange
+          - dps_val: "9"
+            value: pink
+          - dps_val: "10"
+            value: grey
+          - value: grey
+            hidden: true
+      - id: 103
+        type: string
+        name: effect
+        mapping:
+          - dps_val: "4"
+            value: "off"
+          - dps_val: "10"
+            value: Rainbow fade
+          - dps_val: "11"
+            value: Rainbow blink
+          - dps_val: "12"
+            value: Rainbow smooth
+          - value: "off"
+            hidden: true
   - entity: select
     name: Light
+    deprecated: light
     icon: "mdi:led-strip-variant"
     dps:
       - id: 103
@@ -71,27 +127,27 @@ secondary_entities:
             value: "Off"
           - dps_val: "1"
             value: Red
-          - dps_val: 2
+          - dps_val: "2"
             value: Green
-          - dps_val: 3
+          - dps_val: "3"
             value: Blue
-          - dps_val: 4
+          - dps_val: "4"
             value: White
-          - dps_val: 5
+          - dps_val: "5"
             value: Yellow
-          - dps_val: 6
+          - dps_val: "6"
             value: Cyan
-          - dps_val: 7
+          - dps_val: "7"
             value: Purple
-          - dps_val: 8
+          - dps_val: "8"
             value: Orange
-          - dps_val: 9
+          - dps_val: "9"
             value: Pink
-          - dps_val: 10
+          - dps_val: "10"
             value: Rainbow fade
-          - dps_val: 11
+          - dps_val: "11"
             value: Rainbow blink
-          - dps_val: 12
+          - dps_val: "12"
             value: Rainbow smooth
   - entity: sensor
     class: temperature

+ 29 - 29
custom_components/tuya_local/devices/modernflames_orion_fireplace.yaml

@@ -184,21 +184,21 @@ secondary_entities:
           - dps_val: HIGH
             value: 255
       - id: 102
-        name: effect
+        name: named_color
         type: string
         mapping:
           - dps_val: YELLOW
-            value: Yellow
+            value: yellow
           - dps_val: ORANGE
-            value: Orange
+            value: orange
           - dps_val: BLUE
-            value: Blue
+            value: blue
           - dps_val: GREEN
-            value: Green
+            value: green
           - dps_val: PURPLE
-            value: Purple
+            value: purple
           - dps_val: WHITE
-            value: White
+            value: white
   - entity: select
     name: Flame speed
     icon: "mdi:fire"
@@ -228,29 +228,29 @@ secondary_entities:
         mapping:
           - scale: 0.392
       - id: 105
-        name: effect
+        name: named_color
         type: string
         mapping:
           - dps_val: "1"
-            value: Orange
+            value: orange
           - dps_val: "2"
-            value: Red
+            value: red
           - dps_val: "3"
-            value: Blue
+            value: blue
           - dps_val: "4"
-            value: Yellow
+            value: yellow
           - dps_val: "5"
-            value: Green
+            value: green
           - dps_val: "6"
-            value: Purple
+            value: purple
           - dps_val: "7"
-            value: Sky blue
+            value: skyblue
           - dps_val: "8"
-            value: Magenta
+            value: magenta
           - dps_val: "9"
-            value: White
+            value: white
           - dps_val: "10"
-            value: Pink
+            value: pink
   - entity: switch
     name: Downlight night mode
     category: config
@@ -278,29 +278,29 @@ secondary_entities:
           min: 0
           max: 100
       - id: 109
-        name: effect
+        name: named_color
         type: string
         mapping:
           - dps_val: "1"
-            value: Orange
+            value: orange
           - dps_val: "2"
-            value: Red
+            value: red
           - dps_val: "3"
-            value: Blue
+            value: blue
           - dps_val: "4"
-            value: Yellow
+            value: yellow
           - dps_val: "5"
-            value: Green
+            value: green
           - dps_val: "6"
-            value: Purple
+            value: purple
           - dps_val: "7"
-            value: Sky blue
+            value: skyblue
           - dps_val: "8"
-            value: Magenta
+            value: magenta
           - dps_val: "9"
-            value: White
+            value: white
           - dps_val: "10"
-            value: Pink
+            value: pink
   - entity: switch
     name: Ember extras
     icon: "mdi:campfire"

+ 25 - 0
custom_components/tuya_local/devices/tesla_s300_purifier.yaml

@@ -151,8 +151,33 @@ secondary_entities:
       - id: 104
         type: boolean
         name: switch
+  - entity: light
+    dps:
+      - id: 105
+        type: string
+        name: switch
+        mapping:
+          - dps_val: "off"
+            value: false
+          - dps_val: yellow
+            value: true
+      - id: 105
+        type: string
+        name: named_color
+        mapping:
+          - dps_val: "off"
+            value: black
+          - dps_val: orange
+            value: orange
+          - dps_val: green
+            value: green
+          - dps_val: blue
+            value: blue
+          - dps_val: yellow
+            value: yellow
   - entity: select
     name: Light
+    deprecated: light
     icon: "mdi:lightbulb"
     dps:
       - id: 105

+ 24 - 13
custom_components/tuya_local/devices/touchstone_sideline_fireplace.yaml

@@ -149,33 +149,44 @@ secondary_entities:
             value: 0
             hidden: true
       - id: 104
-        name: effect
+        name: named_color
         type: string
         mapping:
           - dps_val: "1"
-            value: "Orange"
+            value: orange
           - dps_val: "2"
-            value: "Red"
+            value: red
           - dps_val: "3"
-            value: "Blue"
+            value: blue
           - dps_val: "4"
-            value: "Yellow"
+            value: yellow
           - dps_val: "5"
-            value: "Green"
+            value: green
           - dps_val: "6"
-            value: "Purple"
+            value: purple
           - dps_val: "7"
-            value: "Teal"
+            value: teal
           - dps_val: "8"
-            value: "Pink"
+            value: pink
           - dps_val: "9"
-            value: "White"
+            value: white
           - dps_val: "10"
-            value: "Peach"
+            value: peachpuff
           - dps_val: "11"
-            value: "Cycle"
+            value: black
           - dps_val: "12"
-            value: "Mystery"
+            value: grey
+      - id: 104
+        name: effect
+        type: string
+        mapping:
+          - dps_val: "12"
+            value: Mystery
+          - dps_val: "11"
+            value: Cycle
+          - dps_val: "1"
+            value: "off"
+          - value: "off"
   - entity: light
     translation_key: logs
     category: config

+ 43 - 3
custom_components/tuya_local/light.py

@@ -54,6 +54,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
         self._color_mode_dps = dps_map.pop("color_mode", None)
         self._color_temp_dps = dps_map.pop("color_temp", None)
         self._rgbhsv_dps = dps_map.pop("rgbhsv", None)
+        self._named_color_dps = dps_map.pop("named_color", None)
         self._effect_dps = dps_map.pop("effect", None)
         self._init_end(dps_map)
 
@@ -106,6 +107,8 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
 
         if self._rgbhsv_dps:
             return ColorMode.HS
+        elif self._named_color_dps:
+            return ColorMode.HS
         elif self._color_temp_dps:
             return ColorMode.COLOR_TEMP
         elif self._brightness_dps:
@@ -156,9 +159,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
             return True
         if b_available:
             return False
-        if v_available:
-            return True
-        return False
+        return v_available
 
     @property
     def brightness(self):
@@ -203,6 +204,11 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                     idx += 1
 
                 return rgbhsv
+        elif self._named_color_dps:
+            colour = self._named_color_dps.get_value(self._device)
+            if colour:
+                rgb = color_util.color_name_to_rgb(colour)
+                return {"r": rgb[0], "g": rgb[1], "b": rgb[2]}
 
     @property
     def _hsv_brightness(self):
@@ -251,6 +257,25 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                 return mode
             return EFFECT_OFF
 
+    def named_color_from_hsv(self, hs, brightness):
+        """Get the named color from the rgb value"""
+        if self._named_color_dps:
+            palette = self._named_color_dps.values(self._device)
+            xy = color_util.color_hs_to_xy(*hs)
+            distance = float("inf")
+            best_match = None
+            for entry in palette:
+                rgb = color_util.color_name_to_rgb(entry)
+                xy_entry = color_util.color_RGB_to_xy(*rgb)
+                d = color_util.get_distance_between_two_points(
+                    color_util.XYPoint(*xy),
+                    color_util.XYPoint(*xy_entry),
+                )
+                if d < distance:
+                    distance = d
+                    best_match = entry
+            return best_match
+
     async def async_turn_on(self, **params):
         settings = {}
         color_mode = None
@@ -356,6 +381,21 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                         self._rgbhsv_dps.encode_value(binary),
                     ),
                 }
+        elif self._named_color_dps and ATTR_HS_COLOR in params:
+            if self.color_mode != ColorMode.HS:
+                color_mode = ColorMode.HS
+            hs = params.get(ATTR_HS_COLOR, self.hs_color or (0, 0))
+            brightness = params.get(ATTR_BRIGHTNESS, self.brightness or 255)
+            best_match = self.named_color_from_hsv(hs, brightness)
+            _LOGGER.debug("Setting color to %s", best_match)
+            if best_match:
+                settings = {
+                    **settings,
+                    **self._named_color_dps.get_values_to_set(
+                        self._device,
+                        best_match,
+                    ),
+                }
         if self._color_mode_dps:
             if color_mode:
                 _LOGGER.debug("Auto setting color mode to %s", color_mode)

+ 29 - 0
tests/devices/test_blitzwolf_bsh2_humidifier.py

@@ -20,6 +20,7 @@ class TestBlitzwolfSH2Humidifier(MultiSelectTests, TuyaDeviceTestCase):
             BLITZWOLF_BWSH2_PAYLOAD,
         )
         self.subject = self.entities.get("fan")
+        self.light = self.entities.get("light")
         self.setUpMultiSelect(
             [
                 {
@@ -84,3 +85,31 @@ class TestBlitzwolfSH2Humidifier(MultiSelectTests, TuyaDeviceTestCase):
             {SPEED_DP: "grade3"},
         ):
             await self.subject.async_set_percentage(50)
+
+    def test_light_named_color(self):
+        self.dps[LIGHT_DP] = "colour"
+        self.assertEqual(self.light.hs_color, (0, 0))
+        self.dps[LIGHT_DP] = "red"
+        self.assertEqual(self.light.hs_color, (0, 100))
+
+    async def test_async_set_light_color(self):
+        self.dps[LIGHT_DP] = "red"
+        async with assert_device_properties_set(
+            self.light._device,
+            {LIGHT_DP: "green"},
+        ):
+            await self.light.async_turn_on(hs_color=(120, 100))
+
+    async def test_async_light_turn_off(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {LIGHT_DP: "close"},
+        ):
+            await self.light.async_turn_off()
+
+    async def test_async_light_turn_on(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {LIGHT_DP: "colour"},
+        ):
+            await self.light.async_turn_on()

+ 1 - 1
tests/test_device_config.py

@@ -221,7 +221,7 @@ KNOWN_DPS = {
     "lawn_mower": {"required": ["activity", "command"], "optional": []},
     "light": {
         "required": [{"or": ["switch", "brightness", "effect"]}],
-        "optional": ["color_mode", "color_temp", "rgbhsv"],
+        "optional": ["color_mode", "color_temp", {"xor": ["rgbhsv", "named_color"]}],
     },
     "lock": {
         "required": [],