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

Add support for Nedis HTPL20F heaters.

Issue #74
Jason Rumney 4 лет назад
Родитель
Сommit
7e6e7f1397

+ 1 - 0
ACKNOWLEDGEMENTS.md

@@ -55,3 +55,4 @@ Further device support has been made with the assistance of users.  Please consi
  - [voed](https://github.com/voed) for assistance in supporting Advanced Energy monitoring smart switch, based on CBE smart switch but seeming to follow a Tuya Standard Template, so probably applicable to others.
  - [voed](https://github.com/voed) for assistance in supporting Advanced Energy monitoring smart switch, based on CBE smart switch but seeming to follow a Tuya Standard Template, so probably applicable to others.
  - [myevit](https://github.com/myevit) for assistance in supporting simple garage doors.
  - [myevit](https://github.com/myevit) for assistance in supporting simple garage doors.
  - [maartendamen](https://github.com/maartendamen) for assistance in supporting Eurom Mon Soleil 601 heaters.
  - [maartendamen](https://github.com/maartendamen) for assistance in supporting Eurom Mon Soleil 601 heaters.
+ - [TeddyLafrite](https://github.com/TeddyLafrite) for assistance in supporting Nedis HTPL20F heaters.

+ 1 - 0
README.md

@@ -40,6 +40,7 @@ the device will not work despite being listed below.
 - Purline Hoti M100 heater
 - Purline Hoti M100 heater
 - Wetair WCH-750 heater
 - Wetair WCH-750 heater
 - Kogan Flame effect heater - KAWHMFP20BA model
 - Kogan Flame effect heater - KAWHMFP20BA model
+- Nedis convection heater - WIFIHTPL20F models
 
 
 ### Air Conditioners / Heatpumps
 ### Air Conditioners / Heatpumps
 
 

+ 62 - 0
custom_components/tuya_local/devices/nedis_htpl20f_heater.yaml

@@ -0,0 +1,62 @@
+name: Nedis Smart Convection Heater
+primary_entity:
+  entity: climate
+  dps:
+    - id: 1
+      type: boolean
+      name: hvac_mode
+      mapping:
+        - dps_val: true
+          value: heat
+          icon: "mdi:radiator"
+        - dps_val: false
+          value: "off"
+          icon: "mdi:radiator-disabled"
+    - id: 2
+      type: integer
+      name: temperature
+      range:
+        min: 15
+        max: 35
+    - id: 3
+      type: integer
+      name: current_temperature
+    - id: 4
+      type: string
+      name: preset_mode
+      mapping:
+        - dps_val: "1"
+          value: eco
+        - dps_val: "2"
+          value: comfort
+        - dps_val: "3"
+          value: away
+    - id: 11
+      type: string
+      name: unknown_11
+    - id: 101
+      type: boolean
+      name: unknown_101
+secondary_entities:
+  - entity: lock
+    name: Child Lock
+    category: config
+    dps:
+      - id: 7
+        type: boolean
+        name: lock
+        mapping:
+          - dps_val: true
+            icon: "mdi:hand-back-right-off"
+          - dps_val: false
+            icon: "mdi:hand-back-right"
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 13
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 1440

+ 11 - 0
tests/const.py

@@ -622,3 +622,14 @@ SIMPLE_GARAGE_DOOR_PAYLOAD = {
     "1": True,
     "1": True,
     "101": False,
     "101": False,
 }
 }
+
+NEDIS_HTPL20F_PAYLOAD = {
+    "1": True,
+    "2": 25,
+    "3": 25,
+    "4": "1",
+    "7": False,
+    "11": "0",
+    "13": 0,
+    "101": False,
+}

+ 8 - 1
tests/devices/test_eurom_601_heater.py

@@ -147,8 +147,15 @@ class TestEurom601Heater(BasicBinarySensorTests, TuyaDeviceTestCase):
         ):
         ):
             await self.subject.async_set_preset_mode(PRESET_ECO)
             await self.subject.async_set_preset_mode(PRESET_ECO)
 
 
-    async def test_set_preset_more_to_eco(self):
+    async def test_set_preset_more_to_comfort(self):
         async with assert_device_properties_set(
         async with assert_device_properties_set(
             self.subject._device, {PRESET_DPS: False}
             self.subject._device, {PRESET_DPS: False}
         ):
         ):
             await self.subject.async_set_preset_mode(PRESET_COMFORT)
             await self.subject.async_set_preset_mode(PRESET_COMFORT)
+
+    def test_device_state_attributes(self):
+        self.dps[ERROR_DPS] = 13
+        self.assertCountEqual(
+            self.subject.device_state_attributes,
+            {"error": 13},
+        )

+ 175 - 0
tests/devices/test_nedis_htpl20f_heater.py

@@ -0,0 +1,175 @@
+from homeassistant.components.climate.const import (
+    HVAC_MODE_HEAT,
+    HVAC_MODE_OFF,
+    PRESET_AWAY,
+    PRESET_COMFORT,
+    PRESET_ECO,
+    SUPPORT_TARGET_TEMPERATURE,
+    SUPPORT_PRESET_MODE,
+)
+from homeassistant.const import STATE_UNAVAILABLE
+
+from ..const import NEDIS_HTPL20F_PAYLOAD
+from ..helpers import assert_device_properties_set
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from .base_device_tests import TuyaDeviceTestCase
+
+HVACMODE_DPS = "1"
+TEMPERATURE_DPS = "2"
+CURRENTTEMP_DPS = "3"
+PRESET_DPS = "4"
+LOCK_DPS = "7"
+UNKNOWN11_DPS = "11"
+TIMER_DPS = "13"
+UNKNOWN101_DPS = "101"
+
+
+class TestNedisHtpl20fHeater(BasicLockTests, BasicNumberTests, TuyaDeviceTestCase):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig("nedis_htpl20f_heater.yaml", NEDIS_HTPL20F_PAYLOAD)
+        self.subject = self.entities.get("climate")
+        self.setUpBasicLock(
+            LOCK_DPS,
+            self.entities.get("lock_child_lock"),
+        )
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=1440)
+
+    def test_supported_features(self):
+        self.assertEqual(
+            self.subject.supported_features,
+            SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE,
+        )
+
+    def test_icon(self):
+        self.dps[HVACMODE_DPS] = True
+        self.assertEqual(self.subject.icon, "mdi:radiator")
+
+        self.dps[HVACMODE_DPS] = False
+        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[TEMPERATURE_DPS] = 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, 15)
+
+    def test_maximum_target_temperature(self):
+        self.assertEqual(self.subject.max_temp, 35)
+
+    async def test_legacy_set_temperature_with_temperature(self):
+        async with assert_device_properties_set(
+            self.subject._device, {TEMPERATURE_DPS: 24}
+        ):
+            await self.subject.async_set_temperature(temperature=24)
+
+    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,
+            {TEMPERATURE_DPS: 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, {TEMPERATURE_DPS: 23}
+        ):
+            await self.subject.async_set_target_temperature(22.6)
+
+    async def test_set_target_temperature_fails_outside_valid_range(self):
+        with self.assertRaisesRegex(
+            ValueError, "temperature \\(14\\) must be between 15 and 35"
+        ):
+            await self.subject.async_set_target_temperature(14)
+
+        with self.assertRaisesRegex(
+            ValueError, "temperature \\(36\\) must be between 15 and 35"
+        ):
+            await self.subject.async_set_target_temperature(36)
+
+    def test_current_temperature(self):
+        self.dps[CURRENTTEMP_DPS] = 25
+        self.assertEqual(self.subject.current_temperature, 25)
+
+    def test_hvac_mode(self):
+        self.dps[HVACMODE_DPS] = True
+        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_HEAT)
+
+        self.dps[HVACMODE_DPS] = False
+        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_OFF)
+
+        self.dps[HVACMODE_DPS] = None
+        self.assertEqual(self.subject.hvac_mode, STATE_UNAVAILABLE)
+
+    def test_hvac_modes(self):
+        self.assertCountEqual(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, {HVACMODE_DPS: 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, {HVACMODE_DPS: False}
+        ):
+            await self.subject.async_set_hvac_mode(HVAC_MODE_OFF)
+
+    def test_preset_modes(self):
+        self.assertCountEqual(
+            self.subject.preset_modes,
+            [PRESET_COMFORT, PRESET_ECO, PRESET_AWAY],
+        )
+
+    def test_preset_mode(self):
+        self.dps[PRESET_DPS] = "1"
+        self.assertEqual(self.subject.preset_mode, PRESET_ECO)
+
+        self.dps[PRESET_DPS] = "2"
+        self.assertEqual(self.subject.preset_mode, PRESET_COMFORT)
+
+        self.dps[PRESET_DPS] = "3"
+        self.assertEqual(self.subject.preset_mode, PRESET_AWAY)
+
+    async def test_set_preset_more_to_eco(self):
+        async with assert_device_properties_set(
+            self.subject._device, {PRESET_DPS: "1"}
+        ):
+            await self.subject.async_set_preset_mode(PRESET_ECO)
+
+    async def test_set_preset_more_to_comfort(self):
+        async with assert_device_properties_set(
+            self.subject._device, {PRESET_DPS: "2"}
+        ):
+            await self.subject.async_set_preset_mode(PRESET_COMFORT)
+
+    async def test_set_preset_more_to_away(self):
+        async with assert_device_properties_set(
+            self.subject._device, {PRESET_DPS: "3"}
+        ):
+            await self.subject.async_set_preset_mode(PRESET_AWAY)
+
+    def test_device_state_attributes(self):
+        self.dps[UNKNOWN11_DPS] = "11"
+        self.dps[UNKNOWN101_DPS] = True
+
+        self.assertCountEqual(
+            self.subject.device_state_attributes,
+            {"unknown_11": "11", "unknown_101": True},
+        )