Просмотр исходного кода

Refactor conditions, and apply them to range and invalid judgement.

Unskip tests which can now pass due to this.
Jason Rumney 4 лет назад
Родитель
Сommit
3ccb1a5551

+ 21 - 16
custom_components/tuya_local/devices/README.md

@@ -163,16 +163,18 @@ Home Assistant UI.
 
 
 ## Mapping Rules
 ## Mapping Rules
 
 
-Mapping rules can change the behavior of attributes beyond simple copying
-of DPS values to attribute values.  Rules can be defined at the top level
-of the mapping element to apply to all values, or a list of rules that apply
-to particular dps values can be defined to change only particular cases.
-Rules can even depend on the values of other elements.
+Mapping rules can change the behavior of attributes beyond simple
+copying of DPS values to attribute values.  Rules can be defined
+without a dps_val to apply to all values, or a list of rules that
+apply to particular dps values can be defined to change only
+particular cases.  Rules can even depend on the values of other
+elements.
 
 
 ### `dps_val`
 ### `dps_val`
 
 
-//Mandatory for lists, not used at top level.//
-When a list of rules is defined, `dps_val` defines the DPS value that each
+//Optional, if not provided, the rule is a default that will apply to all
+values not covered by their own dps_val rule.//
+`dps_val` defines the DPS value that each
 rule in the list applies to. This can be used to map specific values from the
 rule in the list applies to. This can be used to map specific values from the
 Tuya protocol into attribute values that have specific meaning in Home
 Tuya protocol into attribute values that have specific meaning in Home
 Assistant.  For example, climate entities in Home Assistant define modes
 Assistant.  For example, climate entities in Home Assistant define modes
@@ -211,15 +213,6 @@ If you don't specify any priorities, the icons will all get the same priority,
 so if any overlap exists in the rules, it won't always be predictable which
 so if any overlap exists in the rules, it won't always be predictable which
 icon will be displayed.
 icon will be displayed.
 
 
-### `invalid`
-
-//Optional. Boolean, default false.//
-Invalid set to true allows an attribute to temporarily be set read-only in
-some conditions.  Rather than passing requests to set the attribute through
-to the Tuya protocol, attempts to set it will throw an error while it meets
-the conditions to be `invalid`.
-
-
 ### `value-redirect`
 ### `value-redirect`
 
 
 //Optional.//
 //Optional.//
@@ -233,6 +226,18 @@ one or the other. But Home Assistant just has one `temperature` attribute for
 setting target temperature, so the mapping needs to be done before passing to
 setting target temperature, so the mapping needs to be done before passing to
 Home Assistant.
 Home Assistant.
 
 
+### `invalid`
+
+//Optional. Boolean, default false.//
+Invalid set to true allows an attribute to temporarily be set read-only in
+some conditions.  Rather than passing requests to set the attribute through
+to the Tuya protocol, attempts to set it will throw an error while it meets
+the conditions to be `invalid`.  It does not make sense to set this at mapping
+level, as it would cause a situation where you can set a value then not be
+able to unset it.  Instead, this should be used with conditions, below, to
+make the behaviour dependent on another DPS, such as disabling fan speed 
+control when the preset is in sleep mode (since sleep mode should force low).
+
 
 
 ### `constraint`
 ### `constraint`
 
 

+ 10 - 11
custom_components/tuya_local/devices/goldair_dehumidifier.yaml

@@ -90,19 +90,18 @@ secondary_entities:
         mapping:
         mapping:
           - dps_val: "1"
           - dps_val: "1"
             value: 50
             value: 50
+            constraint: dehumidifier_mode
+            conditions:
+              - dps_val: "2"
+                invalid: true
+              - dps_val: "3"
+                invalid: true              
           - dps_val: "3"
           - dps_val: "3"
             value: 100
             value: 100
