Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

🏊 iaqualink-py

Asynchronous Python library for Jandy iAqualink pool control systems

License Python

📖 Overview

iaqualink-py is a modern, fully asynchronous Python library for interacting with Jandy iAqualink pool and spa control systems. It provides a clean, Pythonic interface to monitor and control your pool equipment from your Python applications.

✨ Features

  • 🔄 Fully Asynchronous - Built with asyncio and httpx for efficient, non-blocking I/O
  • 🔐 401 Replay for Auth-Bearing Requests - Rebuilds and replays systems discovery and iaqua/exo system requests after auth refresh
  • 🏗️ Multi-System Support
    • iAqua systems (iaqualink.net API)
    • eXO systems (zodiac-io.com API)
    • i2d systems — iQPump variable-speed pumps (r-api.iaqualink.net API)
    • Cyclobat — Zodiac battery-powered robot cleaners (read-only sensors + start/stop/return-to-base commands over WebSocket)
    • HPM — standalone heat-pump-only devices, not attached to a pool controller (zodiac-io.com AWS IoT shadow API)
    • TCX systems — filtration and spa controllers (prod.zodiac-io.com API)
  • 🌡️ Comprehensive Device Support
    • Temperature sensors (pool, spa, air)
    • Thermostats with adjustable set points
    • Pumps and heaters, including paired HPM heat pumps
    • Lights with toggle control
    • ICL (IntelliCenter) lights with color control, custom RGBW, and dimming
    • Auxiliary switches
    • Water chemistry sensors (pH, ORP, salinity)
    • Freeze protection monitoring
  • 🔌 Context Manager Support - Automatic resource cleanup
  • 🛡️ Type Safe - Full type hints for modern Python development

📦 Installation

pip install iaqualink

Or using uv:

uv add iaqualink

To install the optional CLI as well:

pip install 'iaqualink[cli]'

Or with uv:

uv add 'iaqualink[cli]'

💻 CLI

The optional cli extra installs an iaqualink command for common discovery and control tasks.

Credentials can be provided in this order:

  • command-line options such as --username and --password
  • environment variables IAQUALINK_USERNAME and IAQUALINK_PASSWORD
  • a YAML config file, defaulting to typer.get_app_dir("iaqualink") / "config.yaml"

Example config:

username: user@example.com
password: super-secret-password

Example commands:

# Enable verbose debug logging
iaqualink --debug list-systems

# List the systems on the account
iaqualink list-systems

# Show devices for a system
iaqualink list-devices --system YOUR-SERIAL

# Show a tree view of all systems and devices
iaqualink status

# Turn on a device by key or label
iaqualink turn-on pool_pump --system YOUR-SERIAL

# Change a thermostat set point
iaqualink set-temperature spa_set_point 102 --system YOUR-SERIAL

Session Persistence

The CLI can persist login state across runs so repeated commands do not need to authenticate every time.

By default, session state is stored at typer.get_app_dir("iaqualink") / "session.json", which resolves to an app-specific config directory on macOS, Linux, and Windows.

All commands accept --cookie-jar to override that location:

iaqualink list-systems --cookie-jar ~/.cache/iaqualink/session.json

Session lifecycle:

  1. First run logs in normally and writes the current auth state to the jar.
  2. Later runs restore that state when the saved username matches the requested username.
  3. If a restored session is stale during systems discovery, the CLI reauthenticates and updates the jar automatically.

Security notes:

  • The cookie jar stores authentication tokens in plain text.
  • On shared systems, use a location with appropriate file permissions.
  • If you change credentials or want to force a fresh login, delete the jar file.
  • Use --debug on any command to enable verbose logging while troubleshooting CLI or API behavior.

🚀 Quick Start

Basic Usage

from iaqualink import AqualinkClient

