Parcourir la source

Remove legacy_type from files where the filename matches the legacy_name.

- Use filename if legacy_name is not explicitly defined in the file.

Later, migration to use filenames will be done.
Jason Rumney il y a 4 ans
Parent
commit
403af21091

+ 4 - 5
custom_components/tuya_local/devices/eanons_humidifier.yaml

@@ -1,5 +1,4 @@
 name: Eanons/pureenjoy Humidifier
-legacy_type: eanons_humidifier
 primary_entity:
   entity: humidifier
   name: Humidifier
@@ -47,14 +46,14 @@ secondary_entities:
     dps:
       - id: 2
         type: string
-        name: preset_mode
+        name: speed
         mapping:
           - dps_val: small
-            value: low
+            value: 33
           - dps_val: middle
-            value: medium
+            value: 67
           - dps_val: large
-            value: high
+            value: 100
       - id: 10
         type: boolean
         name: switch

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

@@ -1,5 +1,4 @@
 name: GardenPAC Pool Heatpump
-legacy_type: gardenpac_heatpump
 primary_entity:
   entity: climate
   dps:

+ 6 - 6
custom_components/tuya_local/devices/goldair_dehumidifier.yaml

@@ -86,22 +86,22 @@ secondary_entities:
         hidden: true
       - id: 6
         type: string
-        name: preset_mode
+        name: speed
         mapping:
           - dps_val: "1"
-            value: "low"
+            value: 50
           - dps_val: "3"
-            value: "high"
+            value: 100
         constraint: dehumidifier_mode
         conditions:
           - dps_val: "1"
-            value: "low"
+            value: 50
             invalid: true
           - dps_val: "2"
-            value: "high"
+            value: 100
             invalid: true
           - dps_val: "3"
-            value: "high"
+            value: 100
             invalid: true
   - entity: climate
     legacy_class: ".dehumidifier.climate.GoldairDehumidifier"

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

@@ -1,5 +1,4 @@
 name: Kogan Panel Heater
-legacy_type: kogan_heater
 primary_entity:
   entity: climate
   dps:

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

@@ -1,5 +1,4 @@
 name: Kogan Smart Switch
-legacy_type: kogan_switch
 primary_entity:
   entity: switch
   class: outlet

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

@@ -1,5 +1,4 @@
 name: Purline Hoti M100 Heater
-legacy_type: purline_m100_heater
 primary_entity:
   entity: climate
   dps:

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

@@ -1,5 +1,4 @@
 name: Remora Pool Heatpump
-legacy_type: remora_heatpump
 primary_entity:
   entity: climate
   dps:

+ 17 - 1
custom_components/tuya_local/generic/fan.py

@@ -133,12 +133,28 @@ class TuyaLocalFan(FanEntity):
         """Return the step for percentage."""
         if self._speed_dps is None:
             return None
-        return self._speed_dps.step(self._device)
+        if self._speed_dps.values is None:
+            return self._speed_dps.step(self._device)
+        else:
+            return 100 / len(self._speed_dps.values)
+
+    @property
+    def speed_count(self):
+        """Return the number of speeds supported by the fan."""
+        if self._speed_dps is None:
+            return 0
+        if self._speed_dps.values is not None:
+            return len(self._speed_dps.values)
+        return int(round(100 / self.percentage_step))
 
     async def async_set_percentage(self, percentage):
         """Set the fan speed as a percentage."""
         if self._speed_dps is None:
             return None
+        # If there is a fixed list of values, snap to the closest one
+        if self._speed_dps.values is not None:
+            percentage = min(self._speed_dps.values, key=lambda x: abs(x - percentage))
+
         await self._speed_dps.async_set_value(self._device, percentage)
 
     @property

+ 2 - 2
custom_components/tuya_local/helpers/device_config.py

@@ -4,7 +4,7 @@ Config parser for Tuya Local devices.
 from fnmatch import fnmatch
 import logging
 from os import walk
-from os.path import join, dirname
+from os.path import join, dirname, splitext
 from pydoc import locate
 
 from homeassistant.util.yaml import load_yaml
@@ -58,7 +58,7 @@ class TuyaDeviceConfig:
     @property
     def legacy_type(self):
         """Return the legacy conf_type associated with this device."""
