diff --git a/README.md b/README.md index 42fc763..1324c8f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # GeckoLib + Library to interface with Gecko Alliance spa pack systems via in.touch2 Written from the ground up using info gleaned from Wireshark captures to sniff @@ -17,20 +18,20 @@ a new integration and seaching for Gecko_ The core of the library has been rewritten to be async based. This is for several reasons; - 1) Home Assistant, my main client of the library prefers this pattern. I'd like to - get away from the "can't connect", "not supported" pattern and have the spa - connect immediately to the facade (which will do the handshake to the actual spa - asynchronously so that connection state can be shown in the UI if required). - This will improve HA startup performance and allow me to control - the retry connection pattern in the library without having to burden the HA - integration with this (HA doesn't like protocol in integrations) - 2) I've done loads of multi-threaded programming in my life and think I'm familiar - with almost all kinds of problems this brings, but ... why bother when this isn't - necessary - 3) While trying to implement a feature that supports occasionally disconnected - spas without generating reams of logging, I realized that I was fighting against - the previous architecture, so it's time to refactor that. - 4) Every day is a school day. I've not seriously explored Python's async support :-) +1. Home Assistant, my main client of the library prefers this pattern. I'd like to + get away from the "can't connect", "not supported" pattern and have the spa + connect immediately to the facade (which will do the handshake to the actual spa + asynchronously so that connection state can be shown in the UI if required). + This will improve HA startup performance and allow me to control + the retry connection pattern in the library without having to burden the HA + integration with this (HA doesn't like protocol in integrations) +2. I've done loads of multi-threaded programming in my life and think I'm familiar + with almost all kinds of problems this brings, but ... why bother when this isn't + necessary +3. While trying to implement a feature that supports occasionally disconnected + spas without generating reams of logging, I realized that I was fighting against + the previous architecture, so it's time to refactor that. +4. Every day is a school day. I've not seriously explored Python's async support :-) Currently this isn't a breaking change, the sync library still has the functionality that it always had (albeit with some major refactoring). There is a completely parallel @@ -149,7 +150,6 @@ Spa$ logfile client.log ``` - The file `client.log` will contain diagnostic information that may be useful for tracking down issues @@ -272,7 +272,6 @@ if __name__ == "__main__": There is also a complete async sample which can be found in the repo under the /sample folder. This can be run using `python3 complete.py` - # Sync API Usage **WARNING** Sync functionality will be removed in a future release @@ -427,181 +426,202 @@ facade.complete() # Acknowledgements - - Inspired by https://github.com/chicago6061/in.touch2. - - Thanks to the folk at Gecko for building this system as a local device rather than mandating a cloud solution. - - Wireshark is an awesome tool for sniffing out what is going on on your network. Take a look one day, you might be horrified :-) +- Inspired by https://github.com/chicago6061/in.touch2. +- Thanks to the folk at Gecko for building this system as a local device rather than mandating a cloud solution. +- Wireshark is an awesome tool for sniffing out what is going on on your network. Take a look one day, you might be horrified :-) # License + https://www.gnu.org/licenses/gpl-3.0.html # Todo - - Reminders - - Spa state (errors) - - Error handling (ongoing) - - Pythonize where possible - - APIs to support integration into automation systems (Ongoing) - * Warnings/Errors - * Reminders - * Diagnostics - - More unit tests - - Handle other device types such as Waterfall - - Handle inMix for lighting control - - Add API documentation - - RF signal strength for EN(Home) -> CO(Spa) - - Merge reminders branch from @kalinrow - - List property for User demands in pack classes - - List property for errors in pack classes - - Tidy up support files. One class per file - - Full sweep for typing hints - Ongoing - - Add sensor for reminders - - Add sensor for errors - - Add switch for winterizing - - Add sensor for RF signal strength - - Add ability to set hours so we can implement a crude clock sync mechanism - - Think about a way to provide access to behaviour refresh frequencies so that - it can be customised - - Add accessor get/set commands to simulator for investigating other spas - - Look into getting shell & simulator using async API so that there are no - internal dependencies on the sync code any longer - - Move to pytest unit test framework - - Use snapshots to generate some specific tests - - Build some documentation - - Add coverage to GitHub package workflow - - There is a lock issue when a command is being retried as the protocol lock - 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) - +- Reminders +- Spa state (errors) +- Error handling (ongoing) +- Pythonize where possible +- APIs to support integration into automation systems (Ongoing) + - Warnings/Errors + - Reminders + - Diagnostics +- More unit tests +- Handle other device types such as Waterfall +- Handle inMix for lighting control +- Add API documentation +- RF signal strength for EN(Home) -> CO(Spa) +- Merge reminders branch from @kalinrow +- List property for User demands in pack classes +- List property for errors in pack classes +- Tidy up support files. One class per file +- Full sweep for typing hints - Ongoing +- Add sensor for reminders +- Add sensor for errors +- Add switch for winterizing +- Add sensor for RF signal strength +- Add ability to set hours so we can implement a crude clock sync mechanism +- Think about a way to provide access to behaviour refresh frequencies so that + it can be customised +- Look into getting shell & simulator using async API so that there are no + internal dependencies on the sync code any longer +- Move to pytest unit test framework +- Use snapshots to generate some specific tests +- Build some documentation +- Add coverage to GitHub package workflow +- There is a lock issue when a command is being retried as the protocol lock + 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.0 - - Supports both sync and async clients. The sync clients ought to be backward - compatible but there has been a huge chunk of refactoring to get the async - built and even though I've run all the tests and clients, it's possible there - are issues - - Sensor for connection status is available early in facade lifetime - - Ping sensor available after spa begins connection sequence - - Re-added readline library to simulator as it was doing auto-complete for snapshot - loading and was useful when testing, but added it inside a try/catch block so - it won't upset Windows clients - - Manage ping failure and RF errors with retry mechanism - - Watercare regular refresh from facade - - Create Spa manager class to run the connection logic so that clients get cleanup - opportunity when reconnections are needed + +- Supports both sync and async clients. The sync clients ought to be backward + compatible but there has been a huge chunk of refactoring to get the async + built and even though I've run all the tests and clients, it's possible there + are issues +- Sensor for connection status is available early in facade lifetime +- Ping sensor available after spa begins connection sequence +- Re-added readline library to simulator as it was doing auto-complete for snapshot + loading and was useful when testing, but added it inside a try/catch block so + it won't upset Windows clients +- Manage ping failure and RF errors with retry mechanism +- Watercare regular refresh from facade +- Create Spa manager class to run the connection logic so that clients get cleanup + opportunity when reconnections are needed +- Handle disconnected spas, ping failures, RF errors and so on +- Simulator can get/set accessors for experimentation ## Done/Fixed in 0.3.24 - - Fix error found by Github workflow - - Added extra logging into to find out more about issue #28 - - Removed readline library as it isn't supported on Windows and it wasn't really doing anything - - Added some extra doc for issue #18 - - Added RFERR handler to client and simulator to start investigations - - Handle Watercare index out of range - - Accessors can now deal with Time type entries - - Added diag.py to aid tracking issue#27 - - Added eco mode control to facade and shell - - Temperature accessor in new generator as it doesn't need to be handled at runtime - - Removed decorators.py + +- Fix error found by Github workflow +- Added extra logging into to find out more about issue #28 +- Removed readline library as it isn't supported on Windows and it wasn't really doing anything +- Added some extra doc for issue #18 +- Added RFERR handler to client and simulator to start investigations +- Handle Watercare index out of range +- Accessors can now deal with Time type entries +- Added diag.py to aid tracking issue#27 +- Added eco mode control to facade and shell +- Temperature accessor in new generator as it doesn't need to be handled at runtime +- Removed decorators.py ## Done/Fixed in 0.3.23 - - Demoted some INFO logging to DEBUG to reduce HA log file clutter + +- Demoted some INFO logging to DEBUG to reduce HA log file clutter ## Done/Fixed in 0.3.22 - - Increase connection timeout to help with laggy tubs and busy networks - - Watercare setting updated locally rather than wait for tub response to - improve HA UI responsiveness - - Fast locator for static IP + +- Increase connection timeout to help with laggy tubs and busy networks +- Watercare setting updated locally rather than wait for tub response to + improve HA UI responsiveness +- Fast locator for static IP ## Done/Fixed in 0.3.21 - - Demoted some benign debugging data that clutters log files - - Replaced "config" and "live" with "accessors" commands in shell to reduce direct access to XML - - Added "monitor" command in shell to give a live view of changes from other sources e.g. control panel or app - - Removed runtime reliance on SpaPackStruct.xml, this is replaced by the python code in driver/pack/* - - Accessor can handle byte, word, bool value updates if provided as a string (i.e. GeckoShell set command) +- Demoted some benign debugging data that clutters log files +- Replaced "config" and "live" with "accessors" commands in shell to reduce direct access to XML +- Added "monitor" command in shell to give a live view of changes from other sources e.g. control panel or app +- Removed runtime reliance on SpaPackStruct.xml, this is replaced by the python code in driver/pack/\* +- Accessor can handle byte, word, bool value updates if provided as a string (i.e. GeckoShell set command) ## Done/Fixed in 0.3.20 - - Merge changes for variable speed pumps. Thanks https://github.com/los93sol - - Prevent new "pump" command showing in help UI + +- Merge changes for variable speed pumps. Thanks https://github.com/los93sol +- Prevent new "pump" command showing in help UI ## Done/Fixed in 0.3.19 - - Ensure STATP changes are cleared after processing rather than accumulating for-all-time! Thanks https://github.com/maegibbons + +- Ensure STATP changes are cleared after processing rather than accumulating for-all-time! Thanks https://github.com/maegibbons ## Done/Fixed in 0.3.18 - - Added some more snapshots - - Attempt to handle spas that return unsupported config/log versions - - Add ability to provide an IP address to the library + +- Added some more snapshots +- Attempt to handle spas that return unsupported config/log versions +- Add ability to provide an IP address to the library ## Done/Fixed in 0.3.17 - - Attempt to fix urllib3 requirement in pip install. It was in the wrong place + +- Attempt to fix urllib3 requirement in pip install. It was in the wrong place ## Done/Fixed in 0.3.16 - - More robust to missed packets during spa connection - - Mechanism to access raw pack values from the facade, e.g. facade.pumps[0].state_sensor.accessor.raw_value - - Add API to facade to get device by key, e.g. facade.get_device("P1") will return the first pump. - - Add property to facade to get all device keys; facade.devices + +- More robust to missed packets during spa connection +- Mechanism to access raw pack values from the facade, e.g. facade.pumps[0].state_sensor.accessor.raw_value +- Add API to facade to get device by key, e.g. facade.get_device("P1") will return the first pump. +- Add property to facade to get all device keys; facade.devices ## Done/Fixed in 0.3.15 - - Trying out Github publish actions + +- Trying out Github publish actions ## Done/Fixed in 0.3.14 - - Added SmartWinterMode sensors - - Added Filter Clean/Purge sensors + +- Added SmartWinterMode sensors +- Added Filter Clean/Purge sensors ## Done/Fixed in 0.3.13 - - Move MrSteam handling on so that we can get a proper structure dump + +- Move MrSteam handling on so that we can get a proper structure dump ## Done/Fixed in 0.3.12 - - UnicodeEncodeError: 'latin-1' codec can't encode character '\u0101' in position 108: ordinal not in range(256) - - Massive refactor of protocol handlers to make building a simulator easier - - Changes to allow library to be more suitable for async clients - - Ping frequency returned to 15 seconds - - Simulator added to allow investigation using snapshots sent in from other folk - - Heating state fixed to show heating/cooling as appropriate - - Issue #1 Waterfall now recognised and responding to button press - - Issue #3 P1 Twice - deduped the device list - - Issue #8 The library should be able to provide temp and heater usage stats now - - Issue #9 Water Heater current operation should now be working correctly + +- UnicodeEncodeError: 'latin-1' codec can't encode character '\u0101' in position 108: ordinal not in range(256) +- Massive refactor of protocol handlers to make building a simulator easier +- Changes to allow library to be more suitable for async clients +- Ping frequency returned to 15 seconds +- Simulator added to allow investigation using snapshots sent in from other folk +- Heating state fixed to show heating/cooling as appropriate +- Issue #1 Waterfall now recognised and responding to button press +- Issue #3 P1 Twice - deduped the device list +- Issue #8 The library should be able to provide temp and heater usage stats now +- Issue #9 Water Heater current operation should now be working correctly ## Done/Fixed in 0.3.11 - - Ping frequency set to 45 seconds - - Reset method to GeckoReponse class to handle retries in GeckoGetStatus class - - Add mechanism to locate a spa in the locator class based on it's identifier - - Set worker threads to daemon mode - - Re-structure for better lifetime management and easier clienting - - Merged PR from dukey32123 supplying waterfall constants ... need to find keypad code too. - - flake8 and black formatting and tidy-up - - Observable added to propagate change notification so we can be cliented as a - local-push integration in Home Assistant - - Moved all the functionality out of client.py and put it into GeckoShell class + +- Ping frequency set to 45 seconds +- Reset method to GeckoReponse class to handle retries in GeckoGetStatus class +- Add mechanism to locate a spa in the locator class based on it's identifier +- Set worker threads to daemon mode +- Re-structure for better lifetime management and easier clienting +- Merged PR from dukey32123 supplying waterfall constants ... need to find keypad code too. +- flake8 and black formatting and tidy-up +- Observable added to propagate change notification so we can be cliented as a + local-push integration in Home Assistant +- Moved all the functionality out of client.py and put it into GeckoShell class ## Done/Fixed in 0.3.10 - - Try upload to PiPY + +- Try upload to PiPY ## Done/Fixed in 0.3.9 - - Message encoding -> latin1 from utf-8 to avoid mangling raw bytes. This fixes - the turning pump 2 off when turning pump 1 on using `set UdP2=HI` then - `set UdP1=HI`. There must be a better way to do this without switching between - strings and bytes ... - - Major source restructure to get ready for PyPI package - - Moved to pyproject.toml/setup.cfg for modern Python library - - Updated README.md with better examples + +- Message encoding -> latin1 from utf-8 to avoid mangling raw bytes. This fixes + the turning pump 2 off when turning pump 1 on using `set UdP2=HI` then + `set UdP1=HI`. There must be a better way to do this without switching between + strings and bytes ... +- Major source restructure to get ready for PyPI package +- Moved to pyproject.toml/setup.cfg for modern Python library +- Updated README.md with better examples ## Done/Fixed in 0.3.8 - - Restructure code to be in line with Python library style - - Code auto formatted with Black - - Most pylint warnings/issues fixed - - Unit tests added for GeckoStructAccessor - - Watercare handled - - Client.py program restructured to use python Cmd class + +- Restructure code to be in line with Python library style +- Code auto formatted with Black +- Most pylint warnings/issues fixed +- Unit tests added for GeckoStructAccessor +- Watercare handled +- Client.py program restructured to use python Cmd class ## Done/Fixed in 0.3.7 - - Deal with unhandled devices + +- Deal with unhandled devices ## Done/Fixed in 0.3.6 - - Limit buttons & devices to those available based on configuration (i.e. don't show P3 if not installed) - - Dump state block & intro sequence so we can build a simulator - - Deal with temp decorators that might not be present on different modules - - Automation interface added - - Timeout retry of command to make it more robust on busy networks - - # Version - Using Semantic versioning https://semver.org/ + +- Limit buttons & devices to those available based on configuration (i.e. don't show P3 if not installed) +- Dump state block & intro sequence so we can build a simulator +- Deal with temp decorators that might not be present on different modules +- Automation interface added +- Timeout retry of command to make it more robust on busy networks + +# Version + +Using Semantic versioning https://semver.org/ diff --git a/tests/test_async_locator.py b/tests/test_async_locator.py index 44bd160..6c52333 100644 --- a/tests/test_async_locator.py +++ b/tests/test_async_locator.py @@ -28,6 +28,3 @@ def test_on_construct(self): self.assertFalse(self.locator.is_running) self.assertEqual(self.locator.age, 0) self.assertFalse(self.locator.has_had_enough_time) - - def test_discover_none(self): - self.assertFalse(True) diff --git a/tests/test_spaman.py b/tests/test_spaman.py index c70bfc1..231f6d2 100644 --- a/tests/test_spaman.py +++ b/tests/test_spaman.py @@ -10,7 +10,7 @@ class SpaManImpl(GeckoAsyncSpaMan): """A Spa Manager to test with""" def __init__(self): - super().__init__("CLIENT_UUID") + super().__init__("CLIENT_UUID", spa_identifier="TestID") self.events = [] async def handle_event(self, event: GeckoSpaEvent, **kwargs) -> None: @@ -62,6 +62,7 @@ async def test_locate_spas(self): [ GeckoSpaEvent.SPA_MAN_ENTER, GeckoSpaEvent.LOCATING_STARTED, + GeckoSpaEvent.LOCATING_STARTED, GeckoSpaEvent.LOCATING_DISCOVERED_SPA, GeckoSpaEvent.LOCATING_FINISHED, ], @@ -69,16 +70,18 @@ async def test_locate_spas(self): async def test_connect_spa(self): facade = await self.spaman.async_connect_to_spa(mock_spa_descriptor) - self.assertIsNotNone(facade) self.assertListEqual( self.spaman.events, [ GeckoSpaEvent.SPA_MAN_ENTER, GeckoSpaEvent.LOCATING_STARTED, - GeckoSpaEvent.LOCATING_DISCOVERED_SPA, - GeckoSpaEvent.LOCATING_FINISHED, + GeckoSpaEvent.CLIENT_HAS_STATUS_SENSOR, + GeckoSpaEvent.CLIENT_HAS_RECONNECT_BUTTON, + GeckoSpaEvent.CONNECTION_STARTED, + GeckoSpaEvent.CONNECTION_FINISHED, ], ) + self.assertIsNone(facade) async def atest_connect_twice_fails(self): await self.spaman.async_connect_to_spa(mock_spa_descriptor)