Asynchronous library for Jandy iAqualink
Project description
🏊 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file iaqualink-0.7.0rc2.tar.gz.
File metadata
- Download URL: iaqualink-0.7.0rc2.tar.gz
- Upload date:
- Size: 96.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d7be2064dc3f4f25c465419de4147bcdd4e7a98bf735810ed7e6750b2b7daa51
|
|
| MD5 |
5240bdb8f9d453fd3a89e3013d110e16
|
|
| BLAKE2b-256 |
3c480cb723c6c6bf8cd8307d003dd0e07b980016d3762150f4a4d2bfd472c8a9
|
Provenance
The following attestation bundles were made for iaqualink-0.7.0rc2.tar.gz:
Publisher:
release.yaml on flz/iaqualink-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
iaqualink-0.7.0rc2.tar.gz -
Subject digest:
d7be2064dc3f4f25c465419de4147bcdd4e7a98bf735810ed7e6750b2b7daa51 - Sigstore transparency entry: 1360664062
- Sigstore integration time:
-
Permalink:
flz/iaqualink-py@27a0633822d779bdfc1eb6b158497b1b1485e212 -
Branch / Tag:
refs/tags/v0.7.0-rc.2 - Owner: https://github.com/flz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@27a0633822d779bdfc1eb6b158497b1b1485e212 -
Trigger Event:
push
-
Statement type:
File details
Details for the file iaqualink-0.7.0rc2-py3-none-any.whl.
File metadata
- Download URL: iaqualink-0.7.0rc2-py3-none-any.whl
- Upload date:
- Size: 28.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5822fbb1f3b8ff7e0ddadc54af8ba0a1505e7913901b11226200116759caef54
|
|
| MD5 |
26bfb052b48900a0d49115da0159470e
|
|
| BLAKE2b-256 |
f22e2ecfb7cb62ebdeb7a4e4b98c358053710bb25c46d7cd31cf66d355fcaf9f
|
Provenance
The following attestation bundles were made for iaqualink-0.7.0rc2-py3-none-any.whl:
Publisher:
release.yaml on flz/iaqualink-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
iaqualink-0.7.0rc2-py3-none-any.whl -
Subject digest:
5822fbb1f3b8ff7e0ddadc54af8ba0a1505e7913901b11226200116759caef54 - Sigstore transparency entry: 1360664117
- Sigstore integration time:
-
Permalink:
flz/iaqualink-py@27a0633822d779bdfc1eb6b158497b1b1485e212 -
Branch / Tag:
refs/tags/v0.7.0-rc.2 - Owner: https://github.com/flz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@27a0633822d779bdfc1eb6b158497b1b1485e212 -
Trigger Event:
push
-
Statement type: