Avoid concurrent radio operations with powerview hubs (#114399)

Co-authored-by: kingy444 <toddlesking4@hotmail.com>
This commit is contained in:
J. Nick Koston 2024-03-29 05:20:26 -10:00 committed by GitHub
parent 5e3ce80488
commit 63ccdcb786
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 19 additions and 6 deletions

View File

@ -119,4 +119,5 @@ class PowerviewShadeButton(ShadeEntity, ButtonEntity):
async def async_press(self) -> None: async def async_press(self) -> None:
"""Handle the button press.""" """Handle the button press."""
await self.entity_description.press_action(self._shade) async with self.coordinator.radio_operation_lock:
await self.entity_description.press_action(self._shade)

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -25,6 +26,10 @@ class PowerviewShadeUpdateCoordinator(DataUpdateCoordinator[PowerviewShadeData])
"""Initialize DataUpdateCoordinator to gather data for specific Powerview Hub.""" """Initialize DataUpdateCoordinator to gather data for specific Powerview Hub."""
self.shades = shades self.shades = shades
self.hub = hub self.hub = hub
# The hub tends to crash if there are multiple radio operations at the same time
# but it seems to handle all other requests that do not use RF without issue
# so we have a lock to prevent multiple radio operations at the same time
self.radio_operation_lock = asyncio.Lock()
super().__init__( super().__init__(
hass, hass,
_LOGGER, _LOGGER,

View File

@ -67,7 +67,8 @@ async def async_setup_entry(
for shade in pv_entry.shade_data.values(): for shade in pv_entry.shade_data.values():
_LOGGER.debug("Initial refresh of shade: %s", shade.name) _LOGGER.debug("Initial refresh of shade: %s", shade.name)
await shade.refresh(suppress_timeout=True) # default 15 second timeout async with coordinator.radio_operation_lock:
await shade.refresh(suppress_timeout=True) # default 15 second timeout
entities: list[ShadeEntity] = [] entities: list[ShadeEntity] = []
for shade in pv_entry.shade_data.values(): for shade in pv_entry.shade_data.values():
@ -207,7 +208,8 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity):
async def _async_execute_move(self, move: ShadePosition) -> None: async def _async_execute_move(self, move: ShadePosition) -> None:
"""Execute a move that can affect multiple positions.""" """Execute a move that can affect multiple positions."""
_LOGGER.debug("Move request %s: %s", self.name, move) _LOGGER.debug("Move request %s: %s", self.name, move)
response = await self._shade.move(move) async with self.coordinator.radio_operation_lock:
response = await self._shade.move(move)
_LOGGER.debug("Move response %s: %s", self.name, response) _LOGGER.debug("Move response %s: %s", self.name, response)
# Process the response from the hub (including new positions) # Process the response from the hub (including new positions)
@ -318,7 +320,10 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity):
# error if are already have one in flight # error if are already have one in flight
return return
# suppress timeouts caused by hub nightly reboot # suppress timeouts caused by hub nightly reboot
await self._shade.refresh(suppress_timeout=True) # default 15 second timeout async with self.coordinator.radio_operation_lock:
await self._shade.refresh(
suppress_timeout=True
) # default 15 second timeout
_LOGGER.debug("Process update %s: %s", self.name, self._shade.current_position) _LOGGER.debug("Process update %s: %s", self.name, self._shade.current_position)
self._async_update_shade_data(self._shade.current_position) self._async_update_shade_data(self._shade.current_position)

View File

@ -114,5 +114,6 @@ class PowerViewSelect(ShadeEntity, SelectEntity):
"""Change the selected option.""" """Change the selected option."""
await self.entity_description.select_fn(self._shade, option) await self.entity_description.select_fn(self._shade, option)
# force update data to ensure new info is in coordinator # force update data to ensure new info is in coordinator
await self._shade.refresh() async with self.coordinator.radio_operation_lock:
await self._shade.refresh(suppress_timeout=True)
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -153,5 +153,6 @@ class PowerViewSensor(ShadeEntity, SensorEntity):
async def async_update(self) -> None: async def async_update(self) -> None:
"""Refresh sensor entity.""" """Refresh sensor entity."""
await self.entity_description.update_fn(self._shade) async with self.coordinator.radio_operation_lock:
await self.entity_description.update_fn(self._shade)
self.async_write_ha_state() self.async_write_ha_state()