MapleEve / lifesmart-for-homeassistant

HACS LifeSmart Integration
GNU General Public License v3.0
11 stars 3 forks source link

杜亚窗帘电机没有吗? SL_DOOYA_V1 #6

Open hellojazy opened 5 months ago

hellojazy commented 5 months ago

谢谢,如果有就完美了。

hellojazy commented 5 months ago

另外发现插座也不能工作。设备类型为OD_WE_OT1

MapleEve commented 5 months ago

DOOYA 电机应该是正常支持了的,请问集成里面是没有这个设备吗?

hellojazy commented 5 months ago

对的 没有这个设备 ,除了流光开关有。像人体感应、无线插座、窗帘电机都没同步过来。

获取 Outlook for iOShttps://aka.ms/o0ukef


发件人: Maple Gao @.> 发送时间: Monday, June 3, 2024 12:14:52 PM 收件人: MapleEve/lifesmart-for-homeassistant @.> 抄送: hellojazy @.>; Author @.> 主题: Re: [MapleEve/lifesmart-for-homeassistant] 杜亚窗帘电机没有吗? SL_DOOYA_V1 (Issue #6)

DOOYA 电机应该是正常支持了的,请问集成里面是没有这个设备吗?

― Reply to this email directly, view it on GitHubhttps://github.com/MapleEve/lifesmart-for-homeassistant/issues/6#issuecomment-2144242430, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AZYDWOGR22COIJXDWO3CERDZFPUTZAVCNFSM6AAAAABIJ4P7AWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNBUGI2DENBTGA. You are receiving this because you authored the thread.Message ID: @.***>

MapleEve commented 5 months ago

对的 没有这个设备 ,除了流光开关有。像人体感应、无线插座、窗帘电机都没同步过来。 获取 Outlook for iOShttps://aka.ms/o0ukef ____ 发件人: Maple Gao @.> 发送时间: Monday, June 3, 2024 12:14:52 PM 收件人: MapleEve/lifesmart-for-homeassistant @.> 抄送: hellojazy @.>; Author @.> 主题: Re: [MapleEve/lifesmart-for-homeassistant] 杜亚窗帘电机没有吗? SL_DOOYA_V1 (Issue #6) DOOYA 电机应该是正常支持了的,请问集成里面是没有这个设备吗? ― Reply to this email directly, view it on GitHub<#6 (comment)>, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AZYDWOGR22COIJXDWO3CERDZFPUTZAVCNFSM6AAAAABIJ4P7AWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNBUGI2DENBTGA. You are receiving this because you authored the thread.Message ID: @.***>

了解了,我看下是不是窗帘那块写 bug 了,因为有使用最新的那个窗帘电机的反馈是 OK 的

hellojazy commented 4 months ago

我修复了一下错误,现在可以用了 """Support for LifeSmart covers. by @MapleEve"""

import logging import asyncio

from homeassistant.components.cover import ( ATTR_POSITION, CoverEntity, CoverEntityFeature, CoverDeviceClass, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo

from . import LifeSmartDevice, generate_entity_id from .const import ( DOMAIN, DEVICE_ID_KEY, DEVICE_TYPE_KEY, DEVICE_DATA_KEY, DEVICE_NAME_KEY, HUB_ID_KEY, COVER_TYPES, LIFESMART_SIGNAL_UPDATE_ENTITY, MANUFACTURER, )

_LOGGER = logging.getLogger(name)

async def async_setup_entry(hass, config_entry, async_add_entities):

设置 LifeSmart 窗帘设备

devices = hass.data[DOMAIN][config_entry.entry_id]["devices"]
exclude_devices = hass.data[DOMAIN][config_entry.entry_id]["exclude_devices"]
exclude_hubs = hass.data[DOMAIN][config_entry.entry_id]["exclude_hubs"]
client = hass.data[DOMAIN][config_entry.entry_id]["client"]
cover_devices = []

for device in devices:
    if (
        device[DEVICE_ID_KEY] in exclude_devices
        or device[HUB_ID_KEY] in exclude_hubs
    ):
        continue

    device_type = device[DEVICE_TYPE_KEY]
    if device_type in COVER_TYPES:
        ha_device = LifeSmartDevice(device, client)

        if device_type in ["SL_CN_IF", "SL_CN_FE"]:
            idx = ["P1", "P2", "P3"]
            val = {i: device[DEVICE_DATA_KEY][i] for i in idx}
        elif device_type == "SL_P_V2":
            idx = ["P2", "P3", "P4", "P8"]  # 增加了P8电量属性
            val = {i: device[DEVICE_DATA_KEY][i] for i in idx}
        elif device_type == "SL_SW_WIN":
            idx = ["OP", "CL", "ST"]
            val = {i: device[DEVICE_DATA_KEY][i] for i in idx}
        elif device_type == "SL_DOOYA":
            idx = "P1"
            val = device[DEVICE_DATA_KEY][idx]
        else:
            continue

        cover_devices.append(LifeSmartCover(ha_device, device, idx, val, client))

async_add_entities(cover_devices)

class LifeSmartCover(LifeSmartDevice, CoverEntity): """LifeSmart cover devices."""

def __init__(self, device, raw_device_data, idx, val, client):
    """Init LifeSmart cover device."""
    super().__init__(raw_device_data, client)

    device_name = raw_device_data[DEVICE_NAME_KEY]
    device_type = raw_device_data[DEVICE_TYPE_KEY]
    hub_id = raw_device_data[HUB_ID_KEY]
    device_id = raw_device_data[DEVICE_ID_KEY]

    self._attr_has_entity_name = True
    self.device_name = device_name
    self.sensor_device_name = raw_device_data[DEVICE_NAME_KEY]
    self.device_id = device_id
    self.hub_id = hub_id
    self.device_type = device_type
    self.raw_device_data = raw_device_data
    self._device = device
    self.entity_id = generate_entity_id(device_type, hub_id, device_id, idx)
    self._client = client

    self._attr_device_class = CoverDeviceClass.CURTAIN
    self._supported_features = (
        CoverEntityFeature.OPEN
        | CoverEntityFeature.CLOSE
        | CoverEntityFeature.STOP  # 默认支持打开、关闭和停止
    )
    self._is_opening = False
    self._is_closing = False
    self._is_closed = False

    if device_type in COVER_TYPES:
        if device_type == "SL_DOOYA":
            self._pos = val["val"]
            self._open_cmd = {"type": "0xCF", "val": 100, "idx": "P2"}
            self._close_cmd = {"type": "0xCF", "val": 0, "idx": "P2"}
            self._stop_cmd = {"type": "0xCE", "val": 0x80, "idx": "P2"}
            self._position_cmd = {"type": "0xCF", "idx": "P2"}
            self._supported_features |= (
                CoverEntityFeature.SET_POSITION
            )  # 额外支持设置位置
        elif device_type == "SL_P_V2":
            self._open_cmd = {"type": "0x81", "val": 1, "idx": "P2"}
            self._close_cmd = {"type": "0x81", "val": 1, "idx": "P3"}
            self._stop_cmd = {"type": "0x81", "val": 1, "idx": "P4"}
            self._attr_battery_level = val["P8"]["v"]
        elif device_type == "SL_SW_WIN":
            self._open_cmd = {"type": "0x81", "val": 1, "idx": "OP"}
            self._close_cmd = {"type": "0x81", "val": 1, "idx": "CL"}
            self._stop_cmd = {"type": "0x81", "val": 1, "idx": "ST"}
        elif device_type in ["SL_CN_IF", "SL_CN_FE"]:
            self._open_cmd = {"type": "0x81", "val": 1, "idx": "P1"}
            self._close_cmd = {"type": "0x81", "val": 1, "idx": "P2"}
            self._stop_cmd = {"type": "0x81", "val": 1, "idx": "P3"}

@property
def current_cover_position(self):
    """Return the current position of the cover."""
    if CoverEntityFeature.SET_POSITION:
        return self._pos
    else:
        return None

@property
def device_info(self) -> DeviceInfo:
    """Return the device info."""
    return DeviceInfo(
        identifiers={(DOMAIN, self.hub_id, self.device_id)},
        name=self.sensor_device_name,
        manufacturer=MANUFACTURER,
        model=self.device_type,
        sw_version=self.raw_device_data["ver"],
        via_device=(DOMAIN, self.hub_id),
    )

@property
def device_class(self):
    """Return the class of binary sensor."""
    return self._attr_device_class

@property
def unique_id(self):
    """A unique identifier for this entity."""
    return self.entity_id

@property
def is_closed(self):
    """Return if the cover is closed."""
    if self.device_type == "SL_DOOYA":
        return self.current_cover_position <= 0
    else:
        return self._is_closed

async def async_close_cover(self, **kwargs):
    """Close the cover."""
    await super().async_lifesmart_epset(
        self._close_cmd["type"], self._close_cmd["val"], self._close_cmd["idx"]
    )
    self.schedule_update_ha_state()
    await self.async_poll_status()

async def async_open_cover(self, **kwargs):
    """Open the cover."""
    await super().async_lifesmart_epset(
        self._open_cmd["type"], self._open_cmd["val"], self._open_cmd["idx"]
    )
    self.schedule_update_ha_state()
    await self.async_poll_status()

async def async_stop_cover(self, **kwargs):
    """Stop the cover."""
    if not self._supported_features & CoverEntityFeature.STOP:
        return
    await super().async_lifesmart_epset(
        self._stop_cmd["type"], self._stop_cmd["val"], self._stop_cmd["idx"]
    )
    self.schedule_update_ha_state()

async def async_toggle(self, **kwargs):
    """Toggle the entity."""
    if self.is_opening or self.is_closing:
        await self.async_stop_cover()
    elif self.is_closed:
        await self.async_open_cover()
    else:
        await self.async_close_cover()

async def async_set_cover_position(self, **kwargs):
    """Move the cover to a specific position."""
    if not self._supported_features & CoverEntityFeature.SET_POSITION:
        return
    position = kwargs.get(ATTR_POSITION)
    await super().async_lifesmart_epset(
        self._position_cmd["type"], position, self._position_cmd["idx"]
    )
    self.schedule_update_ha_state()
    await self.async_poll_status()

async def async_poll_status(self):
    """Poll cover status until it stops moving."""
    max_polls = 40  # 最大轮询次数40
    poll_interval = 0.8  # 轮询间隔0.5秒

    for _ in range(max_polls):
        if not self._is_opening and not self._is_closing:
            break  # 如果已停止,跳出循环
        await asyncio.sleep(poll_interval)
        await self.async_lifesmart_epget()
        self.schedule_update_ha_state()

async def async_added_to_hass(self) -> None:
    """Register callbacks."""
    await super().async_added_to_hass()
    self.async_on_remove(
        async_dispatcher_connect(
            self.hass,
            f"{LIFESMART_SIGNAL_UPDATE_ENTITY}_{self.entity_id}",
            self._update_state,
        )
    )
    self.async_schedule_update_ha_state(force_refresh=True)

async def _update_state(self, data) -> None:
    """Update cover state."""
    if data is not None:
        if self.device_type == "SL_DOOYA":
            if isinstance(data, dict):  # 判断data是否为字典类型
                pos = data["val"] & 0x7F
                if pos <= 100:
                    self._pos = pos
                    self._state = self._pos
                else:
                    self._pos = None
                    self._state = None

                if data["type"] & 0x01 == 1:  # 正在运行
                    if data["val"] & 0x80 == 0x80:
                        self._is_opening = True
                        self._is_closing = False
                    else:
                        self._is_opening = False
                        self._is_closing = True
                else:  # 没有运行
                    self._is_opening = False
                    self._is_closing = False
                    if self._pos == 100:
                        self._is_closed = False
                    elif self._pos == 0:
                        self._is_closed = True
            else:
                self._pos = None
                self._state = None
                self._is_opening = False
                self._is_closing = False
                self._is_closed = None  # 无法判断窗帘状态

        self.schedule_update_ha_state()
hellojazy commented 4 months ago

同时 switch 也有版本号报错。 def device_info(self) -> DeviceInfo: """Return the device info.""" sw_version = self.raw_device_data.get("ver", "unknown") # 使用 get 方法提供默认版本号 return DeviceInfo( identifiers={(DOMAIN, self.hub_id, self.device_id)}, name=self.switch_name, manufacturer=MANUFACTURER, model=self.device_type, sw_version=sw_version, via_device=(DOMAIN, self.hub_id), )

MapleEve commented 3 months ago

同时 switch 也有版本号报错。 def device_info(self) -> DeviceInfo: """Return the device info.""" sw_version = self.raw_device_data.get("ver", "unknown") # 使用 get 方法提供默认版本号 return DeviceInfo( identifiers={(DOMAIN, self.hub_id, self.device_id)}, name=self.switch_name, manufacturer=MANUFACTURER, model=self.device_type, sw_version=sw_version, via_device=(DOMAIN, self.hub_id), )

额,这个还要单独处理版本号?他的接口好奇怪,我之前是直接封装完成的。

hellojazy commented 3 months ago

我用chatgpt解决的,这个报错不改程序会卡住,像插座,人体传感器都同步不过来。

最近运行了几周都没问题,唯一问题好像是API那边的问题,一两天同步状态会被卡住一次。 辛苦在下个版本更新中把这2个地方修正一下,以免下次我又下错了。谢谢