async with AqualinkClient('user@example.com', 'password') as client:
    # Discover your pool systems
    systems = await client.get_systems()

    # Get the first system
    system = list(systems.values())[0]
    print(f"Found system: {system.name}")

    # Get all devices
    devices = await system.get_devices()

    # Access specific devices
    pool_temp = devices.get('pool_temp')
    if pool_temp:
        print(f"Pool temperature: {pool_temp.state}°F")

    spa_heater = devices.get('spa_heater')
    if spa_heater:
        print(f"Spa heater: {'ON' if spa_heater.is_on else 'OFF'}")

Controlling Devices

# Turn on pool pump
pool_pump = devices.get('pool_pump')
if pool_pump:
    await pool_pump.turn_on()

# Set spa temperature
spa_thermostat = devices.get('spa_set_point')
if spa_thermostat:
    await spa_thermostat.set_temperature(102)

# Toggle pool light
pool_light = devices.get('aux_3')
if pool_light:
    await pool_light.toggle()

Controlling ICL (IntelliCenter) Lights

# Get ICL light zone
icl_light = devices.get('icl_zone_1')
if icl_light:
    # Turn on/off
    await icl_light.turn_on()
    await icl_light.turn_off()

    # Set preset color by name
    await icl_light.set_effect("Emerald Green")

    # Set custom RGBW color (0-255 each)
    await icl_light.set_rgbw(255, 0, 128)
    await icl_light.set_rgbw(100, 200, 255, white=50)

    # Set brightness (0-100)
    await icl_light.set_brightness_percentage(75)

    # Read current state
    print(f"Status: {'ON' if icl_light.is_on else 'OFF'}")
    print(f"Color: {icl_light.effect}")
    print(f"Brightness: {icl_light.brightness_percentage}%")
    print(f"RGBW: {icl_light.rgbw}")

Monitoring System Status

# Update system state
await system.update()

# Check if system is online
if system.online:
    print(f"System {system.name} is online")

    # Get all temperature readings
    for device_name, device in devices.items():
        if 'temp' in device_name and device.state:
            print(f"{device.label}: {device.state}°")

🔧 Advanced Usage

Working with Multiple Systems

async with AqualinkClient('user@example.com', 'password') as client:
    systems = await client.get_systems()

    for serial, system in systems.items():
        print(f"System: {system.name} ({serial})")
        print(f"Type: {system.data.get('device_type')}")

        devices = await system.get_devices()
        print(f"Devices: {len(devices)}")

🏗️ Architecture

The library uses a plugin-style architecture with base classes and system-specific implementations:

  • AqualinkClient - Authentication and system discovery
  • AqualinkSystem - Base class with iAqua, eXO, and i2d implementations
  • AqualinkDevice - Device hierarchy with type-specific subclasses

See CLAUDE.md for detailed architecture documentation.

🧪 Development

Setup Development Environment

# Clone the repository
git clone https://github.com/flz/iaqualink-py.git
cd iaqualink-py

# Install dependencies
uv sync --group dev --group test

Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov-report=xml --cov=iaqualink

# Run specific test file
uv run pytest tests/test_client.py

# Run CLI tests
uv run pytest tests/test_cli.py

Code Quality

# Run all pre-commit hooks (ruff, mypy)
uv run pre-commit run --all-files

# Auto-fix linting issues
uv run ruff check --fix .

# Format code
uv run ruff format .

# Type checking
uv run mypy src/

📋 Requirements

  • Python 3.14 or higher
  • httpx with HTTP/2 support

📄 License

This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

⚠️ Disclaimer

This is an unofficial library and is not affiliated with or endorsed by Jandy, Zodiac Pool Systems, or Fluidra. Use at your own risk.


Made with ❤️ by Florent Thoumie

Metadata

Release files for iaqualink 0.8.0a0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for iaqualink 0.8.0a0
File Size Uploaded Provenance Actions
iaqualink-0.8.0a0.tar.gz 319.1 kB With provenance Details for iaqualink-0.8.0a0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for iaqualink 0.8.0a0
File Interpreter ABI Platform Actions
iaqualink-0.8.0a0-py3-none-any.whl Python 3 none any Details for iaqualink-0.8.0a0-py3-none-any.whl

Total release size: 411.7 kB

Security for iaqualink 0.8.0a0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page