gazoodle / geckolib

Library to interface with Gecko Alliance spa pack systems via in.touch2 module
GNU General Public License v3.0
62 stars 24 forks source link
gecko hot-tub in-touch2 intouch2 jacuzzi spa

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 the conversation between the iOS app and the inTouch2 home transmitter.

Designed to be used by home automation systems such as Home Assistant or openHAB

This 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 at https://github.com/gazoodle/gecko-home-assistant, or from HACS by adding a new integration and seaching for Gecko

Async support

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 :-)

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.0

Installation

This library is hosted on PyPI and can be installed with the following command-line

pip install geckolib

GeckoShell usage

Once the library is installed, you should be able to start a Python interpreter session by using the command python3, then executing the following commands

>>> from geckolib import GeckoShell
>>> GeckoShell.run()

    <Disclaimer>
    :
    <snip/>
    :
    </Disclaimer>

Starting discovery process...Found 1 spas
Connecting to spa `Spa` at 10.1.2.3 ... connected!
Heater: Temperature 39.0°C, SetPoint 39.0°C, Operation Idle
Pump 1: OFF
Pump 2: OFF
Blower: OFF
Lights: OFF
WaterCare: Standard
Welcome to the Gecko shell. Type help or ? to list commands.

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 below


Spa$ P1 HI
Spa$ P1 OFF

Spa$ LI ON
Spa$ LI OFF

You can get help on the GeckoShell module


Spa$ 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 Weekender

If you have more than one spa/device detected, you can use the list and manage commands


Starting 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

>>> from geckolib import GeckoShell
>>> GeckoShell.run(["logfile client.log"])

  :
  :

or it can be used later after you've connected to your spa with the logfile command


Spa$ logfile client.log

The file client.log will contain diagnostic information that may be useful for tracking down issues

If 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

>>> from geckolib import GeckoShell
>>> GeckoShell.run(["logfile client.log", "discover 192.168.1.2"])

  :
  :

Simulator Usage

It'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.py

You should see a prompt

Welcome 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 """

import asyncio
import logging

from geckolib import GeckoAsyncSpaMan, 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-net
SPA_ADDRESS = None

class SampleSpaMan(GeckoAsyncSpaMan):
    """Sample spa man implementation"""

    async def handle_event(self, event: GeckoSpaEvent, **kwargs) -> None:
        # Uncomment this line to see events generated
        # print(f"{event}: {kwargs}")
        pass

async def main() -> None:

    async with SampleSpaMan(CLIENT_ID, spa_address=SPA_ADDRESS) as spaman:
        print("Looking for spas on your network ...")

        # Wait for descriptors to be available
        await spaman.wait_for_descriptors()

        if len(spaman.spa_descriptors) == 0:
            print("**** There were no spas found on your network.")
            return

        spa_descriptor = spaman.spa_descriptors[0]
        print(f"Connecting to {spa_descriptor.name} at {spa_descriptor.ipaddress} ...")
        await spaman.async_set_spa_info(
            spa_descriptor.ipaddress,
            spa_descriptor.identifier_as_string,
            spa_descriptor.name,
        )

        # Wait for the facade to be ready
        await spaman.wait_for_facade()

        print(spaman.facade.water_heater)

        print("Turning pump 1 on")
        await spaman.facade.pumps[0].async_set_mode("HI")

        await asyncio.sleep(5)

        print("Turning pump 1 off")
        await spaman.facade.pumps[0].async_set_mode("OFF")

        await asyncio.sleep(5)

if __name__ == "__main__":
    # Install logging
    stream_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 this

Looking 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 0

Complete sample

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. Full path https://github.com/gazoodle/geckolib/tree/main/sample

Home Assistant integration

The best example of use is in the Home Assistant integration which can be found here https://github.com/gazoodle/gecko-home-assistant

Sync API Usage

WARNING Sync functionality will be removed in a future release, examples removed from README

Acknowledgements

License

https://www.gnu.org/licenses/gpl-3.0.html

Todo

Done/Fixed in 0.4.15

Done/Fixed in 0.4.9

Done/Fixed in 0.4.8

Done/Fixed in 0.4.7

Done/Fixed in 0.4.6

Done/Fixed in 0.4.5

Done/Fixed in 0.4.4

Done/Fixed in 0.4.2

Done/Fixed in 0.4.1

Done/Fixed in 0.4.0

Done/Fixed in 0.3.24

Done/Fixed in 0.3.23

Done/Fixed in 0.3.22

Done/Fixed in 0.3.21

Done/Fixed in 0.3.20

Done/Fixed in 0.3.19

Done/Fixed in 0.3.18

Done/Fixed in 0.3.17

Done/Fixed in 0.3.16

Done/Fixed in 0.3.15

Done/Fixed in 0.3.14

Done/Fixed in 0.3.13

Done/Fixed in 0.3.12

Done/Fixed in 0.3.11

Done/Fixed in 0.3.10

Done/Fixed in 0.3.9

Done/Fixed in 0.3.8

Done/Fixed in 0.3.7

Done/Fixed in 0.3.6

Version

Using Semantic versioning https://semver.org/