Browse Source

Implement tests for motion sensor light, and fix issues.

- Implement motion sensor light tests to provide coverage of special
  handling of readonly switch with on/off effects
- Implement testdata in number tests so we can handle inverted ranges
- fix entity naming to follow HA convention
- don't override an already set effect with the turn_on by effect
  workaround, to allow setting to auto when lights are off
- fix typo when turning off by effect dp

PR #356, issue #282
Jason Rumney 3 years ago
parent
commit
bbe6587f0f

+ 1 - 1
custom_components/tuya_local/devices/motion_sensor_light.yaml

@@ -52,7 +52,7 @@ secondary_entities:
         mapping:
           - step: 10
   - entity: number
-    name: Light Level
+    name: Light level
     icon: "mdi:theme-light-dark"
     category: config
     dps:

+ 5 - 4
custom_components/tuya_local/generic/light.py

@@ -313,9 +313,10 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
             ):
                 # Special case for motion sensor lights with readonly switch
                 # that have tristate switch available as effect
-                settings = settings | self._effect_dps.get_values_to_set(
-                    self._device, "on"
-                )
+                if self._effect_dps.id not in settings:
+                    settings = settings | self._effect_dps.get_values_to_set(
+                        self._device, "on"
+                    )
             else:
                 settings = settings | self._switch_dps.get_values_to_set(
                     self._device, True
@@ -329,7 +330,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
             if (
                 self._switch_dps.readonly
                 and self._effect_dps
-                and "off" in _self._effect_dps.values(self._device)
+                and "off" in self._effect_dps.values(self._device)
             ):
                 # Special case for motion sensor lights with readonly switch
                 # that have tristate switch available as effect

+ 9 - 0
tests/const.py

@@ -1524,3 +1524,12 @@ TREATLIFE_DS02F_PAYLOAD = {
     "2": 0,
     "3": "level_2",
 }
+
+MOTION_LIGHT_PAYLOAD = {
+    "101": "mode_auto",
+    "102": False,
+    "103": 0,
+    "104": 249,
+    "105": 374,
+    "106": False,
+}

+ 101 - 0
tests/devices/test_motion_sensor_light.py

@@ -0,0 +1,101 @@
+from homeassistant.const import LIGHT_LUX, UnitOfTime
+
+from ..const import MOTION_LIGHT_PAYLOAD
+from ..helpers import assert_device_properties_set
+from ..mixins.number import MultiNumberTests
+from ..mixins.switch import BasicSwitchTests
+from .base_device_tests import TuyaDeviceTestCase
+
+EFFECT_DPS = "101"
+SWITCH_DPS = "102"
+PROX_DPS = "103"
+TIME_DPS = "104"
+LUX_DPS = "105"
+RESET_DPS = "106"
+
+
+class TestMotionLight(BasicSwitchTests, MultiNumberTests, TuyaDeviceTestCase):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig("motion_sensor_light.yaml", MOTION_LIGHT_PAYLOAD)
+        self.subject = self.entities.get("light")
+
+        self.setUpBasicSwitch(RESET_DPS, self.entities.get("switch_auto_reset"))
+        self.setUpMultiNumber(
+            [
+                {
+                    "dps": PROX_DPS,
+                    "name": "number_sensitivity",
+                    "min": 0,
+                    "max": 4,
+                    "testdata": (1, 3),
+                },
+                {
+                    "dps": TIME_DPS,
+                    "name": "number_duration",
+                    "min": 10,
+                    "max": 900,
+                    "step": 10,
+                    "unit": UnitOfTime.SECONDS,
+                },
+                {
+                    "dps": LUX_DPS,
+                    "name": "number_light_level",
+                    "min": 0,
+                    "max": 3900,
+                    "unit": LIGHT_LUX,
+                    "testdata": (1900, 2000),
+                },
+            ]
+        )
+        self.mark_secondary(
+            [
+                "number_duration",
+                "number_light_level",
+                "number_sensitivity",
+                "switch_auto_reset",
+            ]
+        )
+
+    def test_effects(self):
+        self.assertCountEqual(
+            self.subject.effect_list,
+            ["auto", "off", "on"],
+        )
+
+    def test_effect(self):
+        self.dps[EFFECT_DPS] = "mode_on"
+        self.assertEqual(self.subject.effect, "on")
+        self.dps[EFFECT_DPS] = "mode_off"
+        self.assertEqual(self.subject.effect, "off")
+        self.dps[EFFECT_DPS] = "mode_auto"
+        self.assertEqual(self.subject.effect, "auto")
+
+    def test_is_on_reflects_switch(self):
+        self.dps[SWITCH_DPS] = True
+        self.assertTrue(self.subject.is_on)
+        self.dps[SWITCH_DPS] = False
+        self.assertFalse(self.subject.is_on)
+
+    async def test_turn_on_via_effect(self):
+        self.dps[SWITCH_DPS] = False
+        async with assert_device_properties_set(
+            self.subject._device,
+            {EFFECT_DPS: "mode_on"},
+        ):
+            await self.subject.async_turn_on()
+
+    async def test_turn_off_via_effect(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {EFFECT_DPS: "mode_off"},
+        ):
+            await self.subject.async_turn_off()
+
+    async def test_set_to_auto(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {EFFECT_DPS: "mode_auto"},
+        ):
+            await self.subject.async_turn_on(effect="auto")

+ 54 - 17
tests/mixins/number.py

@@ -4,7 +4,16 @@ from ..helpers import assert_device_properties_set
 
 class BasicNumberTests:
     def setUpBasicNumber(
-        self, dps, subject, max, min=0, step=1, mode="auto", scale=1, unit=None
+        self,
+        dps,
+        subject,
+        max,
+        min=0,
+        step=1,
+        mode="auto",
+        scale=1,
+        unit=None,
+        testdata=None,
     ):
         self.basicNumber = subject
         self.basicNumberDps = dps
@@ -14,6 +23,7 @@ class BasicNumberTests:
         self.basicNumberMode = mode
         self.basicNumberScale = scale
         self.basicNumberUnit = unit
+        self.basicNumberTestData = testdata
 
     def test_number_min_value(self):
         self.assertEqual(self.basicNumber.native_min_value, self.basicNumberMin)
@@ -33,14 +43,29 @@ class BasicNumberTests:
         )
 
     def test_number_value(self):
