Browse Source

fix (device_config): use pending changes when setting multiple dps

When a dps is reused multiple times with different masks, and those
attributes get set together in a single function, the different values
were overwriting each other.

In this case, allow the pending changes that have already been
calculated but not actioned yet to be passed in so they can be used
instead of the current value from the device in further
recalculations.

A more general solution to the suggested change for time entity in
PR #3902
Jason Rumney 3 tháng trước cách đây
mục cha
commit
fc6ddd81b2

+ 2 - 2
custom_components/tuya_local/climate.py

@@ -269,11 +269,11 @@ class TuyaLocalClimate(TuyaLocalEntity, ClimateEntity):
         dps_map = {}
         if low is not None and self._temp_low_dps is not None:
             dps_map.update(
-                self._temp_low_dps.get_values_to_set(self._device, low),
+                self._temp_low_dps.get_values_to_set(self._device, low, dps_map),
             )
         if high is not None and self._temp_high_dps is not None:
             dps_map.update(
-                self._temp_high_dps.get_values_to_set(self._device, high),
+                self._temp_high_dps.get_values_to_set(self._device, high, dps_map),
             )
         if dps_map:
             await self._device.async_set_properties(dps_map)

+ 8 - 4
custom_components/tuya_local/fan.py

@@ -92,7 +92,7 @@ class TuyaLocalFan(TuyaLocalEntity, FanEntity):
         if self._switch_dps:
             settings = {
                 **settings,
-                **self._switch_dps.get_values_to_set(self._device, True),
+                **self._switch_dps.get_values_to_set(self._device, True, settings),
             }
 
         if percentage is not None and self._speed_dps:
@@ -104,12 +104,14 @@ class TuyaLocalFan(TuyaLocalEntity, FanEntity):
 
             settings = {
                 **settings,
-                **self._speed_dps.get_values_to_set(self._device, percentage),
+                **self._speed_dps.get_values_to_set(self._device, percentage, settings),
             }
         if preset_mode and self._preset_dps:
             settings = {
                 **settings,
-                **self._preset_dps.get_values_to_set(self._device, preset_mode),
+                **self._preset_dps.get_values_to_set(
+                    self._device, preset_mode, settings
+                ),
             }
         # TODO: potentially handle other kwargs.
         if settings:
@@ -183,7 +185,9 @@ class TuyaLocalFan(TuyaLocalEntity, FanEntity):
 
         values_to_set = self._speed_dps.get_values_to_set(self._device, percentage)
         if not self.is_on and self._switch_dps:
-            values_to_set.update(self._switch_dps.get_values_to_set(self._device, True))
+            values_to_set.update(
+                self._switch_dps.get_values_to_set(self._device, True, values_to_set)
+            )
 
         await self._device.async_set_properties(values_to_set)
 

+ 11 - 3
custom_components/tuya_local/helpers/device_config.py

@@ -494,6 +494,9 @@ class TuyaDpsConfig:
 
     def decoded_value(self, device):
         v = self._map_from_dps(device.get_property(self.id), device)
+        return self.decode_value(v, device)
+
+    def decode_value(self, v, device):
         if self.rawtype == "hex" and isinstance(v, str):
             try:
                 return bytes.fromhex(v)
@@ -922,10 +925,10 @@ class TuyaDpsConfig:
 
         return c_match
 
-    def get_values_to_set(self, device, value):
+    def get_values_to_set(self, device, value, pending_map={}):
         """Return the dps values that would be set when setting to value"""
         result = value
-        dps_map = {}
+        dps_map = pending_map
         if self.readonly:
             return dps_map
 
@@ -1055,7 +1058,12 @@ class TuyaDpsConfig:
             # Convert to int
             endianness = self.endianness
             mask_scale = mask & (1 + ~mask)
-            current_value = int.from_bytes(self.decoded_value(device), endianness)
+            decoded_value = self.decoded_value(device)
+            # if we have already updated it as part of this update,
+            # use it to preserve bits
+            if self.id in dps_map:
+                decoded_value = self.decode_value(dps_map[self.id], device)
+            current_value = int.from_bytes(decoded_value, endianness)
             result = (current_value & ~mask) | (mask & int(result * mask_scale))
             result = self.encode_value(result.to_bytes(length, endianness))
 

+ 11 - 3
custom_components/tuya_local/light.py

@@ -296,6 +296,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                     **self._brightness_dps.get_values_to_set(
                         self._device,
                         bright,
+                        settings,
                     ),
                 }
         elif self._color_temp_dps and ATTR_COLOR_TEMP_KELVIN in params:
@@ -316,6 +317,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                 **self._color_temp_dps.get_values_to_set(
                     self._device,
                     color_temp,
+                    settings,
                 ),
             }
         elif self._rgbhsv_dps and (
@@ -382,6 +384,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                     **self._rgbhsv_dps.get_values_to_set(
                         self._device,
                         self._rgbhsv_dps.encode_value(binary),
+                        settings,
                     ),
                 }
         elif self._named_color_dps and ATTR_HS_COLOR in params:
@@ -397,6 +400,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                     **self._named_color_dps.get_values_to_set(
                         self._device,
                         best_match,
+                        settings,
                     ),
                 }
         if self._color_mode_dps:
