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

Add a second variant of Awow TH213 thermostat.

The known dps are the same, but three unknown boolean and integer dps
are replaced with a different unknown single string dp.

Issue #122
Jason Rumney 4 лет назад
Родитель
Сommit
4cde7d4fd8

+ 2 - 0
ACKNOWLEDGEMENTS.md

@@ -73,3 +73,5 @@ Further device support has been made with the assistance of users.  Please consi
  - [ishioni](https://github.com/ishioni) for contributing support for Eberg Cooly C32HD air conditioner.
  - [Gekko47](https://github.com/Gekko47) for contributing support for ElectriQ CD12v2 dehumidifiers.
  - [andreq](https://github.com/andreq) for assistance with Inkbird ITC-308 thermostats.
+ - [dlosito](https://github.com/dlosito) for assistance with a second variant of Awow TH213 thermostat.
+ 

+ 1 - 1
README.md

@@ -78,7 +78,7 @@ the device will not work despite being listed below.
 - Beca BHT-002/3000 Floor Heating thermostat (with external temp sensor)
 - Moes BHT-002 thermostat (without external temp sensor)
 - Beca BAC-002 thermostat
-- Awow/Mi-heat TH213 thermostat
+- Awow/Mi-heat TH213 thermostat (two variants)
 - Siswell T29UTW thermostat
 - Siswell C16 thermostat _(rebadged as Warmme, Klima and others)_
 - Minco MH-1823D thermostat

+ 133 - 0
custom_components/tuya_local/devices/awow_th213v2_thermostat.yaml

@@ -0,0 +1,133 @@
+name: AWOW/Mi-Heat TH213 Thermostat for electrical floor heating
+primary_entity:
+  entity: climate
+  dps:
+    - id: 1
+      name: hvac_mode
+      type: boolean
+      mapping:
+        - dps_val: true
+          value: "heat"
+        - dps_val: false
+          value: "off"
+    - id: 2
+      name: temperature
+      type: integer
+      range:
+        min: 5
+        max: 30
+    - id: 3
+      name: current_temperature
+      type: integer
+      readonly: true
+    - id: 4
+      name: preset_mode
+      type: integer
+      mapping:
+        - dps_val: 1
+          value: "Home"
+        - dps_val: 2
+          value: "Away"
+        - dps_val: 3
+          value: "Smart"
+        - dps_val: 4
+          value: "Sleep"
+    - id: 12
+      type: bitfield
+      name: error
+      mapping:
+        - dps_val: 0
+          value: OK
+    - id: 105
+      type: boolean
+      name: hvac_action
+      mapping:
+        - dps_val: true
+          value: heating
+          icon: "mdi:thermometer"
+        - dps_val: false
+          icon: "mdi:thermometer-off"
+          constraint: hvac_mode
+          conditions:
+            - dps_val: true
+              value: idle
+            - dps_val: false
+              value: "off"
+      readonly: true
+    - id: 116
+      type: string
+      name: unknown_116
+secondary_entities:
+  - entity: lock
+    name: Child Lock
+    category: config
+    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"
+  - entity: sensor
+    name: External Temperature
+    class: temperature
+    dps:
+      - id: 101
+        type: integer
+        name: sensor
+        class: measurement
+        unit: C
+        readonly: true
+  - entity: select
+    name: Temperature Sensor
+    icon: "mdi:thermometer"
+    category: config
+    dps:
+      - id: 102
+        type: integer
+        name: option
+        mapping:
+          - dps_val: 0
+            value: Internal
+          - dps_val: 1
+            value: External
+          - dps_val: 2
+            value: Both
+  - entity: number
+    name: Calibration Offset
+    category: config
+    icon: "mdi:arrow-collapse-up"
+    dps:
+      - id: 103
+        type: integer
+        name: value
+        unit: C
+        range:
+          min: -9
+          max: 9
+  - entity: number
+    name: Calibration Swing
+    category: config
+    icon: "mdi:arrow-expand-vertical"
+    dps:
+      - id: 104
+        type: integer
+        name: value
+        unit: C
+        range:
+          min: 1
+          max: 9
+  - entity: binary_sensor
+    name: Error
+    category: diagnostic
+    class: problem
+    dps:
+      - id: 12
+        type: bitfield
+        name: sensor
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 15 - 0
tests/const.py

@@ -525,6 +525,21 @@ TH213_THERMOSTAT_PAYLOAD = {
     "110": 0,
 }
 
+TH213V2_THERMOSTAT_PAYLOAD = {
+    "1": True,
+    "2": 16,
+    "3": 21,
+    "4": "3",
+    "6": False,
+    "12": 0,
+    "101": 23,
+    "102": "2",
+    "103": 1,
+    "104": 1,
+    "105": False,
+    "116": "1",
+}
+
 WETAIR_WCH750_HEATER_PAYLOAD = {
     "1": False,
     "2": 17,

+ 252 - 0
tests/devices/test_awow_th213v2_thermostat.py

@@ -0,0 +1,252 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
+from homeassistant.components.climate.const import (
+    CURRENT_HVAC_HEAT,
+    CURRENT_HVAC_IDLE,
+    CURRENT_HVAC_OFF,
+    HVAC_MODE_HEAT,
+    HVAC_MODE_OFF,
+    SUPPORT_PRESET_MODE,
+    SUPPORT_TARGET_TEMPERATURE,
+)
+from homeassistant.const import (
+    DEVICE_CLASS_TEMPERATURE,
+    STATE_UNAVAILABLE,
+    TEMP_CELSIUS,
+)
+
+from ..const import TH213V2_THERMOSTAT_PAYLOAD
+from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import BasicBinarySensorTests
+from ..mixins.climate import TargetTemperatureTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import BasicSelectTests
+from ..mixins.sensor import BasicSensorTests
+from .base_device_tests import TuyaDeviceTestCase
+
+HVACMODE_DPS = "1"
+TEMPERATURE_DPS = "2"
+CURRENTTEMP_DPS = "3"
+PRESET_DPS = "4"
+LOCK_DPS = "6"
+ERROR_DPS = "12"
+EXTERNTEMP_DPS = "101"
+SENSOR_DPS = "102"
+CALIBRATE_DPS = "103"
+CALIBSWING_DPS = "104"
+HVACACTION_DPS = "105"
+UNKNOWN116_DPS = "116"
+
+
+class TestAwowTH213v2Thermostat(
+    BasicBinarySensorTests,
+    BasicLockTests,
+    BasicSelectTests,
+    BasicSensorTests,
+    MultiNumberTests,
+    TargetTemperatureTests,
+    TuyaDeviceTestCase,
+):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig("awow_th213v2_thermostat.yaml", TH213V2_THERMOSTAT_PAYLOAD)
+        self.subject = self.entities.get("climate")
+        self.setUpTargetTemperature(
+            TEMPERATURE_DPS,
+            self.subject,
+            min=5,
+            max=30,
+        )
+        self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicSensor(
+            EXTERNTEMP_DPS,
+            self.entities.get("sensor_external_temperature"),
+            unit=TEMP_CELSIUS,
+            device_class=DEVICE_CLASS_TEMPERATURE,
+            state_class="measurement",
+        )
+        self.setUpBasicSelect(
+            SENSOR_DPS,
+            self.entities.get("select_temperature_sensor"),
+            {
+                0: "Internal",
+                1: "External",
+                2: "Both",
+            },
+        )
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_error"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
+        self.setUpMultiNumber(
+            [
+                {
+                    "name": "number_calibration_offset",
+                    "dps": CALIBRATE_DPS,
+                    "min": -9,
+                    "max": 9,
+                    "unit": TEMP_CELSIUS,
+                },
+                {
+                    "name": "number_calibration_swing",
+                    "dps": CALIBSWING_DPS,
+                    "min": 1,
+                    "max": 9,
+                    "unit": TEMP_CELSIUS,
+                },
+            ]
+        )
+        self.mark_secondary(
+            [
+                "lock_child_lock",
+                "select_temperature_sensor",
+                "number_calibration_offset",
+                "number_calibration_swing",
+                "binary_sensor_error",
+            ]
+        )
+
+    def test_supported_features(self):
+        self.assertEqual(
+            self.subject.supported_features,
+            SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE,
+        )
+
+    def test_icon(self):
+        self.dps[HVACACTION_DPS] = True
+        self.assertEqual(self.subject.icon, "mdi:thermometer")
+
+        self.dps[HVACACTION_DPS] = False
+        self.assertEqual(self.subject.icon, "mdi:thermometer-off")
+
+        self.dps[LOCK_DPS] = True
+        self.assertEqual(self.basicLock.icon, "mdi:hand-back-right-off")
+
+        self.dps[LOCK_DPS] = False
+        self.assertEqual(self.basicLock.icon, "mdi:hand-back-right")
+
+    def test_temperature_unit_returns_device_temperature_unit(self):
+        self.assertEqual(
+            self.subject.temperature_unit, self.subject._device.temperature_unit
+        )
+
+    async def test_legacy_set_temperature_with_preset_mode(self):
+        async with assert_device_properties_set(self.subject._device, {PRESET_DPS: 2}):
+            await self.subject.async_set_temperature(preset_mode="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: 3,
+            },
+        ):
+            await self.subject.async_set_temperature(
+                temperature=25, preset_mode="Smart"
+            )
+
+    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_mode(self):
+        self.dps[PRESET_DPS] = "1"
+        self.assertEqual(self.subject.preset_mode, "Home")
+
+        self.dps[PRESET_DPS] = "2"
+        self.assertEqual(self.subject.preset_mode, "Away")
+
+        self.dps[PRESET_DPS] = "3"
+        self.assertEqual(self.subject.preset_mode, "Smart")
+
+        self.dps[PRESET_DPS] = "4"
+        self.assertEqual(self.subject.preset_mode, "Sleep")
+
+        self.dps[PRESET_DPS] = None
+        self.assertEqual(self.subject.preset_mode, None)
+
+    def test_preset_modes(self):
+        self.assertCountEqual(
+            self.subject.preset_modes,
+            ["Home", "Away", "Smart", "Sleep"],
+        )
+
+    async def test_set_preset_mode_to_home(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: 1},
+        ):
+            await self.subject.async_set_preset_mode("Home")
+
+    async def test_set_preset_mode_to_away(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: 2},
+        ):
+            await self.subject.async_set_preset_mode("Away")
+
+    async def test_set_preset_mode_to_smart(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: 3},
+        ):
+            await self.subject.async_set_preset_mode("Smart")
+
+    async def test_set_preset_mode_to_sleep(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: 4},
+        ):
+            await self.subject.async_set_preset_mode("Sleep")
+
+    def test_hvac_action(self):
+        self.dps[HVACMODE_DPS] = True
+        self.dps[HVACACTION_DPS] = True
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_HEAT)
+
+        self.dps[HVACACTION_DPS] = False
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_IDLE)
+
+        self.dps[HVACMODE_DPS] = False
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_OFF)
+
+    def test_extra_state_attributes(self):
+        self.dps[ERROR_DPS] = 8
+        self.dps[UNKNOWN116_DPS] = "116"
+
+        self.assertDictEqual(
+            self.subject.extra_state_attributes,
+            {
+                "error": 8,
+                "unknown_116": "116",
+            },
+        )