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

Refactor and simplify value mapping functions.

Some logic surrounding defaults changed to simplify it, but it does not affect existing configs.

Remove a redundant level in fan config.
Jason Rumney 4 лет назад
Родитель
Сommit
3d4cc2fd68

+ 22 - 23
custom_components/tuya_local/devices/goldair_fan.yaml

@@ -15,29 +15,28 @@ primary_entity:
       name: hvac_mode
     - id: 2
       type: integer
-      range:
-        constraint: preset_mode
-        conditions:
-          - dps_val: normal
-            range:
-              min: 1
-              max: 12
-          - dps_val: nature
-            mapping:
-              - dps_val: 4
-                value: low
-              - dps_val: 8
-                value: medium
-              - dps_val: 12
-                value: high
-          - dps_val: sleep
-            mapping:
-              - dps_val: 4
-                value: low
-              - dps_val: 8
-                value: medium
-              - dps_val: 12
-                value: high            
+      constraint: preset_mode
+      conditions:
+        - dps_val: normal
+          range:
+            min: 1
+            max: 12
+        - dps_val: nature
+          mapping:
+            - dps_val: 4
+              value: low
+            - dps_val: 8
+              value: medium
+            - dps_val: 12
+              value: high
+        - dps_val: sleep
+          mapping:
+            - dps_val: 4
+              value: low
+            - dps_val: 8
+              value: medium
+            - dps_val: 12
+              value: high            
       name: fan_mode
     - id: 3
       type: string

+ 127 - 108
custom_components/tuya_local/helpers/device_config.py

@@ -84,6 +84,29 @@ class TuyaDeviceConfig:
         _LOGGER.debug("Matched config for %s", self.name)
         return True
 
+    def _entity_match_analyse(self, entity, keys, matched, dps):
+        """
+        Determine whether this entity can be a match for the dps
+          Args:
+            entity - the TuyaEntityConfig to check against
+            keys - the unmatched keys for the device
+            matched - the matched keys for the device
+            dps - the dps values to be matched
+        Side Effects:
+            Moves items from keys to matched if they match dps
+        Return Value:
+            True if all dps in entity could be matched to dps, False otherwise
+        """
+        for d in entity.dps():
+            if (d.id not in keys and d.id not in matched) or not _typematch(
+                d.type, dps[d.id]
+            ):
+                return False
+            if d.id in keys:
+                matched.append(d.id)
+                keys.remove(d.id)
+        return True
+
     def match_quality(self, dps):
         """Determine the match quality for the provided dps map."""
         keys = list(dps.keys())
@@ -91,22 +114,13 @@ class TuyaDeviceConfig:
         if "updated_at" in keys:
             keys.remove("updated_at")
         total = len(keys)
-        for d in self.primary_entity.dps():
-            if d.id not in keys or not _typematch(d.type, dps[d.id]):
-                if d.id not in matched:
-                    return 0
-            if d.id in keys:
-                matched.append(d.id)
-                keys.remove(d.id)
+        if not self._entity_match_analyse(self.primary_entity, keys, matched, dps):
+            return 0
+
+        for e in self.secondary_entities():
+            if not self._entity_match_analyse(e, keys, matched, dps):
+                return 0
 
-        for dev in self.secondary_entities():
-            for d in dev.dps():
-                if d.id not in keys or not _typematch(d.type, dps[d.id]):
-                    if d.id not in matched:
-                        return 0
-                if d.id in keys:
-                    matched.append(d.id)
-                    keys.remove(d.id)
         return round((total - len(keys)) * 100 / total)
 
 
@@ -226,112 +240,117 @@ class TuyaDpsConfig:
     def hidden(self):
         return "hidden" in self._config.keys() and self._config["hidden"] is True
 
+    def _find_map_for_dps(self, value):
+        if "mapping" not in self._config.keys():
+            return None
+        default = None
+        for map in self._config["mapping"]:
+            if "dps_val" not in map:
+                default = map
+            elif str(map["dps_val"]) == str(value):
+                return map
+        return default
+
     def _map_from_dps(self, value, device):
         result = value
-        replaced = False
-        default_value = None
-        scale = 1
-        if "mapping" in self._config.keys():
-            for map in self._config["mapping"]:
-                if "dps_val" not in map:
-                    if "value" in map:
-                        default_value = map["value"]
-                    if "scale" in map:
-                        scale = map["scale"]
-
-                elif str(map["dps_val"]) == str(value):
-                    if "value" in map:
-                        result = map["value"]
+        map = self._find_map_for_dps(value)
+        if map is not None:
+            scale = map.get("scale", 1)
+            if not isinstance(scale, (int, float)):
+                scale = 1
+            replaced = "value" in map
+            result = map.get("value", result)
+            if "conditions" in map:
+                cond_dps = (
+                    self
+                    if "constraint" not in map
+                    else self._entity.find_dps(map["constraint"])
+                )
+                for c in map["conditions"]:
+                    if (
+                        "dps_val" in c
+                        and c["dps_val"] == device.get_property(cond_dps.id)
+                        and "value" in c
+                    ):
+                        result = c["value"]
                         replaced = True
