Asynchronous Python library for Jandy iAqualink pool control systems
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.
- π 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
pip install iaqualinkOr using uv:
uv add iaqualinkTo install the optional CLI as well:
pip install 'iaqualink[cli]'Or with uv:
uv add 'iaqualink[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-passwordExample 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-SERIALThe 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.jsonSession 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.
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'}")# 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()# 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}Β°")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)}")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()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.
# Clone the repository
git clone https://github.com/flz/iaqualink-py.git
cd iaqualink-py
# Install dependencies
uv sync --group dev --group test# 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# 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/- Python 3.14 or higher
- httpx with HTTP/2 support
- httpx-retries for transport-level 429 retry handling
This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
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