mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Motion Blinds API lock (#68587)
This commit is contained in:
parent
cec3a08b95
commit
425b825ae9
@ -1,4 +1,5 @@
|
|||||||
"""The motion_blinds component."""
|
"""The motion_blinds component."""
|
||||||
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from socket import timeout
|
from socket import timeout
|
||||||
@ -20,6 +21,7 @@ from .const import (
|
|||||||
DEFAULT_INTERFACE,
|
DEFAULT_INTERFACE,
|
||||||
DEFAULT_WAIT_FOR_PUSH,
|
DEFAULT_WAIT_FOR_PUSH,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
KEY_API_LOCK,
|
||||||
KEY_COORDINATOR,
|
KEY_COORDINATOR,
|
||||||
KEY_GATEWAY,
|
KEY_GATEWAY,
|
||||||
KEY_MULTICAST_LISTENER,
|
KEY_MULTICAST_LISTENER,
|
||||||
@ -56,6 +58,7 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
|
|||||||
update_interval=update_interval,
|
update_interval=update_interval,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.api_lock = coordinator_info[KEY_API_LOCK]
|
||||||
self._gateway = coordinator_info[KEY_GATEWAY]
|
self._gateway = coordinator_info[KEY_GATEWAY]
|
||||||
self._wait_for_push = coordinator_info[CONF_WAIT_FOR_PUSH]
|
self._wait_for_push = coordinator_info[CONF_WAIT_FOR_PUSH]
|
||||||
|
|
||||||
@ -88,7 +91,8 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _async_update_data(self):
|
async def _async_update_data(self):
|
||||||
"""Fetch the latest data from the gateway and blinds."""
|
"""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())
|
all_available = all(device[ATTR_AVAILABLE] for device in data.values())
|
||||||
if all_available:
|
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):
|
if not await connect_gateway_class.async_connect_gateway(host, key):
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
motion_gateway = connect_gateway_class.gateway_device
|
motion_gateway = connect_gateway_class.gateway_device
|
||||||
|
api_lock = asyncio.Lock()
|
||||||
coordinator_info = {
|
coordinator_info = {
|
||||||
KEY_GATEWAY: motion_gateway,
|
KEY_GATEWAY: motion_gateway,
|
||||||
|
KEY_API_LOCK: api_lock,
|
||||||
CONF_WAIT_FOR_PUSH: wait_for_push,
|
CONF_WAIT_FOR_PUSH: wait_for_push,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ DEFAULT_WAIT_FOR_PUSH = False
|
|||||||
DEFAULT_INTERFACE = "any"
|
DEFAULT_INTERFACE = "any"
|
||||||
|
|
||||||
KEY_GATEWAY = "gateway"
|
KEY_GATEWAY = "gateway"
|
||||||
|
KEY_API_LOCK = "api_lock"
|
||||||
KEY_COORDINATOR = "coordinator"
|
KEY_COORDINATOR = "coordinator"
|
||||||
KEY_MULTICAST_LISTENER = "multicast_listener"
|
KEY_MULTICAST_LISTENER = "multicast_listener"
|
||||||
KEY_VERSION = "version"
|
KEY_VERSION = "version"
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Support for Motion Blinds using their WLAN API."""
|
"""Support for Motion Blinds using their WLAN API."""
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from motionblinds import DEVICE_TYPES_WIFI, BlindType
|
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 import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
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.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util import dt as dt_util
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ABSOLUTE_POSITION,
|
ATTR_ABSOLUTE_POSITION,
|
||||||
@ -94,7 +92,6 @@ async def async_setup_entry(
|
|||||||
coordinator,
|
coordinator,
|
||||||
blind,
|
blind,
|
||||||
POSITION_DEVICE_MAP[blind.type],
|
POSITION_DEVICE_MAP[blind.type],
|
||||||
config_entry,
|
|
||||||
sw_version,
|
sw_version,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -105,7 +102,6 @@ async def async_setup_entry(
|
|||||||
coordinator,
|
coordinator,
|
||||||
blind,
|
blind,
|
||||||
TILT_DEVICE_MAP[blind.type],
|
TILT_DEVICE_MAP[blind.type],
|
||||||
config_entry,
|
|
||||||
sw_version,
|
sw_version,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -116,7 +112,6 @@ async def async_setup_entry(
|
|||||||
coordinator,
|
coordinator,
|
||||||
blind,
|
blind,
|
||||||
TDBU_DEVICE_MAP[blind.type],
|
TDBU_DEVICE_MAP[blind.type],
|
||||||
config_entry,
|
|
||||||
sw_version,
|
sw_version,
|
||||||
"Top",
|
"Top",
|
||||||
)
|
)
|
||||||
@ -126,7 +121,6 @@ async def async_setup_entry(
|
|||||||
coordinator,
|
coordinator,
|
||||||
blind,
|
blind,
|
||||||
TDBU_DEVICE_MAP[blind.type],
|
TDBU_DEVICE_MAP[blind.type],
|
||||||
config_entry,
|
|
||||||
sw_version,
|
sw_version,
|
||||||
"Bottom",
|
"Bottom",
|
||||||
)
|
)
|
||||||
@ -136,7 +130,6 @@ async def async_setup_entry(
|
|||||||
coordinator,
|
coordinator,
|
||||||
blind,
|
blind,
|
||||||
TDBU_DEVICE_MAP[blind.type],
|
TDBU_DEVICE_MAP[blind.type],
|
||||||
config_entry,
|
|
||||||
sw_version,
|
sw_version,
|
||||||
"Combined",
|
"Combined",
|
||||||
)
|
)
|
||||||
@ -152,7 +145,6 @@ async def async_setup_entry(
|
|||||||
coordinator,
|
coordinator,
|
||||||
blind,
|
blind,
|
||||||
POSITION_DEVICE_MAP[BlindType.RollerBlind],
|
POSITION_DEVICE_MAP[BlindType.RollerBlind],
|
||||||
config_entry,
|
|
||||||
sw_version,
|
sw_version,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -170,12 +162,12 @@ async def async_setup_entry(
|
|||||||
class MotionPositionDevice(CoordinatorEntity, CoverEntity):
|
class MotionPositionDevice(CoordinatorEntity, CoverEntity):
|
||||||
"""Representation of a Motion Blind Device."""
|
"""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."""
|
"""Initialize the blind."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
|
||||||
self._blind = blind
|
self._blind = blind
|
||||||
self._config_entry = config_entry
|
self._api_lock = coordinator.api_lock
|
||||||
self._requesting_position = False
|
self._requesting_position = False
|
||||||
self._previous_positions = []
|
self._previous_positions = []
|
||||||
|
|
||||||
@ -249,7 +241,9 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity):
|
|||||||
if len(self._previous_positions) > 2:
|
if len(self._previous_positions) > 2:
|
||||||
del self._previous_positions[: 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()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
if len(self._previous_positions) < 2 or not all(
|
if len(self._previous_positions) < 2 or not all(
|
||||||
@ -257,53 +251,58 @@ class MotionPositionDevice(CoordinatorEntity, CoverEntity):
|
|||||||
for prev_position in self._previous_positions
|
for prev_position in self._previous_positions
|
||||||
):
|
):
|
||||||
# keep updating the position @UPDATE_INTERVAL_MOVING until the position does not change.
|
# keep updating the position @UPDATE_INTERVAL_MOVING until the position does not change.
|
||||||
async_track_point_in_time(
|
async_call_later(
|
||||||
self.hass,
|
self.hass, UPDATE_INTERVAL_MOVING, self.async_scheduled_update_request
|
||||||
self.async_scheduled_update_request,
|
|
||||||
dt_util.utcnow() + timedelta(seconds=UPDATE_INTERVAL_MOVING),
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._previous_positions = []
|
self._previous_positions = []
|
||||||
self._requesting_position = False
|
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."""
|
"""Request the position of the blind every UPDATE_INTERVAL_MOVING seconds until it stops moving."""
|
||||||
self._previous_positions = []
|
self._previous_positions = []
|
||||||
if self._requesting_position or self.current_cover_position is None:
|
if self._requesting_position or self.current_cover_position is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._requesting_position = True
|
self._requesting_position = True
|
||||||
track_point_in_time(
|
async_call_later(
|
||||||
self.hass,
|
self.hass, UPDATE_INTERVAL_MOVING, self.async_scheduled_update_request
|
||||||
self.async_scheduled_update_request,
|
|
||||||
dt_util.utcnow() + timedelta(seconds=UPDATE_INTERVAL_MOVING),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def open_cover(self, **kwargs):
|
async def async_open_cover(self, **kwargs):
|
||||||
"""Open the cover."""
|
"""Open the cover."""
|
||||||
self._blind.Open()
|
async with self._api_lock:
|
||||||
self.request_position_till_stop()
|
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."""
|
"""Close cover."""
|
||||||
self._blind.Close()
|
async with self._api_lock:
|
||||||
self.request_position_till_stop()
|
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."""
|
"""Move the cover to a specific position."""
|
||||||
position = kwargs[ATTR_POSITION]
|
position = kwargs[ATTR_POSITION]
|
||||||
self._blind.Set_position(100 - position)
|
async with self._api_lock:
|
||||||
self.request_position_till_stop()
|
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)."""
|
"""Move the cover to a specific absolute position (see TDBU)."""
|
||||||
position = kwargs[ATTR_ABSOLUTE_POSITION]
|
position = kwargs[ATTR_ABSOLUTE_POSITION]
|
||||||
self._blind.Set_position(100 - position)
|
async with self._api_lock:
|
||||||
self.request_position_till_stop()
|
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."""
|
"""Stop the cover."""
|
||||||
self._blind.Stop()
|
async with self._api_lock:
|
||||||
|
await self.hass.async_add_executor_job(self._blind.Stop)
|
||||||
|
|
||||||
|
|
||||||
class MotionTiltDevice(MotionPositionDevice):
|
class MotionTiltDevice(MotionPositionDevice):
|
||||||
@ -320,32 +319,34 @@ class MotionTiltDevice(MotionPositionDevice):
|
|||||||
return None
|
return None
|
||||||
return self._blind.angle * 100 / 180
|
return self._blind.angle * 100 / 180
|
||||||
|
|
||||||
def open_cover_tilt(self, **kwargs):
|
async def async_open_cover_tilt(self, **kwargs):
|
||||||
"""Open the cover tilt."""
|
"""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."""
|
"""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."""
|
"""Move the cover tilt to a specific position."""
|
||||||
angle = kwargs[ATTR_TILT_POSITION] * 180 / 100
|
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."""
|
"""Stop the cover."""
|
||||||
self._blind.Stop()
|
async with self._api_lock:
|
||||||
|
await self.hass.async_add_executor_job(self._blind.Stop)
|
||||||
|
|
||||||
|
|
||||||
class MotionTDBUDevice(MotionPositionDevice):
|
class MotionTDBUDevice(MotionPositionDevice):
|
||||||
"""Representation of a Motion Top Down Bottom Up blind Device."""
|
"""Representation of a Motion Top Down Bottom Up blind Device."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, coordinator, blind, device_class, sw_version, motor):
|
||||||
self, coordinator, blind, device_class, config_entry, sw_version, motor
|
|
||||||
):
|
|
||||||
"""Initialize the blind."""
|
"""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 = motor
|
||||||
self._motor_key = motor[0]
|
self._motor_key = motor[0]
|
||||||
self._attr_name = f"{blind.blind_type}-{motor}-{blind.mac[12:]}"
|
self._attr_name = f"{blind.blind_type}-{motor}-{blind.mac[12:]}"
|
||||||
@ -389,33 +390,40 @@ class MotionTDBUDevice(MotionPositionDevice):
|
|||||||
attributes[ATTR_WIDTH] = self._blind.width
|
attributes[ATTR_WIDTH] = self._blind.width
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
def open_cover(self, **kwargs):
|
async def async_open_cover(self, **kwargs):
|
||||||
"""Open the cover."""
|
"""Open the cover."""
|
||||||
self._blind.Open(motor=self._motor_key)
|
async with self._api_lock:
|
||||||
self.request_position_till_stop()
|
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."""
|
"""Close cover."""
|
||||||
self._blind.Close(motor=self._motor_key)
|
async with self._api_lock:
|
||||||
self.request_position_till_stop()
|
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."""
|
"""Move the cover to a specific scaled position."""
|
||||||
position = kwargs[ATTR_POSITION]
|
position = kwargs[ATTR_POSITION]
|
||||||
self._blind.Set_scaled_position(100 - position, motor=self._motor_key)
|
async with self._api_lock:
|
||||||
self.request_position_till_stop()
|
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."""
|
"""Move the cover to a specific absolute position."""
|
||||||
position = kwargs[ATTR_ABSOLUTE_POSITION]
|
position = kwargs[ATTR_ABSOLUTE_POSITION]
|
||||||
target_width = kwargs.get(ATTR_WIDTH, None)
|
target_width = kwargs.get(ATTR_WIDTH, None)
|
||||||
|
|
||||||
self._blind.Set_position(
|
async with self._api_lock:
|
||||||
100 - position, motor=self._motor_key, width=target_width
|
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."""
|
"""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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user