ソースを参照

climate: add support for new swing_horizontal_mode

New feature in HA 2024.12 to control horizontal swing independently of
vertical swing.

The following configs modified to use it:
- daizuki_heatpump
- electriq_12wminv_heatpump
- electriq_ecosilent12wap_aircon
- fujicool_yuzu_heatpump
- goldair_portable_airconditioner
- idea_heatingbelt_airconditioner
- inventor_comfort_airconditioner
- inventor_tokenslegend_airconditioner
- rotenso_ronix_heatpump
- royal_airconditioner
- royalclima_fresh_climate
- starlight_heatpump
- tesla_airconditioner
- vivax_heatpump
- vivion_airconditioner

Issue #2648
Jason Rumney 1 年間 前
コミット
a7429af6fb

+ 29 - 1
custom_components/tuya_local/climate.py

@@ -18,6 +18,7 @@ from homeassistant.components.climate.const import (
     ATTR_HVAC_ACTION,
     ATTR_HVAC_MODE,
     ATTR_PRESET_MODE,
+    ATTR_SWING_HORIZONTAL_MODE,
     ATTR_SWING_MODE,
     ATTR_TARGET_TEMP_HIGH,
     ATTR_TARGET_TEMP_LOW,
@@ -84,6 +85,10 @@ class TuyaLocalClimate(TuyaLocalEntity, ClimateEntity):
         self._hvac_mode_dps = dps_map.pop(ATTR_HVAC_MODE, None)
         self._hvac_action_dps = dps_map.pop(ATTR_HVAC_ACTION, None)
         self._preset_mode_dps = dps_map.pop(ATTR_PRESET_MODE, None)
+        self._swing_horizontal_mode_dps = dps_map.pop(
+            ATTR_SWING_HORIZONTAL_MODE,
+            None,
+        )
         self._swing_mode_dps = dps_map.pop(ATTR_SWING_MODE, None)
         self._temperature_dps = dps_map.pop(ATTR_TEMPERATURE, None)
         self._temp_high_dps = dps_map.pop(ATTR_TARGET_TEMP_HIGH, None)
@@ -108,7 +113,8 @@ class TuyaLocalClimate(TuyaLocalEntity, ClimateEntity):
             self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
         if self._swing_mode_dps:
             self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
-
+        if self._swing_horizontal_mode_dps:
+            self._attr_supported_features |= ClimateEntityFeature.SWING_HORIZONTAL_MODE
         if self._temp_high_dps and self._temp_low_dps:
             self._attr_supported_features |= (
                 ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
@@ -417,6 +423,28 @@ class TuyaLocalClimate(TuyaLocalEntity, ClimateEntity):
             raise NotImplementedError()
         await self._swing_mode_dps.async_set_value(self._device, swing_mode)
 
+    @property
+    def swing_horizontal_mode(self):
+        """Return the current horizontal swing mode."""
+        if self._swing_horizontal_mode_dps is None:
+            raise NotImplementedError()
+        return self._swing_horizontal_mode_dps.get_value(self._device)
+
+    @property
+    def swing_horizontal_modes(self):
+        """Return the list of swing modes that this device supports."""
+        if self._swing_horizontal_mode_dps:
+            return self._swing_horizontal_mode_dps.values(self._device)
+
+    async def async_set_swing_horizontal_mode(self, swing_mode):
+        """Set the preset mode."""
+        if self._swing_horizontal_mode_dps is None:
+            raise NotImplementedError()
+        await self._swing_horizontal_mode_dps.async_set_value(
+            self._device,
+            swing_mode,
+        )
+
     @property
     def fan_mode(self):
         """Return the current fan mode."""

+ 3 - 1
custom_components/tuya_local/devices/README.md

@@ -620,7 +620,9 @@ from the camera.
     `none, eco, away, boost, comfort, home, sleep, activity`
    There are also some presets defined by this integration for use with various `translation_key`s, see translations/en.json for details.
 - **swing_mode** (optional, mapping of strings) a dp to control swing modes of the device.
-   Possible values are: `"off", vertical, horizontal`
+   Standard values are: `"off", "on", vertical, horizontal, both`, non-standard values can also be used if needed.
+- **swing_horizontal_mode** (optional, mapping of strings) a dp to control horizontal swing independently of the vertical swing.
+   Standard values are: `"off", "on"`, non-standard values can also be used if needed.
 - **temperature** (optional, number) a dp to set the target temperature of the device.
       A unit may be specified as part of the attribute if a temperature_unit dp is not available, if not
       the default unit configured in HA will be used.

+ 12 - 21
custom_components/tuya_local/devices/daizuki_heatpump.yaml

@@ -67,30 +67,21 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: "0"
-          value: horizontal
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: "off"
-            - dps_val: "1"
-              value: horizontal
+          value: "off"
         - dps_val: "1"
-          value: both
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
-            - dps_val: "1"
-              value: both
-        - value: both
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 114
       type: string
-      name: horizontal_swing
-      hidden: true
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: "0"
+          value: "off"
+        - dps_val: "1"
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 119
       type: string
       name: electricity_management

+ 8 - 14
custom_components/tuya_local/devices/electriq_12wminv_heatpump.yaml

@@ -86,23 +86,17 @@ primary_entity:
       type: boolean
       mapping:
         - dps_val: false
-          constraint: horiz_swing
-          conditions:
-            - dps_val: false
-              value: "off"
-            - dps_val: true
-              value: horizontal
+          value: "off"
         - dps_val: true
-          constraint: horiz_swing
-          conditions:
-            - dps_val: false
-              value: vertical
-            - dps_val: true
-              value: both
+          value: "on"
     - id: 107
-      name: horiz_swing
+      name: swing_horizontal_mode
       type: boolean
-      hidden: true
+      mapping:
+        - dps_val: true
+          value: "on"
+        - dps_val: false
+          value: "off"
     - id: 108
       name: unknown_108
       type: integer

+ 0 - 1
custom_components/tuya_local/devices/electriq_ecosilent12wap_aircon.yaml

@@ -115,7 +115,6 @@ primary_entity:
       type: boolean
       mapping:
         - dps_val: false
-          constraint: hvac_mode
           value: "off"
         - dps_val: true
           value: vertical

+ 12 - 21
custom_components/tuya_local/devices/fujicool_yuzu_heatpump.yaml

@@ -76,30 +76,21 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: "0"
-          value: horizontal
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: "off"
-            - dps_val: "1"
-              value: horizontal
+          value: "off"
         - dps_val: "1"
-          value: both
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
-            - dps_val: "1"
-              value: both
-        - value: both
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 114
       type: string
-      name: horizontal_swing
-      hidden: true
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: "0"
+          value: "off"
+        - dps_val: "1"
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 119
       type: string
       name: electricity_management

+ 10 - 25
custom_components/tuya_local/devices/goldair_portable_airconditioner.yaml

@@ -66,7 +66,14 @@ primary_entity:
           value: high
     - id: 15
       type: string
-      name: swing_1
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: "on"
+          value: "on"
+          available: support_hswing
+        - dps_val: "off"
+          value: "off"
+          available: support_hswing
     - id: 107
       type: integer
       optional: true
@@ -90,13 +97,6 @@ primary_entity:
         - dps_val: 1
           value: true
         - value: false
-    - id: 109
-      type: bitfield
-      name: unsupport_vswing
-      mapping:
-        - dps_val: 1
-          value: false
-        - value: true
     - id: 109
       type: bitfield
       name: support_hswing
@@ -131,25 +131,10 @@ primary_entity:
       mapping:
         - dps_val: true
           value: "on"
-          available: unsupport_vswing
-          constraint: swing_1
-          conditions:
-            - dps_val: "on"
-              value: both
-              available: support_vswing
-            - dps_val: "off"
-              value: horizontal
-              available: support_vswing
+          available: support_vswing
         - dps_val: false
           value: "off"
-          constraint: swing_1
-          conditions:
-            - dps_val: "on"
-              value: vertical
-              available: support_vswing
-            - dps_val: "off"
-              value: "off"
-              available: support_vswing
+          available: support_vswing
 secondary_entities:
   - entity: switch
     translation_key: ionizer

+ 12 - 28
custom_components/tuya_local/devices/idea_heatingbelt_airconditioner.yaml

@@ -72,38 +72,22 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: false
-          constraint: swing_horiz
-          conditions:
-            - dps_val: false
-              value: "off"
-            - dps_val: true
-              value: horizontal
-            - dps_val: null
-              value: "off"
-              hidden: true
+          value: "off"
         - dps_val: true
-          constraint: swing_horiz
-          conditions:
-            - dps_val: false
-              value: vertical
-            - dps_val: true
-              value: both
-            - dps_val: null
-              value: vertical
-              hidden: true
-        - constraint: swing_horiz
-          conditions:
-            - dps_val: true
-              value: horizontal
-              hidden: true
-            - dps_val: null
-              value: "off"
-              hidden: true
+          value: "on"
+        - value: "off"
+          hidden: true
     - id: 33
       type: boolean
       optional: true
-      name: swing_horiz
-      hidden: true
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: true
+          value: "on"
+        - dps_val: false
+          value: "off"
+        - value: "off"
+          hidden: true
 secondary_entities:
   - entity: switch
     name: Aux heat

+ 12 - 14
custom_components/tuya_local/devices/inventor_comfort_airconditioner.yaml

@@ -98,23 +98,21 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: ud_0c
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: lr_07
-              value: both
-            - dps_val: lr_00
-              value: vertical
+          value: "on"
         - dps_val: ud_00
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: lr_07
-              value: horizontal
-            - dps_val: lr_00
-              value: "off"
+          value: "off"
+        - value: "off"
+          hidden: true
     - id: 118
       type: string
-      name: horizontal_swing
-      hidden: true
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: lr_00
+          value: "off"
+        - dps_val: lr_07
+          value: "on"
+        - value: "off"
+          hidden: true
     - id: 101
       type: boolean
       name: mood_lighting

+ 10 - 20
custom_components/tuya_local/devices/inventor_tokenslegend_airconditioner.yaml

@@ -72,30 +72,20 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: false
-          constraint: swing_horiz
-          conditions:
-            - dps_val: false
-              value: "off"
-            - dps_val: true
-              value: horizontal
-            - dps_val: null
-              value: "off"
-              hidden: true
+          value: "off"
         - dps_val: true
-          constraint: swing_horiz
-          conditions:
-            - dps_val: false
-              value: vertical
-            - dps_val: true
-              value: both
-            - dps_val: null
-              value: vertical
-              hidden: true
+          value: "on"
     - id: 33
       type: boolean
-      name: swing_horiz
-      hidden: true
+      name: swing_horizontal_mode
       optional: true
+      mapping:
+        - dps_val: false
+          value: "off"
+        - dps_val: true
+          value: "on"
+        - value: "off"
+          hidden: true
     - id: 101
       type: boolean
       name: preset_mode

+ 11 - 25
custom_components/tuya_local/devices/rotenso_ronix_heatpump.yaml

@@ -73,35 +73,21 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: "0"
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: "off"
-            - dps_val: "1"
-              value: horizontal
-            - dps_val: ["2", "3", "4"]
-              value: horizontal
-              hidden: true
+          value: "off"
         - dps_val: "1"
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
-            - dps_val: "1"
-              value: both
-            - dps_val: ["2", "3", "4"]
-              value: both
-              hidden: true
+          value: "on"
         - hidden: true
-          value: both
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
+          value: "on"
     - id: 114
       type: string
-      name: horizontal_swing
-      hidden: true
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: "0"
+          value: "off"
+        - dps_val: "1"
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 120
       type: string
       name: energy

+ 8 - 14
custom_components/tuya_local/devices/royal_airconditioner.yaml

@@ -72,23 +72,17 @@ primary_entity:
       type: boolean
       mapping:
         - dps_val: true
-          constraint: swing_horiz
-          conditions:
-            - dps_val: true
-              value: both
-            - dps_val: false
-              value: vertical
+          value: "on"
         - dps_val: false
-          constraint: swing_horiz
-          conditions:
-            - dps_val: true
-              value: horizontal
-            - dps_val: false
-              value: "off"
+          value: "off"
     - id: 105
-      name: swing_horiz
+      name: swing_horizontal_mode
       type: boolean
-      hidden: true
+      mapping:
+        - dps_val: false
+          value: "off"
+        - dps_val: true
+          value: "on"
     - id: 103
       name: temperature_unit
       type: boolean

+ 12 - 28
custom_components/tuya_local/devices/royalclima_fresh_climate.yaml

@@ -70,37 +70,21 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: "0"
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: "off"
-            - dps_val: "1"
-              value: horizontal
-            - dps_val: ["2", "3", "4"]
-              value: horizontal
-              hidden: true
+          value: "off"
         - dps_val: "1"
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
-            - dps_val: "1"
-              value: both
-            - dps_val: ["2", "3", "4"]
-              value: both
-              hidden: true
-        - constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
-              hidden: true
-            - dps_val: ["1", "2", "3", "4"]
-              value: both
-              hidden: true
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 114
       type: string
-      name: horizontal_swing
-      hidden: true
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: "0"
+          value: "off"
+        - dps_val: "1"
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 119
       type: string
       name: electricity_management

+ 11 - 25
custom_components/tuya_local/devices/starlight_heatpump.yaml

@@ -76,35 +76,21 @@ primary_entity:
       type: string
       mapping:
         - dps_val: "0"
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: "off"
-            - dps_val: "1"
-              value: horizontal
-            - dps_val: ["2", "3", "4"]
-              value: horizontal
-              hidden: true
+          value: "off"
         - dps_val: "1"
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
-            - dps_val: "1"
-              value: both
-            - dps_val: ["2", "3", "4"]
-              value: both
-              hidden: true
-        - value: both
+          value: "on"
+        - value: "on"
           hidden: true
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: "0"
-              value: vertical
     - id: 114
-      name: horizontal_swing
+      name: swing_horizontal_mode
       type: string
-      hidden: true
+      mapping:
+        - dps_val: "0"
+          value: "off"
+        - dps_val: "1"
+          value: "on"
+        - value: "on"
+          hidden: true
     - id: 119
       name: electricity_management
       type: string

+ 9 - 15
custom_components/tuya_local/devices/tesla_airconditioner.yaml

@@ -75,26 +75,20 @@ primary_entity:
       name: temperature_unit
     - id: 33
       type: boolean
-      name: swing_mode
+      name: swing_horizontal_mode
       mapping:
         - dps_val: false
-          constraint: swing_vert
-          conditions:
-            - dps_val: false
-              value: "off"
-            - dps_val: true
-              value: vertical
+          value: "off"
         - dps_val: true
-          constraint: swing_vert
-          conditions:
-            - dps_val: false
-              value: horizontal
-            - dps_val: true
-              value: both
+          value: "on"
     - id: 105
       type: boolean
-      name: swing_vert
-      hidden: true
+      name: swing_mode
+      mapping:
+        - dps_val: false
+          value: "off"
+        - dps_val: true
+          value: "on"
     - id: 108
       type: bitfield
       name: model

+ 10 - 23
custom_components/tuya_local/devices/vivax_heatpump.yaml

@@ -126,34 +126,21 @@ primary_entity:
       name: swing_mode
       mapping:
         - dps_val: ud_0c
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: lr_0c
-              value: both
-            - dps_val: lr_00
-              value: vertical
-            - dps_val: [lr_01, lr_02, lr_03, lr_04]
-              value: vertical
-              hidden: true
+          value: "on"
         - dps_val: ud_00
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: lr_0c
-              value: horizontal
-            - dps_val: lr_00
-              value: "off"
-            - dps_val: [lr_01, lr_02, lr_03, lr_04]
-              value: "off"
-              hidden: true
+          value: "off"
         - value: "off"
           hidden: true
-          constraint: horizontal_swing
-          conditions:
-            - dps_val: lr_0c
-              value: horizontal
     - id: 118
       type: string
-      name: horizontal_swing
+      name: swing_horizontal_mode
+      mapping:
+        - dps_val: lr_00
+          value: "off"
+        - dps_val: lr_0c
+          value: "on"
+        - value: "off"
+          hidden: true
     - id: 101
       type: boolean
       name: mood_lighting

+ 11 - 21
custom_components/tuya_local/devices/vivion_airconditioner.yaml

@@ -65,32 +65,22 @@ primary_entity:
           value: sleep
     - id: 31
       type: string
-      name: vertical
-      hidden: true
+      name: swing_mode
+      mapping:
+        - dps_val: "off"
+          value: "off"
+        - dps_val: same
+          value: "on"
+        - value: "off"
+          hidden: true
     - id: 33
       type: boolean
-      name: swing_mode
+      name: swing_horizontal_mode
       mapping:
         - dps_val: false
-          constraint: vertical
-          conditions:
-            - dps_val: "off"
-              value: "off"
-            - dps_val: same
-              value: vertical
-            - dps_val: [vane_1, vane_2, vane_3, vane_4, vane_5]
-              value: "off"
-              hidden: true
+          value: "off"
         - dps_val: true
-          constraint: vertical
-          conditions:
-            - dps_val: "off"
-              value: horizontal
-            - dps_val: same
-              value: both
-            - dps_val: [vane_1, vane_2, vane_3, vane_4, vane_5]
-              value: horizontal
-              hidden: true
+          value: "on"
     - id: 103
       type: string
       name: unknown_103

+ 16 - 32
tests/devices/test_goldair_portable_airconditioner.py

@@ -1,9 +1,6 @@
 from homeassistant.components.climate.const import (
-    SWING_BOTH,
-    SWING_HORIZONTAL,
     SWING_OFF,
     SWING_ON,
-    SWING_VERTICAL,
 )
 
 from ..const import GOLDAIR_PORTABLE_AIR_CONDITIONER_PAYLOAD
@@ -16,7 +13,7 @@ CURRENT_TEMPERATURE_DP = "3"
 MODE_DP = "4"
 FANMODE_DP = "5"
 IONIZER_DP = "11"
-SWING1_DP = "15"
+SWINGH_DP = "15"
 FAULT_DP = "20"
 SLEEP_DP = "103"
 ONTIMER_DP = "104"
@@ -24,7 +21,7 @@ OFFTIMER_DP = "105"
 TEMPF_DP = "107"
 CURTEMPF_DP = "108"
 FEATURE_DP = "109"
-SWING2_DP = "110"
+SWINGV_DP = "110"
 
 
 class TestGoldairPortableAir(TargetTemperatureTests, TuyaDeviceTestCase):
@@ -53,41 +50,28 @@ class TestGoldairPortableAir(TargetTemperatureTests, TuyaDeviceTestCase):
 
     def test_swing_modes_with_vswing_unavailable(self):
         self.dps[FEATURE_DP] = 26
-        self.assertCountEqual(self.subject.swing_modes, [SWING_OFF, SWING_ON])
+        self.assertCountEqual(self.subject.swing_modes, [])
+        self.assertCountEqual(
+            self.subject.swing_horizontal_modes, [SWING_OFF, SWING_ON]
+        )
 
     def test_swing_modes_with_vswing_available(self):
         self.dps[FEATURE_DP] = 27
+        self.assertCountEqual(self.subject.swing_modes, [SWING_OFF, SWING_ON])
         self.assertCountEqual(
-            self.subject.swing_modes,
-            [
-                SWING_OFF,
-                SWING_VERTICAL,
-                SWING_HORIZONTAL,
-                SWING_BOTH,
-            ],
+            self.subject.swing_horizontal_modes, [SWING_OFF, SWING_ON]
         )
 
-    def test_swing_when_no_vswing(self):
-        self.dps[FEATURE_DP] = 26
-        self.dps[SWING1_DP] = "on"
-        self.dps[SWING2_DP] = True
-        self.assertEqual(self.subject.swing_mode, SWING_ON)
-        self.dps[SWING1_DP] = "off"
-        self.assertEqual(self.subject.swing_mode, SWING_ON)
-        self.dps[SWING2_DP] = False
-        self.assertEqual(self.subject.swing_mode, SWING_OFF)
-
-    def test_swing_when_vswing(self):
+    def test_swing(self):
         self.dps[FEATURE_DP] = 27
-        self.dps[SWING1_DP] = "on"
-        self.dps[SWING2_DP] = True
-        self.assertEqual(self.subject.swing_mode, SWING_BOTH)
-        self.dps[SWING1_DP] = "off"
-        self.assertEqual(self.subject.swing_mode, SWING_HORIZONTAL)
-        self.dps[SWING2_DP] = False
+        self.dps[SWINGH_DP] = "on"
+        self.dps[SWINGV_DP] = True
+        self.assertEqual(self.subject.swing_mode, SWING_ON)
+        self.assertEqual(self.subject.swing_horizontal_mode, SWING_ON)
+        self.dps[SWINGH_DP] = "off"
+        self.assertEqual(self.subject.swing_horizontal_mode, SWING_OFF)
+        self.dps[SWINGV_DP] = False
         self.assertEqual(self.subject.swing_mode, SWING_OFF)
-        self.dps[SWING1_DP] = "on"
-        self.assertEqual(self.subject.swing_mode, SWING_VERTICAL)
 
     def test_available(self):
         """Override the base class, as this has availability logic."""

+ 1 - 0
tests/devices/test_starlight_heatpump.py

@@ -56,6 +56,7 @@ class TestStarLightHeatpump(
                 ClimateEntityFeature.TARGET_TEMPERATURE
                 | ClimateEntityFeature.FAN_MODE
                 | ClimateEntityFeature.SWING_MODE
+                | ClimateEntityFeature.SWING_HORIZONTAL_MODE
                 | ClimateEntityFeature.TURN_OFF
                 | ClimateEntityFeature.TURN_ON
             ),