소스 검색

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 년 전
부모
커밋
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
 - **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.
 - **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.
 - **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.
 - **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,
     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.
 	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
         - dps_val: grade6
           value: 100
           value: 100
 secondary_entities:
 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
   - entity: select
     name: Light
     name: Light
+    deprecated: light
     icon: "mdi:lightbulb"
     icon: "mdi:lightbulb"
     category: config
     category: config
     dps:
     dps:

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

@@ -153,7 +153,53 @@ secondary_entities:
             value: Medium
             value: Medium
           - dps_val: "3"
           - dps_val: "3"
             value: High
             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
   - entity: select
+    deprecated: light_embers
     name: Embers
     name: Embers
     icon: "mdi:campfire"
     icon: "mdi:campfire"
     category: config
     category: config

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

@@ -59,8 +59,64 @@ secondary_entities:
         name: sensor
         name: sensor
         unit: "%"
         unit: "%"
         class: measurement
         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
   - entity: select
     name: Light
     name: Light
+    deprecated: light
     icon: "mdi:led-strip-variant"
     icon: "mdi:led-strip-variant"
     dps:
     dps:
       - id: 103
       - id: 103
@@ -71,27 +127,27 @@ secondary_entities:
             value: "Off"
             value: "Off"
           - dps_val: "1"
           - dps_val: "1"
             value: Red
             value: Red
-          - dps_val: 2
+          - dps_val: "2"
             value: Green
             value: Green
-          - dps_val: 3
+          - dps_val: "3"
             value: Blue
             value: Blue
-          - dps_val: 4
+          - dps_val: "4"
             value: White
             value: White
-          - dps_val: 5
+          - dps_val: "5"
             value: Yellow
             value: Yellow
-          - dps_val: 6
+          - dps_val: "6"
             value: Cyan
             value: Cyan
-          - dps_val: 7
+          - dps_val: "7"
             value: Purple
             value: Purple
-          - dps_val: 8
+          - dps_val: "8"
             value: Orange
             value: Orange
-          - dps_val: 9
+          - dps_val: "9"
             value: Pink
             value: Pink
-          - dps_val: 10
+          - dps_val: "10"
             value: Rainbow fade
             value: Rainbow fade
-          - dps_val: 11
+          - dps_val: "11"
             value: Rainbow blink
             value: Rainbow blink
-          - dps_val: 12
+          - dps_val: "12"
             value: Rainbow smooth
             value: Rainbow smooth
   - entity: sensor
   - entity: sensor
     class: temperature
     class: temperature

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

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

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

@@ -151,8 +151,33 @@ secondary_entities:
       - id: 104
       - id: 104
         type: boolean
         type: boolean
         name: switch
         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
   - entity: select
     name: Light
     name: Light
+    deprecated: light
     icon: "mdi:lightbulb"
     icon: "mdi:lightbulb"
     dps:
     dps:
       - id: 105
       - id: 105

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

@@ -149,33 +149,44 @@ secondary_entities:
             value: 0
             value: 0
             hidden: true
             hidden: true
       - id: 104
       - id: 104
-        name: effect
+        name: named_color
         type: string
         type: string
         mapping:
         mapping:
           - dps_val: "1"
           - dps_val: "1"
-            value: "Orange"
+            value: orange
           - dps_val: "2"
           - dps_val: "2"
-            value: "Red"
+            value: red
           - dps_val: "3"
           - dps_val: "3"
-            value: "Blue"
+            value: blue
           - dps_val: "4"
           - dps_val: "4"
-            value: "Yellow"
+            value: yellow
           - dps_val: "5"
           - dps_val: "5"
-            value: "Green"
+            value: green
           - dps_val: "6"
           - dps_val: "6"
-            value: "Purple"
+            value: purple
           - dps_val: "7"
           - dps_val: "7"
-            value: "Teal"
+            value: teal
           - dps_val: "8"
           - dps_val: "8"
-            value: "Pink"
+            value: pink
           - dps_val: "9"
           - dps_val: "9"
-            value: "White"
+            value: white
           - dps_val: "10"
           - dps_val: "10"
-            value: "Peach"
+            value: peachpuff
           - dps_val: "11"
           - dps_val: "11"
-            value: "Cycle"
+            value: black
           - dps_val: "12"
           - 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
   - entity: light
     translation_key: logs
     translation_key: logs
     category: config
     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_mode_dps = dps_map.pop("color_mode", None)
         self._color_temp_dps = dps_map.pop("color_temp", None)
         self._color_temp_dps = dps_map.pop("color_temp", None)
         self._rgbhsv_dps = dps_map.pop("rgbhsv", 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._effect_dps = dps_map.pop("effect", None)
         self._init_end(dps_map)
         self._init_end(dps_map)
 
 
@@ -106,6 +107,8 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
 
 
         if self._rgbhsv_dps:
         if self._rgbhsv_dps:
             return ColorMode.HS
             return ColorMode.HS
+        elif self._named_color_dps:
+            return ColorMode.HS
         elif self._color_temp_dps:
         elif self._color_temp_dps:
             return ColorMode.COLOR_TEMP
             return ColorMode.COLOR_TEMP
         elif self._brightness_dps:
         elif self._brightness_dps:
@@ -156,9 +159,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
             return True
             return True
         if b_available:
         if b_available:
             return False
             return False
-        if v_available:
-            return True
-        return False
+        return v_available
 
 
     @property
     @property
     def brightness(self):
     def brightness(self):
@@ -203,6 +204,11 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                     idx += 1
                     idx += 1
 
 
                 return rgbhsv
                 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
     @property
     def _hsv_brightness(self):
     def _hsv_brightness(self):
@@ -251,6 +257,25 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                 return mode
                 return mode
             return EFFECT_OFF
             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):
     async def async_turn_on(self, **params):
         settings = {}
         settings = {}
         color_mode = None
         color_mode = None
@@ -356,6 +381,21 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                         self._rgbhsv_dps.encode_value(binary),
                         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 self._color_mode_dps:
             if color_mode:
             if color_mode:
                 _LOGGER.debug("Auto setting color mode to %s", 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,
             BLITZWOLF_BWSH2_PAYLOAD,
         )
         )
         self.subject = self.entities.get("fan")
         self.subject = self.entities.get("fan")
+        self.light = self.entities.get("light")
         self.setUpMultiSelect(
         self.setUpMultiSelect(
             [
             [
                 {
                 {
@@ -84,3 +85,31 @@ class TestBlitzwolfSH2Humidifier(MultiSelectTests, TuyaDeviceTestCase):
             {SPEED_DP: "grade3"},
             {SPEED_DP: "grade3"},
         ):
         ):
             await self.subject.async_set_percentage(50)
             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": []},
     "lawn_mower": {"required": ["activity", "command"], "optional": []},
     "light": {
     "light": {
         "required": [{"or": ["switch", "brightness", "effect"]}],
         "required": [{"or": ["switch", "brightness", "effect"]}],
-        "optional": ["color_mode", "color_temp", "rgbhsv"],
+        "optional": ["color_mode", "color_temp", {"xor": ["rgbhsv", "named_color"]}],
     },
     },
     "lock": {
     "lock": {
         "required": [],
         "required": [],