Parcourir la source

Lefant M213: add fan_speed dp, separate cmd from status.

- additional dps identified for status and fan speed (suction level)
- changes in generic vacuum to allow separation of commands from status.

Status basically follows command, though it does distinguish charging and standby states when docked.  There are possibly other states such as errors...
Jason Rumney il y a 3 ans
Parent
commit
bccc9d9cab

+ 29 - 5
custom_components/tuya_local/devices/lefant_m213_vacuum.yaml

@@ -10,7 +10,7 @@ primary_entity:
       name: activate
     - id: 3
       type: string
-      name: status
+      name: command
       mapping:
         - dps_val: standby
           value: standby
@@ -40,7 +40,24 @@ primary_entity:
           value: stop
     - id: 5
       type: string
-      name: unknown_5
+      name: status
+      mapping:
+        - dps_val: "0"
+          value: paused
+        - dps_val: "1"
+          value: smart
+        - dps_val: "2"
+          value: wall follow
+        - dps_val: "3"
+          value: spiral
+        - dps_val: "4"
+          value: returning
+        - dps_val: "5"
+          value: charging
+        - dps_val: "6"
+          value: random
+        - dps_val: "7"
+          value: standby
     - id: 6
       name: battery
       type: integer
@@ -73,13 +90,20 @@ primary_entity:
           value: collision
     - id: 101
       type: string
-      name: unknown_101
+      name: fan_speed
+      mapping:
+        - dps_val: low
+          value: Low
+        - dps_val: nar
+          value: Medium
+        - dps_val: high
+          value: High
     - id: 102
       type: integer
       name: unknown_102
     - id: 103
       type: integer
-      name: unknown_103
+      name: heading
     - id: 104
       type: integer
       name: unknown_104
@@ -88,7 +112,7 @@ primary_entity:
 #    - id: 106
 #      type: string
 #      name: unknown_106
-# dp 108 returned only by Lefant M213, not be M213S or APOSEN A550
+# dp 108 returned only sometimes?
 # seems to be some encoded ASCII descriptor of battery voltage
 #    - id: 108
 #      type: string

+ 17 - 16
custom_components/tuya_local/generic/vacuum.py

@@ -28,6 +28,7 @@ class TuyaLocalVacuum(TuyaLocalEntity, StateVacuumEntity):
         """
         dps_map = self._init_begin(device, config)
         self._status_dps = dps_map.get("status")
+        self._command_dps = dps_map.get("command")
         self._locate_dps = dps_map.get("locate")
         self._power_dps = dps_map.get("power")
         self._active_dps = dps_map.get("activate")
@@ -59,10 +60,11 @@ class TuyaLocalVacuum(TuyaLocalEntity, StateVacuumEntity):
         if self._locate_dps:
             support |= VacuumEntityFeature.LOCATE
 
-        status_support = self._status_dps.values(self._device)
-        if SERVICE_RETURN_TO_BASE in status_support:
+        cmd_dps = self._command_dps or self._status_dps
+        cmd_support = cmd_dps.values(self._device)
+        if SERVICE_RETURN_TO_BASE in cmd_support:
             support |= VacuumEntityFeature.RETURN_HOME
-        if SERVICE_CLEAN_SPOT in status_support:
+        if SERVICE_CLEAN_SPOT in cmd_support:
             support |= VacuumEntityFeature.CLEAN_SPOT
         return support
 
@@ -80,12 +82,12 @@ class TuyaLocalVacuum(TuyaLocalEntity, StateVacuumEntity):
     @property
     def state(self):
         """Return the state of the vacuum cleaner."""
-        status = self._status_dps.get_value(self._device)
+        status = self.status
         if self._error_dps and self._error_dps.get_value(self._device) != 0:
             return STATE_ERROR
-        elif status == SERVICE_RETURN_TO_BASE:
+        elif status in [SERVICE_RETURN_TO_BASE, "returning"]:
             return STATE_RETURNING
-        elif status == "standby":
+        elif status in ["standby", "charging"]:
             return STATE_DOCKED
         elif self._power_dps and not self._power_dps.get_value(self._device):
             return STATE_DOCKED
@@ -124,17 +126,15 @@ class TuyaLocalVacuum(TuyaLocalEntity, StateVacuumEntity):
 
     async def async_return_to_base(self, **kwargs):
         """Tell the vacuum cleaner to return to its base."""
-        if self._status_dps and SERVICE_RETURN_TO_BASE in self._status_dps.values(
-            self._device
-        ):
-            await self._status_dps.async_set_value(self._device, SERVICE_RETURN_TO_BASE)
+        dps = self._command_dps or self._status_dps
+        if dps and SERVICE_RETURN_TO_BASE in dps.values(self._device):
+            await dps.async_set_value(self._device, SERVICE_RETURN_TO_BASE)
 
     async def async_clean_spot(self, **kwargs):
         """Tell the vacuum cleaner do a spot clean."""
-        if self._status_dps and SERVICE_CLEAN_SPOT in self._status_dps.values(
-            self._device
-        ):
-            await self._status_dps.async_set_value(self._device, SERVICE_CLEAN_SPOT)
+        dps = self._command_dps or self._status_dps
+        if dps and SERVICE_CLEAN_SPOT in dps.values(self._device):
+            await dps.async_set_value(self._device, SERVICE_CLEAN_SPOT)
 
     async def async_locate(self, **kwargs):
         """Locate the vacuum cleaner."""
@@ -143,8 +143,9 @@ class TuyaLocalVacuum(TuyaLocalEntity, StateVacuumEntity):
 
     async def async_send_command(self, command, params=None, **kwargs):
         """Send a command to the vacuum cleaner."""
-        if command in self._status_dps.values(self._device):
-            await self._status_dps.async_set_value(self._device, command)
+        dps = self._command_dps or self._status_dps
+        if command in dps.values(self._device):
+            await dps.async_set_value(self._device, command)
         elif self._direction_dps and command in self._direction_dps.values(
             self._device
         ):

+ 1 - 1
requirements-dev.txt

@@ -4,5 +4,5 @@ pytest-homeassistant-custom-component>=0.9.0
 pytest
 pytest-asyncio
 pytest-cov
-pycryptodome~=3.14.1
+pycryptodome~=3.15.0
 tinytuya~=1.6.0

+ 1 - 1
requirements.txt

@@ -1,2 +1,2 @@
-pycryptodome~=3.14.1
+pycryptodome~=3.15.0
 tinytuya~=1.6.0

+ 35 - 22
tests/devices/test_lefant_m213_vacuum.py

@@ -17,15 +17,15 @@ from .base_device_tests import TuyaDeviceTestCase
 
 POWER_DPS = "1"
 SWITCH_DPS = "2"
-STATUS_DPS = "3"
+COMMAND_DPS = "3"
 DIRECTION_DPS = "4"
-UNKNOWN5_DPS = "5"
+STATUS_DPS = "5"
 BATTERY_DPS = "6"
 LOCATE_DPS = "13"
 AREA_DPS = "16"
 TIME_DPS = "17"
 ERROR_DPS = "18"
-UNKNOWN101_DPS = "101"
+FAN_DPS = "101"
 UNKNOWN102_DPS = "102"
 UNKNOWN103_DPS = "103"
 UNKNOWN104_DPS = "104"
@@ -70,6 +70,7 @@ class TestLefantM213Vacuum(MultiSensorTests, TuyaDeviceTestCase):
                 | VacuumEntityFeature.LOCATE
                 | VacuumEntityFeature.RETURN_HOME
                 | VacuumEntityFeature.CLEAN_SPOT
+                | VacuumEntityFeature.FAN_SPEED
             ),
         )
 
@@ -77,29 +78,41 @@ class TestLefantM213Vacuum(MultiSensorTests, TuyaDeviceTestCase):
         self.dps[BATTERY_DPS] = 50
         self.assertEqual(self.subject.battery_level, 50)
 
+    def test_fan_speed(self):
+        self.dps[FAN_DPS] = "low"
+        self.assertEqual(self.subject.fan_speed, "Low")
+        self.dps[FAN_DPS] = "nar"
+        self.assertEqual(self.subject.fan_speed, "Medium")
+        self.dps[FAN_DPS] = "high"
+        self.assertEqual(self.subject.fan_speed, "High")
+
     def test_status(self):
-        self.dps[STATUS_DPS] = "standby"
-        self.assertEqual(self.subject.status, "standby")
-        self.dps[STATUS_DPS] = "smart"
+        self.dps[STATUS_DPS] = "0"
+        self.assertEqual(self.subject.status, "paused")
+        self.dps[STATUS_DPS] = "1"
         self.assertEqual(self.subject.status, "smart")
-        self.dps[STATUS_DPS] = "chargego"
-        self.assertEqual(self.subject.status, "return_to_base")
-        self.dps[STATUS_DPS] = "random"
+        self.dps[STATUS_DPS] = "2"
+        self.assertEqual(self.subject.status, "wall follow")
+        self.dps[STATUS_DPS] = "3"
+        self.assertEqual(self.subject.status, "spiral")
+        self.dps[STATUS_DPS] = "4"
+        self.assertEqual(self.subject.status, "returning")
+        self.dps[STATUS_DPS] = "5"
+        self.assertEqual(self.subject.status, "charging")
+        self.dps[STATUS_DPS] = "6"
         self.assertEqual(self.subject.status, "random")
-        self.dps[STATUS_DPS] = "wall_follow"
-        self.assertEqual(self.subject.status, "wall_follow")
-        self.dps[STATUS_DPS] = "spiral"
-        self.assertEqual(self.subject.status, "clean_spot")
+        self.dps[STATUS_DPS] = "7"
+        self.assertEqual(self.subject.status, "standby")
 
     def test_state(self):
         self.dps[POWER_DPS] = True
         self.dps[SWITCH_DPS] = True
         self.dps[ERROR_DPS] = 0
-        self.dps[STATUS_DPS] = "return_to_base"
+        self.dps[STATUS_DPS] = "4"
         self.assertEqual(self.subject.state, STATE_RETURNING)
-        self.dps[STATUS_DPS] = "standby"
+        self.dps[STATUS_DPS] = "7"
         self.assertEqual(self.subject.state, STATE_DOCKED)
-        self.dps[STATUS_DPS] = "random"
+        self.dps[STATUS_DPS] = "6"
         self.assertEqual(self.subject.state, STATE_CLEANING)
         self.dps[POWER_DPS] = False
         self.assertEqual(self.subject.state, STATE_DOCKED)
@@ -148,14 +161,14 @@ class TestLefantM213Vacuum(MultiSensorTests, TuyaDeviceTestCase):
     async def test_async_return_to_base(self):
         async with assert_device_properties_set(
             self.subject._device,
-            {STATUS_DPS: "chargego"},
+            {COMMAND_DPS: "chargego"},
         ):
             await self.subject.async_return_to_base()
 
     async def test_async_clean_spot(self):
         async with assert_device_properties_set(
             self.subject._device,
-            {STATUS_DPS: "spiral"},
+            {COMMAND_DPS: "spiral"},
         ):
             await self.subject.async_clean_spot()
 
@@ -169,28 +182,28 @@ class TestLefantM213Vacuum(MultiSensorTests, TuyaDeviceTestCase):
     async def test_async_send_standby_command(self):
         async with assert_device_properties_set(
             self.subject._device,
-            {STATUS_DPS: "standby"},
+            {COMMAND_DPS: "standby"},
         ):
             await self.subject.async_send_command("standby")
 
     async def test_async_send_smart_command(self):
         async with assert_device_properties_set(
             self.subject._device,
-            {STATUS_DPS: "smart"},
+            {COMMAND_DPS: "smart"},
         ):
             await self.subject.async_send_command("smart")
 
     async def test_async_send_random_command(self):
         async with assert_device_properties_set(
             self.subject._device,
-            {STATUS_DPS: "random"},
+            {COMMAND_DPS: "random"},
         ):
             await self.subject.async_send_command("random")
 
     async def test_async_send_wall_follow_command(self):
         async with assert_device_properties_set(
             self.subject._device,
-            {STATUS_DPS: "wall_follow"},
+            {COMMAND_DPS: "wall_follow"},
         ):
             await self.subject.async_send_command("wall_follow")