-        constraint: dehumidifier_mode
-        conditions:
-          - dps_val: "1"
-            value: 50
-            invalid: true
-          - dps_val: "2"
-            value: 100
-            invalid: true
-          - dps_val: "3"
-            value: 100
-            invalid: true
+            constraint: dehumidifier_mode
+            conditions:
+              - dps_val: "1"
+                invalid: true
   - entity: climate
   - entity: climate
     legacy_class: ".dehumidifier.climate.GoldairDehumidifier"
     legacy_class: ".dehumidifier.climate.GoldairDehumidifier"
     name: Dehumidifier as Climate
     name: Dehumidifier as Climate

+ 32 - 30
custom_components/tuya_local/devices/goldair_fan.yaml

@@ -14,14 +14,14 @@ primary_entity:
         max: 12
         max: 12
       mapping:
       mapping:
         - scale: 0.12
         - scale: 0.12
-      constraint: preset_mode
-      conditions:
-        - dps_val: nature
-          mapping:
-            - step: 4
-        - dps_val: sleep
-          mapping:
-            - step: 4
+          constraint: preset_mode
+          conditions:
+            - dps_val: nature
+              mapping:
+                - step: 4
+            - dps_val: sleep
+              mapping:
+                - step: 4
     - id: 3
     - id: 3
       type: string
       type: string
       mapping:
       mapping:
@@ -53,28 +53,30 @@ secondary_entities:
         name: hvac_mode
         name: hvac_mode
       - id: 2
       - id: 2
         type: integer
         type: integer
-        constraint: preset_mode
-        conditions:
-          - dps_val: normal
-            range:
-              min: 1
-              max: 12
-          - dps_val: nature
-            mapping:
-              - dps_val: 4
-                value: low
-              - dps_val: 8
-                value: medium
-              - dps_val: 12
-                value: high
-          - dps_val: sleep
-            mapping:
-              - dps_val: 4
-                value: low
-              - dps_val: 8
-                value: medium
-              - dps_val: 12
-                value: high            
+        range:
+          min: 1
+          max: 12
+        mapping:
+          - constraint: present_mode
+            conditions:
+              - dps_val: nature
+                step: 4
+                mapping:
+                  - dps_val: 4
+                    value: low
+                  - dps_val: 8
+                    value: medium
+                  - dps_val: 12
+                    value: high
+              - dps_val: sleep
+                step: 4
+                mapping:
+                  - dps_val: 4
+                    value: low
+                  - dps_val: 8
+                    value: medium
+                  - dps_val: 12
+                    value: high
         name: fan_mode
         name: fan_mode
       - id: 3
       - id: 3
         type: string
         type: string

+ 6 - 0
custom_components/tuya_local/devices/goldair_gpph_heater.yaml

@@ -26,9 +26,15 @@ primary_entity:
           conditions:
           conditions:
             - dps_val: "ECO"
             - dps_val: "ECO"
               value-redirect: eco_temperature
               value-redirect: eco_temperature
+              range:
+                min: 5
+                max: 21
             - dps_val: "AF"
             - dps_val: "AF"
               invalid: true
               invalid: true
               value: 5
               value: 5
+              range:
+                min: 5
+                max: 5
       name: temperature
       name: temperature
     - id: 3
     - id: 3
       type: integer
       type: integer

+ 8 - 12
custom_components/tuya_local/generic/climate.py

@@ -191,18 +191,16 @@ class TuyaLocalClimate(ClimateEntity):
         """Return the minimum supported target temperature."""
         """Return the minimum supported target temperature."""
         if self._temperature_dps is None:
         if self._temperature_dps is None:
             return None
             return None
-        if self._temperature_dps.range is None:
-            return DEFAULT_MIN_TEMP
-        return self._temperature_dps.range["min"]
+        range = self._temperature_dps.range(self._device)
+        return DEFAULT_MIN_TEMP if range is None else range["min"]
 
 
     @property
     @property
     def max_temp(self):
     def max_temp(self):
         """Return the maximum supported target temperature."""
         """Return the maximum supported target temperature."""
         if self._temperature_dps is None:
         if self._temperature_dps is None:
             return None
             return None
