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

Implement device specified temperature unit.

Move Gardenpac Heatpump to generic backend.
Although the temperature range is not yet properly handled when the range
is set to Fahrenheit, this was also the case with the legacy class, so only
cosmetic icon logic is lost by this move.
Jason Rumney 4 лет назад
Родитель
Сommit
458b3818e7

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

@@ -2,7 +2,6 @@ name: GardenPAC Pool Heatpump
 legacy_type: gardenpac_heatpump
 primary_entity:
   entity: climate
-  legacy_class: ".gardenpac_heatpump.climate.GardenPACPoolHeatpump"
   dps:
     - id: 1
       name: hvac_mode

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


+ 0 - 192
custom_components/tuya_local/gardenpac_heatpump/climate.py

@@ -1,192 +0,0 @@
-"""
-Garden PAC InverTech Swimming Pool Heatpump 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_TARGET_TEMPERATURE,
-)
-from homeassistant.const import (
-    ATTR_TEMPERATURE,
-    STATE_UNAVAILABLE,
-    TEMP_CELSIUS,
-    TEMP_FAHRENHEIT,
-)
-
-from ..device import TuyaLocalDevice
-from .const import (
-    ATTR_OPERATING_MODE,
-    ATTR_POWER_LEVEL,
-    ATTR_TARGET_TEMPERATURE,
-    ATTR_TEMP_UNIT,
-    HVAC_MODE_TO_DPS_MODE,
-    PRESET_MODE_TO_DPS_MODE,
-    PROPERTY_TO_DPS_ID,
-)
-
-SUPPORT_FLAGS = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE
-
-
-class GardenPACPoolHeatpump(ClimateEntity):
-    """Representation of a GardenPAC InverTech Heatpump WiFi pool heater."""
-
-    def __init__(self, device):
-        """Initialize the heater.
-        Args:
-            device (TuyaLocalDevice): The device API instance."""
-        self._device = device
-
-        self._support_flags = SUPPORT_FLAGS
-
-        self._TEMPERATURE_STEP = 1
-        self._TEMPERATURE_LIMITS = {"min": 18, "max": 45}
-
-    @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
-
-        if hvac_mode == HVAC_MODE_HEAT:
-            return "mdi:hot-tub"
-        else:
-            return "mdi:radiator-disabled"
-
-    @property
-    def temperature_unit(self):
-        """Return the unit of measurement."""
-        dps_unit = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TEMP_UNIT])
-        if dps_unit:
-            return TEMP_CELSIUS
-        else:
-            return TEMP_FAHRENHEIT
-
-    @property
-    def target_temperature(self):
-        """Return the temperature we try to reach."""
-        return self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE])
-
-    @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."""
-        return self._TEMPERATURE_LIMITS["min"]
-
-    @property
-    def max_temp(self):
-        """Return the maximum temperature."""
-        return self._TEMPERATURE_LIMITS["max"]
-
-    async def async_set_temperature(self, **kwargs):
-        """Set new target temperatures."""
-        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))
-
-        limits = self._TEMPERATURE_LIMITS
-        if not limits["min"] <= target_temperature <= limits["max"]:
-            raise ValueError(
-                f"Target temperature ({target_temperature}) must be between "
-                f'{limits["min"]} and {limits["max"]}.'
-            )
-
-        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 the current preset mode."""
-        dps_preset = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE])
-        if dps_preset is not None:
-            return TuyaLocalDevice.get_key_for_value(
-                PRESET_MODE_TO_DPS_MODE, dps_preset
-            )
-        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 device_state_attributes(self):
-        """Get additional attributes that HA doesn't naturally support."""
-        power_level = self._device.get_property(PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL])
-
-        operating_mode = self._device.get_property(
-            PROPERTY_TO_DPS_ID[ATTR_OPERATING_MODE]
-        )
-
-        return {ATTR_POWER_LEVEL: power_level, ATTR_OPERATING_MODE: operating_mode}
-
-    async def async_update(self):
-        await self._device.async_refresh()

+ 0 - 27
custom_components/tuya_local/gardenpac_heatpump/const.py

