Package: geckolib
GeckoLibLibrary to interface with Gecko Alliance spa pack systems via in.touch2Written from the ground up using info gleaned from Wireshark captures to sniff
the conversation between the iOS app and the inTouch2 home transmitter.Designed to be used by home automation systems such as Home Assistant or openHABThis library is currently in Alpha, meaning that there may be large changes
in library shape, class definitions, behaviours etc as I client it in my ongoing
Home Assistant integration. This has now been released, in preview, and can be
found athttps://github.com/gazoodle/gecko-home-assistant, or from HACS by adding
a new integration and seaching for GeckoAsync supportThe core of the library has been rewritten to be async based. This is for several
reasons;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)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
necessaryWhile 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.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
API and class set to support async clients.I'll update the HA integration to use the async version as it's much faster to start
and it has more functionality. I know there are other automation clients using this
library, so the sync API and classes will stay here for a while until those clients have
had a chance to use the new async code, but I will deprecate them at some point,
probably when the library goes to v1.0.0InstallationThis library is hosted on PyPI and can be installed with the following command-linepip install geckolibGeckoShell usageOnce the library is installed, you should be able to start a Python interpreter session
by using the commandpython3, then executing the following commands>>>fromgeckolibimportGeckoShell>>>GeckoShell.run()<Disclaimer>:<snip/>:</Disclaimer>Startingdiscoveryprocess...Found1spasConnectingtospa`Spa`at10.1.2.3...connected!Heater:Temperature39.0°C,SetPoint39.0°C,OperationIdlePump1:OFFPump2:OFFBlower:OFFLights:OFFWaterCare:StandardWelcometotheGeckoshell.Typehelpor?tolistcommands.Spa$This ought to find your spa on your local network. If it doesn't, please open an issue, you can
exercise some of your spa features using the commands belowSpa$ P1 HI
Spa$ P1 OFF

Spa$ LI ON
Spa$ LI OFFYou can get help on the GeckoShell moduleSpa$ help

Documented commands (type help <topic>):
========================================
BL  P2      discover  get      list    refresh   snapshot  watercare
LI  about   download  help     live    set       state
P1  config  exit      license  manage  setpoint  version

Spa$ help watercare
Set the active watercare mode to one of ['Away From Home', 'Standard', 'Energy Saving', 'Super Energy Saving', 'Weekender'] : WATERCARE <mode>
Spa$ watercare WeekenderIf you have more than one spa/device detected, you can use thelistandmanagecommandsStarting discovery process...Found 2 spas
Welcome to the Gecko shell. Type help or ? to list commands.

(Gecko) list
1. Spa
2. Dummy Spa
(Gecko) manage 1
Connecting to spa `Spa` at 10.1.2.3 ... connected!
Heater: Temperature 39.0°C, SetPoint 39.0°C, Operation Idle
P1: OFF
P2: OFF
BL: OFF
LI: OFF
WaterCare: Standard
Spa$If you want to get some diagnostics you can enable file logging at the start of the session>>>fromgeckolibimportGeckoShell>>>GeckoShell.run(["logfile client.log"])::or it can be used later after you've connected to your spa with thelogfilecommandSpa$ logfile client.logThe fileclient.logwill contain diagnostic information that may be useful
for tracking down issuesIf you want to start the client and point it at a specific IP address (maybe you have your SPA on a different subnet), you can issue the discovery command as part of the launch parameters>>>fromgeckolibimportGeckoShell>>>GeckoShell.run(["logfile client.log","discover 192.168.1.2"])::Simulator UsageIt's best if you download the repo for using the simulator. Once you've done that,
open a terminal to your repo test folder (./tests)python3 simulator.pyYou should see a promptWelcome to the Gecko simulator. Type help or ? to list commands.

