Parcourir la source

Resolve skipped tests for GPPH heater and remove special class for handling it.

Cross interaction between power level and "swing mode" can be represented in the config.  Remaining skipped test of restricting values does not match with the
way that we allow unknown values to go straight through without mapping, but the list of possible values is still taken from the mapping list.
Jason Rumney il y a 4 ans
Parent
commit
fcbfb4788c

+ 9 - 2
custom_components/tuya_local/devices/goldair_gpph_heater.yaml

@@ -2,7 +2,6 @@ name: Goldair GPPH Heater
 legacy_type: heater
 primary_entity:
   entity: climate
-  legacy_class: ".heater.climate.GoldairHeater"
   dps:
     - id: 1
       type: boolean
@@ -61,9 +60,12 @@ primary_entity:
       type: string
       mapping:
         - dps_val: "stop"
-          value: "Stop"
           icon: "mdi:radiator-disabled"
           icon_priority: 2
+          constraint: swing_mode
+          conditions:
+            - dps_val: stop
+              value: "Stop"
         - dps_val: "1"
           value: "1"
         - dps_val: "2"
@@ -85,6 +87,11 @@ primary_entity:
       type: string
       mapping:
         - dps_val: "stop"
+          constraint: power_level
+          conditions:
+            - dps_val: stop
+              value: "Stop"
+          value-redirect: power_level
           value: "Stop"
         - dps_val: "auto"
           value: "Auto"

+ 0 - 0
custom_components/tuya_local/heater/__init__.py


+ 0 - 246
custom_components/tuya_local/heater/climate.py

