Motion Blinds API lock (#68587)

This commit is contained in:
starkillerOG 2022-03-29 20:29:29 +02:00 committed by GitHub
parent cec3a08b95
commit 425b825ae9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 64 deletions

View file

@ -1,4 +1,5 @@
"""The motion_blinds component."""
import asyncio
from datetime import timedelta
import logging
from socket import timeout
@ -20,6 +21,7 @@ from .const import (
DEFAULT_INTERFACE,
DEFAULT_WAIT_FOR_PUSH,
DOMAIN,
KEY_API_LOCK,
KEY_COORDINATOR,
KEY_GATEWAY,
KEY_MULTICAST_LISTENER,
@ -56,6 +58,7 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
update_interval=update_interval,
)
self.api_lock = coordinator_info[KEY_API_LOCK]
self._gateway = coordinator_info[KEY_GATEWAY]
self._wait_for_push = coordinator_info[CONF_WAIT_FOR_PUSH]
@ -88,7 +91,8 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
async def _async_update_data(self):
"""Fetch the latest data from the gateway and blinds."""
data = await self.hass.async_add_executor_job(self.update_gateway)
async with self.api_lock:
data = await self.hass.async_add_executor_job(self.update_gateway)
all_available = all(device[ATTR_AVAILABLE] for device in data.values())
if all_available:
@ -130,8 +134,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not await connect_gateway_class.async_connect_gateway(host, key):
raise ConfigEntryNotReady
motion_gateway = connect_gateway_class.gateway_device
api_lock = asyncio.Lock()
coordinator_info = {
KEY_GATEWAY: motion_gateway,
KEY_API_LOCK: api_lock,
CONF_WAIT_FOR_PUSH: wait_for_push,
}

View file

@ -13,6 +13,7 @@ DEFAULT_WAIT_FOR_PUSH = False
DEFAULT_INTERFACE = "any"
KEY_GATEWAY = "gateway"
KEY_API_LOCK = "api_lock"
KEY_COORDINATOR = "coordinator"
KEY_MULTICAST_LISTENER = "multicast_listener"
KEY_VERSION = "version"

View file

@ -1,5 +1,4 @@
"""Support for Motion Blinds using their WLAN API."""
from datetime import timedelta
import logging
from motionblinds import DEVICE_TYPES_WIFI, BlindType
@ -20,9 +19,8 @@ from homeassistant.helpers import (
)
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_point_in_time, track_point_in_time
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import (
ATTR_ABSOLUTE_POSITION,
@ -94,7 +92,6 @@ async def async_setup_entry(
coordinator,
blind,
POSITION_DEVICE_MAP[blind.type],
config_entry,
sw_version,
)
)
@ -105,7 +102,6 @@ async def async_setup_entry(
coordinator,
blind,
TILT_DEVICE_MAP[blind.type],
config_entry,
sw_version,
)
)
@ -116,7 +112,6 @@ async def async_setup_entry(
coordinator,
blind,
TDBU_DEVICE_MAP[blind.type],
config_entry,
sw_version,
"Top",
)
@ -126,7 +121,6 @@ async def async_setup_entry(
coordinator,
blind,
TDBU_DEVICE_MAP[blind.type],
config_entry,
sw_version,
"Bottom",
)
@ -136,7 +130,6 @@ async def async_setup_entry(
coordinator,
blind,
TDBU_DEVICE_MAP[blind.type],
config_entry,
sw_version,
"Combined",
)
@ -152,7 +145,6 @@ async def async_setup_entry(
coordinator,
blind,
POSITION_DEVICE_MAP[BlindType.RollerBlind],
config_entry,
sw_version,
)
)
@ -170,12 +162,12 @@ async def async_setup_entry(
class MotionPositionDevice(CoordinatorEntity, CoverEntity):
"""Representation of a Motion Blind Device."""
def __init__(self, coordinator, blind, device_class, config_entry, sw_version):
def __init__(self, coordinator, blind, device_class, sw_version):
"""Initialize the blind."""
super().__init__(coordinator)
self._blind = blind
self._config_entry = config_entry
self._api_lock = coordinator.api_lock
self._requesting_position = False
self._previous_positions = []
@ -249,7 +241,9 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity):
if len(self._previous_positions) > 2:
del self._previous_positions[: len(self._previous_positions) - 2]
await self.hass.async_add_executor_job(self._blind.Update_trigger)
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Update_trigger)
self.async_write_ha_state()
if len(self._previous_positions) < 2 or not all(
@ -257,53 +251,58 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity):
for prev_position in self._previous_positions
):
# keep updating the position @UPDATE_INTERVAL_MOVING until the position does not change.
async_track_point_in_time(
self.hass,
self.async_scheduled_update_request,
dt_util.utcnow() + timedelta(seconds=UPDATE_INTERVAL_MOVING),
async_call_later(
self.hass, UPDATE_INTERVAL_MOVING, self.async_scheduled_update_request
)
else:
self._previous_positions = []
self._requesting_position = False
def request_position_till_stop(self):
async def async_request_position_till_stop(self):
"""Request the position of the blind every UPDATE_INTERVAL_MOVING seconds until it stops moving."""
self._previous_positions = []
if self._requesting_position or self.current_cover_position is None:
return
self._requesting_position = True
track_point_in_time(
self.hass,
self.async_scheduled_update_request,
dt_util.utcnow() + timedelta(seconds=UPDATE_INTERVAL_MOVING),
async_call_later(
self.hass, UPDATE_INTERVAL_MOVING, self.async_scheduled_update_request
)
def open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs):
"""Open the cover."""
self._blind.Open()
self.request_position_till_stop()
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Open)
await self.async_request_position_till_stop()
def close_cover(self, **kwargs):
async def async_close_cover(self, **kwargs):
"""Close cover."""
self._blind.Close()
self.request_position_till_stop()
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Close)
await self.async_request_position_till_stop()
def set_cover_position(self, **kwargs):
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs[ATTR_POSITION]
self._blind.Set_position(100 - position)
self.request_position_till_stop()
async with self._api_lock:
await self.hass.async_add_executor_job(
self._blind.Set_position, 100 - position
)
await self.async_request_position_till_stop()
def set_absolute_position(self, **kwargs):
async def async_set_absolute_position(self, **kwargs):
"""Move the cover to a specific absolute position (see TDBU)."""
position = kwargs[ATTR_ABSOLUTE_POSITION]
self._blind.Set_position(100 - position)
self.request_position_till_stop()
async with self._api_lock:
await self.hass.async_add_executor_job(
self._blind.Set_position, 100 - position
)
await self.async_request_position_till_stop()
def stop_cover(self, **kwargs):
async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
self._blind.Stop()
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Stop)
class MotionTiltDevice(MotionPositionDevice):
@ -320,32 +319,34 @@ class MotionTiltDevice(MotionPositionDevice):
return None
return self._blind.angle * 100 / 180
def open_cover_tilt(self, **kwargs):
async def async_open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
self._blind.Set_angle(180)
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, 180)
def close_cover_tilt(self, **kwargs):
async def async_close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
self._blind.Set_angle(0)
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, 0)
def set_cover_tilt_position(self, **kwargs):
async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
angle = kwargs[ATTR_TILT_POSITION] * 180 / 100
self._blind.Set_angle(angle)
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, angle)
def stop_cover_tilt(self, **kwargs):
async def async_stop_cover_tilt(self, **kwargs):
"""Stop the cover."""
self._blind.Stop()
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Stop)
class MotionTDBUDevice(MotionPositionDevice):
"""Representation of a Motion Top Down Bottom Up blind Device."""
def __init__(
self, coordinator, blind, device_class, config_entry, sw_version, motor
):
def __init__(self, coordinator, blind, device_class, sw_version, motor):
"""Initialize the blind."""
super().__init__(coordinator, blind, device_class, config_entry, sw_version)
super().__init__(coordinator, blind, device_class, sw_version)
self._motor = motor
self._motor_key = motor[0]
self._attr_name = f"{blind.blind_type}-{motor}-{blind.mac[12:]}"
@ -389,33 +390,40 @@ class MotionTDBUDevice(MotionPositionDevice):
attributes[ATTR_WIDTH] = self._blind.width
return attributes
def open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs):
"""Open the cover."""
self._blind.Open(motor=self._motor_key)
self.request_position_till_stop()
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Open, self._motor_key)
await self.async_request_position_till_stop()
def close_cover(self, **kwargs):
async def async_close_cover(self, **kwargs):
"""Close cover."""
self._blind.Close(motor=self._motor_key)
self.request_position_till_stop()
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Close, self._motor_key)
await self.async_request_position_till_stop()
def set_cover_position(self, **kwargs):
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific scaled position."""
position = kwargs[ATTR_POSITION]
self._blind.Set_scaled_position(100 - position, motor=self._motor_key)
self.request_position_till_stop()
async with self._api_lock:
await self.hass.async_add_executor_job(
self._blind.Set_scaled_position, 100 - position, self._motor_key
)
await self.async_request_position_till_stop()
def set_absolute_position(self, **kwargs):
async def async_set_absolute_position(self, **kwargs):
"""Move the cover to a specific absolute position."""
position = kwargs[ATTR_ABSOLUTE_POSITION]
target_width = kwargs.get(ATTR_WIDTH, None)
self._blind.Set_position(
100 - position, motor=self._motor_key, width=target_width
)
async with self._api_lock:
await self.hass.async_add_executor_job(
self._blind.Set_position, 100 - position, self._motor_key, target_width
)
self.request_position_till_stop()
await self.async_request_position_till_stop()
def stop_cover(self, **kwargs):
async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
self._blind.Stop(motor=self._motor_key)
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Stop, self._motor_key)