-        if self._temperature_dps.range is None:
-            return DEFAULT_MAX_TEMP
-        return self._temperature_dps.range["max"]
+        range = self._temperature_dps.range(self._device)
+        return DEFAULT_MAX_TEMP if range is None else range["max"]
 
 
     async def async_set_temperature(self, **kwargs):
     async def async_set_temperature(self, **kwargs):
         """Set new target temperature."""
         """Set new target temperature."""
@@ -250,18 +248,16 @@ class TuyaLocalClimate(ClimateEntity):
         """Return the minimum supported target humidity."""
         """Return the minimum supported target humidity."""
         if self._humidity_dps is None:
         if self._humidity_dps is None:
             return None
             return None
-        if self._humidity_dps.range is None:
-            return DEFAULT_MIN_HUMIDITY
-        return self._humidity_dps.range["min"]
+        range = self._humidity_dps.range(self._device)
+        return DEFAULT_MIN_HUMIDITY if range is None else range["min"]
 
 
     @property
     @property
     def max_humidity(self):
     def max_humidity(self):
         """Return the maximum supported target humidity."""
         """Return the maximum supported target humidity."""
         if self._humidity_dps is None:
         if self._humidity_dps is None:
             return None
             return None
-        if self._humidity_dps.range is None:
-            return DEFAULT_MAX_HUMIDITY
-        return self._humidity_dps.range["max"]
+        range = self._humidity_dps.range(self._device)
+        return DEFAULT_MAX_HUMIDITY if range is None else range["max"]
 
 
     async def async_set_humidity(self, target_humidity):
     async def async_set_humidity(self, target_humidity):
         if self._humidity_dps is None:
         if self._humidity_dps is None:

+ 4 - 6
custom_components/tuya_local/generic/humidifier.py

@@ -126,18 +126,16 @@ class TuyaLocalHumidifier(HumidifierEntity):
         """Return the minimum supported target humidity."""
         """Return the minimum supported target humidity."""
         if self._humidity_dps is None:
         if self._humidity_dps is None:
             return None
             return None
-        if self._humidity_dps.range is None:
-            return DEFAULT_MIN_HUMIDITY
-        return self._humidity_dps.range["min"]
+        range = self._humidity_dps.range(self._device)
+        return DEFAULT_MIN_HUMIDITY if range is None else range["min"]
 
 
     @property
     @property
     def max_humidity(self):
     def max_humidity(self):
         """Return the maximum supported target humidity."""
         """Return the maximum supported target humidity."""
         if self._humidity_dps is None:
         if self._humidity_dps is None:
             return None
             return None
-        if self._humidity_dps.range is None:
-            return DEFAULT_MAX_HUMIDITY
-        return self._humidity_dps.range["max"]
+        range = self._humidity_dps.range(self._device)
+        return DEFAULT_MAX_HUMIDITY if range is None else range["max"]
 
 
     async def async_set_humidity(self, humidity):
     async def async_set_humidity(self, humidity):
         if self._humidity_dps is None:
         if self._humidity_dps is None:

+ 50 - 32
custom_components/tuya_local/helpers/device_config.py

@@ -222,7 +222,7 @@ class TuyaDpsConfig:
         """Set the value of the dps in the given device to given value."""
         """Set the value of the dps in the given device to given value."""
         if self.readonly:
         if self.readonly:
             raise TypeError(f"{self.name} is read only")
             raise TypeError(f"{self.name} is read only")
-        if self.invalid:
+        if self.invalid_for(value, device):
             raise AttributeError(f"{self.name} cannot be set at this time")
             raise AttributeError(f"{self.name} cannot be set at this time")
 
 
         settings = self.get_values_to_set(device, value)
         settings = self.get_values_to_set(device, value)
@@ -244,15 +244,26 @@ class TuyaDpsConfig:
 
 
         return list(set(val)) if len(val) > 0 else None
         return list(set(val)) if len(val) > 0 else None
 
 