@@ -1,246 +0,0 @@
-"""
-Goldair WiFi Heater device.
-"""
-from homeassistant.components.climate import ClimateEntity
-from homeassistant.components.climate.const import (
-    ATTR_HVAC_MODE,
-    ATTR_PRESET_MODE,
-    HVAC_MODE_HEAT,
-    SUPPORT_PRESET_MODE,
-    SUPPORT_SWING_MODE,
-    SUPPORT_TARGET_TEMPERATURE,
-)
-from homeassistant.const import ATTR_TEMPERATURE, STATE_UNAVAILABLE
-
-from ..device import TuyaLocalDevice
-from .const import (
-    ATTR_ECO_TARGET_TEMPERATURE,
-    ATTR_ERROR,
-    ATTR_ERROR_CODE,
-    ATTR_POWER_LEVEL,
-    ATTR_POWER_MODE,
-    ATTR_POWER_MODE_AUTO,
-    ATTR_POWER_MODE_USER,
-    ATTR_TARGET_TEMPERATURE,
-    HVAC_MODE_TO_DPS_MODE,
-    POWER_LEVEL_STOP,
-    POWER_LEVEL_TO_DPS_LEVEL,
-    PRESET_MODE_TO_DPS_MODE,
-    PROPERTY_TO_DPS_ID,
-    STATE_ANTI_FREEZE,
-    STATE_COMFORT,
-    STATE_ECO,
-)
-
-SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE | SUPPORT_SWING_MODE
-
-
-class GoldairHeater(ClimateEntity):
-    """Representation of a Goldair WiFi heater."""
-
-    def __init__(self, device):
-        """Initialize the heater.
-        Args:
-            name (str): The device's name.
-            device (TuyaLocalDevice): The device API instance."""
-        self._device = device
-
-        self._support_flags = SUPPORT_FLAGS
-
-        self._TEMPERATURE_STEP = 1
-        self._TEMPERATURE_LIMITS = {
-            STATE_COMFORT: {"min": 5, "max": 35},
-            STATE_ECO: {"min": 5, "max": 21},
-        }
-
-    @property
-    def supported_features(self):
-        """Return the list of supported features."""
-        return self._support_flags
-
-    @property
-    def should_poll(self):
-        """Return the polling state."""
-        return True
-
-    @property
-    def name(self):
-        """Return the name of the climate device."""
-        return self._device.name
-
-    @property
-    def unique_id(self):
-        """Return the unique id for this heater."""
-        return self._device.unique_id
-
-    @property
-    def device_info(self):
-        """Return device information about this heater."""
-        return self._device.device_info
-
-    @property
-    def icon(self):
-        """Return the icon to use in the frontend for this device."""
-        hvac_mode = self.hvac_mode
-        power_level = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
-        if hvac_mode == HVAC_MODE_HEAT and power_level != POWER_LEVEL_STOP:
-            return "mdi:radiator"
-        else:
-            return "mdi:radiator-disabled"
-
-    @property
-    def temperature_unit(self):
-        """Return the unit of measurement."""
-        return self._device.temperature_unit
-
-    @property
-    def target_temperature(self):
-        """Return the temperature we try to reach."""
-        if self.preset_mode == STATE_COMFORT:
-            return self._device.get_property(
-                PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE]
-            )
-        elif self.preset_mode == STATE_ECO:
-            return self._device.get_property(
-                PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE]
-            )
-        else:
-            return None
-
-    @property
-    def target_temperature_step(self):
-        """Return the supported step of target temperature."""
-        return self._TEMPERATURE_STEP
-
-    @property
-    def min_temp(self):
-        """Return the minimum temperature."""
-        if self.preset_mode and self.preset_mode != STATE_ANTI_FREEZE:
-            return self._TEMPERATURE_LIMITS[self.preset_mode]["min"]
-        else:
-            return None
-
-    @property
-    def max_temp(self):
-        """Return the maximum temperature."""
-        if self.preset_mode and self.preset_mode != STATE_ANTI_FREEZE:
-            return self._TEMPERATURE_LIMITS[self.preset_mode]["max"]
-        else:
-            return None
-
-    async def async_set_temperature(self, **kwargs):
-        """Set new target temperatures."""
-        if kwargs.get(ATTR_PRESET_MODE) is not None:
-            await self.async_set_preset_mode(kwargs.get(ATTR_PRESET_MODE))
-        if kwargs.get(ATTR_TEMPERATURE) is not None:
-            await self.async_set_target_temperature(kwargs.get(ATTR_TEMPERATURE))
-
-    async def async_set_target_temperature(self, target_temperature):
-        target_temperature = int(round(target_temperature))
-        preset_mode = self.preset_mode
-
-        if preset_mode == STATE_ANTI_FREEZE:
-            raise ValueError("You cannot set the temperature in Anti-freeze mode.")
-
-        limits = self._TEMPERATURE_LIMITS[preset_mode]
-        if not limits["min"] <= target_temperature <= limits["max"]:
-            raise ValueError(
-                f"Target temperature ({target_temperature}) must be between "
-                f'{limits["min"]} and {limits["max"]}.'
-            )
-
-        if preset_mode == STATE_ECO:
-            await self._device.async_set_property(
-                PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE], target_temperature
-            )
-        else:
-            await self._device.async_set_property(
-                PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE], target_temperature
-            )
-
-    @property
-    def current_temperature(self):
-        """Return the current temperature."""
-        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TEMPERATURE])
-
-    @property
-    def hvac_mode(self):
-        """Return current HVAC mode, ie Heat or Off."""
-        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE])
-
-        if dps_mode is not None:
-            return TuyaLocalDevice.get_key_for_value(HVAC_MODE_TO_DPS_MODE, dps_mode)
-        else:
-            return STATE_UNAVAILABLE
-
-    @property
-    def hvac_modes(self):
-        """Return the list of available HVAC modes."""
-        return list(HVAC_MODE_TO_DPS_MODE.keys())
-
-    async def async_set_hvac_mode(self, hvac_mode):
-        """Set new HVAC mode."""
-        dps_mode = HVAC_MODE_TO_DPS_MODE[hvac_mode]
-        await self._device.async_set_property(
-            PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE], dps_mode
-        )
-
-    @property
-    def preset_mode(self):
-        """Return current preset mode, ie Comfort, Eco, Anti-freeze."""
-        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE])
-        if dps_mode is not None:
-            return TuyaLocalDevice.get_key_for_value(PRESET_MODE_TO_DPS_MODE, dps_mode)
-        else:
-            return None
-
-    @property
-    def preset_modes(self):
-        """Return the list of available preset modes."""
-        return list(PRESET_MODE_TO_DPS_MODE.keys())
-
-    async def async_set_preset_mode(self, preset_mode):
-        """Set new preset mode."""
-        dps_mode = PRESET_MODE_TO_DPS_MODE[preset_mode]
-        await self._device.async_set_property(
-            PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE], dps_mode
-        )
-
-    @property
-    def swing_mode(self):
-        """Return the power level."""
-        dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_MODE])
-        if dps_mode == None:
-            return None
-
-        if dps_mode == ATTR_POWER_MODE_USER:
-            dps_mode = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
-        return TuyaLocalDevice.get_key_for_value(POWER_LEVEL_TO_DPS_LEVEL, dps_mode)
-
-    @property
-    def swing_modes(self):
-        """List of power levels."""
-        return list(POWER_LEVEL_TO_DPS_LEVEL.keys())
-
-    async def async_set_swing_mode(self, swing_mode):
-        """Set new power level."""
-        new_level = swing_mode
-        if new_level not in POWER_LEVEL_TO_DPS_LEVEL.keys():
-            raise ValueError(f"Invalid power level: {new_level}")
-        dps_level = POWER_LEVEL_TO_DPS_LEVEL[new_level]
-        await self._device.async_set_property(
-            PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL], dps_level
-        )
-
-    @property
-    def device_state_attributes(self):
-        """Get additional attributes that HA doesn't naturally support."""
-        error_code = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_ERROR])
-        if error_code:
-            error = f"Error {error_code}"
-        else:
-            error = "OK"
-        return {ATTR_ERROR: error, ATTR_ERROR_CODE: error_code}
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 0 - 54
custom_components/tuya_local/heater/const.py

