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

Add support for Inkbird ITC-308 Thermometer

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

+ 1 - 0
ACKNOWLEDGEMENTS.md

@@ -72,3 +72,4 @@ Further device support has been made with the assistance of users.  Please consi
  - [WildeRNS](https://github.com/WildeRNS) for assistance with Nashone MTS-700-WB thermostat smartplugs, SmartMCB Energy meter.
  - [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.

+ 5 - 4
README.md

@@ -71,7 +71,8 @@ the device will not work despite being listed below.
   be figured out.
 
 ### Thermostats
-- Inkbird ITC306A thermostat smartplug (not fully functional)
+- Inkbird ITC306A thermostat smartplug
+- Inkbird ITC308 thermostat smartplug
 - Beca BHP-6000 Room Heat Pump control thermostat
 - Beca BHT-6000/8000 Floor Heating thermostat
 - Beca BHT-002/3000 Floor Heating thermostat (with external temp sensor)
@@ -86,9 +87,6 @@ the device will not work despite being listed below.
 - Hysen HY08WE-2 thermostat
 - Nashone MTS-700-WB thermostat smartplug
 
-### Kettles
-- Kogan Glass 1.7L Smart Kettle (not reliably detected)
-
 ### Fans
 - Goldair GCPF315 fan
 - Anko HEGSM40 fan
@@ -117,6 +115,9 @@ the device will not work despite being listed below.
 - Eanons QT-JS2014 Purifying humidifier
 - Wetair WAW-H1210LW humidifier
 
+### Kitchen Appliances
+- Kogan Glass 1.7L Smart Kettle (not reliably detected)
+
 ### Smart Meter/Circuit Breaker
 - SmartMCB SMT006 Energy Meter
 

+ 256 - 0
custom_components/tuya_local/devices/inkbird_itc308_thermostat.yaml

@@ -0,0 +1,256 @@
+name: Inkbird ITC-308 Thermostat
+primary_entity:
+  entity: climate
+  dps:
+    - id: 12
+      type: bitfield
+      name: error
+      mapping:
+        - dps_val: 0
+          value: OK
+    - id: 101
+      type: string
+      name: temperature_unit
+    - id: 104
+      type: integer
+      name: current_temperature
+      mapping:
+        - scale: 10
+          constraint: temperature_unit
+          conditions:
+            - dps_val: F
+              value_redirect: current_temperature_f
+    - id: 106
+      type: integer
+      name: temperature
+      range:
+        min: -500
+        max: 999
+      mapping:
+        - scale: 10
+          constraint: temperature_unit
+          conditions:
+            - dps_val: F
+              range:
+                min: -580
+                max: 2100
+    - id: 111
+      type: boolean
+      name: high_temp_alarm
+      hidden: true
+      mapping:
+        - dps_val: true
+          icon: "mdi:thermometer-alert"
+          icon_priority: 1
+    - id: 112
+      type: boolean
+      name: low_temp_alarm
+      hidden: true
+      mapping:
+        - dps_val: true
+          icon: "mdi:snowflake-alert"
+          icon_priority: 2
+    - id: 113
+      type: boolean
+      name: sensor_fault_alarm
+      hidden: true
+      mapping:
+        - dps_val: true
+          icon: "mdi:thermometer-alert"
+          icon_priority: 3
+    - id: 115
+      type: string
+      name: hvac_action
+      mapping:
+        - dps_val: "1"
+          icon: "mdi:snowflake"
+          icon_priority: 5
+          value: cooling
+        - dps_val: "2"
+          icon: "mdi:thermometer-off"
+          icon_priority: 5
+          value: idle
+        - dps_val: "3"
+          icon: "mdi:fire"
+          icon_priority: 6
+          value: heating
+    - id: 116
+      type: integer
+      name: current_temperature_f
+      mapping:
+        - scale: 10
+      hidden: true
+secondary_entities:
+  - entity: number
+    category: config
+    name: Calibration Offset
+    icon: "mdi:arrow-collapse-up"
+    dps:
+      - id: 102
+        name: value
+        type: integer
+        range:
+          min: -99
+          max: 99
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: -150
+                  max: 150
+  - entity: number
+    name: Compressor Delay
+    icon: "mdi:clock"
+    category: config
+    dps:
+      - id: 108
+        type: integer
+        name: value
+        unit: min
+        range:
+          min: 0
+          max: 10
+  - entity: number
+    name: High Temperature Limit
+    category: config
+    icon: "mdi:thermometer-alert"
+    dps:
+      - id: 109
+        name: value
+        type: integer
+        range:
+          min: -500
+          max: 999
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: -500
+                  max: 2100
+      - id: 101
+        name: unit
+        type: string
+        hidden: true
+  - entity: number
+    name: Low Temperature Limit
+    icon: "mdi:snowflake-alert"
+    category: config
+    dps:
+      - id: 110
+        name: value
+        type: integer
+        range:
+          min: -500
+          max: 999
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: -500
+                  max: 2100
+      - id: 101
+        name: unit
+        type: string
+        hidden: true
+  - entity: select
+    category: config
+    name: Temperature Unit
+    icon: "mdi:temperature-celsius"
+    dps:
+      - id: 101
+        name: option
+        type: string
+        mapping:
+          - dps_val: C
+            value: Celsius
+          - dps_val: F
+            value: Fahrenheit
+  - entity: binary_sensor
+    class: heat
+    category: diagnostic
+    name: High Temperature
+    dps:
+      - id: 111
+        type: boolean
+        name: sensor
+  - entity: binary_sensor
+    class: cold
+    category: diagnostic
+    name: Low Temperature
+    dps:
+      - id: 112
+        type: boolean
+        name: sensor
+  - entity: binary_sensor
+    name: Error
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 12
+        name: sensor
+        type: bitfield
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true
+  - entity: binary_sensor
+    class: problem
+    category: diagnostic
+    name: Sensor Fault
+    icon: "mdi:thermometer-alert"
+    dps:
+      - id: 113
+        type: boolean
+        name: sensor
+  - entity: number
+    category: config
+    name: Heating Hysteresis
+    icon: "mdi: thermometer-chevron-up"
+    dps:
+      - id: 117
+        name: value
+        type: integer
+        range:
+          min: 3
+          max: 150
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: 10
+                  max: 300
+      - id: 101
+        name: unit
+        type: string
+        hidden: true            
+  - entity: number
+    category: config
+    name: Cooling Hysteresis
+    icon: "mdi:thermometer-chevron-down"
+    dps:
+      - id: 118
+        name: value
+        type: integer
+        range:
+          min: 3
+          max: 150
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: 10
+                  max: 300
+      - id: 101
+        name: unit
+        type: string
+        hidden: true

+ 1 - 1
custom_components/tuya_local/manifest.json

@@ -2,7 +2,7 @@
     "domain": "tuya_local",
     "iot_class": "local_polling",
     "name": "Tuya Local",
-    "version": "0.14.3",
+    "version": "0.14.4",
     "documentation": "https://github.com/make-all/tuya-local",
     "issue_tracker": "https://github.com/make-all/tuya-local/issues",
     "dependencies": [],

+ 8 - 0
custom_components/tuya_local/translations/en.json

@@ -37,6 +37,7 @@
                     "binary_sensor_error": "Include error as a binary_sensor entity.",
                     "binary_sensor_high_temperature": "Include high temperature alarm as a binary_sensor entity",
                     "binary_sensor_low_temperature": "Include low temperature alarm as a binary_sensor entity",
+		    "binary_sensor_sensor_fault": "Include sensor fault as a binary_sensor entity",
                     "binary_sensor_tank": "Include tank as a binary_sensor entity",
                     "binary_sensor_valve": "Include valve as a binary_sensor entity",
                     "binary_sensor_water_flow": "Include water flow warning as a binary_sensor entity",
@@ -70,6 +71,9 @@
                     "number_calibration_swing": "Include calibration swing as a number entity",
                     "number_calibration_swing_external": "Include calibration swing for external sensor as a number entity",
                     "number_calibration_swing_internal": "Include calibration swing for internal sensor as a number entity",
+		    "number_compressor_delay": "Include compressor delay as a number entity",
+		    "number_cooling_hysteresis": "Include cooling hysteresis as a number entity",
+		    "number_heating_hysteresis": "Include heating hysteresis as a number entity",
                     "number_continuous_heat_hours": "Include Continuous Heating Time as a number entity",
                     "number_fan_runtime": "Include fan run time as a number entity",
                     "number_high_temperature_limit": "Include High Temperature Limit as a number entity",
@@ -176,6 +180,7 @@
                     "binary_sensor_error": "Include error as a binary_sensor entity.",
                     "binary_sensor_high_temperature": "Include high temperature alarm as a binary_sensor entity",
                     "binary_sensor_low_temperature": "Include low temperature alarm as a binary_sensor entity",
+		    "binary_sensor_sensor_fault": "Include sensor fault as a binary_sensor entity",
                     "binary_sensor_tank": "Include tank as a binary_sensor entity",
                     "binary_sensor_valve": "Include valve as a binary_sensor entity",
                     "binary_sensor_water_flow": "Include water flow warning as a binary_sensor entity",
@@ -209,6 +214,9 @@
                     "number_calibration_swing": "Include calibration swing as a number entity",
                     "number_calibration_swing_external": "Include calibration swing for external sensor as a number entity",
                     "number_calibration_swing_internal": "Include calibration swing for internal sensor as a number entity",
+		    "number_compressor_delay": "Include compressor delay as a number entity",
+		    "number_cooling_hysteresis": "Include cooling hysteresis as a number entity",
+		    "number_heating_hysteresis": "Include heating hysteresis as a number entity",
                     "number_continuous_heat_hours": "Include Continuous Heating Time as a number entity",
                     "number_fan_runtime": "Include fan run time as a number entity",
                     "number_high_temperature_limit": "Include High Temperature Limit as a number entity",

+ 19 - 1
tests/const.py

@@ -238,7 +238,7 @@ EANONS_HUMIDIFIER_PAYLOAD = {
     "22": True,
 }
 
-INKBIRD_THERMOSTAT_PAYLOAD = {
+INKBIRD_ITC306A_THERMOSTAT_PAYLOAD = {
     "12": 0,
     "101": "C",
     "102": 0,
@@ -260,6 +260,24 @@ INKBIRD_THERMOSTAT_PAYLOAD = {
     "120": False,
 }
 
+INKBIRD_ITC308_THERMOSTAT_PAYLOAD = {
+    "12": 0,
+    "101": "C",
+    "102": 0,
+    "104": 136,
+    "106": 15,
+    "108": 3,
+    "109": 370,
+    "110": 10,
+    "111": False,
+    "112": False,
+    "113": False,
+    "115": "1",
+    "116": 565,
+    "117": 10,
+    "118": 5,
+}
+
 ANKO_FAN_PAYLOAD = {
     "1": True,
     "2": "normal",

+ 2 - 2
tests/devices/test_inkbird_itc306a_thermostat.py

@@ -12,7 +12,7 @@ from homeassistant.components.climate.const import (
 from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, TIME_HOURS
 
 
-from ..const import INKBIRD_THERMOSTAT_PAYLOAD
+from ..const import INKBIRD_ITC306A_THERMOSTAT_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..mixins.binary_sensor import MultiBinarySensorTests
 from ..mixins.number import MultiNumberTests
@@ -50,7 +50,7 @@ class TestInkbirdThermostat(
 
     def setUp(self):
         self.setUpForConfig(
-            "inkbird_itc306a_thermostat.yaml", INKBIRD_THERMOSTAT_PAYLOAD
+            "inkbird_itc306a_thermostat.yaml", INKBIRD_ITC306A_THERMOSTAT_PAYLOAD
         )
         self.subject = self.entities.get("climate")
         self.setUpBasicSelect(

+ 231 - 0
tests/devices/test_inkbird_itc308_thermostat.py

@@ -0,0 +1,231 @@
+from homeassistant.components.binary_sensor import (
+    DEVICE_CLASS_COLD,
+    DEVICE_CLASS_HEAT,
+    DEVICE_CLASS_PROBLEM,
+)
+from homeassistant.components.climate.const import (
+    CURRENT_HVAC_COOL,
+    CURRENT_HVAC_HEAT,
+    CURRENT_HVAC_IDLE,
+    SUPPORT_TARGET_TEMPERATURE,
+)
+from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, TIME_MINUTES
+
+
+from ..const import INKBIRD_ITC308_THERMOSTAT_PAYLOAD
+from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import MultiBinarySensorTests
+from ..mixins.climate import TargetTemperatureTests
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import BasicSelectTests
+from .base_device_tests import TuyaDeviceTestCase
+
+ERROR_DPS = "12"
+UNIT_DPS = "101"
+CALIBRATE_DPS = "102"
+CURRENTTEMP_DPS = "104"
+TEMPERATURE_DPS = "106"
+TIME_THRES_DPS = "108"
+HIGH_THRES_DPS = "109"
+LOW_THRES_DPS = "110"
+ALARM_HIGH_DPS = "111"
+ALARM_LOW_DPS = "112"
+ALARM_SENSOR_DPS = "113"
+STATUS_DPS = "115"
+TEMPF_DPS = "116"
+HEATDIFF_DPS = "117"
+COOLDIFF_DPS = "118"
+
+
+class TestInkbirdITC308Thermostat(
+    BasicSelectTests,
+    MultiBinarySensorTests,
+    MultiNumberTests,
+    TargetTemperatureTests,
+    TuyaDeviceTestCase,
+):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig(
+            "inkbird_itc308_thermostat.yaml", INKBIRD_ITC308_THERMOSTAT_PAYLOAD
+        )
+        self.subject = self.entities.get("climate")
+        self.setUpTargetTemperature(
+            TEMPERATURE_DPS,
+            self.subject,
+            min=-50.0,
+            max=99.9,
+            scale=10,
+        )
+        self.setUpBasicSelect(
+            UNIT_DPS,
+            self.entities.get("select_temperature_unit"),
+            {
+                "C": "Celsius",
+                "F": "Fahrenheit",
+            },
+        )
+        self.setUpMultiBinarySensors(
+            [
+                {
+                    "name": "binary_sensor_high_temperature",
+                    "dps": ALARM_HIGH_DPS,
+                    "device_class": DEVICE_CLASS_HEAT,
+                },
+                {
+                    "name": "binary_sensor_low_temperature",
+                    "dps": ALARM_LOW_DPS,
+                    "device_class": DEVICE_CLASS_COLD,
+                },
+                {
+                    "name": "binary_sensor_sensor_fault",
+                    "dps": ALARM_SENSOR_DPS,
+                    "device_class": DEVICE_CLASS_PROBLEM,
+                },
+                {
+                    "name": "binary_sensor_error",
+                    "dps": ERROR_DPS,
+                    "device_class": DEVICE_CLASS_PROBLEM,
+                    "testdata": (1, 0),
+                },
+            ]
+        )
+        self.setUpMultiNumber(
+            [
+                {
+                    "name": "number_calibration_offset",
+                    "dps": CALIBRATE_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": -9.9,
+                    "max": 9.9,
+                },
+                {
+                    "name": "number_compressor_delay",
+                    "dps": TIME_THRES_DPS,
+                    "max": 10,
+                    "unit": TIME_MINUTES,
+                },
+                {
+                    "name": "number_high_temperature_limit",
+                    "dps": HIGH_THRES_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": -50,
+                    "max": 99.9,
+                    "unit": TEMP_CELSIUS,
+                },
+                {
+                    "name": "number_low_temperature_limit",
+                    "dps": LOW_THRES_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": -50,
+                    "max": 99.9,
+                    "unit": TEMP_CELSIUS,
+                },
+                {
+                    "name": "number_cooling_hysteresis",
+                    "dps": COOLDIFF_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": 0.3,
+                    "max": 15.0,
+                    "unit": TEMP_CELSIUS,
+                },
+                {
+                    "name": "number_heating_hysteresis",
+                    "dps": HEATDIFF_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": 0.3,
+                    "max": 15.0,
+                    "unit": TEMP_CELSIUS,
+                },
+            ]
+        )
+        self.mark_secondary(
+            [
+                "select_temperature_unit",
+                "binary_sensor_high_temperature",
+                "binary_sensor_low_temperature",
+                "binary_sensor_sensor_fault",
+                "binary_sensor_error",
+                "number_calibration_offset",
+                "number_compressor_delay",
+                "number_high_temperature_limit",
+                "number_low_temperature_limit",
+                "number_cooling_hysteresis",
+                "number_heating_hysteresis",
+            ]
+        )
+
+    def test_supported_features(self):
+        self.assertEqual(
+            self.subject.supported_features,
+            SUPPORT_TARGET_TEMPERATURE,
+        )
+
+    def test_icon(self):
+        """Test that the icon is as expected."""
+        self.dps[ALARM_HIGH_DPS] = False
+        self.dps[ALARM_LOW_DPS] = False
+        self.dps[ALARM_SENSOR_DPS] = False
+        self.dps[STATUS_DPS] = "3"
+        self.assertEqual(self.subject.icon, "mdi:fire")
+        self.dps[STATUS_DPS] = "1"
+        self.assertEqual(self.subject.icon, "mdi:snowflake")
+
+        self.dps[STATUS_DPS] = "2"
+        self.assertEqual(self.subject.icon, "mdi:thermometer-off")
+
+        self.dps[ALARM_HIGH_DPS] = True
+        self.assertEqual(self.subject.icon, "mdi:thermometer-alert")
+
+        self.dps[STATUS_DPS] = "3"
+        self.assertEqual(self.subject.icon, "mdi:thermometer-alert")
+
+        self.dps[ALARM_HIGH_DPS] = False
+        self.dps[ALARM_LOW_DPS] = True
+        self.assertEqual(self.subject.icon, "mdi:snowflake-alert")
+
+        self.dps[ALARM_LOW_DPS] = False
+        self.dps[ALARM_SENSOR_DPS] = True
+        self.assertEqual(self.subject.icon, "mdi:thermometer-alert")
+
+    def test_climate_hvac_modes(self):
+        self.assertEqual(self.subject.hvac_modes, [])
+
+    def test_current_temperature(self):
+        self.dps[UNIT_DPS] = "C"
+        self.dps[CURRENTTEMP_DPS] = 289
+        self.assertEqual(self.subject.current_temperature, 28.9)
+        self.dps[UNIT_DPS] = "F"
+        self.dps[TEMPF_DPS] = 789
+        self.assertEqual(self.subject.current_temperature, 78.9)
+
+    def test_temperature_unit(self):
+        self.dps[UNIT_DPS] = "F"
+        self.assertEqual(self.subject.temperature_unit, TEMP_FAHRENHEIT)
+
+        self.dps[UNIT_DPS] = "C"
+        self.assertEqual(self.subject.temperature_unit, TEMP_CELSIUS)
+
+    def test_hvac_action(self):
+        self.dps[STATUS_DPS] = "1"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_COOL)
+        self.dps[STATUS_DPS] = "2"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_IDLE)
+        self.dps[STATUS_DPS] = "3"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_HEAT)
+
+    def test_extra_state_attributes(self):
+        self.dps[ERROR_DPS] = 12
+
+        self.assertDictEqual(
+            self.subject.extra_state_attributes,
+            {
+                "error": 12,
+            },
+        )