-    @property
-    def range(self):
+    def range(self, device):
         """Return the range for this dps if configured."""
         """Return the range for this dps if configured."""
-        if (
-            "range" in self._config.keys()
-            and "min" in self._config["range"].keys()
-            and "max" in self._config["range"].keys()
-        ):
-            return self._config["range"]
+        mapping = self._find_map_for_dps(device.get_property(self.id))
+        if mapping is not None:
+            _LOGGER.debug(f"Considering mapping for range of {self.name}")
+            cond = self._active_condition(mapping, device)
+            if cond is not None:
+                constraint = mapping.get("constraint")
+                _LOGGER.debug(f"Considering condition on {constraint}")
+            range = None if cond is None else cond.get("range")
+            if range is not None and "min" in range and "max" in range:
+                _LOGGER.info(f"Conditional range returned for {self.name}")
+                return range
+            range = mapping.get("range")
+            if range is not None and "min" in range and "max" in range:
+                _LOGGER.info(f"Mapped range returned for {self.name}")
+                return range
+        range = self._config.get("range")
+        if range is not None and "min" in range and "max" in range:
+            return range
         else:
         else:
             return None
             return None
 
 
@@ -269,8 +280,12 @@ class TuyaDpsConfig:
     def readonly(self):
     def readonly(self):
         return "readonly" in self._config.keys() and self._config["readonly"] is True
         return "readonly" in self._config.keys() and self._config["readonly"] is True
 
 
-    @property
-    def invalid(self):
+    def invalid_for(self, value, device):
+        mapping = self._find_map_for_value(value)
+        if mapping is not None:
+            cond = self._active_condition(mapping, device)
+            if cond is not None:
+                return cond.get("invalid", False)
         return False
         return False
 
 
     @property
     @property
@@ -297,20 +312,11 @@ class TuyaDpsConfig:
                 scale = 1
                 scale = 1
             replaced = "value" in mapping
             replaced = "value" in mapping
             result = mapping.get("value", result)
             result = mapping.get("value", result)
-            if "conditions" in mapping:
-                cond_dps = (
-                    self
-                    if "constraint" not in mapping
-                    else self._entity.find_dps(mapping["constraint"])
-                )
-                for c in mapping["conditions"]:
-                    if (
-                        "dps_val" in c
-                        and c["dps_val"] == device.get_property(cond_dps.id)
-                        and "value" in c
-                    ):
-                        result = c["value"]
-                        replaced = True
+            cond = self._active_condition(mapping, device)
+            if cond is not None:
+                replaced = replaced or "value" in cond
+                result = cond.get("value", result)
+                scale = cond.get("scale", scale)
 
 
             if scale != 1 and isinstance(result, (int, float)):
             if scale != 1 and isinstance(result, (int, float)):
                 result = result / scale
                 result = result / scale
@@ -342,6 +348,18 @@ class TuyaDpsConfig:
                         return m
                         return m
         return default
         return default
 
 
+    def _active_condition(self, mapping, device):
+        constraint = mapping.get("constraint")
+        conditions = mapping.get("conditions")
+        if constraint is not None and conditions is not None:
+            c_dps = self._entity.find_dps(constraint)
+            c_val = None if c_dps is None else device.get_property(c_dps.id)
+            if c_val is not None:
+                for cond in conditions:
+                    if c_val == cond.get("dps_val"):
+                        return cond
+        return None
+
     def get_values_to_set(self, device, value):
     def get_values_to_set(self, device, value):
         """Return the dps values that would be set when setting to value"""
         """Return the dps values that would be set when setting to value"""
         result = value
         result = value
@@ -359,11 +377,10 @@ class TuyaDpsConfig:
                 result = mapping["dps_val"]
                 result = mapping["dps_val"]
                 replaced = True
                 replaced = True
             # Conditions may have side effect of setting another value.
             # Conditions may have side effect of setting another value.
