Allow erroring modules to recover (#1080)

Re-query failed modules after some delay instead of immediately disabling them.
Changes to features so they can still be created when modules are erroring.
This commit is contained in:
Steven B.
2024-07-30 19:23:07 +01:00
committed by GitHub
parent 445f74eed7
commit 7bba9926ed
23 changed files with 264 additions and 187 deletions

View File

@@ -165,28 +165,25 @@ class SmartDevice(Device):
if first_update:
await self._negotiate()
await self._initialize_modules()
# Run post update for the cloud module
if cloud_mod := self.modules.get(Module.Cloud):
self._handle_module_post_update(cloud_mod, now, had_query=True)
resp = await self._modular_update(first_update, now)
# Call child update which will only update module calls, info is updated
# from get_child_device_list. update_children only affects hub devices, other
# devices will always update children to prevent errors on module access.
if update_children or self.device_type != DeviceType.Hub:
for child in self._children.values():
await child._update()
if child_info := self._try_get_response(
self._last_update, "get_child_device_list", {}
):
for info in child_info["child_device_list"]:
self._children[info["device_id"]]._update_internal_state(info)
for child in self._children.values():
errors = []
for child_module_name, child_module in child._modules.items():
if not self._handle_module_post_update_hook(child_module):
errors.append(child_module_name)
for error in errors:
child._modules.pop(error)
# Call child update which will only update module calls, info is updated
# from get_child_device_list. update_children only affects hub devices, other
# devices will always update children to prevent errors on module access.
# This needs to go after updating the internal state of the children so that
# child modules have access to their sysinfo.
if update_children or self.device_type != DeviceType.Hub:
for child in self._children.values():
await child._update()
# We can first initialize the features after the first update.
# We make here an assumption that every device has at least a single feature.
@@ -197,18 +194,26 @@ class SmartDevice(Device):
updated = self._last_update if first_update else resp
_LOGGER.debug("Update completed %s: %s", self.host, list(updated.keys()))
def _handle_module_post_update_hook(self, module: SmartModule) -> bool:
def _handle_module_post_update(
self, module: SmartModule, update_time: float, had_query: bool
):
if module.disabled:
return # pragma: no cover
if had_query:
module._last_update_time = update_time
try:
module._post_update_hook()
return True
module._set_error(None)
except Exception as ex:
_LOGGER.warning(
"Error processing %s for device %s, module will be unavailable: %s",
module.name,
self.host,
ex,
)
return False
# Only set the error if a query happened.
if had_query:
module._set_error(ex)
_LOGGER.warning(
"Error processing %s for device %s, module will be unavailable: %s",
module.name,
self.host,
ex,
)
async def _modular_update(
self, first_update: bool, update_time: float
@@ -221,17 +226,16 @@ class SmartDevice(Device):
mq = {
module: query
for module in self._modules.values()
if (query := module.query())
if module.disabled is False and (query := module.query())
}
for module, query in mq.items():
if first_update and module.__class__ in FIRST_UPDATE_MODULES:
module._last_update_time = update_time
continue
if (
not module.MINIMUM_UPDATE_INTERVAL_SECS
not module.update_interval
or not module._last_update_time
or (update_time - module._last_update_time)
>= module.MINIMUM_UPDATE_INTERVAL_SECS
or (update_time - module._last_update_time) >= module.update_interval
):
module_queries.append(module)
req.update(query)
@@ -254,16 +258,10 @@ class SmartDevice(Device):
self._info = self._try_get_response(info_resp, "get_device_info")
# Call handle update for modules that want to update internal data
errors = []
for module_name, module in self._modules.items():
if not self._handle_module_post_update_hook(module):
errors.append(module_name)
for error in errors:
self._modules.pop(error)
# Set the last update time for modules that had queries made.
for module in module_queries:
module._last_update_time = update_time
for module in self._modules.values():
self._handle_module_post_update(
module, update_time, had_query=module in module_queries
)
return resp
@@ -392,7 +390,7 @@ class SmartDevice(Device):
name="RSSI",
attribute_getter=lambda x: x._info["rssi"],
icon="mdi:signal",
unit="dBm",
unit_getter=lambda: "dBm",
category=Feature.Category.Debug,
type=Feature.Type.Sensor,
)