Pārlūkot izejas kodu

Add support for Inkbird Sous Vide Cooker.

Issue #219
Jason Rumney 3 gadi atpakaļ
vecāks
revīzija
bc5c5de577

+ 1 - 0
ACKNOWLEDGEMENTS.md

@@ -110,3 +110,4 @@ Further device support has been made with the assistance of users.  Please consi
 - [alexmaras](https://github.com/alexmaras) for contributing support for Catit Pixi smart fountain.
 - [jamiergrs](https://github.com/jamiergrs) for assistance supporting Orion Grid Connect outdoor sirens.
 - [myhomeiot](https://github.com/myhomeiot) for contributing support for Bresser 7-in-1 Weather Station.
+- [Condorello](https://github.com/Condorello) for assistance supporting Inkbird sous vide cookers.

+ 1 - 0
README.md

@@ -219,6 +219,7 @@ Other brands may work with the above configurations
 - Universal Remote Control (supports sensors only)
 - Catit Pixi Smart Fountain
 - Bresser Smart 7-in-1 Weather Station
+- Inkbird Sous Vide Cooker
 
 ---
 

+ 126 - 0
custom_components/tuya_local/devices/inkbird_sousvide_cooker.yaml

@@ -0,0 +1,126 @@
+name: Inkbird Sous Vide
+primary_entity:
+  entity: climate
+  dps:
+    - id: 101
+      type: boolean
+      name: hvac_mode
+      mapping:
+        - dps_val: false
+          value: "off"
+        - dps_val: true
+          value: heat
+    - id: 102
+      type: string
+      name: hvac_action
+      icon_priority: 2
+      mapping:
+        - dps_val: stop
+          value: "off"
+          icon: "mdi:pot-outline"
+        - dps_val: working
+          value: heating
+          icon: "mdi:pot-steam"
+        - dps_val: complete
+          value: idle
+          icon: "mdi:pot"
+    - id: 103
+      type: integer
+      name: temperature
+      range:
+        min: 0
+        max: 960
+      mapping:
+        - scale: 10
+    - id: 104
+      type: integer
+      name: current_temperature
+      mapping:
+        - scale: 10
+    - id: 107
+      type: integer
+      name: fault
+      mapping:
+        - dps_val: 0
+          value: 0
+        - icon: "mdi:alert"
+          icon_priority: 1
+    - id: 108
+      type: boolean
+      name: temperature_unit
+      mapping:
+        - dps_val: false
+          value: C
+        - dps_val: true
+          value: F
+secondary_entities:
+  - entity: number
+    name: Cooking Time
+    category: config
+    icon: "mdi:timer"
+    dps:
+      - id: 105
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 5999
+        unit: min
+  - entity: sensor
+    name: Remaining Time
+    category: diagnostic
+    icon: "mdi:timer"
+    dps:
+      - id: 106
+        type: integer
+        name: sensor
+        unit: min
+  - entity: binary_sensor
+    name: Fault
+    category: diagnostic
+    class: problem
+    dps:
+      - id: 107
+        type: integer
+        name: sensor
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true
+  - entity: select
+    name: Temperature Unit
+    category: config
+    icon: "mdi:temperature-celsius"
+    dps:
+      - id: 108
+        type: boolean
+        name: option
+        mapping:
+          - dps_val: false
+            value: Celsius
+          - dps_val: true
+            value: Fahrenheit
+  - entity: number
+    name: Recipe
+    category: config
+    icon: "mdi:pot-steam"
+    dps:
+      - id: 109
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 1000
+  - entity: number
+    name: Temperature Calibration
+    category: config
+    icon: "mdi:arrow-collapse-up"
+    dps:
+      - id: 110
+        type: integer
+        name: value
+        range:
+          min: -99
+          max: 99
+        mapping:
+          - scale: 10

+ 13 - 0
tests/const.py

@@ -1483,3 +1483,16 @@ ORION_SIREN_PAYLOAD = {
     "15": 0,
     "20": True,
 }
+
+INKBIRD_SOUSVIDE_PAYLOAD = {
+    "101": False,
+    "102": "stop",
+    "103": 0,
+    "104": 297,
+    "105": 0,
+    "106": 0,
+    "107": 3,
+    "108": True,
+    "109": 0,
+    "110": 0,
+}

+ 176 - 0
tests/devices/test_inkbird_sousvide.py

@@ -0,0 +1,176 @@
+from homeassistant.components.binary_sensor import BinarySensorDeviceClass
+from homeassistant.components.climate.const import (
+    ClimateEntityFeature,
+    HVACAction,
+    HVACMode,
+)
+from homeassistant.const import TIME_MINUTES, TEMP_CELSIUS, TEMP_FAHRENHEIT
+
+from ..const import INKBIRD_SOUSVIDE_PAYLOAD
+from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import BasicBinarySensorTests
+from ..mixins.climate import TargetTemperatureTests
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import BasicSelectTests
+from ..mixins.sensor import BasicSensorTests
+from .base_device_tests import TuyaDeviceTestCase
+
+HVACMODE_DPS = "101"
+HVACACTION_DPS = "102"
+TEMPERATURE_DPS = "103"
+CURRENTTEMP_DPS = "104"
+TIMER_DPS = "105"
+REMAIN_DPS = "106"
+ERROR_DPS = "107"
+UNIT_DPS = "108"
+RECIPE_DPS = "109"
+CALIBRATE_DPS = "110"
+
+
+class TestInkbirdSousVideCooker(
+    BasicBinarySensorTests,
+    MultiNumberTests,
+    BasicSelectTests,
+    BasicSensorTests,
+    TargetTemperatureTests,
+    TuyaDeviceTestCase,
+):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig("inkbird_sousvide_cooker.yaml", INKBIRD_SOUSVIDE_PAYLOAD)
+        self.subject = self.entities.get("climate")
+        self.setUpTargetTemperature(
+            TEMPERATURE_DPS,
+            self.subject,
+            min=0.0,
+            max=96.0,
+            scale=10,
+        )
+        self.setUpMultiNumber(
+            [
+                {
+                    "dps": TIMER_DPS,
+                    "name": "number_cooking_time",
+                    "max": 5999,
+                    "unit": TIME_MINUTES,
+                },
+                {
+                    "dps": RECIPE_DPS,
+                    "name": "number_recipe",
+                    "max": 1000,
+                },
+                {
+                    "dps": CALIBRATE_DPS,
+                    "name": "number_temperature_calibration",
+                    "min": -9.9,
+                    "max": 9.9,
+                    "scale": 10,
+                    "step": 0.1,
+                },
+            ]
+        )
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_fault"),
+            device_class=BinarySensorDeviceClass.PROBLEM,
+            testdata=(1, 0),
+        )
+        self.setUpBasicSelect(
+            UNIT_DPS,
+            self.entities.get("select_temperature_unit"),
+            {
+                True: "Fahrenheit",
+                False: "Celsius",
+            },
+        )
+        self.setUpBasicSensor(
+            REMAIN_DPS,
+            self.entities.get("sensor_remaining_time"),
+            unit=TIME_MINUTES,
+        )
+        self.mark_secondary(
+            [
+                "number_cooking_time",
+                "number_recipe",
+                "number_temperature_calibration",
+                "binary_sensor_fault",
+                "select_temperature_unit",
+                "sensor_remaining_time",
+            ]
+        )
+
+    def test_supported_features(self):
+        self.assertEqual(
+            self.subject.supported_features,
+            ClimateEntityFeature.TARGET_TEMPERATURE,
+        )
+
+    def test_icon(self):
+        self.dps[ERROR_DPS] = 0
+        self.dps[HVACACTION_DPS] = "stop"
+        self.assertEqual(self.subject.icon, "mdi:pot-outline")
+
+        self.dps[HVACACTION_DPS] = "working"
+        self.assertEqual(self.subject.icon, "mdi:pot-steam")
+        self.dps[HVACACTION_DPS] = "complete"
+        self.assertEqual(self.subject.icon, "mdi:pot")
+
+        self.dps[ERROR_DPS] = 2
+        self.assertEqual(self.subject.icon, "mdi:alert")
+
+    def test_temperature_unit(self):
+        self.dps[UNIT_DPS] = False
+        self.assertEqual(self.subject.temperature_unit, TEMP_CELSIUS)
+        self.dps[UNIT_DPS] = True
+        self.assertEqual(self.subject.temperature_unit, TEMP_FAHRENHEIT)
+
+    def test_current_temperature(self):
+        self.dps[CURRENTTEMP_DPS] = 522
+        self.assertEqual(self.subject.current_temperature, 52.2)
+
+    def test_hvac_mode(self):
+        self.dps[HVACMODE_DPS] = True
+        self.assertEqual(self.subject.hvac_mode, HVACMode.HEAT)
+
+        self.dps[HVACMODE_DPS] = False
+        self.assertEqual(self.subject.hvac_mode, HVACMode.OFF)
+
+    def test_hvac_modes(self):
+        self.assertCountEqual(self.subject.hvac_modes, [HVACMode.OFF, HVACMode.HEAT])
+
+    def test_hvac_action(self):
+        self.dps[HVACACTION_DPS] = "stop"
+        self.assertEqual(self.subject.hvac_action, HVACAction.OFF)
+
+        self.dps[HVACACTION_DPS] = "working"
+        self.assertEqual(self.subject.hvac_action, HVACAction.HEATING)
+
+        self.dps[HVACACTION_DPS] = "complete"
+        self.assertEqual(self.subject.hvac_action, HVACAction.IDLE)
+
+    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(HVACMode.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(HVACMode.OFF)
+
+    def test_extra_state_attributes(self):
+        # There are currently no known error states; update this as
+        # they are discovered
+        self.dps[ERROR_DPS] = 2
+        self.assertDictEqual(
+            self.subject.extra_state_attributes,
+            {"fault": 2},
+        )
+        self.dps[ERROR_DPS] = "0"
+        self.assertDictEqual(
+            self.subject.extra_state_attributes,
+            {"fault": 0},
+        )