@@ -1,27 +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_TEMP_UNIT = "temperature_unit"
-ATTR_POWER_LEVEL = "power_level"
-ATTR_OPERATING_MODE = "operating_mode"
-
-PROPERTY_TO_DPS_ID = {
-    ATTR_HVAC_MODE: "1",
-    ATTR_TEMPERATURE: "102",
-    ATTR_TEMP_UNIT: "103",
-    ATTR_POWER_LEVEL: "104",
-    ATTR_OPERATING_MODE: "105",
-    ATTR_TARGET_TEMPERATURE: "106",
-    ATTR_PRESET_MODE: "117",
-}
-
-HVAC_MODE_TO_DPS_MODE = {HVAC_MODE_OFF: False, HVAC_MODE_HEAT: True}
-PRESET_SILENT = "Silent"
-PRESET_SMART = "Smart"
-PRESET_MODE_TO_DPS_MODE = {PRESET_SILENT: False, PRESET_SMART: True}

+ 19 - 1
custom_components/tuya_local/generic/climate.py

@@ -17,7 +17,13 @@ from homeassistant.components.climate.const import (
     SUPPORT_TARGET_HUMIDITY,
     SUPPORT_TARGET_TEMPERATURE,
 )
-from homeassistant.const import ATTR_TEMPERATURE, STATE_UNAVAILABLE
+from homeassistant.const import (
+    ATTR_TEMPERATURE,
+    STATE_UNAVAILABLE,
+    TEMP_CELSIUS,
+    TEMP_FAHRENHEIT,
+    TEMP_KELVIN,
+)
 
 from ..device import TuyaLocalDevice
 from ..helpers.device_config import TuyaEntityConfig
@@ -46,6 +52,7 @@ class TuyaLocalClimate(ClimateEntity):
         self._swing_mode_dps = None
         self._fan_mode_dps = None
         self._hvac_mode_dps = None
+        self._unit_dps = None
         self._attr_dps = []
         self._temperature_step = 1
 
@@ -71,6 +78,8 @@ class TuyaLocalClimate(ClimateEntity):
             elif d.name == "fan_mode":
                 self._fan_mode_dps = d
                 self._support_flags |= SUPPORT_FAN_MODE
+            elif d.name == "temperature_unit":
+                self._unit_dps = d
             else:
                 self._attr_dps.append(d)
 
@@ -115,6 +124,15 @@ class TuyaLocalClimate(ClimateEntity):
     @property
     def temperature_unit(self):
         """Return the unit of measurement."""
+        if self._unit_dps is not None:
+            unit = self._unit_dps.get_value(self._device)
+            # Only return valid units
+            if unit == "C":
+                return TEMP_CELSIUS
+            elif unit == "F":
+                return TEMP_FAHRENHEIT
+            elif unit == "K":
+                return TEMP_KELVIN
         return self._device.temperature_unit
 
     @property

+ 10 - 2
tests/devices/test_gardenpac_heatpump.py

@@ -72,7 +72,6 @@ class TestGardenPACPoolHeatpump(IsolatedAsyncioTestCase):
         self.dps[HVACMODE_DPS] = False
         self.assertEqual(self.subject.icon, "mdi:radiator-disabled")
 
-    @skip("Temperature units not supported yet")
     def test_temperature_unit(self):
         self.dps[UNITS_DPS] = False
         self.assertEqual(self.subject.temperature_unit, TEMP_FAHRENHEIT)
@@ -92,6 +91,16 @@ class TestGardenPACPoolHeatpump(IsolatedAsyncioTestCase):
     def test_maximum_target_temperature(self):
         self.assertEqual(self.subject.max_temp, 45)
 
+    @skip("Conditional ranges not supported yet")
+    def test_minimum_fahrenheit_temperature(self):
+        self.dps[UNITS_DPS] = "F"
+        self.assertEqual(self.subject.min_temp, 60)
+
+    @skip("Conditional ranges not supported yet")
+    def test_maximum_fahrenheit_temperature(self):
+        self.dps[UNITS_DPS] = "F"
+        self.assertEqual(self.subject.max_temp, 115)
+
     async def test_legacy_set_temperature_with_temperature(self):
         async with assert_device_properties_set(
             self.subject._device, {TEMPERATURE_DPS: 25}
@@ -182,7 +191,6 @@ class TestGardenPACPoolHeatpump(IsolatedAsyncioTestCase):
         ):
             await self.subject.async_set_preset_mode("Smart")
 
-    @skip("Temperature units not supported yet")
     def test_device_state_attributes(self):
         self.dps[POWERLEVEL_DPS] = 50
         self.dps[OPMODE_DPS] = "cool"

