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

Add support for Wetair WCH-750 AL heater

From Issue #44, info supplied by @vikedlol
Jason Rumney 4 лет назад
Родитель
Сommit
31b86af805

+ 80 - 0
custom_components/tuya_local/devices/wetair_wch750_heater.yaml

@@ -0,0 +1,80 @@
+name: Wetair WCH-750 AL WiFi Heater
+primary_entity:
+  entity: climate
+  dps:
+    - id: 1
+      type: boolean
+      mapping:
+        - dps_val: false
+          value: "off"
+          icon: "mdi:radiator-disabled"
+        - dps_val: true
+          value: heat
+          icon: "mdi:radiator"
+      name: hvac_mode
+    - id: 2
+      type: integer
+      range:
+        min: 5
+        max: 35
+      mapping:
+        - constraint: preset_mode
+          conditions:
+            - dps_val: mod_antiforst
+              invalid: true
+      name: temperature
+    - id: 4
+      type: string
+      name: preset_mode
+      mapping:
+        - dps_val: "mod_free"
+          value: "comfort"
+        - dps_val: "mod_max12h"
+          value: "boost"
+        - dps_val: "mod_antiforst"
+          value: "away"
+    - id: 11
+      type: string
+      name: hvac_action
+      readonly: true
+      mapping:
+        - dps_val: heating
+          constraint: hvac_mode
+          conditions:
+            - dps_val: false
+              value: "off"
+            - dps_val: true
+              value: heating
+        - dps_val: warm
+          constraint: hvac_mode
+          conditions:
+            - dps_val: false
+              value: "off"
+            - dps_val: true
+              value: idle
+    - id: 19
+      type: string
+      name: timer
+    - id: 20
+      type: integer
+      name: countdown
+    - id: 21
+      type: integer
+      name: unknown_21
+secondary_entities:
+  - entity: light
+    name: Display
+    dps:
+      - id: 101
+        type: string
+        name: brightness
+        mapping:
+          - dps_val: level0
+            value: 0
+          - dps_val: level1
+            value: 85
+          - dps_val: level2
+            value: 170
+          - dps_val: level3
+            value: 255
+          - step: 85

+ 2 - 1
custom_components/tuya_local/generic/light.py

@@ -116,9 +116,10 @@ class TuyaLocalLight(LightEntity):
 
         if self._brightness_dps:
             bright = params.get(ATTR_BRIGHTNESS, 255)
+            bright_values = self._brightness_dps.get_values_to_set(self._device, bright)
             settings = {
                 **settings,
-                **self._brightness_dps.get_values_to_set(self._device, bright),
+                **bright_values,
             }
         await self._device.async_set_properties(settings)
 

+ 11 - 0
tests/const.py

@@ -348,3 +348,14 @@ TH213_THERMOSTAT_PAYLOAD = {
     "108": False,
     "110": 0,
 }
+
+WETAIR_WCH750_HEATER_PAYLOAD = {
+    "1": False,
+    "2": 17,
+    "4": "mod_free",
+    "11": "heating",
+    "19": "0h",
+    "20": 0,
+    "21": 0,
+    "101": "level1",
+}

+ 297 - 0
tests/devices/test_wetair_wch750_heater.py