@@ -1,54 +0,0 @@
-from homeassistant.components.climate.const import (
-    ATTR_HVAC_MODE,
-    ATTR_PRESET_MODE,
-    HVAC_MODE_HEAT,
-    HVAC_MODE_OFF,
-)
-from homeassistant.const import ATTR_TEMPERATURE
-
-ATTR_TARGET_TEMPERATURE = "target_temperature"
-ATTR_CHILD_LOCK = "child_lock"
-ATTR_ERROR = "error"
-ATTR_ERROR_CODE = "error_code"
-ATTR_POWER_MODE_AUTO = "auto"
-ATTR_POWER_MODE_USER = "user"
-ATTR_POWER_LEVEL = "power_level"
-ATTR_DISPLAY_ON = "display_on"
-ATTR_POWER_MODE = "power_mode"
-ATTR_ECO_TARGET_TEMPERATURE = "eco_" + ATTR_TARGET_TEMPERATURE
-
-STATE_COMFORT = "Comfort"
-STATE_ECO = "Eco"
-STATE_ANTI_FREEZE = "Anti-freeze"
-
-PROPERTY_TO_DPS_ID = {
-    ATTR_HVAC_MODE: "1",
-    ATTR_TARGET_TEMPERATURE: "2",
-    ATTR_TEMPERATURE: "3",
-    ATTR_PRESET_MODE: "4",
-    ATTR_CHILD_LOCK: "6",
-    ATTR_ERROR: "12",
-    ATTR_POWER_LEVEL: "101",
-    ATTR_DISPLAY_ON: "104",
-    ATTR_POWER_MODE: "105",
-    ATTR_ECO_TARGET_TEMPERATURE: "106",
-}
-
-HVAC_MODE_TO_DPS_MODE = {HVAC_MODE_OFF: False, HVAC_MODE_HEAT: True}
-PRESET_MODE_TO_DPS_MODE = {
-    STATE_COMFORT: "C",
-    STATE_ECO: "ECO",
-    STATE_ANTI_FREEZE: "AF",
-}
-
-POWER_LEVEL_STOP = "stop"
-POWER_LEVEL_AUTO = "auto"
-POWER_LEVEL_TO_DPS_LEVEL = {
-    "Stop": POWER_LEVEL_STOP,
-    "1": "1",
-    "2": "2",
-    "3": "3",
-    "4": "4",
-    "5": "5",
-    "Auto": POWER_LEVEL_AUTO,
-}

