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

Wistar roller blind: Add support

Issue #228

- cover: add support for "reversed" dp (control_back in tuya docs),
         assuming the purpose is to indicate/control when the
         direction of open/close is reversed.
Jason Rumney 3 лет назад
Родитель
Сommit
cdd633f36f

+ 141 - 0
custom_components/tuya_local/devices/wistar_roller_blind.yaml

@@ -0,0 +1,141 @@
+name: Wistar Roller Blinds
+product:
+  - id: c4hBLohzUgxULw5a
+primary_entity:
+  entity: cover
+  class: blind
+  dps:
+    - id: 101
+      type: string
+      name: control
+      mapping:
+        - dps_val: "0"
+          value: open
+        - dps_val: "1"
+          value: stop
+        - dps_val: "2"
+          value: close
+    - id: 102
+      type: integer
+      name: position
+      range:
+        min: 0
+        max: 100
+    - id: 103
+      type: boolean
+      name: reversed
+    - id: 105
+      type: boolean
+      name: curtain_hand
+    - id: 106
+      type: string
+      name: state
+      mapping:
+        - dps_val: "0"
+          value: status_0
+        - dps_val: "1"
+          value: status_1
+        - dps_val: "2"
+          value: status_2
+        - dps_val: "3"
+          value: status_3
+    - id: 107
+      type: string
+      name: ac_control
+      mapping:
+        - dps_val: "1"
+          value: control_1
+        - dps_val: "2"
+          value: control_2
+        - dps_val: "3"
+          value: control_3
+        - dps_val: "4"
+          value: control_4
+    - id: 108
+      type: string
+      name: dc_control
+      mapping:
+        - dps_val: "1"
+          value: control_1
+        - dps_val: "2"
+          value: control_2
+        - dps_val: "3"
+          value: control_3
+        - dps_val: "4"
+          value: control_4
+    - id: 109
+      type: string
+      name: curtain_type
+      optional: true
+    - id: 110
+      type: string
+      name: report
+      optional: true
+    - id: 111
+      type: boolean
+      name: border_state
+      readonly: true
+    - id: 112
+      type: integer
+      name: current_position
+secondary_entities:
+  - entity: select
+    name: Border Setting
+    category: config
+    icon: "mdi:border-outside"
+    dps:
+      - id: 104
+        type: string
+        name: option
+        mapping:
+          - dps_val: "1"
+            value: Upper Limit Set
+          - dps_val: "2"
+            value: Upper Limit Remove
+          - dps_val: "3"
+            value: Lower Limit Set
+          - dps_val: "4"
+            value: Lower Limit Remove
+          - dps_val: "5"
+            value: Both Limits Remove
+  - entity: select
+    category: config
+    name: Style
+    icon: "mdi:blinds-horizontal"
+    dps:
+      - id: 113
+        type: string
+        name: option
+        mapping:
+          - dps_val: "0"
+            value: Sihousette blind
+          - dps_val: "1"
+            value: Roman blind
+          - dps_val: "2"
+            value: Roller blind
+          - dps_val: "3"
+            value: Soft blind
+          - dps_val: "4"
+            value: Roller shutter
+          - dps_val: "5"
+            value: Honeycomb blind
+          - dps_val: "6"
+            value: Pleated blind
+          - dps_val: "7"
+            value: Roller door
+          - dps_val: "8"
+            value: Awning
+          - dps_val: "9"
+            value: Curtain
+          - dps_val: "10"
+            value: Roman pole
+          - dps_val: "11"
+            value: Other
+  - entity: switch
+    category: config
+    name: Reversed
+    icon: "mdi:arrow-u-down-left"
+    dps:
+      - id: 103
+        type: boolean
+        name: switch

+ 53 - 39
custom_components/tuya_local/generic/cover.py