@@ -0,0 +1,297 @@
+from homeassistant.components.climate.const import (
+    HVAC_MODE_HEAT,
+    HVAC_MODE_OFF,
+    PRESET_AWAY,
+    PRESET_COMFORT,
+    PRESET_BOOST,
+    SUPPORT_PRESET_MODE,
+    SUPPORT_TARGET_TEMPERATURE,
+)
+
+from homeassistant.const import STATE_UNAVAILABLE
+
+from ..const import WETAIR_WCH750_HEATER_PAYLOAD
+from ..helpers import assert_device_properties_set
+from .base_device_tests import TuyaDeviceTestCase
+
+HVACMODE_DPS = "1"
+TEMPERATURE_DPS = "2"
+PRESET_DPS = "4"
+HVACACTION_DPS = "11"
+TIMER_DPS = "19"
+COUNTDOWN_DPS = "20"
+UNKNOWN21_DPS = "21"
+BRIGHTNESS_DPS = "101"
+
+
+class TestWetairWCH750Heater(TuyaDeviceTestCase):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig("wetair_wch750_heater.yaml", WETAIR_WCH750_HEATER_PAYLOAD)
+        self.subject = self.entities.get("climate")
+        self.light = self.entities.get("light")
+
+    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_temperatre_unit_retrns_device_temperatre_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_in_af_mode(self):
+        self.dps[TEMPERATURE_DPS] = 25
+        self.dps[PRESET_DPS] = "mod_antiforst"
+        self.assertEqual(self.subject.target_temperature, None)
+
+    def test_target_temperature_step(self):
+        self.assertEqual(self.subject.target_temperature_step, 1)
+
+    def test_minimum_temperature(self):
+        self.assertEqual(self.subject.min_temp, 5)
+
+    def test_maximum_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: 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, {PRESET_DPS: "mod_antiforst"}
+        ):
+            await self.subject.async_set_temperature(preset_mode=PRESET_AWAY)
+
+    async def test_legacy_set_temperature_with_both_properties(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {TEMPERATURE_DPS: 25, PRESET_DPS: "mod_max12h"},
+        ):
+            await self.subject.async_set_temperature(
+                preset_mode=PRESET_BOOST, 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(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: 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, "temperature \\(4\\) must be between 5 and 35"
+        ):
+            await self.subject.async_set_target_temperature(4)
+
+        with self.assertRaisesRegex(
+            ValueError, "temperature \\(36\\) must be between 5 and 35"
+        ):
+            await self.subject.async_set_target_temperature(36)
+
+    async def test_set_target_temperature_fails_in_anti_frost(self):
+        self.dps[PRESET_DPS] = "mod_antiforst"
+
+        with self.assertRaisesRegex(
+            AttributeError, "temperature cannot be set at this time"
+        ):
+            await self.subject.async_set_target_temperature(25)
+
+    def test_current_temperature_not_supported(self):
+        self.assertIsNone(self.subject.current_temperature)
+
+    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_trn_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_mode(self):
+        self.dps[PRESET_DPS] = "mod_free"
+        self.assertEqual(self.subject.preset_mode, PRESET_COMFORT)
+
+        self.dps[PRESET_DPS] = "mod_max12h"
+        self.assertEqual(self.subject.preset_mode, PRESET_BOOST)
+
+        self.dps[PRESET_DPS] = "mod_antiforst"
+        self.assertEqual(self.subject.preset_mode, PRESET_AWAY)
+
+    def test_preset_modes(self):
+        self.assertCountEqual(self.subject.preset_modes, ["comfort", "boost", "away"])
+
+    async def test_set_preset_mode_to_comfort(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: "mod_free"},
+        ):
+            await self.subject.async_set_preset_mode(PRESET_COMFORT)
+
+    async def test_set_preset_mode_to_boost(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: "mod_max12h"},
+        ):
+            await self.subject.async_set_preset_mode(PRESET_BOOST)
+
+    async def test_set_preset_mode_to_away(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: "mod_antiforst"},
+        ):
+            await self.subject.async_set_preset_mode(PRESET_AWAY)
+
+    def test_device_state_attributes(self):
+        self.dps[TIMER_DPS] = "1h"
+        self.dps[COUNTDOWN_DPS] = 20
+        self.dps[UNKNOWN21_DPS] = 21
+
+        self.assertCountEqual(
+            self.subject.device_state_attributes,
+            {
+                "timer": "1h",
+                "countdown": 20,
+                "unknown_21": 21,
+            },
+        )
+
+    def test_light_is_on(self):
+        self.dps[BRIGHTNESS_DPS] = "level0"
+        self.assertEqual(self.light.is_on, False)
+
+        self.dps[BRIGHTNESS_DPS] = "level1"
+        self.assertEqual(self.light.is_on, True)
+        self.dps[BRIGHTNESS_DPS] = "level2"
+        self.assertEqual(self.light.is_on, True)
+        self.dps[BRIGHTNESS_DPS] = "level3"
+        self.assertEqual(self.light.is_on, True)
+
+    def test_light_brightness(self):
+        self.dps[BRIGHTNESS_DPS] = "level0"
+        self.assertEqual(self.light.brightness, 0)
+
+        self.dps[BRIGHTNESS_DPS] = "level1"
+        self.assertEqual(self.light.brightness, 85)
+
+        self.dps[BRIGHTNESS_DPS] = "level2"
+        self.assertEqual(self.light.brightness, 170)
+
+        self.dps[BRIGHTNESS_DPS] = "level3"
+        self.assertEqual(self.light.brightness, 255)
+
+    def test_light_state_attributes(self):
+        self.assertEqual(self.light.device_state_attributes, {})
+
+    async def test_light_turn_on(self):
+        async with assert_device_properties_set(
+            self.light._device, {BRIGHTNESS_DPS: "level3"}
+        ):
+            await self.light.async_turn_on()
+
+    async def test_light_turn_off(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level0"},
+        ):
+            await self.light.async_turn_off()
+
+    async def test_light_brightness_to_low(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level1"},
+        ):
+            await self.light.async_turn_on(brightness=85)
+
+    async def test_light_brightness_to_mid(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level2"},
+        ):
+            await self.light.async_turn_on(brightness=170)
+
+    async def test_light_brightness_to_high(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level3"},
+        ):
+            await self.light.async_turn_on(brightness=255)
+
+    async def test_light_brightness_to_off(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level0"},
+        ):
+            await self.light.async_turn_on(brightness=0)
+
+    async def test_toggle_turns_the_light_on_when_it_was_off(self):
+        self.dps[BRIGHTNESS_DPS] = "level0"
+
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level3"},
+        ):
+            await self.light.async_toggle()
+
+    async def test_toggle_turns_the_light_off_when_it_was_on(self):
+        self.dps[BRIGHTNESS_DPS] = "level2"
+
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level0"},
+        ):
+            await self.light.async_toggle()
+
+    async def test_light_brightness_snaps(self):
+        async with assert_device_properties_set(
+            self.light._device,
+            {BRIGHTNESS_DPS: "level1"},
+        ):
+            await self.light.async_turn_on(brightness=100)