-            if "conditions" in mapping and "constraint" in mapping:
+            cond = self._active_condition(mapping, device)
+            if cond is not None and cond.get("value") == value:
                 c_dps = self._entity.find_dps(mapping["constraint"])
                 c_dps = self._entity.find_dps(mapping["constraint"])
-                for c in mapping["conditions"]:
-                    if "value" in c and c["value"] == value:
-                        dps_map.update(c_dps.get_values_to_set(device, c["dps_val"]))
+                dps_map.update(c_dps.get_values_to_set(device, cond["dps_val"]))
 
 
             if scale != 1 and isinstance(result, (int, float)):
             if scale != 1 and isinstance(result, (int, float)):
                 _LOGGER.debug(f"Scaling {result} by {scale}")
                 _LOGGER.debug(f"Scaling {result} by {scale}")
@@ -384,9 +401,10 @@ class TuyaDpsConfig:
                     value,
                     value,
                 )
                 )
 
 
-        if self.range is not None:
-            minimum = self.range["min"]
-            maximum = self.range["max"]
+        range = self.range(device)
+        if range is not None:
+            minimum = range["min"]
+            maximum = range["max"]
             if result < minimum or result > maximum:
             if result < minimum or result > maximum:
                 raise ValueError(
                 raise ValueError(
                     f"Target {self.name} ({value}) must be between "
                     f"Target {self.name} ({value}) must be between "

+ 2 - 4
tests/devices/test_gardenpac_heatpump.py

@@ -91,14 +91,12 @@ class TestGardenPACPoolHeatpump(IsolatedAsyncioTestCase):
     def test_maximum_target_temperature(self):
     def test_maximum_target_temperature(self):
         self.assertEqual(self.subject.max_temp, 45)
         self.assertEqual(self.subject.max_temp, 45)
 
 
-    @skip("Conditional ranges not supported yet")
     def test_minimum_fahrenheit_temperature(self):
     def test_minimum_fahrenheit_temperature(self):
-        self.dps[UNITS_DPS] = "F"
+        self.dps[UNITS_DPS] = False
         self.assertEqual(self.subject.min_temp, 60)
         self.assertEqual(self.subject.min_temp, 60)
 
 
-    @skip("Conditional ranges not supported yet")
     def test_maximum_fahrenheit_temperature(self):
     def test_maximum_fahrenheit_temperature(self):
-        self.dps[UNITS_DPS] = "F"
+        self.dps[UNITS_DPS] = False
         self.assertEqual(self.subject.max_temp, 115)
         self.assertEqual(self.subject.max_temp, 115)
 
 
     async def test_legacy_set_temperature_with_temperature(self):
     async def test_legacy_set_temperature_with_temperature(self):

+ 10 - 10
tests/devices/test_goldair_dehumidifier.py

@@ -264,32 +264,32 @@ class TestGoldairDehumidifier(IsolatedAsyncioTestCase):
     async def test_set_target_humidity_raises_error_outside_of_normal_preset(self):
     async def test_set_target_humidity_raises_error_outside_of_normal_preset(self):
         self.dps[PRESET_DPS] = PRESET_LOW
         self.dps[PRESET_DPS] = PRESET_LOW
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "Target humidity can only be changed while in Normal mode"
+            AttributeError, "target_humidity cannot be set at this time"
         ):
         ):
             await self.subject.async_set_humidity(50)
             await self.subject.async_set_humidity(50)
 
 
         self.dps[PRESET_DPS] = PRESET_HIGH
         self.dps[PRESET_DPS] = PRESET_HIGH
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "Target humidity can only be changed while in Normal mode"
+            AttributeError, "target_humidity cannot be set at this time"
         ):
         ):
             await self.subject.async_set_humidity(50)
             await self.subject.async_set_humidity(50)
 
 
         self.dps[PRESET_DPS] = PRESET_LOW
         self.dps[PRESET_DPS] = PRESET_LOW
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "Target humidity can only be changed while in Normal mode"
+            AttributeError, "target_humidity cannot be set at this time"
         ):
         ):
             await self.subject.async_set_humidity(50)
             await self.subject.async_set_humidity(50)
 
 
         self.dps[PRESET_DPS] = PRESET_DRY_CLOTHES
         self.dps[PRESET_DPS] = PRESET_DRY_CLOTHES
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "Target humidity can only be changed while in Normal mode"
+            AttributeError, "target_humidity cannot be set at this time"
         ):
         ):
             await self.subject.async_set_humidity(50)
             await self.subject.async_set_humidity(50)
 
 
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[AIRCLEAN_DPS] = True
         self.dps[AIRCLEAN_DPS] = True
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "Target humidity can only be changed while in Normal mode"
+            AttributeError, "target_humidity cannot be set at this time"
         ):
         ):
             await self.subject.async_set_humidity(50)
             await self.subject.async_set_humidity(50)
 
 