@@ -407,6 +411,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                     **self._color_mode_dps.get_values_to_set(
                         self._device,
                         color_mode,
+                        settings,
                     ),
                 }
             elif not self._effect_dps:
@@ -429,6 +434,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                         **self._color_mode_dps.get_values_to_set(
                             self._device,
                             effect,
+                            settings,
                         ),
                     }
 
@@ -449,6 +455,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                 **self._brightness_dps.get_values_to_set(
                     self._device,
                     bright,
+                    settings,
                 ),
             }
 
@@ -461,6 +468,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                     **self._effect_dps.get_values_to_set(
                         self._device,
                         effect,
+                        settings,
                     ),
                 }
 
@@ -474,11 +482,11 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                 # that have tristate switch available as effect
                 if self._effect_dps.id not in settings:
                     settings = settings | self._effect_dps.get_values_to_set(
-                        self._device, "on"
+                        self._device, "on", settings
                     )
             else:
                 settings = settings | self._switch_dps.get_values_to_set(
-                    self._device, True
+                    self._device, True, settings
                 )
         elif self._brightness_dps and not self.is_on:
             bright = 255
@@ -487,7 +495,7 @@ class TuyaLocalLight(TuyaLocalEntity, LightEntity):
                 bright = color_util.brightness_to_value(r, bright)
 
             settings = settings | self._brightness_dps.get_values_to_set(
-                self._device, bright
+                self._device, bright, settings
             )
 
         if settings:

+ 6 - 5
custom_components/tuya_local/remote.py

@@ -190,12 +190,12 @@ class TuyaLocalRemote(TuyaLocalEntity, RemoteEntity):
         dps = {}
         if self._control_dp:
             # control and code are sent in seperate dps.
-            dps = dps | self._control_dp.get_values_to_set(self._device, CMD_SEND)
-            dps = dps | self._send_dp.get_values_to_set(self._device, code)
+            dps = dps | self._control_dp.get_values_to_set(self._device, CMD_SEND, dps)
+            dps = dps | self._send_dp.get_values_to_set(self._device, code, dps)
             if self._delay_dp:
-                dps = dps | self._delay_dp.get_values_to_set(self._device, delay)
+                dps = dps | self._delay_dp.get_values_to_set(self._device, delay, dps)
             if self._type_dp:
-                dps = dps | self._type_dp.get_values_to_set(self._device, 0)
+                dps = dps | self._type_dp.get_values_to_set(self._device, 0, dps)
         else:
             dps = dps | self._send_dp.get_values_to_set(
                 self._device,
@@ -207,8 +207,9 @@ class TuyaLocalRemote(TuyaLocalEntity, RemoteEntity):
                         "key1": "1" + code,
                         "type": 0,
                         "delay": int(delay),
-                    }
+                    },
                 ),
+                dps,
             )
 
         return dps

+ 4 - 4
custom_components/tuya_local/siren.py

@@ -89,13 +89,13 @@ class TuyaLocalSiren(TuyaLocalEntity, SirenEntity):
 
             set_dps = {
                 **set_dps,
-                **self._tone_dp.get_values_to_set(self._device, tone),
+                **self._tone_dp.get_values_to_set(self._device, tone, set_dps),
             }
 
         if duration is not None and self._duration_dp:
             set_dps = {
                 **set_dps,
-                **self._duration_dp.get_values_to_set(self._device, duration),
+                **self._duration_dp.get_values_to_set(self._device, duration, set_dps),
             }
 
         if volume is not None and self._volume_dp:
@@ -111,13 +111,13 @@ class TuyaLocalSiren(TuyaLocalEntity, SirenEntity):
 
             set_dps = {
                 **set_dps,
-                **self._volume_dp.get_values_to_set(self._device, volume),
+                **self._volume_dp.get_values_to_set(self._device, volume, set_dps),
             }
 
         if self._switch_dp and not self.is_on:
             set_dps = {
                 **set_dps,
-                **self._switch_dp.get_values_to_set(self._device, True),
+                **self._switch_dp.get_values_to_set(self._device, True, set_dps),
             }
 
         await self._device.async_set_properties(set_dps)

+ 9 - 3
custom_components/tuya_local/time.py

@@ -104,17 +104,23 @@ class TuyaLocalTime(TuyaLocalEntity, TimeEntity):
         minutes = value.minute
         seconds = value.second
         if self._hour_dps:
-            settings.update(self._hour_dps.get_values_to_set(self._device, hours))
+            settings.update(
+                self._hour_dps.get_values_to_set(self._device, hours, settings)
+            )
         else:
             minutes = minutes + hours * 60
 
         if self._minute_dps:
-            settings.update(self._minute_dps.get_values_to_set(self._device, minutes))
+            settings.update(
+                self._minute_dps.get_values_to_set(self._device, minutes, settings)
+            )
         else:
             seconds = seconds + minutes * 60
 
         if self._second_dps:
-            settings.update(self._second_dps.get_values_to_set(self._device, seconds))
+            settings.update(
+                self._second_dps.get_values_to_set(self._device, seconds, settings)
+            )
         else:
             _LOGGER.debug(
                 "%s: Discarding unused precision: %d seconds",