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

Add unit tests for Renpho Air Purifier.

- Rename config with underscores as per convention used by other configs.
- Remove fan custom attributes that are covered by other entities.
- Standardize child lock icons, move sleep icon to where it will work as intended.
- Document support, bump version for next release.
Jason Rumney 4 лет назад
Родитель
Сommit
9bc5d87f9c

+ 1 - 0
ACKNOWLEDGEMENTS.md

@@ -38,3 +38,4 @@ Further device support has been made with the assistance of users.  Please consi
  - [Vikedlol](https://github.com/Vikedlol) for assistance in supporting Wetair WCH-750 heaters.
  - [Vikedlol](https://github.com/Vikedlol) for assistance in supporting Wetair WCH-750 heaters.
  - [wwalczyszyn](https://github.com/wwalczyszyn) for contributing support for Fersk Vind 2 heatpumps.
  - [wwalczyszyn](https://github.com/wwalczyszyn) for contributing support for Fersk Vind 2 heatpumps.
  - [xbmcnut](https://github.com/xbmcnut) for assistance in supporting Kogan Smart Kettles and the new type of Kogan heater.
  - [xbmcnut](https://github.com/xbmcnut) for assistance in supporting Kogan Smart Kettles and the new type of Kogan heater.
+ - [ThomasADavis](https://github.com/ThomasADavis) for contributing support for Renpho RP-AP001S air purifiers.

+ 12 - 9
README.md

@@ -44,12 +44,24 @@ Note that devices sometimes get firmware upgrades, or incompatible versions are
   or if any of the "unknown" values that are returned as attributes can
   or if any of the "unknown" values that are returned as attributes can
   be figured out.
   be figured out.
 
 
+### Thermostats
+- Inkbird ITC306A thermostat smartplug (not fully functional)
+- Beca BHP-6000 Room Heat Pump control Thermostat
+- Awow/Mi-heat TH213 Thermostat
+- Siswell T29UTW Thermostat
+
+### Kettles
+- Kogan Glass 1.7L Smart Kettle
+
 ### Fans
 ### Fans
 - Goldair GCPF315 fan
 - Goldair GCPF315 fan
 - Anko HEGSM40 fan
 - Anko HEGSM40 fan
 - Lexy F501 fan
 - Lexy F501 fan
 - Deta fan controller
 - Deta fan controller
 
 
+### Air Purifiers
+- Renpho RP-AP001S air purifier
+
 ### Dehumidifiers
 ### Dehumidifiers
 - Goldair GPDH420 dehumidifier
 - Goldair GPDH420 dehumidifier
 - ElectriQ CD20PRO-LE-V2 dehumidifier
 - ElectriQ CD20PRO-LE-V2 dehumidifier
@@ -60,15 +72,6 @@ Note that devices sometimes get firmware upgrades, or incompatible versions are
 ### Humidifiers
 ### Humidifiers
 - Eanons QT-JS2014 Purifying humidifer
 - Eanons QT-JS2014 Purifying humidifer
 
 
-### Thermostats
-- Inkbird ITC306A thermostat smartplug (not fully functional)
-- Beca BHP-6000 Room Heat Pump control Thermostat
-- Awow/Mi-heat TH213 Thermostat
-- Siswell T29UTW Thermostat
-
-### Kettles
-- Kogan Glass 1.7L Smart Kettle
-
 ### SmartPlugs
 ### SmartPlugs
 - Kogan Single Smartplug with Energy Monitoring
 - Kogan Single Smartplug with Energy Monitoring
 - Kogan Single Smartplug with Energy Monitoring and USB charging
 - Kogan Single Smartplug with Energy Monitoring and USB charging

+ 4 - 14
custom_components/tuya_local/devices/renpho-rp-ap001s.yaml → custom_components/tuya_local/devices/renpho_rp_ap001s.yaml

@@ -3,6 +3,7 @@ products:
   - id: oaleouzsq0sk4quk
   - id: oaleouzsq0sk4quk
 primary_entity:
 primary_entity:
   entity: fan
   entity: fan
+  icon: "mdi:air-purifier"
   dps:
   dps:
     - id: 1
     - id: 1
       name: switch
       name: switch
@@ -19,22 +20,12 @@ primary_entity:
           value: high
           value: high
         - dps_val: "auto"
         - dps_val: "auto"
           value: auto
           value: auto
-    - id: 7
-      name: child_lock
-      type: boolean
-    - id: 8
-      name: aqi_mode
-      type: boolean
     - id: 19
     - id: 19
       name: timer
       name: timer
       type: string
       type: string
     - id: 22
     - id: 22
       name: air_quality
       name: air_quality
       type: string
       type: string
-    - id: 101
-      name: sleep_mode
-      type: boolean
-      icon: "hass:sleep"
     - id: 102
     - id: 102
       name: prefilter_life
       name: prefilter_life
       type: integer
       type: integer
@@ -47,7 +38,6 @@ primary_entity:
     - id: 105
     - id: 105
       name: hepa_filter_life
       name: hepa_filter_life
       type: integer
       type: integer
-
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
@@ -57,9 +47,9 @@ secondary_entities:
         type: boolean
         type: boolean
         mapping:
         mapping:
           - dps_val: true
           - dps_val: true
-            icon: "mdi:led-on"
+            icon: "mdi:hand-back-right"
           - dps_val: false
           - dps_val: false
-            icon: "mdi:led-off"
+            icon: "mdi:hand-back-right-off"
   - entity: light
   - entity: light
     name: AQI mode
     name: AQI mode
     class: switch
     class: switch
@@ -75,8 +65,8 @@ secondary_entities:
   - entity: switch
   - entity: switch
     name: Sleep Mode
     name: Sleep Mode
     class: switch
     class: switch
+    icon: "mdi:power-sleep"
     dps:
     dps:
       - id: 101
       - id: 101
         name: switch
         name: switch
         type: boolean
         type: boolean
-        icon: "hass:sleep"

+ 1 - 1
custom_components/tuya_local/manifest.json

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

+ 14 - 0
tests/const.py

@@ -400,3 +400,17 @@ KOGAN_GLASS_1_7L_KETTLE_PAYLOAD = {
     "5": 99,
     "5": 99,
     "102": "90",
     "102": "90",
 }
 }
+
+RENPHO_PURIFIER_PAYLOAD = {
+    "1": True,
+    "4": "low",
+    "7": False,
+    "8": False,
+    "19": "0",
+    "22": "0",
+    "101": False,
+    "102": 0,
+    "103": 0,
+    "104": 0,
+    "105": 0,
+}

+ 237 - 0
tests/devices/test_renpho_rp_ap001s.py

@@ -0,0 +1,237 @@
+from homeassistant.components.fan import SUPPORT_PRESET_MODE
+from homeassistant.components.light import COLOR_MODE_ONOFF
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
+from homeassistant.const import STATE_UNAVAILABLE
+
+from ..const import RENPHO_PURIFIER_PAYLOAD
+from ..helpers import assert_device_properties_set
+from .base_device_tests import TuyaDeviceTestCase
+
+SWITCH_DPS = "1"
+PRESET_DPS = "4"
+LOCK_DPS = "7"
+LIGHT_DPS = "8"
+TIMER_DPS = "19"
+QUALITY_DPS = "22"
+SLEEP_DPS = "101"
+PREFILTER_DPS = "102"
+CHARCOAL_DPS = "103"
+ACTIVATED_DPS = "104"
+HEPA_DPS = "105"
+
+
+class TestRenphoPurifier(TuyaDeviceTestCase):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig("renpho_rp_ap001s.yaml", RENPHO_PURIFIER_PAYLOAD)
+        self.subject = self.entities["fan"]
+        self.light = self.entities["light"]
+        self.lock = self.entities["lock"]
+        self.switch = self.entities["switch"]
+
+    def test_supported_features(self):
+        self.assertEqual(self.subject.supported_features, SUPPORT_PRESET_MODE)
+
+    def test_is_on(self):
+        self.dps[SWITCH_DPS] = True
+        self.assertTrue(self.subject.is_on)
+        self.dps[SWITCH_DPS] = False
+        self.assertFalse(self.subject.is_on)
+        self.dps[SWITCH_DPS] = None
+        self.assertEqual(self.subject.is_on, STATE_UNAVAILABLE)
+
+    async def test_turn_on(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {SWITCH_DPS: True},
+        ):
+            await self.subject.async_turn_on()
+
+    async def test_turn_off(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {SWITCH_DPS: False},
+        ):
+            await self.subject.async_turn_off()
+
+    def test_preset_modes(self):
+        self.assertCountEqual(
+            self.subject.preset_modes,
+            ["low", "mid", "high", "auto"],
+        )
+
+    def test_preset_mode(self):
+        self.dps[PRESET_DPS] = "low"
+        self.assertEqual(self.subject.preset_mode, "low")
+        self.dps[PRESET_DPS] = "mid"
+        self.assertEqual(self.subject.preset_mode, "mid")
+        self.dps[PRESET_DPS] = "high"
+        self.assertEqual(self.subject.preset_mode, "high")
+        self.dps[PRESET_DPS] = "auto"
+        self.assertEqual(self.subject.preset_mode, "auto")
+
+    async def test_set_preset_mode_to_low(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: "low"},
+        ):
+            await self.subject.async_set_preset_mode("low")
+
+    async def test_set_preset_mode_to_mid(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: "mid"},
+        ):
+            await self.subject.async_set_preset_mode("mid")
+
+    async def test_set_preset_mode_to_high(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: "high"},
+        ):
+            await self.subject.async_set_preset_mode("high")
+
+    async def test_set_preset_mode_to_auto(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: "auto"},
+        ):
+            await self.subject.async_set_preset_mode("auto")
+
+    def test_device_state_attributes(self):
+        self.dps[TIMER_DPS] = "19"
+        self.dps[QUALITY_DPS] = "22"
+        self.dps[PREFILTER_DPS] = 102
+        self.dps[CHARCOAL_DPS] = 103
+        self.dps[ACTIVATED_DPS] = 104
+        self.dps[HEPA_DPS] = 105
+
+        self.assertDictEqual(
+            self.subject.device_state_attributes,
+            {
+                "timer": "19",
+                "air_quality": "22",
+                "prefilter_life": 102,
+                "charcoal_filter_life": 103,
+                "activated_charcoal_filter_life": 104,
+                "hepa_filter_life": 105,
+            },
+        )
+
+    def test_lock_state(self):
+        self.dps[LOCK_DPS] = True
+        self.assertEqual(self.lock.state, STATE_LOCKED)
+
+        self.dps[LOCK_DPS] = False
+        self.assertEqual(self.lock.state, STATE_UNLOCKED)
+
+        self.dps[LOCK_DPS] = None
+        self.assertEqual(self.lock.state, STATE_UNAVAILABLE)
+
+    def test_lock_is_locked(self):
+        self.dps[LOCK_DPS] = True
+        self.assertTrue(self.lock.is_locked)
+
+        self.dps[LOCK_DPS] = False
+        self.assertFalse(self.lock.is_locked)
+
+        self.dps[LOCK_DPS] = None
+        self.assertFalse(self.lock.is_locked)
+
+    async def test_lock_locks(self):
+        async with assert_device_properties_set(
+            self.lock._device,
+            {LOCK_DPS: True},
+        ):
+            await self.lock.async_lock()
+
+    async def test_lock_unlocks(self):
+        async with assert_device_properties_set(
+            self.lock._device,
+            {LOCK_DPS: False},
+        ):
+            await self.lock.async_unlock()
+
+    def test_light_supported_color_modes(self):
+        self.assertCountEqual(
+            self.light.supported_color_modes,
+            [COLOR_MODE_ONOFF],
+        )
+
+    def test_light_color_mode(self):
+        self.assertEqual(self.light.color_mode, COLOR_MODE_ONOFF)
+
+    def test_light_has_no_brightness(self):
+        self.assertIsNone(self.light.brightness)
+
+    def test_light_icon(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")
+
+    def test_light_is_on(self):
+        self.dps[LIGHT_DPS] = True
+        self.assertEqual(self.light.is_on, True)
+
+        self.dps[LIGHT_DPS] = False
+        self.assertEqual(self.light.is_on, False)
+
+    async def test_light_turn_on(self):
+        async with assert_device_properties_set(self.light._device, {LIGHT_DPS: True}):
+            await self.light.async_turn_on()
+
+    async def test_light_turn_off(self):
+        async with assert_device_properties_set(self.light._device, {LIGHT_DPS: False}):
+            await self.light.async_turn_off()
+
+    async def test_toggle_turns_the_light_on_when_it_was_off(self):
+        self.dps[LIGHT_DPS] = False
+
+        async with assert_device_properties_set(self.light._device, {LIGHT_DPS: True}):
+            await self.light.async_toggle()
+
+    async def test_toggle_turns_the_light_off_when_it_was_on(self):
+        self.dps[LIGHT_DPS] = True
+
+        async with assert_device_properties_set(self.light._device, {LIGHT_DPS: False}):
+            await self.light.async_toggle()
+
+    def test_switch_icon(self):
+        self.assertEqual(self.switch.icon, "mdi:power-sleep")
+
+    def test_switch_is_on(self):
+        self.dps[SLEEP_DPS] = True
+        self.assertEqual(self.switch.is_on, True)
+
+        self.dps[SLEEP_DPS] = False
+        self.assertEqual(self.switch.is_on, False)
+
+    def test_switch_state_attributes(self):
+        self.assertEqual(self.switch.device_state_attributes, {})
+
+    async def test_switch_turn_on(self):
+        async with assert_device_properties_set(self.switch._device, {SLEEP_DPS: True}):
+            await self.switch.async_turn_on()
+
+    async def test_switch_turn_off(self):
+        async with assert_device_properties_set(
+            self.switch._device, {SLEEP_DPS: False}
+        ):
+            await self.switch.async_turn_off()
+
+    async def test_toggle_turns_the_switch_on_when_it_was_off(self):
+        self.dps[SLEEP_DPS] = False
+
+        async with assert_device_properties_set(self.switch._device, {SLEEP_DPS: True}):
+            await self.switch.async_toggle()
+
+    async def test_toggle_turns_the_switch_off_when_it_was_on(self):
+        self.dps[SLEEP_DPS] = True
+
+        async with assert_device_properties_set(
+            self.switch._device, {SLEEP_DPS: False}
+        ):
+            await self.switch.async_toggle()