-        return self._config.get("legacy_type")
+        return self._config.get("legacy_type", splitext(self.config)[0])
 
     @property
     def primary_entity(self):

+ 59 - 4
tests/devices/test_eanons_humidifier.py

@@ -11,6 +11,9 @@ from homeassistant.components.climate.const import (
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_HUMIDITY,
 )
+from homeassistant.components.fan import (
+    SUPPORT_SET_SPEED,
+)
 from homeassistant.components.humidifier.const import (
     MODE_NORMAL,
     MODE_AUTO,
@@ -21,6 +24,7 @@ from homeassistant.components.switch import DEVICE_CLASS_SWITCH
 from homeassistant.const import STATE_UNAVAILABLE
 
 from custom_components.tuya_local.generic.climate import TuyaLocalClimate
+from custom_components.tuya_local.generic.fan import TuyaLocalFan
 from custom_components.tuya_local.generic.humidifier import TuyaLocalHumidifier
 from custom_components.tuya_local.generic.switch import TuyaLocalSwitch
 from custom_components.tuya_local.helpers.device_config import TuyaDeviceConfig
@@ -59,12 +63,13 @@ class TestEanonsHumidifier(IsolatedAsyncioTestCase):
         self.humidifier_name = (
             "missing" if "humidifier" not in entities else entities["humidifier"].name
         )
-
+        self.fan_name = "missing" if "fan" not in entities else entities["fan"].name
         self.subject = TuyaLocalHumidifier(
             self.mock_device(), entities.get("humidifier")
         )
         self.climate = TuyaLocalClimate(self.mock_device(), entities.get("climate"))
         self.switch = TuyaLocalSwitch(self.mock_device(), entities.get("switch"))
+        self.fan = TuyaLocalFan(self.mock_device(), entities.get("fan"))
 
         self.dps = EANONS_HUMIDIFIER_PAYLOAD.copy()
         self.subject._device.get_property.side_effect = lambda id: self.dps[id]
@@ -75,30 +80,36 @@ class TestEanonsHumidifier(IsolatedAsyncioTestCase):
             SUPPORT_TARGET_HUMIDITY | SUPPORT_PRESET_MODE | SUPPORT_FAN_MODE,
         )
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)
+        self.assertEqual(self.fan.supported_features, SUPPORT_SET_SPEED)
 
     def test_shouldPoll(self):
         self.assertTrue(self.subject.should_poll)
         self.assertTrue(self.climate.should_poll)
+        self.assertTrue(self.fan.should_poll)
         self.assertTrue(self.switch.should_poll)
 
     def test_name_returns_device_name(self):
         self.assertEqual(self.subject.name, self.subject._device.name)
         self.assertEqual(self.climate.name, self.subject._device.name)
+        self.assertEqual(self.fan.name, self.subject._device.name)
         self.assertEqual(self.switch.name, self.subject._device.name)
 
     def test_friendly_name_returns_config_name(self):
         self.assertEqual(self.subject.friendly_name, self.humidifier_name)
         self.assertEqual(self.climate.friendly_name, self.climate_name)
+        self.assertEqual(self.fan.friendly_name, self.fan_name)
         self.assertEqual(self.switch.friendly_name, self.switch_name)
 
     def test_unique_id_returns_device_unique_id(self):
         self.assertEqual(self.subject.unique_id, self.subject._device.unique_id)
         self.assertEqual(self.climate.unique_id, self.subject._device.unique_id)
+        self.assertEqual(self.fan.unique_id, self.subject._device.unique_id)
         self.assertEqual(self.switch.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)
         self.assertEqual(self.climate.device_info, self.subject._device.device_info)
+        self.assertEqual(self.fan.device_info, self.subject._device.device_info)
         self.assertEqual(self.switch.device_info, self.subject._device.device_info)
 
     @skip("Icon customisation not supported yet")
@@ -151,13 +162,15 @@ class TestEanonsHumidifier(IsolatedAsyncioTestCase):
 
     def test_is_on(self):
         self.dps[HVACMODE_DPS] = True
-        self.assertEqual(self.subject.is_on, True)
-
+        self.assertTrue(self.subject.is_on)
+        self.assertTrue(self.fan.is_on)
         self.dps[HVACMODE_DPS] = False