@@ -495,24 +495,24 @@ class TestGoldairDehumidifier(IsolatedAsyncioTestCase):
     def test_fan_modes_reflect_preset_mode(self):
     def test_fan_modes_reflect_preset_mode(self):
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.assertCountEqual(self.subject.fan_modes, [FAN_LOW, FAN_HIGH])
         self.assertCountEqual(self.subject.fan_modes, [FAN_LOW, FAN_HIGH])
-        self.assertCountEqual(self.fan.speed_count, 2)
+        self.assertEqual(self.fan.speed_count, 2)
 
 
         self.dps[PRESET_DPS] = PRESET_LOW
         self.dps[PRESET_DPS] = PRESET_LOW
         self.assertEqual(self.subject.fan_modes, [FAN_LOW])
         self.assertEqual(self.subject.fan_modes, [FAN_LOW])
-        self.assertCountEqual(self.fan.speed_count, 0)
+        self.assertEqual(self.fan.speed_count, 0)
 
 
         self.dps[PRESET_DPS] = PRESET_HIGH
         self.dps[PRESET_DPS] = PRESET_HIGH
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
-        self.assertCountEqual(self.fan.speed_count, 0)
+        self.assertEqual(self.fan.speed_count, 0)
 
 
         self.dps[PRESET_DPS] = PRESET_DRY_CLOTHES
         self.dps[PRESET_DPS] = PRESET_DRY_CLOTHES
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
-        self.assertCountEqual(self.fan.speed_count, 0)
+        self.assertEqual(self.fan.speed_count, 0)
 
 
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[AIRCLEAN_DPS] = True
         self.dps[AIRCLEAN_DPS] = True
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
-        self.assertCountEqual(self.fan.speed_count, 0)
+        self.assertEqual(self.fan.speed_count, 0)
 
 
     async def test_set_fan_mode_to_low_succeeds_in_normal_preset(self):
     async def test_set_fan_mode_to_low_succeeds_in_normal_preset(self):
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[PRESET_DPS] = PRESET_NORMAL

+ 8 - 11
tests/devices/test_goldair_fan.py

@@ -272,13 +272,12 @@ class TestGoldairFan(IsolatedAsyncioTestCase):
         ):
         ):
             await self.subject.async_set_percentage(80)
             await self.subject.async_set_percentage(80)
 
 
-    @skip("Complex conditions not supported yet")
     async def test_set_speed_in_sleep_mode_snaps(self):
     async def test_set_speed_in_sleep_mode_snaps(self):
         self.dps[PRESET_DPS] = "sleep"
         self.dps[PRESET_DPS] = "sleep"
         async with assert_device_properties_set(self.subject._device, {FANMODE_DPS: 8}):
         async with assert_device_properties_set(self.subject._device, {FANMODE_DPS: 8}):
             await self.subject.async_set_percentage(75)
             await self.subject.async_set_percentage(75)
 
 