-        val = min(max(self.basicNumberMin, self.basicNumberStep), self.basicNumberMax)
-        dps_val = val * self.basicNumberScale
-        self.dps[self.basicNumberDps] = dps_val
-        self.assertEqual(self.basicNumber.native_value, val)
+        if self.basicNumberTestData:
+            val = self.basicNumberTestData[0]
+            expected = self.basicNumberTestData[1]
+        else:
+            expected = min(
+                max(self.basicNumberMin, self.basicNumberStep), self.basicNumberMax
+            )
+            val = expected * self.basicNumberScale
+
+        self.dps[self.basicNumberDps] = val
+        self.assertEqual(self.basicNumber.native_value, expected)
 
     async def test_number_set_value(self):
-        val = min(max(self.basicNumberMin, self.basicNumberStep), self.basicNumberMax)
-        dps_val = val * self.basicNumberScale
+        if self.basicNumberTestData:
+            dps_val = self.basicNumberTestData[0]
+            val = self.basicNumberTestData[1]
+        else:
+            val = min(
+                max(self.basicNumberMin, self.basicNumberStep),
+                self.basicNumberMax,
+            )
+            dps_val = val * self.basicNumberScale
+
         async with assert_device_properties_set(
             self.basicNumber._device, {self.basicNumberDps: dps_val}
         ):
@@ -60,6 +85,7 @@ class MultiNumberTests:
         self.multiNumberMode = {}
         self.multiNumberScale = {}
         self.multiNumberUnit = {}
+        self.multiNumberTestData = {}
 
         for n in numbers:
             name = n.get("name")
@@ -74,6 +100,7 @@ class MultiNumberTests:
             self.multiNumberMode[name] = n.get("mode", "auto")
             self.multiNumberScale[name] = n.get("scale", 1)
             self.multiNumberUnit[name] = n.get("unit", None)
+            self.multiNumberTestData[name] = n.get("testdata", None)
 
     def test_multi_number_min_value(self):
         for key, subject in self.multiNumber.items():
@@ -117,21 +144,31 @@ class MultiNumberTests:
 
     def test_multi_number_value(self):
         for key, subject in self.multiNumber.items():
-            val = min(
-                max(self.multiNumberMin[key], self.multiNumberStep[key]),
-                self.multiNumberMax[key],
-            )
-            dps_val = val * self.multiNumberScale[key]
+            if self.multiNumberTestData[key]:
+                val = self.multiNumberTestData[key][1]
+                dps_val = self.multiNumberTestData[key][0]
+            else:
+                val = min(
+                    max(self.multiNumberMin[key], self.multiNumberStep[key]),
+                    self.multiNumberMax[key],
+                )
+                dps_val = val * self.multiNumberScale[key]
+
             self.dps[self.multiNumberDps[key]] = dps_val
             self.assertEqual(subject.native_value, val, f"{key} value mismatch")
 
     async def test_multi_number_set_value(self):
         for key, subject in self.multiNumber.items():
-            val = min(
-                max(self.multiNumberMin[key], self.multiNumberStep[key]),
-                self.multiNumberMax[key],
-            )
-            dps_val = val * self.multiNumberScale[key]
+            if self.multiNumberTestData[key]:
+                val = self.multiNumberTestData[key][1]
+                dps_val = self.multiNumberTestData[key][0]
+            else:
+                val = min(
+                    max(self.multiNumberMin[key], self.multiNumberStep[key]),
+                    self.multiNumberMax[key],
+                )
+                dps_val = val * self.multiNumberScale[key]
+
             async with assert_device_properties_set(
                 subject._device,
                 {self.multiNumberDps[key]: dps_val},