+ 1 - 9
tests/devices/test_goldair_gpph_heater.py

@@ -1,5 +1,3 @@
-from unittest import skip
-
 from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
@@ -313,11 +311,10 @@ class TestGoldairHeater(
             ["Stop", "1", "2", "3", "4", "5", "Auto"],
         )
 
-    @skip("Paired settings not supported yet")
     async def test_set_power_level_to_stop(self):
         async with assert_device_properties_set(
             self.subject._device,
-            {POWERLEVEL_DPS: "stop"},
+            {POWERLEVEL_DPS: "stop", SWING_DPS: "stop"},
         ):
             await self.subject.async_set_swing_mode("Stop")
 
@@ -335,11 +332,6 @@ class TestGoldairHeater(
         ):
             await self.subject.async_set_swing_mode("3")
 
-    @skip("Restriction to mapped values not supported yet")
-    async def test_set_power_level_to_invalid_value_raises_error(self):
-        with self.assertRaisesRegex(ValueError, "Invalid power level: unknown"):
-            await self.subject.async_set_swing_mode("unknown")
-
     def test_device_state_attributes(self):
         self.dps[ERROR_DPS] = "something"
         self.dps[TIMER_DPS] = 5

+ 0 - 0
tests/heater/__init__.py


+ 0 - 409
tests/heater/test_climate.py

