From 27cf1085bd7b239e9304de4b7c98bb8c59c20f01 Mon Sep 17 00:00:00 2001 From: Necroneco Date: Fri, 7 Mar 2025 08:46:17 +0800 Subject: [PATCH] fix: opening and closing for linp.wopener.wd1lb (#826) --- custom_components/xiaomi_home/cover.py | 71 +++++++++++++++++++------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/custom_components/xiaomi_home/cover.py b/custom_components/xiaomi_home/cover.py index dcc512f..08398e6 100644 --- a/custom_components/xiaomi_home/cover.py +++ b/custom_components/xiaomi_home/cover.py @@ -47,7 +47,7 @@ Cover entities for Xiaomi Home. """ from __future__ import annotations import logging -from typing import Optional +from typing import Any, Optional from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -101,7 +101,11 @@ class Cover(MIoTServiceEntity, CoverEntity): _prop_status_closed: Optional[list[int]] _prop_current_position: Optional[MIoTSpecProperty] _prop_target_position: Optional[MIoTSpecProperty] + _prop_position_value_min: Optional[int] + _prop_position_value_max: Optional[int] _prop_position_value_range: Optional[int] + _prop_pos_closing: bool + _prop_pos_opening: bool def __init__(self, miot_device: MIoTDevice, entity_data: MIoTEntityData) -> None: @@ -122,7 +126,11 @@ class Cover(MIoTServiceEntity, CoverEntity): self._prop_status_closed = [] self._prop_current_position = None self._prop_target_position = None + self._prop_position_value_min = None + self._prop_position_value_max = None self._prop_position_value_range = None + self._prop_pos_closing = False + self._prop_pos_opening = False # properties for prop in entity_data.props: @@ -166,6 +174,8 @@ class Cover(MIoTServiceEntity, CoverEntity): 'invalid current-position value_range format, %s', self.entity_id) continue + self._prop_position_value_min = prop.value_range.min_ + self._prop_position_value_max = prop.value_range.max_ self._prop_position_value_range = (prop.value_range.max_ - prop.value_range.min_) self._prop_current_position = prop @@ -175,23 +185,52 @@ class Cover(MIoTServiceEntity, CoverEntity): 'invalid target-position value_range format, %s', self.entity_id) continue + self._prop_position_value_min = prop.value_range.min_ + self._prop_position_value_max = prop.value_range.max_ self._prop_position_value_range = (prop.value_range.max_ - prop.value_range.min_) self._attr_supported_features |= CoverEntityFeature.SET_POSITION self._prop_target_position = prop + # For the device that has the current position property but no status + # property, the current position property will be used to determine the + # opening and the closing status. + if (self._prop_status is None) and (self._prop_current_position + is not None): + self.sub_prop_changed(self._prop_current_position, + self._position_changed_handler) + + def _position_changed_handler(self, prop: MIoTSpecProperty, + ctx: Any) -> None: + self._prop_pos_closing = False + self._prop_pos_opening = False + self.async_write_ha_state() async def async_open_cover(self, **kwargs) -> None: """Open the cover.""" + current = None if (self._prop_current_position + is None) else self.get_prop_value( + prop=self._prop_current_position) + if (current is not None) and (current < self._prop_position_value_max): + self._prop_pos_opening = True + self._prop_pos_closing = False await self.set_property_async(self._prop_motor_control, self._prop_motor_value_open) async def async_close_cover(self, **kwargs) -> None: """Close the cover.""" + current = None if (self._prop_current_position + is None) else self.get_prop_value( + prop=self._prop_current_position) + if (current is not None) and (current > self._prop_position_value_min): + self._prop_pos_opening = False + self._prop_pos_closing = True await self.set_property_async(self._prop_motor_control, self._prop_motor_value_close) async def async_stop_cover(self, **kwargs) -> None: """Stop the cover.""" + self._prop_pos_opening = False + self._prop_pos_closing = False await self.set_property_async(self._prop_motor_control, self._prop_motor_value_pause) @@ -200,6 +239,10 @@ class Cover(MIoTServiceEntity, CoverEntity): pos = kwargs.get(ATTR_POSITION, None) if pos is None: return None + current = self.current_cover_position + if current is not None: + self._prop_pos_opening = pos > current + self._prop_pos_closing = pos < current pos = round(pos * self._prop_position_value_range / 100) await self.set_property_async(prop=self._prop_target_position, value=pos) @@ -214,9 +257,11 @@ class Cover(MIoTServiceEntity, CoverEntity): # Assume that the current position is the same as the target # position when the current position is not defined in the device's # MIoT-Spec-V2. - return None if (self._prop_target_position - is None) else self.get_prop_value( - prop=self._prop_target_position) + if self._prop_target_position is None: + return None + self._prop_pos_opening = False + self._prop_pos_closing = False + return self.get_prop_value(prop=self._prop_target_position) pos = self.get_prop_value(prop=self._prop_current_position) return None if pos is None else round(pos * 100 / self._prop_position_value_range) @@ -227,14 +272,9 @@ class Cover(MIoTServiceEntity, CoverEntity): if self._prop_status and self._prop_status_opening: return (self.get_prop_value(prop=self._prop_status) in self._prop_status_opening) - # The status is prior to the numerical relationship of the current - # position and the target position when determining whether the cover + # The status has higher priority when determining whether the cover # is opening. - if (self._prop_target_position and - self.current_cover_position is not None): - return (self.current_cover_position - < self.get_prop_value(prop=self._prop_target_position)) - return None + return self._prop_pos_opening @property def is_closing(self) -> Optional[bool]: @@ -242,14 +282,9 @@ class Cover(MIoTServiceEntity, CoverEntity): if self._prop_status and self._prop_status_closing: return (self.get_prop_value(prop=self._prop_status) in self._prop_status_closing) - # The status is prior to the numerical relationship of the current - # position and the target position when determining whether the cover + # The status has higher priority when determining whether the cover # is closing. - if (self._prop_target_position and - self.current_cover_position is not None): - return (self.current_cover_position - > self.get_prop_value(prop=self._prop_target_position)) - return None + return self._prop_pos_closing @property def is_closed(self) -> Optional[bool]: