Selaa lähdekoodia

Support setting more than one property in a single call.

Tuya devices take some time to be ready for the next call, so setting two
properties individually one after another does not work well.
Add async_set_properties to device.py, to set multiple properties at once.
Expose a method in device_config helper to just return the dps that would be
set without actually setting them, and use these when setting high and low
target temperatures.
Jason Rumney 4 vuotta sitten
vanhempi
commit
63ab9651b6

+ 3 - 0
custom_components/tuya_local/device.py

@@ -147,6 +147,9 @@ class TuyaLocalDevice(object):
     async def async_set_property(self, dps_id, value):
         await self._hass.async_add_executor_job(self.set_property, dps_id, value)
 
+    async def async_set_properties(self, dps_map):
+        await self._hass.async_add_executor_job(self._set_properties(dps_map))
+
     def anticipate_property_value(self, dps_id, value):
         """
         Update a value in the cached state only. This is good for when you know the device will reflect a new state in

+ 5 - 2
custom_components/tuya_local/generic/climate.py

@@ -223,10 +223,13 @@ class TuyaLocalClimate(ClimateEntity):
 
     async def async_set_target_temperature_range(self, low, high):
         """Set the target temperature range."""
+        dps_map = {}
         if low is not None and self._temp_low_dps is not None:
-            await self._temp_low_dps.async_set_value(self._device, low)
+            dps_map.update(self._temp_low_dps.get_values_to_set(self._device, low))
         if high is not None and self._temp_high_dps is not None:
-            await self._temp_high_dps.async_set_value(self._device, high)
+            dps_map.update(self._temp_high_dps.get_values_to_set(self._device, high))
+        if dps_map:
+            await self._device.async_set_properties(dps_map)
 
     @property
     def current_temperature(self):

+ 14 - 4
custom_components/tuya_local/helpers/device_config.py

@@ -222,8 +222,11 @@ class TuyaDpsConfig:
         """Set the value of the dps in the given device to given value."""
         if self.readonly:
             raise TypeError(f"{self.name} is read only")
+        if self.invalid:
+            raise AttributeError(f"{self.name} cannot be set at this time")
 
-        await device.async_set_property(self.id, self._map_to_dps(value, device))
+        settings = self.get_values_to_set(device, value)
+        await device.async_set_properties(settings)
 
     @property
     def values(self):
@@ -266,6 +269,10 @@ class TuyaDpsConfig:
     def readonly(self):
         return "readonly" in self._config.keys() and self._config["readonly"] is True
 
+    @property
+    def invalid(self):
+        return False
+
     @property
     def hidden(self):
         return "hidden" in self._config.keys() and self._config["hidden"] is True
@@ -335,8 +342,10 @@ class TuyaDpsConfig:
                         return m
         return default
 
-    def _map_to_dps(self, value, device):
+    def get_values_to_set(self, device, value):
+        """Return the dps values that would be set when setting to value"""
         result = value
+        dps_map = {}
         mapping = self._find_map_for_value(value)
         if mapping is not None:
             replaced = False
@@ -354,7 +363,7 @@ class TuyaDpsConfig:
                 c_dps = self._entity.find_dps(mapping["constraint"])
                 for c in mapping["conditions"]:
                     if "value" in c and c["value"] == value:
-                        device.set_property(c_dps.id, c["dps_val"])
+                        dps_map.update(c_dps.get_values_to_set(device, c["dps_val"]))
 
             if scale != 1 and isinstance(result, (int, float)):
                 _LOGGER.debug(f"Scaling {result} by {scale}")
@@ -393,7 +402,8 @@ class TuyaDpsConfig:
             result = float(result)
         elif self.type is str:
             result = str(result)
-        return result
+        dps_map[self.id] = result
+        return dps_map
 
 
 def available_configs():

+ 25 - 11
tests/helpers.py

@@ -7,20 +7,28 @@ from custom_components.tuya_local.device import TuyaLocalDevice
 @asynccontextmanager
 async def assert_device_properties_set(device: TuyaLocalDevice, properties: dict):
     results = []
+    provided = {}
 
     def generate_result(*args):
         result = AsyncMock()
         results.append(result)
+        provided[args[0]] = args[1]
         return result()
 
-    device.async_set_property.side_effect = generate_result
+    def generate_results(*args):
+        result = AsyncMock()
+        results.append(result)
+        provided.update(args[0])
+        return result()
 
+    device.async_set_property.side_effect = generate_result
+    device.async_set_properties.side_effect = generate_results
     try:
         yield
     finally:
-        assert device.async_set_property.call_count == len(properties.keys())
-        for key in properties.keys():
-            device.async_set_property.assert_any_call(key, properties[key])
+        assert len(provided) == len(properties.keys())
+        for p in properties:
+            assert p in provided
         for result in results:
             result.assert_awaited()
 
@@ -32,23 +40,29 @@ async def assert_device_properties_set_optional(
     optional_properties: dict,
 ):
     results = []
+    provided = {}
 
     def generate_result(*args):
         result = AsyncMock()
         results.append(result)
+        provided[args[0]] = args[1]
         return result()
 
-    device.async_set_property.side_effect = generate_result
+    def generate_results(*args):
+        result = AsyncMock()
+        results.append(result)
+        provided.update(args[0])
+        return result()
 
+    device.async_set_property.side_effect = generate_result
+    device.async_set_properties.side_effect = generate_results
     try:
         yield
     finally:
-        assert (device.async_set_property.call_count >= len(properties.keys())) and (
-            device.async_set_property.call_count
-            <= len(properties.keys()) + len(optional_properties.keys())
+        assert len(provided) >= len(properties.keys()) and (
+            len(provided) <= len(properties.keys()) + len(optional_properties.keys())
         )
-
-        for key in properties.keys():
-            device.async_set_property.assert_any_call(key, properties[key])
+        for p in properties:
+            assert p in provided
         for result in results:
             result.assert_awaited()