-    @skip("Complex conditions not supported yet")
+    @skip("Fan modes does not work without mapping")
     def test_climate_fan_modes(self):
     def test_climate_fan_modes(self):
         self.dps[PRESET_DPS] = "normal"
         self.dps[PRESET_DPS] = "normal"
         self.assertCountEqual(self.climate.fan_modes, list(range(1, 13)))
         self.assertCountEqual(self.climate.fan_modes, list(range(1, 13)))
@@ -292,29 +291,27 @@ class TestGoldairFan(IsolatedAsyncioTestCase):
         self.dps[PRESET_DPS] = None
         self.dps[PRESET_DPS] = None
         self.assertEqual(self.climate.fan_modes, [])
         self.assertEqual(self.climate.fan_modes, [])
 
 
-    @skip("Complex conditions not supported yet")
     def test_climate_fan_mode_for_normal_preset(self):
     def test_climate_fan_mode_for_normal_preset(self):
         self.dps[PRESET_DPS] = "normal"
         self.dps[PRESET_DPS] = "normal"
 
 
-        self.dps[FANMODE_DPS] = "1"
+        self.dps[FANMODE_DPS] = 1
         self.assertEqual(self.climate.fan_mode, 1)
         self.assertEqual(self.climate.fan_mode, 1)
 
 
-        self.dps[FANMODE_DPS] = "6"
+        self.dps[FANMODE_DPS] = 6
         self.assertEqual(self.climate.fan_mode, 6)
         self.assertEqual(self.climate.fan_mode, 6)
 
 
-        self.dps[FANMODE_DPS] = "12"
+        self.dps[FANMODE_DPS] = 12
         self.assertEqual(self.climate.fan_mode, 12)
         self.assertEqual(self.climate.fan_mode, 12)
 
 
         self.dps[FANMODE_DPS] = None
         self.dps[FANMODE_DPS] = None
         self.assertEqual(self.climate.fan_mode, None)
         self.assertEqual(self.climate.fan_mode, None)
 
 
-    @skip("Complex conditions not supported yet")
     async def test_climate_set_fan_mode_for_normal_preset(self):
     async def test_climate_set_fan_mode_for_normal_preset(self):
         self.dps[PRESET_DPS] = "normal"
         self.dps[PRESET_DPS] = "normal"
 
 
         async with assert_device_properties_set(
         async with assert_device_properties_set(
             self.climate._device,
             self.climate._device,
-            {FANMODE_DPS: "6"},
+            {FANMODE_DPS: 6},
         ):
         ):
             await self.climate.async_set_fan_mode(6)
             await self.climate.async_set_fan_mode(6)
 
 
@@ -322,13 +319,13 @@ class TestGoldairFan(IsolatedAsyncioTestCase):
     def test_climate_fan_mode_for_eco_preset(self):
     def test_climate_fan_mode_for_eco_preset(self):
         self.dps[PRESET_DPS] = "nature"
         self.dps[PRESET_DPS] = "nature"
 
 
-        self.dps[FANMODE_DPS] = "4"
+        self.dps[FANMODE_DPS] = 4
         self.assertEqual(self.climate.fan_mode, 1)
         self.assertEqual(self.climate.fan_mode, 1)
 
 
-        self.dps[FANMODE_DPS] = "8"
+        self.dps[FANMODE_DPS] = 8
         self.assertEqual(self.climate.fan_mode, 2)
         self.assertEqual(self.climate.fan_mode, 2)
 
 
-        self.dps[FANMODE_DPS] = "12"
+        self.dps[FANMODE_DPS] = 12
         self.assertEqual(self.climate.fan_mode, 3)
         self.assertEqual(self.climate.fan_mode, 3)
 
 
         self.dps[FANMODE_DPS] = None
         self.dps[FANMODE_DPS] = None

+ 3 - 8
tests/devices/test_goldair_gpph_heater.py