-                    if "conditions" in map:
-                        cond_dps = self
-                        if "constraint" in map:
-                            cond_dps = self._entity.find_dps(map["constraint"])
-                        for c in map["conditions"]:
-                            if (
-                                "dps_val" in c
-                                and c["dps_val"] == device.get_property(cond_dps.id)
-                                and "value" in c
-                            ):
-                                result = c["value"]
-                                replaced = True
-
-        if not replaced and default_value is not None:
-            result = default_value
-            replaced = True
-
-        if scale != 1 and isinstance(result, (int, float)):
-            result = result / scale
-            replaced = True
-
-        if replaced:
-            _LOGGER.debug(
-                "%s: Mapped dps %s value from %s to %s",
-                self._entity._device.name,
-                self.id,
-                value,
-                result,
-            )
+
+            if scale != 1 and isinstance(result, (int, float)):
+                result = result / scale
+                replaced = True
+
+            if replaced:
+                _LOGGER.debug(
+                    "%s: Mapped dps %s value from %s to %s",
+                    self._entity._device.name,
+                    self.id,
+                    value,
+                    result,
+                )
 
         return result
 
+    def _find_map_for_value(self, value):
+        if "mapping" not in self._config.keys():
+            return None
+        default = None
+        for map in self._config["mapping"]:
+            if "dps_val" not in map:
+                default = map
+            if "value" in map and str(map["value"]) == str(value):
+                return map
+            if "conditions" in map:
+                for c in map["conditions"]:
+                    if "value" in c and c["value"] == value:
+                        return map
+        return default
+
     def _map_to_dps(self, value, device):
         result = value
-        replaced = False
-        scale = 1
-        step = None
-        if "mapping" in self._config.keys():
-            for map in self._config["mapping"]:
-
-                if (
-                    "value" in map
-                    and "dps_val" in map
-                    and str(map["value"]) == str(value)
-                ):
-                    result = map["dps_val"]
-                    replaced = True
-                elif "conditions" in map:
-                    for c in map["conditions"]:
-                        if "value" in c and c["value"] == value:
-                            result = map["dps_val"]
-                            c_dps = self._entity.find_dps(map["constraint"])
-                            device.set_property(c_dps.id, c["dps_val"])
-                if (
-                    "scale" in map
-                    and "value" not in map
-                    and isinstance(map["scale"], (int, float))
-                ):
-                    scale = map["scale"]
-                if (
-                    "step" in map
-                    and "value" not in map
-                    and isinstance(map["step"], (int, float))
-                ):
-                    step = map["step"]
-
-        if scale != 1 and isinstance(result, (int, float)):
-            result = result / scale
-            replaced = True
-        if step is not None and isinstance(result, (int, float)):
-            result = step * round(float(result) / step)
-            replaced = True
+        map = self._find_map_for_value(value)
+        if map is not None:
+            replaced = False
+            scale = map.get("scale", 1)
+            if not isinstance(scale, (int, float)):
+                scale = 1
+            step = map.get("step", None)
+            if not isinstance(step, (int, float)):
+                step = None
+            if "dps_val" in map:
+                result = map["dps_val"]
+                replaced = True
+            # Conditions may have side effect of setting another value.
+            if "conditions" in map and "constraint" in map:
+                c_dps = self._entity.find_dps(map["constraint"])
+                for c in map["conditions"]:
+                    if "value" in c and c["value"] == value:
+                        device.set_property(c_dps.id, c["dps_val"])
+
+            if scale != 1 and isinstance(result, (int, float)):
+                result = result / scale
+                replaced = True
+            if step is not None and isinstance(result, (int, float)):
+                result = step * round(float(result) / step)
+                replaced = True
+
+            if replaced:
+                _LOGGER.debug(
+                    "%s: Mapped dps %s to %s from %s",
+                    self._entity._device.name,
+                    self.id,
+                    result,
+                    value,
+                )
 
         if self.range is not None:
-            min = self.range["min"]
-            max = self.range["max"]
-            if result < min or result > max:
+            minimum = self.range["min"]
+            maximum = self.range["max"]
+            if result < minimum or result > maximum:
                 raise ValueError(
-                    f"Target {self.name} ({value}) must be between {min} and {max}"
+                    f"Target {self.name} ({value}) must be between "
+                    f"{minimum} and {maximum}"
                 )
 
-        if replaced:
-            _LOGGER.debug(
-                "%s: Mapped dps %s to %s from %s",
-                self._entity._device.name,
-                self.id,
-                result,
-                value,
-            )
         return result