HEX
Server: Apache/2.4.63 (Unix)
System: Linux Synopilou92 4.4.302+ #72806 SMP Mon Jul 21 23:16:00 CST 2025 x86_64
User: pilou92 (1026)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: /volume1/docker/homeassistant2025/custom_components/sonoff/cover.py
from homeassistant.components.cover import CoverDeviceClass, CoverEntity

from .core.const import DOMAIN
from .core.entity import XEntity
from .core.ewelink import SIGNAL_ADD_ENTITIES, XRegistry

PARALLEL_UPDATES = 0  # fix entity_platform parallel_updates Semaphore


async def async_setup_entry(hass, config_entry, add_entities):
    ewelink: XRegistry = hass.data[DOMAIN][config_entry.entry_id]
    ewelink.dispatcher_connect(
        SIGNAL_ADD_ENTITIES,
        lambda x: add_entities([e for e in x if isinstance(e, CoverEntity)]),
    )


# noinspection PyUnresolvedReferences
DEVICE_CLASSES = {cls.value: cls for cls in CoverDeviceClass}


# noinspection PyAbstractClass
class XCover(XEntity, CoverEntity):
    params = {"switch", "setclose"}

    def __init__(self, ewelink: XRegistry, device: dict):
        XEntity.__init__(self, ewelink, device)
        self._attr_device_class = DEVICE_CLASSES.get(device.get("device_class"))

    def set_state(self, params: dict):
        # => command to cover from mobile app
        if len(params) == 1:
            if "switch" in params:
                # device receive command - on=open/off=close/pause=stop
                self._attr_is_opening = params["switch"] == "on"
                self._attr_is_closing = params["switch"] == "off"
            elif "setclose" in params:
                # device receive command - mode to position
                pos = 100 - params["setclose"]
                self._attr_is_closing = pos < self.current_cover_position
                self._attr_is_opening = pos > self.current_cover_position

        # BINTHEN BCM Series payload:
        #   {"sequence":"1652428259464","setclose":38}
        # KingArt KING-Q4 payloads:
        #   {"switch":"off","setclose":21} or {"switch":"on","setclose":0}
        elif "setclose" in params:
            # the device has finished the action
            # reversed position: HA closed at 0, eWeLink closed at 100
            self._attr_current_cover_position = 100 - params["setclose"]
            self._attr_is_closed = self.current_cover_position == 0
            self._attr_is_closing = False
            self._attr_is_opening = False

    async def async_stop_cover(self, **kwargs):
        params = {"switch": "pause"}
        self.set_state(params)
        self._async_write_ha_state()
        await self.ewelink.send(self.device, params, query_cloud=False)

    async def async_open_cover(self, **kwargs):
        params = {"switch": "on"}
        self.set_state(params)
        self._async_write_ha_state()
        await self.ewelink.send(self.device, params, query_cloud=False)

    async def async_close_cover(self, **kwargs):
        params = {"switch": "off"}
        self.set_state(params)
        self._async_write_ha_state()
        await self.ewelink.send(self.device, params, query_cloud=False)

    async def async_set_cover_position(self, position: int, **kwargs):
        params = {"setclose": 100 - position}
        self.set_state(params)
        self._async_write_ha_state()
        await self.ewelink.send(self.device, params, query_cloud=False)


# noinspection PyAbstractClass
class XCoverDualR3(XCover):
    params = {"currLocation", "motorTurn"}

    def set_state(self, params: dict):
        if "currLocation" in params:
            # 0 - closed, 100 - opened
            self._attr_current_cover_position = params["currLocation"]
            self._attr_is_closed = self._attr_current_cover_position == 0

        if "motorTurn" in params:
            if params["motorTurn"] == 0:  # stop
                self._attr_is_opening = False
                self._attr_is_closing = False
            elif params["motorTurn"] == 1:
                self._attr_is_opening = True
                self._attr_is_closing = False
            elif params["motorTurn"] == 2:
                self._attr_is_opening = False
                self._attr_is_closing = True

    async def async_stop_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"motorTurn": 0})

    async def async_open_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"motorTurn": 1})

    async def async_close_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"motorTurn": 2})

    async def async_set_cover_position(self, position: int, **kwargs):
        await self.ewelink.send(self.device, {"location": position})


# noinspection PyAbstractClass
class XZigbeeCover(XCover):
    params = {"curPercent", "curtainAction"}

    def set_state(self, params: dict):
        if "curPercent" in params:
            # reversed position: HA closed at 0, eWeLink closed at 100
            self._attr_current_cover_position = 100 - params["curPercent"]
            self._attr_is_closed = self._attr_current_cover_position == 0

    async def async_stop_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"curtainAction": "pause"})

    async def async_open_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"curtainAction": "open"})

    async def async_close_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"curtainAction": "close"})

    async def async_set_cover_position(self, position: int, **kwargs):
        await self.ewelink.send(self.device, {"openPercent": 100 - position})


class XCoverOP(XEntity, CoverEntity):
    param = "op"

    _attr_is_closed = None  # unknown state

    def set_state(self, params: dict):
        if "per" in params:
            # UIID 67: {'op': 3, 'per': 0, 'statu': 6} - CLOSED
            # UIID 67: {'op': 1, 'per': 100, 'statu': 5} - OPEN
            self._attr_is_closed = params["per"] == 0
            self._attr_is_closing = self._attr_is_opening = False
        elif "op" in params:
            if params["op"] == 1:
                # UIID 67: {"op": 1} - OPENING
                self._attr_is_closing = False
                self._attr_is_opening = True
            elif params["op"] == 2:
                self._attr_is_closed = None
                self._attr_is_closing = self._attr_is_opening = False
            elif params["op"] == 3:
                # UIID 67: {"op": 3} - CLOSING
                self._attr_is_closing = True
                self._attr_is_opening = False

    async def async_stop_cover(self, **kwargs):
        await self.ewelink.send(self.device, {self.param: 2})

    async def async_open_cover(self, **kwargs):
        await self.ewelink.send(self.device, {self.param: 1})

    async def async_close_cover(self, **kwargs):
        await self.ewelink.send(self.device, {self.param: 3})


# noinspection PyAbstractClass
class XCoverT5(XCover):
    params = {"electromotor", "percentageControl"}

    _attr_entity_registry_enabled_default = False

    _attr_is_closed = None  # unknown state

    def set_state(self, params: dict):
        if "percentageControl" in params and params["calibState"] is True:
            self._attr_current_cover_position = 100 - params["percentageControl"]
            self._attr_is_closed = self._attr_current_cover_position == 0

        if "electromotor" in params:
            if params["electromotor"] == 1:  # stop
                self._attr_is_opening = False
                self._attr_is_closing = False
            elif params["electromotor"] == 0:  # open
                self._attr_is_opening = True
                self._attr_is_closing = False
            elif params["electromotor"] == 2:  # close
                self._attr_is_opening = False
                self._attr_is_closing = True

    async def async_stop_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"electromotor": 1})

    async def async_open_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"electromotor": 0})

    async def async_close_cover(self, **kwargs):
        await self.ewelink.send(self.device, {"electromotor": 2})

    async def async_set_cover_position(self, position: int, **kwargs):
        await self.ewelink.send(self.device, {"percentageControl": 100 - position})