@@ -126,7 +126,6 @@ class TestGoldairHeater(IsolatedAsyncioTestCase):
     def test_target_temperature_step(self):
     def test_target_temperature_step(self):
         self.assertEqual(self.subject.target_temperature_step, 1)
         self.assertEqual(self.subject.target_temperature_step, 1)
 
 
-    @skip("Conditional ranges not yet implemented")
     def test_minimum_temperature(self):
     def test_minimum_temperature(self):
         self.dps[PRESET_DPS] = "C"
         self.dps[PRESET_DPS] = "C"
         self.assertEqual(self.subject.min_temp, 5)
         self.assertEqual(self.subject.min_temp, 5)
@@ -135,9 +134,8 @@ class TestGoldairHeater(IsolatedAsyncioTestCase):
         self.assertEqual(self.subject.min_temp, 5)
         self.assertEqual(self.subject.min_temp, 5)
 
 
         self.dps[PRESET_DPS] = "AF"
         self.dps[PRESET_DPS] = "AF"
-        self.assertIs(self.subject.min_temp, None)
+        self.assertIs(self.subject.min_temp, 5)
 
 
-    @skip("Conditional ranges not yet implemented")
     def test_maximum_target_temperature(self):
     def test_maximum_target_temperature(self):
         self.dps[PRESET_DPS] = "C"
         self.dps[PRESET_DPS] = "C"
         self.assertEqual(self.subject.max_temp, 35)
         self.assertEqual(self.subject.max_temp, 35)
@@ -146,7 +144,7 @@ class TestGoldairHeater(IsolatedAsyncioTestCase):
         self.assertEqual(self.subject.max_temp, 21)
         self.assertEqual(self.subject.max_temp, 21)
 
 
         self.dps[PRESET_DPS] = "AF"
         self.dps[PRESET_DPS] = "AF"
-        self.assertIs(self.subject.max_temp, None)
+        self.assertIs(self.subject.max_temp, 5)
 
 
     async def test_legacy_set_temperature_with_temperature(self):
     async def test_legacy_set_temperature_with_temperature(self):
         async with assert_device_properties_set(
         async with assert_device_properties_set(
@@ -200,7 +198,6 @@ class TestGoldairHeater(IsolatedAsyncioTestCase):
         ):
         ):
             await self.subject.async_set_target_temperature(24.6)
             await self.subject.async_set_target_temperature(24.6)
 
 
-    @skip("Conditional ranges not supported yet")
     async def test_set_target_temperature_fails_outside_valid_range_in_comfort(self):
     async def test_set_target_temperature_fails_outside_valid_range_in_comfort(self):
         self.dps[PRESET_DPS] = "C"
         self.dps[PRESET_DPS] = "C"
 
 
@@ -214,7 +211,6 @@ class TestGoldairHeater(IsolatedAsyncioTestCase):
         ):
         ):
             await self.subject.async_set_target_temperature(36)
             await self.subject.async_set_target_temperature(36)
 
 
-    @skip("Conditional ranges not supported yet")
     async def test_set_target_temperature_fails_outside_valid_range_in_eco(self):
     async def test_set_target_temperature_fails_outside_valid_range_in_eco(self):
         self.dps[PRESET_DPS] = "ECO"
         self.dps[PRESET_DPS] = "ECO"
 
 
@@ -228,12 +224,11 @@ class TestGoldairHeater(IsolatedAsyncioTestCase):
         ):
         ):
             await self.subject.async_set_target_temperature(22)
             await self.subject.async_set_target_temperature(22)
 
 
-    @skip("Conditional ranges not supported yet")
     async def test_set_target_temperature_fails_in_anti_freeze(self):
     async def test_set_target_temperature_fails_in_anti_freeze(self):
         self.dps[PRESET_DPS] = "AF"
         self.dps[PRESET_DPS] = "AF"
 
 
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "You cannot set the temperature in Anti-freeze mode"
+            AttributeError, "temperature cannot be set at this time"
         ):
         ):
             await self.subject.async_set_target_temperature(25)
             await self.subject.async_set_target_temperature(25)