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

Add support for Beca/Moes BHT-002 thermometer

Issue #59
Based on info from https://community.home-assistant.io/t/moes-bht-002-thermostat-local-control-tuya-based/151953/47

Almost identical to BHT-6000, but missing DPS 103.
Jason Rumney 4 лет назад
Родитель
Сommit
a149e5ce69

+ 1 - 0
ACKNOWLEDGEMENTS.md

@@ -47,3 +47,4 @@ Further device support has been made with the assistance of users.  Please consi
  - [MrDeon](https://github.com/MrDeon) for assistance in supporting Kogan KAWFPAC09YA air conditioners.
  - [MrDeon](https://github.com/MrDeon) for assistance in supporting Kogan KAWFPAC09YA air conditioners.
  - [darek-margas](https://github.com/darek-margas) for contributing support for Grid Connect double outlet smart switches.
  - [darek-margas](https://github.com/darek-margas) for contributing support for Grid Connect double outlet smart switches.
  - [SatarisGIT](https://github.com/SatarisGIT) for assistance in supporting Eberg Qubo Q40HD portable heatpump.
  - [SatarisGIT](https://github.com/SatarisGIT) for assistance in supporting Eberg Qubo Q40HD portable heatpump.
+ - [lucaxxaa](https://github.com/lucaxxaa) for assistance in supporting Beca BHT-002 thermostat.

+ 1 - 0
README.md

@@ -66,6 +66,7 @@ the device will not work despite being listed below.
 - Inkbird ITC306A thermostat smartplug (not fully functional)
 - Inkbird ITC306A thermostat smartplug (not fully functional)
 - Beca BHP-6000 Room Heat Pump control thermostat
 - Beca BHP-6000 Room Heat Pump control thermostat
 - Beca BHT-6000 Floor Heating thermostat
 - Beca BHT-6000 Floor Heating thermostat
+- Beca BHT-002 Floor Heating thermostat
 - Awow/Mi-heat TH213 thermostat
 - Awow/Mi-heat TH213 thermostat
 - Siswell T29UTW thermostat
 - Siswell T29UTW thermostat
 - Siswell C16 thermostat (rebadged as Warmme, Klima and others)
 - Siswell C16 thermostat (rebadged as Warmme, Klima and others)

+ 64 - 0
custom_components/tuya_local/devices/beca_bht002_thermostat_c.yaml

@@ -0,0 +1,64 @@
+name: Beca BHT-6000 Thermostat (C)
+primary_entity:
+  entity: climate
+  dps:
+    - id: 2
+      type: integer
+      name: temperature
+      range:
+        min: 10
+        max: 70
+      mapping:
+        - scale: 2
+    - id: 3
+      type: integer
+      name: current_temperature
+      mapping:
+        - scale: 2
+    - id: 4
+      type: string
+      name: hvac_mode
+      mapping:
+        - dps_val: "0"
+          value: auto
+        - dps_val: "1"
+          value: heat
+    - id: 5
+      type: boolean
+      name: preset_mode
+      mapping:
+        - dps_val: true
+          value: eco 
+        - dps_val: false
+          value: comfort
+    - id: 102
+      type: integer
+      name: floor_temperature
+      mapping:
+        - scale: 2
+    - id: 104
+      type: boolean
+      name: unknown_104
+secondary_entities:
+  - entity: light
+    name: Display
+    dps:
+      - id: 1
+        type: boolean
+        name: switch
+        mapping:
+          - dps_val: true
+            icon: "mdi:led-on"
+          - dps_val: false
+            icon: "mdi:led-off"
+  - entity: lock
+    name: Child Lock
+    dps:
+      - id: 6
+        type: boolean
+        name: lock
+        mapping:
+          - dps_val: true
+            icon: "mdi:hand-back-right-off"
+          - dps_val: false
+            icon: "mdi:hand-back-right"

+ 11 - 0
tests/const.py

@@ -342,6 +342,17 @@ BECA_BHT6000_PAYLOAD = {
     "104": True,
     "104": True,
 }
 }
 
 
+BECA_BHT002_PAYLOAD = {
+    "1": False,
+    "2": 40,
+    "3": 42,
+    "4": "0",
+    "5": False,
+    "6": False,
+    "102": 0,
+    "104": True,
+}
+
 LEXY_F501_PAYLOAD = {
 LEXY_F501_PAYLOAD = {
     "1": True,
     "1": True,
     "2": "forestwindhigh",
     "2": "forestwindhigh",

+ 153 - 0
tests/devices/test_beca_bht002_thermostat.py

@@ -0,0 +1,153 @@
+from homeassistant.components.climate.const import (
+    HVAC_MODE_AUTO,
+    HVAC_MODE_HEAT,
+    PRESET_ECO,
+    PRESET_COMFORT,
+    SUPPORT_PRESET_MODE,
+    SUPPORT_TARGET_TEMPERATURE,
+)
+from homeassistant.const import STATE_UNAVAILABLE
+
+from ..const import BECA_BHT002_PAYLOAD
+from ..helpers import assert_device_properties_set
+from .base_device_tests import TuyaDeviceTestCase
+
+LIGHT_DPS = "1"
+TEMPERATURE_DPS = "2"
+CURRENTTEMP_DPS = "3"
+HVACMODE_DPS = "4"
+PRESET_DPS = "5"
+LOCK_DPS = "6"
+FLOOR_DPS = "102"
+UNKNOWN104_DPS = "104"
+
+
+class TestBecaBHT002Thermostat(TuyaDeviceTestCase):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig(
+            "beca_bht002_thermostat_c.yaml",
+            BECA_BHT002_PAYLOAD,
+        )
+        self.subject = self.entities.get("climate")
+        self.light = self.entities.get("light_display")
+        self.lock = self.entities.get("lock_child_lock")
+
+    def test_supported_features(self):
+        self.assertEqual(
+            self.subject.supported_features,
+            SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE,
+        )
+
+    def test_temperature_unit(self):
+        self.assertEqual(
+            self.subject.temperature_unit,
+            self.subject._device.temperature_unit,
+        )
+
+    def test_target_temperature(self):
+        self.dps[TEMPERATURE_DPS] = 50
+        self.assertEqual(self.subject.target_temperature, 25)
+
+    def test_target_temperature_step(self):
+        self.assertEqual(self.subject.target_temperature_step, 0.5)
+
+    def test_minimum_target_temperature(self):
+        self.assertEqual(self.subject.min_temp, 5)
+
+    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: 41}
+        ):
+            await self.subject.async_set_temperature(temperature=20.5)
+
+    async def test_legacy_set_temperature_with_preset_mode(self):
+        async with assert_device_properties_set(
+            self.subject._device, {PRESET_DPS: True}
+        ):
+            await self.subject.async_set_temperature(preset_mode=PRESET_ECO)
+
+    async def test_legacy_set_temperature_with_both_properties(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {
+                TEMPERATURE_DPS: 44,
+                PRESET_DPS: False,
+            },
+        ):
+            await self.subject.async_set_temperature(
+                temperature=22, preset_mode=PRESET_COMFORT
+            )
+
+    async def test_set_target_temperature_succeeds_within_valid_range(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {TEMPERATURE_DPS: 45},
+        ):
+            await self.subject.async_set_target_temperature(22.5)
+
+    async def test_set_target_temperature_rounds_value_to_closest_half(self):
+        async with assert_device_properties_set(
+            self.subject._device, {TEMPERATURE_DPS: 35}
+        ):
+            await self.subject.async_set_target_temperature(17.6)
+
+    async def test_set_target_temperature_fails_outside_valid_range(self):
+        with self.assertRaisesRegex(
+            ValueError, "temperature \\(4.5\\) must be between 5.0 and 35.0"
+        ):
+            await self.subject.async_set_target_temperature(4.5)
+
+        with self.assertRaisesRegex(
+            ValueError, "temperature \\(35.5\\) must be between 5.0 and 35.0"
+        ):
+            await self.subject.async_set_target_temperature(35.5)
+
+    def test_current_temperature(self):
+        self.dps[CURRENTTEMP_DPS] = 44
+        self.assertEqual(self.subject.current_temperature, 22)
+
+    def test_hvac_mode(self):
+        self.dps[HVACMODE_DPS] = "0"
+        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_AUTO)
+
+        self.dps[HVACMODE_DPS] = "1"
+        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_HEAT)
+
+        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_HEAT,
+                HVAC_MODE_AUTO,
+            ],
+        )
+
+    def test_device_state_attribures(self):
+        self.dps[FLOOR_DPS] = 45
+        self.dps[UNKNOWN104_DPS] = False
+
+        self.assertDictEqual(
+            self.subject.device_state_attributes,
+            {"floor_temperature": 22.5, "unknown_103": "103", "unknown_104": False},
+        )
+        self.assertDictEqual(self.light.device_state_attributes, {})
+        self.assertDictEqual(self.lock.device_state_attributes, {})
+
+    def test_icons(self):
+        self.dps[LIGHT_DPS] = True
+        self.assertEqual(self.light.icon, "mdi:led-on")
+        self.dps[LIGHT_DPS] = False
+        self.assertEqual(self.light.icon, "mdi:led-off")
+
+        self.dps[LOCK_DPS] = True
+        self.assertEqual(self.lock.icon, "mdi:hand-back-right-off")
+        self.dps[LOCK_DPS] = False
+        self.assertEqual(self.lock.icon, "mdi:hand-back-right")