(GeckoSim)You should load the default snapshot at this point(GeckoSim) load snapshots/default.snapshot
(GeckoSim)Now you can run the client program, or indeed your iOS or Android app and then
attempt to connect to the simulator. At present it only supports loading another
snapshot to change the state. If the changes are too great, for example, if you've
loaded a completly different spa then the iOS and Android apps may be confused.Best to click on the account icon and then reselect the test spa to get it to
reconnect from the start.Also, at present the simulator doesn't respond to any commands issued from the
iOS and Android applications.Async API Usage""" Sample client demonstrating async use of geckolib """importasyncioimportloggingfromgeckolibimportGeckoAsyncSpaMan,GeckoSpaEvent# type: ignore# Replace with your own UUID, see https://www.uuidgenerator.net/>CLIENT_ID="a2d936db-4e95-4e4d-82bc-b4225fa99739"# Replace with your spa IP address if on a sub-netSPA_ADDRESS=NoneclassSampleSpaMan(GeckoAsyncSpaMan):"""Sample spa man implementation"""asyncdefhandle_event(self,event:GeckoSpaEvent,**kwargs)->None:# Uncomment this line to see events generated# print(f"{event}: {kwargs}")passasyncdefmain()->None:asyncwithSampleSpaMan(CLIENT_ID,spa_address=SPA_ADDRESS)asspaman:print("Looking for spas on your network ...")# Wait for descriptors to be availableawaitspaman.wait_for_descriptors()iflen(spaman.spa_descriptors)==0:print("**** There were no spas found on your network.")returnspa_descriptor=spaman.spa_descriptors[0]print(f"Connecting to{spa_descriptor.name}at{spa_descriptor.ipaddress}...")awaitspaman.async_set_spa_info(spa_descriptor.ipaddress,spa_descriptor.identifier_as_string,spa_descriptor.name,)# Wait for the facade to be readyawaitspaman.wait_for_facade()print(spaman.facade.water_heater)print("Turning pump 1 on")awaitspaman.facade.pumps[0].async_set_mode("HI")awaitasyncio.sleep(5)print("Turning pump 1 off")awaitspaman.facade.pumps[0].async_set_mode("OFF")awaitasyncio.sleep(5)if__name__=="__main__":# Install loggingstream_logger=logging.StreamHandler()stream_logger.setLevel(logging.DEBUG)stream_logger.setFormatter(logging.Formatter("%(asctime)s>%(levelname)s%(message)s"))logging.getLogger().addHandler(stream_logger)logging.getLogger().setLevel(logging.INFO)asyncio.run(main())This should output something like thisLooking for spas on your network ...
2022-03-16 07:05:12,842> INFO Found 1 spas ... [My Spa(SPA00:01:02:03:04:05)]
Connecting to My Spa at 10.0.0.123 ...
Heater: Temperature 36.0°C, SetPoint 36.0°C, Real SetPoint 36.0°C, Operation Idle
Turning pump 1 on
2022-03-16 07:05:19,292> INFO Value for UdP2 changed from OFF to HI
2022-03-16 07:05:19,479> INFO Value for P2 changed from OFF to HIGH
2022-03-16 07:05:19,480> INFO Value for UdPumpTime changed from 0 to 45
Turning pump 1 off
2022-03-16 07:05:25,049> INFO Value for UdP2 changed from HI to OFF
2022-03-16 07:05:25,236> INFO Value for P2 changed from HIGH to OFF
2022-03-16 07:05:25,236> INFO Value for UdPumpTime changed from 45 to 0Complete sampleThere is also a complete async sample which can be found in the repo under
the /sample folder. This can be run usingpython3 complete.py. Full pathhttps://github.com/gazoodle/geckolib/tree/main/sampleHome Assistant integrationThe best example of use is in the Home Assistant integration which can be
found herehttps://github.com/gazoodle/gecko-home-assistantSync API UsageWARNINGSync functionality will be removed in a future release,
examples removed from READMEAcknowledgementsInspired byhttps://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 :-)Licensehttps://www.gnu.org/licenses/gpl-3.0.htmlTodoSpa state (errors)Error handling (ongoing)Pythonize where possibleAPIs to support integration into automation systems (Ongoing)Warnings/ErrorsDiagnosticsMore unit testsHandle other device types such as WaterfallHandle inMix for lighting controlAdd API documentationMerge reminders branch from @kalinrowList property for User demands in pack classesList property for errors in pack classesTidy up support files. One class per fileFull sweep for typing hints - OngoingAdd sensor for errorsAdd switch for winterizingAdd ability to set hours so we can implement a crude clock sync mechanismThink about a way to provide access to behaviour refresh frequencies so that
it can be customisedLook into getting shell & simulator using async API so that there are no
internal dependencies on the sync code any longerMove to pytest unit test frameworkUse snapshots to generate some specific testsBuild some documentationAdd coverage to GitHub package workflowThere 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.8Split radio strength & channel into two separate sensorsAdded some extra debugging around protocol sync lockSome unit_of_measurement values were the string "None" instead of the python keyword NoneAggregate SpaPack error properties into a single text sensorRebuild pack accessors from SpaStructPack.xml v36.1Done/Fixed in 0.4.7Merged fix for negative watercare values (thanks @EiNSTeiN-)Demote out-of-range spapack struct enum values to DEBUG and return "Unknown"Demote "No handler for xxxx" to DEBUG to declutter log filesDone/Fixed in 0.4.6Going back to basics and watching the protocol from the iOS app, I noticed that
the commands sent from the app don't use the same sequence numbers as the ones
that are sent during connection, they run a loop around from 192-255 whereas the
protocol ones go from 1-191 ... so replicate that behaviour in case it's confusing
something in the system and adding to the instability of connectionsMoved protocol retry constants to GeckoConfig classAdded 2 second pause between protocol retries to allow intouch2 EN module to settleIncreased default timeout from 2 to 4 secondsAdded radio channel and signal strength sensorDone/Fixed in 0.4.5Config 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 cancelledDisconnected facades now cleared out of the system correctlyAdded some more diagnostics to chase sporadic disconnectsDone/Fixed in 0.4.4Moved config settings out of const class into their own classAdded idle/active config settings and task loop for library to switch between
idle and active config settings. An idle spa is one that is currently not processing
any user demands, an active spa is one that has received a client request such as to
change temp or turn on a pump. Active mode will stay on while any user demand is
currently live.Replaced asyncio.sleep in various places with config_sleep which is aware of when
the configuration values have changed which means that the loops currently waiting on
these values stop waiting and can collect the new values.Done/Fixed in 0.4.2Fixed processor getting pegged at 100% but not using asyncio.sleep(0)Fixed simulator to respect names compatible with WindowsRenamed some snapshotsDone/Fixed in 0.4.1Updated README with simple async examplePull in reminders PR from @kalinrow - Thanks!Add unit tests for remindersAdd async version of reminders - slightly different API as sync version returns
time as first reminderUpdated various clients to use remindersDone/Fixed in 0.4.0Supports 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 issuesSensor for connection status is available early in facade lifetimePing sensor available after spa begins connection sequenceRe-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 clientsManage ping failure and RF errors with retry mechanismWatercare regular refresh from facadeCreate Spa manager class to run the connection logic so that clients get cleanup
opportunity when reconnections are neededHandle disconnected spas, ping failures, RF errors and so onSimulator can get/set accessors for experimentationDone/Fixed in 0.3.24Fix error found by Github workflowAdded extra logging into to find out more about issue #28Removed readline library as it isn't supported on Windows and it wasn't really doing anythingAdded some extra doc for issue #18Added RFERR handler to client and simulator to start investigationsHandle Watercare index out of rangeAccessors can now deal with Time type entriesAdded diag.py to aid tracking issue#27Added eco mode control to facade and shellTemperature accessor in new generator as it doesn't need to be handled at runtimeRemoved decorators.pyDone/Fixed in 0.3.23Demoted some INFO logging to DEBUG to reduce HA log file clutterDone/Fixed in 0.3.22Increase connection timeout to help with laggy tubs and busy networksWatercare setting updated locally rather than wait for tub response to
improve HA UI responsivenessFast locator for static IPDone/Fixed in 0.3.21Demoted some benign debugging data that clutters log filesReplaced "config" and "live" with "accessors" commands in shell to reduce direct access to XMLAdded "monitor" command in shell to give a live view of changes from other sources e.g. control panel or appRemoved 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.20Merge changes for variable speed pumps. Thankshttps://github.com/los93solPrevent new "pump" command showing in help UIDone/Fixed in 0.3.19Ensure STATP changes are cleared after processing rather than accumulating for-all-time! Thankshttps://github.com/maegibbonsDone/Fixed in 0.3.18Added some more snapshotsAttempt to handle spas that return unsupported config/log versionsAdd ability to provide an IP address to the libraryDone/Fixed in 0.3.17Attempt to fix urllib3 requirement in pip install. It was in the wrong placeDone/Fixed in 0.3.16More robust to missed packets during spa connectionMechanism to access raw pack values from the facade, e.g. facade.pumps[0].state_sensor.accessor.raw_valueAdd 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.devicesDone/Fixed in 0.3.15Trying out Github publish actionsDone/Fixed in 0.3.14Added SmartWinterMode sensorsAdded Filter Clean/Purge sensorsDone/Fixed in 0.3.13Move MrSteam handling on so that we can get a proper structure dumpDone/Fixed in 0.3.12UnicodeEncodeError: '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 easierChanges to allow library to be more suitable for async clientsPing frequency returned to 15 secondsSimulator added to allow investigation using snapshots sent in from other folkHeating state fixed to show heating/cooling as appropriateIssue #1 Waterfall now recognised and responding to button pressIssue #3 P1 Twice - deduped the device listIssue #8 The library should be able to provide temp and heater usage stats nowIssue #9 Water Heater current operation should now be working correctlyDone/Fixed in 0.3.11Ping frequency set to 45 secondsReset method to GeckoReponse class to handle retries in GeckoGetStatus classAdd mechanism to locate a spa in the locator class based on it's identifierSet worker threads to daemon modeRe-structure for better lifetime management and easier clientingMerged PR from dukey32123 supplying waterfall constants ... need to find keypad code too.flake8 and black formatting and tidy-upObservable added to propagate change notification so we can be cliented as a
local-push integration in Home AssistantMoved all the functionality out of client.py and put it into GeckoShell classDone/Fixed in 0.3.10Try upload to PiPYDone/Fixed in 0.3.9Message encoding -> latin1 from utf-8 to avoid mangling raw bytes. This fixes
the turning pump 2 off when turning pump 1 on usingset UdP2=HIthenset 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 packageMoved to pyproject.toml/setup.cfg for modern Python libraryUpdated README.md with better examplesDone/Fixed in 0.3.8Restructure code to be in line with Python library styleCode auto formatted with BlackMost pylint warnings/issues fixedUnit tests added for GeckoStructAccessorWatercare handledClient.py program restructured to use python Cmd classDone/Fixed in 0.3.7Deal with unhandled devicesDone/Fixed in 0.3.6Limit 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 simulatorDeal with temp decorators that might not be present on different modulesAutomation interface addedTimeout retry of command to make it more robust on busy networksVersionUsing Semantic versioninghttps://semver.org/