@@ -27,26 +27,30 @@ class TuyaLocalCover(TuyaLocalEntity, CoverEntity):
           config (TuyaEntityConfig): The entity config
         """
         dps_map = self._init_begin(device, config)
-        self._position_dps = dps_map.pop("position", None)
-        self._currentpos_dps = dps_map.pop("current_position", None)
-        self._control_dps = dps_map.pop("control", None)
-        self._action_dps = dps_map.pop("action", None)
-        self._open_dps = dps_map.pop("open", None)
-
+        self._position_dp = dps_map.pop("position", None)
+        self._currentpos_dp = dps_map.pop("current_position", None)
+        self._control_dp = dps_map.pop("control", None)
+        self._action_dp = dps_map.pop("action", None)
+        self._open_dp = dps_map.pop("open", None)
+        self._reversed_dp = dps_map.pop("reversed", None)
         self._init_end(dps_map)
 
         self._support_flags = 0
-        if self._position_dps:
+        if self._position_dp:
             self._support_flags |= CoverEntityFeature.SET_POSITION
-        if self._control_dps:
-            if "stop" in self._control_dps.values(self._device):
+        if self._control_dp:
+            if "stop" in self._control_dp.values(self._device):
                 self._support_flags |= CoverEntityFeature.STOP
-            if "open" in self._control_dps.values(self._device):
+            if "open" in self._control_dp.values(self._device):
                 self._support_flags |= CoverEntityFeature.OPEN
-            if "close" in self._control_dps.values(self._device):
+            if "close" in self._control_dp.values(self._device):
                 self._support_flags |= CoverEntityFeature.CLOSE
         # Tilt not yet supported, as no test devices known
 
+    @property
+    def _is_reversed(self):
+        return self._reversed_dp and self._reversed_dp.get_value(self._device)
+
     @property
     def device_class(self):
         """Return the class of ths device"""
@@ -66,20 +70,26 @@ class TuyaLocalCover(TuyaLocalEntity, CoverEntity):
     @property
     def current_cover_position(self):
         """Return current position of cover."""
-        if self._currentpos_dps:
-            pos = self._currentpos_dps.get_value(self._device)
+        if self._currentpos_dp:
+            pos = self._currentpos_dp.get_value(self._device)
             if pos is not None:
+                if self._is_reversed:
+                    return 100 - pos
                 return pos
-        if self._position_dps:
-            return self._position_dps.get_value(self._device)
 
-        if self._open_dps:
-            state = self._open_dps.get_value(self._device)
+        if self._position_dp:
+            pos = self._position_dp.get_value(self._device)
+            if self._is_reversed:
+                return 100 - pos
+            return pos
+
+        if self._open_dp:
+            state = self._open_dp.get_value(self._device)
             if state is not None:
                 return 100 if state else 0
 
-        if self._action_dps:
-            state = self._action_dps.get_value(self._device)
+        if self._action_dp:
+            state = self._action_dp.get_value(self._device)
             if state == "opened":
                 return 100
             elif state == "closed":
@@ -91,12 +101,12 @@ class TuyaLocalCover(TuyaLocalEntity, CoverEntity):
     def is_opening(self):
         """Return if the cover is opening or not."""
         # If dps is available to inform current action, use that
-        if self._action_dps:
-            return self._action_dps.get_value(self._device) == "opening"
+        if self._action_dp:
+            return self._action_dp.get_value(self._device) == "opening"
         # Otherwise use last command and check it hasn't completed
-        if self._control_dps:
+        if self._control_dp:
             return (
-                self._control_dps.get_value(self._device) == "open"
+                self._control_dp.get_value(self._device) == "open"
                 and self.current_cover_position != 100
             )
 
@@ -104,12 +114,12 @@ class TuyaLocalCover(TuyaLocalEntity, CoverEntity):
     def is_closing(self):
         """Return if the cover is closing or not."""
         # If dps is available to inform current action, use that
-        if self._action_dps:
-            return self._action_dps.get_value(self._device) == "closing"
+        if self._action_dp:
+            return self._action_dp.get_value(self._device) == "closing"
         # Otherwise use last command and check it hasn't completed
-        if self._control_dps:
+        if self._control_dp:
             return (
-                self._control_dps.get_value(self._device) == "close"
+                self._control_dp.get_value(self._device) == "close"
                 and not self.is_closed
             )
 
@@ -120,19 +130,21 @@ class TuyaLocalCover(TuyaLocalEntity, CoverEntity):
 
     async def async_open_cover(self, **kwargs):
         """Open the cover."""
-        if self._control_dps and "open" in self._control_dps.values(self._device):
-            await self._control_dps.async_set_value(self._device, "open")
-        elif self._position_dps:
-            await self._position_dps.async_set_value(self._device, 100)
+        if self._control_dp and "open" in self._control_dp.values(self._device):
+            await self._control_dp.async_set_value(self._device, "open")
+        elif self._position_dp:
+            pos = 0 if self._is_reversed else 100
+            await self._position_dp.async_set_value(self._device, pos)
         else:
             raise NotImplementedError()
 
     async def async_close_cover(self, **kwargs):
         """Close the cover."""
-        if self._control_dps and "close" in self._control_dps.values(self._device):
-            await self._control_dps.async_set_value(self._device, "close")
-        elif self._position_dps:
-            await self._position_dps.async_set_value(self._device, 0)
+        if self._control_dp and "close" in self._control_dp.values(self._device):
+            await self._control_dp.async_set_value(self._device, "close")
+        elif self._position_dp:
+            pos = 100 if self._is_reversed else 0
+            await self._position_dp.async_set_value(self._device, pos)
         else:
             raise NotImplementedError()
 
@@ -140,14 +152,16 @@ class TuyaLocalCover(TuyaLocalEntity, CoverEntity):
         """Set the cover to a specific position."""
         if position is None:
             raise AttributeError()
-        if self._position_dps:
-            await self._position_dps.async_set_value(self._device, position)
+        if self._position_dp:
+            if self._is_reversed:
+                position = 100 - position
+            await self._position_dp.async_set_value(self._device, position)
         else:
             raise NotImplementedError()
 
     async def async_stop_cover(self, **kwargs):
         """Stop the cover."""
-        if self._control_dps and "stop" in self._control_dps.values(self._device):
-            await self._control_dps.async_set_value(self._device, "stop")
+        if self._control_dp and "stop" in self._control_dp.values(self._device):
+            await self._control_dp.async_set_value(self._device, "stop")
         else:
             raise NotImplementedError()