+ 0 - 0
tests/gardenpac_heatpump/__init__.py


+ 0 - 210
tests/gardenpac_heatpump/test_climate.py

@@ -1,210 +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_TARGET_TEMPERATURE,
-)
-from homeassistant.const import (
-    ATTR_TEMPERATURE,
-    STATE_UNAVAILABLE,
-    TEMP_CELSIUS,
-    TEMP_FAHRENHEIT,
-)
-
-from custom_components.tuya_local.gardenpac_heatpump.climate import (
-    GardenPACPoolHeatpump,
-)
-from custom_components.tuya_local.gardenpac_heatpump.const import (
-    ATTR_OPERATING_MODE,
-    ATTR_POWER_LEVEL,
-    ATTR_TARGET_TEMPERATURE,
-    ATTR_TEMP_UNIT,
-    HVAC_MODE_TO_DPS_MODE,
-    PRESET_SILENT,
-    PRESET_SMART,
-    PRESET_MODE_TO_DPS_MODE,
-    PROPERTY_TO_DPS_ID,
-)
-
-from ..const import GARDENPAC_HEATPUMP_PAYLOAD
-from ..helpers import assert_device_properties_set
-
-
-class TestGardenPACPoolHeatpump(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 = GardenPACPoolHeatpump(self.mock_device())
-
-        self.dps = GARDENPAC_HEATPUMP_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,
-        )
-
-    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:hot-tub")
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_HVAC_MODE]] = False
-        self.assertEqual(self.subject.icon, "mdi:radiator-disabled")
-
-    def test_temperature_unit(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_TEMP_UNIT]] = False
-        self.assertEqual(self.subject.temperature_unit, TEMP_FAHRENHEIT)
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_TEMP_UNIT]] = True
-        self.assertEqual(self.subject.temperature_unit, TEMP_CELSIUS)
-
-    def test_target_temperature(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_TARGET_TEMPERATURE]] = 25
-        self.assertEqual(self.subject.target_temperature, 25)
-
-    def test_target_temperature_step(self):
-        self.assertEqual(self.subject.target_temperature_step, 1)
-
-    def test_minimum_target_temperature(self):
-        self.assertEqual(self.subject.min_temp, 18)
-
-    def test_maximum_target_temperature(self):
-        self.assertEqual(self.subject.max_temp, 45)
-
-    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_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_succeeds_within_valid_range(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(25)
-
-    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(self):
-        with self.assertRaisesRegex(
-            ValueError, "Target temperature \\(14\\) must be between 18 and 45"
-        ):
-            await self.subject.async_set_target_temperature(14)
-
-        with self.assertRaisesRegex(
-            ValueError, "Target temperature \\(46\\) must be between 18 and 45"
-        ):
-            await self.subject.async_set_target_temperature(46)
-
-    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[
-            PRESET_SILENT
-        ]
-        self.assertEqual(self.subject.preset_mode, PRESET_SILENT)
-
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]] = PRESET_MODE_TO_DPS_MODE[
-            PRESET_SMART
-        ]
-        self.assertEqual(self.subject.preset_mode, PRESET_SMART)
-
-        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, [PRESET_SILENT, PRESET_SMART])
-
-    async def test_set_preset_mode_to_silent(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {
-                PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]: PRESET_MODE_TO_DPS_MODE[
-                    PRESET_SILENT
-                ]
-            },
-        ):
-            await self.subject.async_set_preset_mode(PRESET_SILENT)
-
-    async def test_set_preset_mode_to_smart(self):
-        async with assert_device_properties_set(
-            self.subject._device,
-            {
-                PROPERTY_TO_DPS_ID[ATTR_PRESET_MODE]: PRESET_MODE_TO_DPS_MODE[
-                    PRESET_SMART
-                ]
-            },
-        ):
-            await self.subject.async_set_preset_mode(PRESET_SMART)
-
-    def test_device_state_attributes(self):
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_POWER_LEVEL]] = 50
-        self.dps[PROPERTY_TO_DPS_ID[ATTR_OPERATING_MODE]] = "cool"
-        self.assertEqual(
-            self.subject.device_state_attributes,
-            {ATTR_POWER_LEVEL: 50, ATTR_OPERATING_MODE: "cool"},
-        )
-
-    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()