@@ -1,409 +0,0 @@
-from unittest import IsolatedAsyncioTestCase
-from unittest.mock import AsyncMock, patch
-
-from homeassistant.components.climate.const import (
-    ATTR_HVAC_MODE,
-    ATTR_PRESET_MODE,
-    HVAC_MODE_HEAT,
-    HVAC_MODE_OFF,
-    SUPPORT_PRESET_MODE,
-    SUPPORT_SWING_MODE,
-    SUPPORT_TARGET_TEMPERATURE,
-)
-from homeassistant.const import ATTR_TEMPERATURE, STATE_UNAVAILABLE
-
-from custom_components.tuya_local.heater.climate import GoldairHeater
-from custom_components.tuya_local.heater.const import (
-    ATTR_ECO_TARGET_TEMPERATURE,
-    ATTR_ERROR,
-    ATTR_ERROR_CODE,
-    ATTR_POWER_LEVEL,
-    ATTR_POWER_MODE,
-    ATTR_POWER_MODE_AUTO,
-    ATTR_POWER_MODE_USER,
-    ATTR_TARGET_TEMPERATURE,
-    HVAC_MODE_TO_DPS_MODE,
-    POWER_LEVEL_AUTO,
-    POWER_LEVEL_STOP,
-    POWER_LEVEL_TO_DPS_LEVEL,
-    PRESET_MODE_TO_DPS_MODE,
-    PROPERTY_TO_DPS_ID,
-    STATE_ANTI_FREEZE,
-    STATE_COMFORT,
-    STATE_ECO,
-)
-
-from ..const import GPPH_HEATER_PAYLOAD
-from ..helpers import assert_device_properties_set
-
-
-class TestGoldairHeater(IsolatedAsyncioTestCase):
-    def setUp(self):
-        device_patcher = patch("custom_components.tuya_local.device.TuyaLocalDevice")
-        self.addCleanup(device_patcher.stop)
-        self.mock_device = device_patcher.start()
-
-        self.subject = GoldairHeater(self.mock_device())
-
-        self.dps = GPPH_HEATER_PAYLOAD.copy()
-        self.subject._device.get_property.side_effect = lambda id: self.dps[id]
-
-    def test_supported_features(self):
-        self.assertEqual(
-            self.subject.supported_features,
-            SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE | SUPPORT_SWING_MODE,
-        )
-
-    def test_should_poll(self):
-        self.assertTrue(self.subject.should_poll)
-
-    def test_name_returns_device_name(self):
-        self.assertEqual(self.subject.name, self.subject._device.name)
-
-    def test_unique_id_returns_device_unique_id(self):
-        self.assertEqual(self.subject.unique_id, self.subject._device.unique_id)
-
-    def test_device_info_returns_device_info_from_device(self):
-        self.assertEqual(self.subject.device_info, self.subject._device.device_info)
-
-    def test_icon(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = True
-        self.assertEqual(self.subject.icon, "mdi:radiator")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = False
-        self.assertEqual(self.subject.icon, "mdi:radiator-disabled")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = True
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]] = POWER_LEVEL_STOP
-        self.assertEqual(self.subject.icon, "mdi:radiator-disabled")
-
-    def test_temperature_unit_returns_device_temperature_unit(self):
-        self.assertEqual(
-            self.subject.temperature_unit, self.subject._device.temperature_unit
-        )
-
-    def test_target_temperature(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE]] = 25
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE]] = 15
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_COMFORT
-        ]
-        self.assertEqual(self.subject.target_temperature, 25)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ECO
-        ]
-        self.assertEqual(self.subject.target_temperature, 15)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ANTI_FREEZE
-        ]
-        self.assertIs(self.subject.target_temperature, None)
-
-    def test_target_temperature_step(self):
-        self.assertEqual(self.subject.target_temperature_step, 1)
-
-    def test_minimum_target_temperature(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_COMFORT
-        ]
-        self.assertEqual(self.subject.min_temp, 5)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ECO
-        ]
-        self.assertEqual(self.subject.min_temp, 5)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ANTI_FREEZE
-        ]
-        self.assertIs(self.subject.min_temp, None)
-
-    def test_maximum_target_temperature(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_COMFORT
-        ]
-        self.assertEqual(self.subject.max_temp, 35)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ECO
-        ]
-        self.assertEqual(self.subject.max_temp, 21)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ANTI_FREEZE
-        ]
-        self.assertIs(self.subject.max_temp, None)
-
-    async def test_legacy_set_temperature_with_temperature(self):
-        async with assert_device_properties_set(
-            self.subject._device, {PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE]: 25}
-        ):
-            await self.subject.async_set_temperature(temperature=25)
-
-    async def test_legacy_set_temperature_with_preset_mode(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {
-                PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]: PRESET_MODE_TO_DPS_MODE[
-                    STATE_COMFORT
-                ]
-            },
-        ):
-            await self.subject.async_set_temperature(preset_mode=STATE_COMFORT)
-
-    async def test_legacy_set_temperature_with_both_properties(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {
-                PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE]: 25,
-                PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]: PRESET_MODE_TO_DPS_MODE[
-                    STATE_COMFORT
-                ],
-            },
-        ):
-            await self.subject.async_set_temperature(
-                temperature=25, preset_mode=STATE_COMFORT
-            )
-
-    async def test_legacy_set_temperature_with_no_valid_properties(self):
-        await self.subject.async_set_temperature(something="else")
-        self.subject._device.async_set_property.assert_not_called
-
-    async def test_set_target_temperature_in_comfort_mode(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_COMFORT
-        ]
-
-        async with assert_device_properties_set(
-            self.subject._device, {PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE]: 25}
-        ):
-            await self.subject.async_set_target_temperature(25)
-
-    async def test_set_target_temperature_in_eco_mode(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ECO
-        ]
-
-        async with assert_device_properties_set(
-            self.subject._device, {PROPERTY_TO_DPS_ID[ATTR_ECO_TARGET_TEMPERATURE]: 15}
-        ):
-            await self.subject.async_set_target_temperature(15)
-
-    async def test_set_target_temperature_rounds_value_to_closest_integer(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE]: 25},
-        ):
-            await self.subject.async_set_target_temperature(24.6)
-
-    async def test_set_target_temperature_fails_outside_valid_range_in_comfort(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_COMFORT
-        ]
-
-        with self.assertRaisesRegex(
-            ValueError, "Target temperature \\(4\\) must be between 5 and 35"
-        ):
-            await self.subject.async_set_target_temperature(4)
-
-        with self.assertRaisesRegex(
-            ValueError, "Target temperature \\(36\\) must be between 5 and 35"
-        ):
-            await self.subject.async_set_target_temperature(36)
-
-    async def test_set_target_temperature_fails_outside_valid_range_in_eco(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ECO
-        ]
-
-        with self.assertRaisesRegex(
-            ValueError, "Target temperature \\(4\\) must be between 5 and 21"
-        ):
-            await self.subject.async_set_target_temperature(4)
-
-        with self.assertRaisesRegex(
-            ValueError, "Target temperature \\(22\\) must be between 5 and 21"
-        ):
-            await self.subject.async_set_target_temperature(22)
-
-    async def test_set_target_temperature_fails_in_anti_freeze(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ANTI_FREEZE
-        ]
-
-        with self.assertRaisesRegex(
-            ValueError, "You cannot set the temperature in Anti-freeze mode"
-        ):
-            await self.subject.async_set_target_temperature(25)
-
-    def test_current_temperature(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_TEMPERATURE]] = 25
-        self.assertEqual(self.subject.current_temperature, 25)
-
-    def test_hvac_mode(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = True
-        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_HEAT)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = False
-        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_OFF)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = None
-        self.assertEqual(self.subject.hvac_mode, STATE_UNAVAILABLE)
-
-    def test_hvac_modes(self):
-        self.assertEqual(self.subject.hvac_modes, [HVAC_MODE_OFF, HVAC_MODE_HEAT])
-
-    async def test_turn_on(self):
-        async with assert_device_properties_set(
-            self.subject._device, {PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]: True}
-        ):
-            await self.subject.async_set_hvac_mode(HVAC_MODE_HEAT)
-
-    async def test_turn_off(self):
-        async with assert_device_properties_set(
-            self.subject._device, {PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]: False}
-        ):
-            await self.subject.async_set_hvac_mode(HVAC_MODE_OFF)
-
-    def test_preset_mode(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_COMFORT
-        ]
-        self.assertEqual(self.subject.preset_mode, STATE_COMFORT)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ECO
-        ]
-        self.assertEqual(self.subject.preset_mode, STATE_ECO)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            STATE_ANTI_FREEZE
-        ]
-        self.assertEqual(self.subject.preset_mode, STATE_ANTI_FREEZE)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = None
-        self.assertIs(self.subject.preset_mode, None)
-
-    def test_preset_modes(self):
-        self.assertEqual(
-            self.subject.preset_modes, [STATE_COMFORT, STATE_ECO, STATE_ANTI_FREEZE]
-        )
-
-    async def test_set_preset_mode_to_comfort(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {
-                PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]: PRESET_MODE_TO_DPS_MODE[
-                    STATE_COMFORT
-                ]
-            },
-        ):
-            await self.subject.async_set_preset_mode(STATE_COMFORT)
-
-    async def test_set_preset_mode_to_eco(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]: PRESET_MODE_TO_DPS_MODE[STATE_ECO]},
-        ):
-            await self.subject.async_set_preset_mode(STATE_ECO)
-
-    async def test_set_preset_mode_to_anti_freeze(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {
-                PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]: PRESET_MODE_TO_DPS_MODE[
-                    STATE_ANTI_FREEZE
-                ]
-            },
-        ):
-            await self.subject.async_set_preset_mode(STATE_ANTI_FREEZE)
-
-    def test_power_level_returns_user_power_level(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_MODE]] = ATTR_POWER_MODE_USER
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]] = POWER_LEVEL_TO_DPS_LEVEL[
-            "Stop"
-        ]
-        self.assertEqual(self.subject.swing_mode, "Stop")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]] = POWER_LEVEL_TO_DPS_LEVEL["3"]
-        self.assertEqual(self.subject.swing_mode, "3")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]] = POWER_LEVEL_TO_DPS_LEVEL[
-            "Auto"
-        ]
-        self.assertEqual(self.subject.swing_mode, "Auto")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]] = None
-        self.assertIs(self.subject.swing_mode, None)
-
-    def test_power_level_in_returns_power_mode_when_not_in_user_power_mode(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_MODE]] = POWER_LEVEL_TO_DPS_LEVEL["Stop"]
-        self.assertEqual(self.subject.swing_mode, "Stop")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_MODE]] = POWER_LEVEL_TO_DPS_LEVEL["3"]
-        self.assertEqual(self.subject.swing_mode, "3")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_MODE]] = POWER_LEVEL_TO_DPS_LEVEL["Auto"]
-        self.assertEqual(self.subject.swing_mode, "Auto")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_MODE]] = None
-        self.assertIs(self.subject.swing_mode, None)
-
-    def test_power_levels(self):
-        self.assertEqual(
-            self.subject.swing_modes,
-            ["Stop", "1", "2", "3", "4", "5", "Auto"],
-        )
-
-    async def test_set_power_level_to_stop(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]: POWER_LEVEL_TO_DPS_LEVEL["Stop"]},
-        ):
-            await self.subject.async_set_swing_mode("Stop")
-
-    async def test_set_power_level_to_auto(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]: POWER_LEVEL_TO_DPS_LEVEL["Auto"]},
-        ):
-            await self.subject.async_set_swing_mode("Auto")
-
-    async def test_set_power_level_to_numeric_value(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]: POWER_LEVEL_TO_DPS_LEVEL["3"]},
-        ):
-            await self.subject.async_set_swing_mode("3")
-
-    async def test_set_power_level_to_invalid_value_raises_error(self):
-        with self.assertRaisesRegex(ValueError, "Invalid power level: unknown"):
-            await self.subject.async_set_swing_mode("unknown")
-
-    def test_error_state(self):
-        # There are currently no known error states; update this as they're discovered
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_ERROR]] = "something"
-        self.assertEqual(
-            self.subject.device_state_attributes,
-            {ATTR_ERROR_CODE: "something", ATTR_ERROR: "Error something"},
-        )
-
-    def test_no_error_state(self):
-        # Test that no error is OK
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_ERROR]] = 0
-        self.assertEqual(
-            self.subject.device_state_attributes,
-            {ATTR_ERROR_CODE: 0, ATTR_ERROR: "OK"},
-        )
-
-    async def test_update(self):
-        result = AsyncMock()
-        self.subject._device.async_refresh.return_value = result()
-
-        await self.subject.async_update()
-
-        self.subject._device.async_refresh.assert_called_once()
-        result.assert_awaited()