-        self.assertEqual(self.subject.is_on, False)
+        self.assertFalse(self.subject.is_on)
+        self.assertFalse(self.fan.is_on)
 
         self.dps[HVACMODE_DPS] = None
         self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+        self.assertEqual(self.fan.is_on, STATE_UNAVAILABLE)
 
     async def test_climate_turn_on(self):
         async with assert_device_properties_set(
@@ -183,6 +196,18 @@ class TestEanonsHumidifier(IsolatedAsyncioTestCase):
         ):
             await self.subject.async_turn_off()
 
+    async def test_fan_turn_on(self):
+        async with assert_device_properties_set(
+            self.subject._device, {HVACMODE_DPS: True}
+        ):
+            await self.fan.async_turn_on()
+
+    async def test_fan_turn_off(self):
+        async with assert_device_properties_set(
+            self.subject._device, {HVACMODE_DPS: False}
+        ):
+            await self.fan.async_turn_off()
+
     def test_preset_mode(self):
         self.dps[PRESET_DPS] = "sleep"
         self.assertEqual(self.climate.preset_mode, MODE_SLEEP)
@@ -312,6 +337,16 @@ class TestEanonsHumidifier(IsolatedAsyncioTestCase):
             },
         )
 
+    def test_fan_speed(self):
+        self.dps[FANMODE_DPS] = "small"
+        self.assertEqual(self.fan.percentage, 33)
+
+        self.dps[FANMODE_DPS] = "middle"
+        self.assertEqual(self.fan.percentage, 67)
+
+        self.dps[FANMODE_DPS] = "large"
+        self.assertEqual(self.fan.percentage, 100)
+
     def test_climate_fan_mode(self):
         self.dps[FANMODE_DPS] = "small"
         self.assertEqual(self.climate.fan_mode, FAN_LOW)
@@ -325,12 +360,32 @@ class TestEanonsHumidifier(IsolatedAsyncioTestCase):
         self.dps[FANMODE_DPS] = None
         self.assertEqual(self.climate.fan_mode, None)
 
+    def test_fan_speed_count(self):
+        self.assertEqual(self.fan.speed_count, 3)
+
+    def test_fan_percentage_step(self):
+        self.assertAlmostEqual(self.fan.percentage_step, 33, 0)
+
     def test_climate_fan_modes(self):
         self.assertCountEqual(
             self.climate.fan_modes,
             {FAN_LOW, FAN_MEDIUM, FAN_HIGH},
         )
 
