🏊 iaqualink-py
Asynchronous Python library for Jandy iAqualink pool control systems
📖 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
asyncioandhttpxfor efficient, non-blocking I/O - 🔁 HTTP Retry Transport - Uses
httpx-retriesfor 429 backoff andRetry-Afterhandling - 🔐 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)
- 🌡️ Comprehensive Device Support
- Temperature sensors (pool, spa, air)
- Thermostats with adjustable set points
- Pumps and heaters
- Lights with toggle control
- 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
- ⚡ Rate Limiting - Built-in throttling to respect API limits
📦 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
--usernameand--password - environment variables
IAQUALINK_USERNAMEandIAQUALINK_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:
- First run logs in normally and writes the current auth state to the jar.
- Later runs restore that state when the saved username matches the requested username.
- 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
--debugon 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()
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)}")
Custom Update Intervals
The library automatically rate-limits updates to once every 5 seconds per system to respect API limits. Subsequent calls within this window return cached data.
# First call - fetches from API
await system.update()
# Immediate second call - returns cached data
await system.update()
# After 5+ seconds - fetches fresh data
await asyncio.sleep(5)
await system.update()
🏗️ 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 and eXO 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
- httpx-retries for transport-level 429 retry handling
📄 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.
🔗 Links
⚠️ 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
Release files for iaqualink 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| iaqualink-0.7.0.tar.gz | 96.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| iaqualink-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 125.1 kB
Release files / iaqualink-0.7.0.tar.gz
| Download URL | iaqualink-0.7.0.tar.gz |
|---|---|
| Size | 96.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d1bf2b638eb5cd93efd9bde578bba4b7e58c44d6f1fd5daf98022ff9521cda29
|
|
BLAKE2b-256 checksum How to use checksums |
3ce39d3acd266134a66944a4c07af245a7585c96a8034faf903c79f4ccc99efc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 27, 2026.
Transparency logRelease files / iaqualink-0.7.0-py3-none-any.whl
| Download URL | iaqualink-0.7.0-py3-none-any.whl |
|---|---|
| Size | 28.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6021cafd1703c35a8f5477a834fe034e1bf2db158204e46d13a573a87c16421b
|
|
BLAKE2b-256 checksum How to use checksums |
582195ade9898a6d503fc2ad2e7a462337d45b002de8d0a5e01e788074a839fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 27, 2026.
Transparency log