From c7a2a6b3bea8ec3f5852f84ace7519ef5584b9f3 Mon Sep 17 00:00:00 2001 From: Gazoodle Date: Mon, 4 Apr 2022 09:40:08 +0100 Subject: [PATCH 1/4] Bump version --- README.md | 2 ++ src/geckolib/_version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fda9955..b8a4de4 100644 --- a/README.md +++ b/README.md @@ -349,6 +349,8 @@ https://www.gnu.org/licenses/gpl-3.0.html is busy and the CUI won't exit until the timeout has been reached (this can be reproduced by making the simulator stop responding to watercare requests) +## Done/Fixed in 0.4.5 + ## Done/Fixed in 0.4.4 - Moved config settings out of const class into their own class diff --git a/src/geckolib/_version.py b/src/geckolib/_version.py index 626ddeb..1224878 100644 --- a/src/geckolib/_version.py +++ b/src/geckolib/_version.py @@ -1,3 +1,3 @@ """ Single module version """ -VERSION = "0.4.4" +VERSION = "0.4.5" From 35e31c34ce4ac35c1a71f455e2607dfbdd8aefee Mon Sep 17 00:00:00 2001 From: Gazoodle Date: Mon, 4 Apr 2022 10:29:43 +0100 Subject: [PATCH 2/4] Protect set_result from a previous call --- README.md | 4 ++++ src/geckolib/config.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b8a4de4..0133cc4 100644 --- a/README.md +++ b/README.md @@ -351,6 +351,10 @@ https://www.gnu.org/licenses/gpl-3.0.html ## Done/Fixed in 0.4.5 +- Config change code could attempt to set result on a future that was already + set leading to an unhandled exception that could result in the partial status + update task being cancelled + ## Done/Fixed in 0.4.4 - Moved config settings out of const class into their own class diff --git a/src/geckolib/config.py b/src/geckolib/config.py index b1a950c..5b11da5 100644 --- a/src/geckolib/config.py +++ b/src/geckolib/config.py @@ -78,7 +78,8 @@ def set_config_mode(active: bool) -> None: for member in CONFIG_MEMBERS: setattr(GeckoConfig, member, getattr(new_config, member)) assert ConfigChange is not None - ConfigChange.set_result(True) + if not ConfigChange.done(): + ConfigChange.set_result(True) async def config_sleep(delay: float) -> None: From 480c60c325c562f335d69349705bb551f9635dd5 Mon Sep 17 00:00:00 2001 From: Gazoodle Date: Tue, 5 Apr 2022 11:01:12 +0100 Subject: [PATCH 3/4] Add unwatch_all to Observable Clear old facade from the system --- src/geckolib/async_spa.py | 1 + src/geckolib/async_spa_manager.py | 1 + src/geckolib/automation/async_facade.py | 6 ++++++ src/geckolib/config.py | 1 + src/geckolib/driver/observable.py | 5 +++++ 5 files changed, 14 insertions(+) diff --git a/src/geckolib/async_spa.py b/src/geckolib/async_spa.py index da42a2d..43685fe 100644 --- a/src/geckolib/async_spa.py +++ b/src/geckolib/async_spa.py @@ -374,6 +374,7 @@ async def disconnect(self) -> None: self._protocol.disconnect() self._protocol = None self._transport = None + self.unwatch_all() @property def isopen(self) -> bool: diff --git a/src/geckolib/async_spa_manager.py b/src/geckolib/async_spa_manager.py index 3b8c136..83f7769 100644 --- a/src/geckolib/async_spa_manager.py +++ b/src/geckolib/async_spa_manager.py @@ -187,6 +187,7 @@ async def async_reset(self) -> None: """Reset the spa manager""" self._spa_descriptors = None if self._facade is not None: + await self._facade.disconnect() self._facade = None if self._spa is not None: await self._spa.disconnect() diff --git a/src/geckolib/automation/async_facade.py b/src/geckolib/automation/async_facade.py index 834f026..e3c9dc4 100644 --- a/src/geckolib/automation/async_facade.py +++ b/src/geckolib/automation/async_facade.py @@ -65,6 +65,12 @@ def __init__(self, spa: GeckoAsyncSpa, taskman: AsyncTasks, **kwargs: str) -> No self._taskman.add_task(self._facade_update(), "Facade update", "FACADE") self._ready = False + async def disconnect(self) -> None: + _LOGGER.debug("Disconnect facade") + self._taskman.cancel_key_tasks("FACADE") + for device in self.all_automation_devices: + device.unwatch_all() + def _on_config_device_change(self, *args) -> None: active_mode = False for device in self.all_config_change_devices: diff --git a/src/geckolib/config.py b/src/geckolib/config.py index 5b11da5..42258c5 100644 --- a/src/geckolib/config.py +++ b/src/geckolib/config.py @@ -74,6 +74,7 @@ class _GeckoIdleConfig(_GeckoConfig): def set_config_mode(active: bool) -> None: """Set config mode to active (true) or idle (false).""" + _LOGGER.debug("set_config_mode: %s", active) new_config = _GeckoActiveConfig() if active else _GeckoIdleConfig() for member in CONFIG_MEMBERS: setattr(GeckoConfig, member, getattr(new_config, member)) diff --git a/src/geckolib/driver/observable.py b/src/geckolib/driver/observable.py index 6013ad6..787ed38 100644 --- a/src/geckolib/driver/observable.py +++ b/src/geckolib/driver/observable.py @@ -25,6 +25,11 @@ def unwatch(self, observer: Callable[[Any, Any, Any], None]) -> None: """Remove an observer to this observable class""" self._observers.remove(observer) + def unwatch_all(self): + """Remove all observers on this observable class""" + _LOGGER.debug("Remove all observers from %s", self) + self._observers.clear() + def _on_change( self, sender: Any = None, old_value: Any = None, new_value: Any = None ) -> None: From 5d0da9d203fbd9c50984622a6a82d6f786be53c5 Mon Sep 17 00:00:00 2001 From: Gazoodle Date: Tue, 5 Apr 2022 11:25:20 +0100 Subject: [PATCH 4/4] Add some diagnostics to trace sporadic disconnects --- README.md | 2 ++ src/geckolib/async_spa.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 0133cc4..50225e3 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,8 @@ https://www.gnu.org/licenses/gpl-3.0.html - Config change code could attempt to set result on a future that was already set leading to an unhandled exception that could result in the partial status update task being cancelled +- Disconnected facades now cleared out of the system correctly +- Added some more diagnostics to chase sporadic disconnects ## Done/Fixed in 0.4.4 diff --git a/src/geckolib/async_spa.py b/src/geckolib/async_spa.py index 43685fe..122f5f4 100644 --- a/src/geckolib/async_spa.py +++ b/src/geckolib/async_spa.py @@ -193,6 +193,7 @@ async def _connect(self) -> None: version_handler = await self._protocol.get(self._get_version_handler_func) if version_handler is None: + _LOGGER.error("Cannot get version, protocol retry count exceeded") await self._event_handler( GeckoSpaEvent.CONNECTION_PROTOCOL_RETRY_COUNT_EXCEEDED ) @@ -218,6 +219,7 @@ async def _connect(self) -> None: get_channel_handler = await self._protocol.get(self._get_channel_handler_func) if get_channel_handler is None: + _LOGGER.error("Cannot get channel, protocol retry count exceeded") await self._event_handler( GeckoSpaEvent.CONNECTION_PROTOCOL_RETRY_COUNT_EXCEEDED ) @@ -237,6 +239,7 @@ async def _connect(self) -> None: self._get_config_file_handler_func ) if config_file_handler is None: + _LOGGER.error("Cannot get file, protocol retry count exceeded") await self._event_handler( GeckoSpaEvent.CONNECTION_PROTOCOL_RETRY_COUNT_EXCEEDED ) @@ -334,6 +337,7 @@ async def _connect(self) -> None: parms=self.sendparms, ), ): + _LOGGER.error("Cannot get full struct, protocol retry count exceeded") await self._event_handler( GeckoSpaEvent.CONNECTION_PROTOCOL_RETRY_COUNT_EXCEEDED ) @@ -523,6 +527,7 @@ async def _on_async_set_value(self, pos, length, newvalue) -> None: ) if pack_command_handler is None: + _LOGGER.error("Cannot set value, protocol retry count exceeded") await self._event_handler(GeckoSpaEvent.ERROR_PROTOCOL_RETRY_COUNT_EXCEEDED) def _on_set_value(self, pos, length, newvalue) -> None: @@ -554,6 +559,7 @@ async def async_press(self, keypad) -> None: ) if pack_command_handler is None: + _LOGGER.error("Cannot press keypad, protocol retry count exceeded") await self._event_handler(GeckoSpaEvent.ERROR_PROTOCOL_RETRY_COUNT_EXCEEDED) def press(self, keypad) -> None: @@ -586,6 +592,7 @@ async def async_get_watercare(self) -> Optional[int]: ) if get_watercare_handler is None: + _LOGGER.error("Cannot get watercare, protocol retry count exceeded") await self._event_handler(GeckoSpaEvent.ERROR_PROTOCOL_RETRY_COUNT_EXCEEDED) return None @@ -609,6 +616,7 @@ async def async_set_watercare(self, new_mode) -> None: ) if set_watercare_handler is None: + _LOGGER.error("Cannot set version, protocol retry count exceeded") await self._event_handler(GeckoSpaEvent.ERROR_PROTOCOL_RETRY_COUNT_EXCEEDED) def _get_reminders_handler_func(self) -> GeckoRemindersProtocolHandler: @@ -632,6 +640,7 @@ async def async_get_reminders(self) -> List[Tuple]: ) if get_reminders_handler is None: + _LOGGER.error("Cannot get reminders, protocol retry count exceeded") await self._event_handler(GeckoSpaEvent.ERROR_PROTOCOL_RETRY_COUNT_EXCEEDED) return []