+    async def test_fan_set_speed(self):
+        async with assert_device_properties_set(
+            self.fan._device,
+            {FANMODE_DPS: "small"},
+        ):
+            await self.fan.async_set_percentage(33)
+
+    async def test_fan_set_speed_snaps(self):
+        async with assert_device_properties_set(
+            self.fan._device,
+            {FANMODE_DPS: "middle"},
+        ):
+            await self.fan.async_set_percentage(60)
+
     async def test_climate_set_fan_mode(self):
         async with assert_device_properties_set(
             self.climate._device,

+ 24 - 16
tests/devices/test_goldair_dehumidifier.py

@@ -458,16 +458,16 @@ class TestGoldairDehumidifier(IsolatedAsyncioTestCase):
         self.dps[FANMODE_DPS] = "1"
         self.dps[PRESET_DPS] = PRESET_HIGH
         self.assertEqual(self.subject.fan_mode, FAN_HIGH)
-        self.assertEqual(self.fan.preset_mode, FAN_HIGH)
+        self.assertEqual(self.fan.percentage, 100)
 
         self.dps[PRESET_DPS] = PRESET_DRY_CLOTHES
         self.assertEqual(self.subject.fan_mode, FAN_HIGH)
-        self.assertEqual(self.fan.preset_mode, FAN_HIGH)
+        self.assertEqual(self.fan.percentage, 100)
 
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[AIRCLEAN_DPS] = True
         self.assertEqual(self.subject.fan_mode, FAN_HIGH)
-        self.assertEqual(self.subject.preset_mode, FAN_HIGH)
+        self.assertEqual(self.subject.percentage, 100)
 
     @skip("Conditions not supported yet")
     def test_fan_mode_is_forced_to_low_in_low_preset(self):
@@ -475,44 +475,44 @@ class TestGoldairDehumidifier(IsolatedAsyncioTestCase):
         self.dps[PRESET_DPS] = PRESET_LOW
 
         self.assertEqual(self.subject.fan_mode, FAN_LOW)
-        self.assertEqual(self.fan.preset_mode, FAN_LOW)
+        self.assertEqual(self.fan.percentage, 50)
 
     def test_fan_mode_reflects_dps_mode_in_normal_preset(self):
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[FANMODE_DPS] = "1"
         self.assertEqual(self.subject.fan_mode, FAN_LOW)
-        self.assertEqual(self.fan.preset_mode, FAN_LOW)
+        self.assertEqual(self.fan.percentage, 50)
 
         self.dps[FANMODE_DPS] = "3"
         self.assertEqual(self.subject.fan_mode, FAN_HIGH)
-        self.assertEqual(self.fan.preset_mode, FAN_HIGH)
+        self.assertEqual(self.fan.percentage, 100)
 
         self.dps[FANMODE_DPS] = None
         self.assertEqual(self.subject.fan_mode, None)
-        self.assertEqual(self.fan.preset_mode, None)
+        self.assertEqual(self.fan.percentage, None)
 
     @skip("Conditions not supported yet")
     def test_fan_modes_reflect_preset_mode(self):
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.assertCountEqual(self.subject.fan_modes, [FAN_LOW, FAN_HIGH])
-        self.assertCountEqual(self.fan.preset_modes, [FAN_LOW, FAN_HIGH])
+        self.assertCountEqual(self.fan.speed_count, 2)
 
         self.dps[PRESET_DPS] = PRESET_LOW
         self.assertEqual(self.subject.fan_modes, [FAN_LOW])
-        self.assertEqual(self.fan.preset_modes, [FAN_LOW])
+        self.assertCountEqual(self.fan.speed_count, 0)
 
         self.dps[PRESET_DPS] = PRESET_HIGH
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
-        self.assertEqual(self.fan.preset_modes, [FAN_HIGH])
+        self.assertCountEqual(self.fan.speed_count, 0)
 
         self.dps[PRESET_DPS] = PRESET_DRY_CLOTHES
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
-        self.assertEqual(self.fan.preset_modes, [FAN_HIGH])
+        self.assertCountEqual(self.fan.speed_count, 0)
 
         self.dps[PRESET_DPS] = PRESET_NORMAL
         self.dps[AIRCLEAN_DPS] = True
         self.assertEqual(self.subject.fan_modes, [FAN_HIGH])
-        self.assertEqual(self.fan.preset_modes, [FAN_HIGH])
+        self.assertCountEqual(self.fan.speed_count, 0)
 
     async def test_set_fan_mode_to_low_succeeds_in_normal_preset(self):
         self.dps[PRESET_DPS] = PRESET_NORMAL
@@ -530,21 +530,29 @@ class TestGoldairDehumidifier(IsolatedAsyncioTestCase):
         ):
             await self.subject.async_set_fan_mode(FAN_HIGH)
 
-    async def test_set_fan_preset_to_low_succeeds_in_normal_preset(self):
+    async def test_set_fan_50_succeeds_in_normal_preset(self):
         self.dps[PRESET_DPS] = PRESET_NORMAL
         async with assert_device_properties_set(
             self.fan._device,
             {FANMODE_DPS: "1"},
         ):
-            await self.fan.async_set_preset_mode(FAN_LOW)
+            await self.fan.async_set_percentage(50)
 
-    async def test_set_fan_preset_to_high_succeeds_in_normal_preset(self):
+    async def test_set_fan_100_succeeds_in_normal_preset(self):
         self.dps[PRESET_DPS] = PRESET_NORMAL
         async with assert_device_properties_set(
             self.fan._device,
             {FANMODE_DPS: "3"},
         ):
-            await self.fan.async_set_preset_mode(FAN_HIGH)
+            await self.fan.async_set_percentage(100)
+
+    async def test_set_fan_30_snaps_to_50_in_normal_preset(self):
+        self.dps[PRESET_DPS] = PRESET_NORMAL
+        async with assert_device_properties_set(
+            self.fan._device,
+            {FANMODE_DPS: "1"},
+        ):
+            await self.fan.async_set_percentage(30)
 
     @skip("Restriction to listed options not supported yet")
     async def test_set_fan_mode_fails_with_invalid_mode(self):