+ 2 - 2
tests/test_climate.py

@@ -9,7 +9,7 @@ from custom_components.tuya_local.const import (
     DOMAIN,
 )
 from custom_components.tuya_local.dehumidifier.climate import GoldairDehumidifier
-from custom_components.tuya_local.heater.climate import GoldairHeater
+from custom_components.tuya_local.generic.climate import TuyaLocalClimate
 from custom_components.tuya_local.climate import async_setup_entry
 
 
@@ -30,7 +30,7 @@ async def test_init_entry(hass):
     hass.data[DOMAIN]["dummy"]["device"] = m_device
 
     await async_setup_entry(hass, entry, m_add_entities)
-    assert type(hass.data[DOMAIN]["dummy"][CONF_CLIMATE]) == GoldairHeater
+    assert type(hass.data[DOMAIN]["dummy"][CONF_CLIMATE]) == TuyaLocalClimate
     m_add_entities.assert_called_once()
 
 

+ 0 - 4
tests/test_device_config.py

@@ -132,10 +132,6 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
                 legacy_class,
             )
 
-    def test_gpph_heater_detection(self):
-        """Test that GPPH heater can be detected from its sample payload."""
-        self._test_detect(GPPH_HEATER_PAYLOAD, "heater", "GoldairHeater")
-
     def test_goldair_dehumidifier_detection(self):
         """Test that Goldair dehumidifier can be detected from its sample payload."""
         self._test_detect(