Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

AqualinkSpaControl

A FastAPI microservice that controls a Jandy iAquaLink pool/spa system via the iAquaLink cloud API. Exposes HTTP endpoints for spa/pool control and runs a background safety watchdog that monitors temperatures, triggers emergency shutdowns, and sends Pushover alerts when thresholds are exceeded.


Features

  • Spa on/off — toggles spa pump, spa heater, air blower (aux_1), and pool heater in the correct sequence
  • One-touch scene execution — All OFF and Spa Mode via the controller's built-in scenes
  • Overheat protection — watchdog shuts everything off and sends a Pushover alert if spa or pool temp exceeds TEMP_OVERHEAT_THRESHOLD (default 105°F)
  • Freeze/underheat protection — watchdog turns on pool heater and sends a Pushover alert if spa or pool temp drops below TEMP_UNDERHEAT_THRESHOLD (default 80°F)
  • Configurable watchdog interval — poll frequency controlled by TEMP_MONITOR_INTERVAL (default 600s)
  • Mechanical cooldown lock — configurable lockout between valve-switching commands to protect the diverter (default 60s)
  • API key authentication — all endpoints require an X-API-Key header
  • Debug endpoints — inspect all devices and one-touch scenes at runtime

Requirements

  • Python 3.14
  • A Jandy iAquaLink-compatible controller registered with the iAquaLink cloud service

Python dependencies

Package Purpose
fastapi Web framework / API server
uvicorn[standard] ASGI server
iaqualink iAquaLink cloud API client
python-dotenv Loads credentials from .env
httpx HTTP client (Pushover alerts, iaqualink, test_tool.py)

Local Setup

  1. Install dependencies

    pip install -r requirements.txt
  2. Create a .env file in the project root:

    AQUALINK_EMAIL=your@email.com
    AQUALINK_PASSWORD=yourpassword
    AQUALINK_SPACONTROL_API_KEY=your-secret-api-key
    
    AQUALINK_SERIAL=your_system_serial_number
    TEMP_OVERHEAT_THRESHOLD=105
    TEMP_UNDERHEAT_THRESHOLD=80
    TEMP_MONITOR_INTERVAL=600
    VALVE_COOLDOWN=60
    
    PUSHOVER_USER=your_pushover_user_key
    PUSHOVER_TOKEN=your_pushover_api_token
    
    BASE_URL=http://127.0.0.1:8000

    AQUALINK_EMAIL, AQUALINK_PASSWORD, and AQUALINK_SPACONTROL_API_KEY are required — the server will refuse to start without them. All other variables are optional; defaults are shown above.

  3. Run the server

    uvicorn main:app --host 0.0.0.0 --port 8000 --reload

    The API is now available at http://127.0.0.1:8000.

  4. Interactive API docs are auto-generated by FastAPI at:

    http://127.0.0.1:8000/docs
    

Authentication

All endpoints require an X-API-Key header matching the AQUALINK_SPACONTROL_API_KEY environment variable. Requests without a valid key receive a 401 Unauthorized response.

Header format:

X-API-Key: your-secret-api-key

API Endpoints

Control

All control endpoints accept both GET and POST for compatibility with browsers and iOS Shortcuts.

Method Path Description
GET/POST /spa-on Turns on spa pump + spa heater; turns off pool heater; enables air blower (aux_1) after 30s
GET/POST /spa-off Turns off spa pump + spa heater + air blower; turns on pool heater
GET/POST /all-off Executes one-touch scene 1 — shuts everything off
GET/POST /spa-mode Executes one-touch scene 2 — activates spa mode via controller scene

Debug / Inspection

Method Path Description
GET /devices Returns all devices with key, type, state, is_on, and label
GET /onetouch Returns raw one-touch scene list from the controller

Lodgify

Requires LODGIFY_API_KEY; returns 503 if not set, 502 if Lodgify returns an error. The client lives in lodgify.py — add new Lodgify endpoints as methods on LodgifyClient.

Method Path Description
GET /lodgify/bookings?page=1&size=50&stay_filter=Current Lists Lodgify bookings (GET /v2/reservations/bookings)

Guest spa portal — /guest

A mobile web page guests use instead of the Leviton switch (no IFTTT in the path). The root URL / redirects here, so a QR code to the domain works.

  • Login: the guest enters their 4-digit door code. It's matched against rooms[].key_code on the current Lodgify booking (stayFilter=Current).
  • Access window: from the arrival date until the departure date at check_out.time, in PROPERTY_TIMEZONE. Update the checkout time in Lodgify for a late checkout and access extends automatically (within ~60s).
  • Every action re-checks Lodgify, so a cancelled booking or changed door code stops working right away.
  • Controls: spa on/off (same logic and VALVE_COOLDOWN as /spa-on//spa-off), blower (aux_1), spa/pool set points limited to SPA_TEMP_* / POOL_TEMP_*, and live spa/pool/air temperatures.
  • Brute-force protection: 5 wrong codes from one IP → 15-minute lockout; 30 wrong codes in an hour from anywhere → guest login disabled for 1 hour. Both send a Pushover alert and can be cleared from the admin page.
  • Pushover alert on each successful guest login.

Admin activity page — /admin

Password login (ADMIN_PASSWORD). Shows the current booking (door code masked), live spa status, active lockouts (with Clear), and an activity log of guest logins/failures, lockouts, guest actions, owner API calls, watchdog alarms and admin logins. Events are stored in SQLite at EVENT_DB_PATH and kept for 90 days.

Response examples

Spa on/off — success

{"status": "success", "action": "spa_on", "note": "aux_1 will enable in 30s"}

Cooldown active

{"status": "ignored", "reason": "Mechanical cooldown in progress"}

Unauthorized

{"detail": "Invalid API Key"}

One-touch — success

{"status": "success", "action": "all_off", "response": {...}}

Confirmed device keys (Aqualink system)

Key Type Label
spa_pump IaquaSwitch Spa Pump
pool_pump IaquaSwitch Pool Pump
spa_heater IaquaSwitch Spa Heater
pool_heater IaquaSwitch Pool Heater
spa_set_point IaquaThermostat Spa Set Point (102°F)
pool_set_point IaquaThermostat Pool Set Point (89°F)
spa_temp IaquaSensor Spa Temp
pool_temp IaquaSensor Pool Temp
air_temp IaquaSensor Air Temp
aux_1 IaquaAuxSwitch Air Blower
aux_2 IaquaAuxSwitch Aux2
aux_3 IaquaAuxSwitch Aux3
aux_EA IaquaAuxSwitch Extra Aux

One-touch scenes

Scene Command Label
onetouch_1 set_onetouch_1 All OFF
onetouch_2 set_onetouch_2 Spa Mode
onetouch_3 set_onetouch_3 Clean Mode
onetouch_4 set_onetouch_4 Pool Heat

Test Tool

test_tool.py is a command-line client for the running server. It reads BASE_URL and AQUALINK_SPACONTROL_API_KEY from .env and sends the API key on every request.

python test_tool.py spa-on
python test_tool.py spa-off
python test_tool.py all-off
python test_tool.py spa-mode
python test_tool.py devices
python test_tool.py onetouch

Set BASE_URL in .env to target the Railway deployment:

BASE_URL=https://your_domain.up.railway.app

Omit BASE_URL to default to http://127.0.0.1:8000 for local testing.


Deploying to Railway

Railway can host this service with a public HTTPS URL and automatic deploys from GitHub.

1. Push to GitHub

Make sure the repo is pushed to GitHub. Railway deploys directly from a branch.

2. Create a Railway project

  1. Go to railway.app and log in
  2. Click New Project → Deploy from GitHub repo
  3. Select this repository and the main branch

3. Start command

The railway.toml in the repo root configures the start command automatically:

[deploy]
startCommand = "uvicorn main:app --host 0.0.0.0 --port $PORT --proxy-headers --forwarded-allow-ips='*'"

Railway injects $PORT — no additional configuration needed. --proxy-headers makes the real client IP visible to the app (needed for guest login lockouts and the activity log).

4. Generate a public URL

In the Railway dashboard, go to your service → Settings → Networking → Generate Domain. When prompted for a port, enter 8000. Railway will assign $PORT=8000 and generate a public HTTPS URL.

5. Set environment variables

In the Railway dashboard, go to your service → Variables and add:

Variable Required Notes
AQUALINK_EMAIL yes iAquaLink account email
AQUALINK_PASSWORD yes iAquaLink account password
AQUALINK_SPACONTROL_API_KEY yes Secret key sent in X-API-Key header
AQUALINK_SERIAL no Controller serial; defaults to first system on account
TEMP_OVERHEAT_THRESHOLD no Default 105
TEMP_UNDERHEAT_THRESHOLD no Default 80
TEMP_MONITOR_INTERVAL no Default 600 (seconds)
VALVE_COOLDOWN no Default 60 (seconds)
PUSHOVER_USER no Pushover user key for alerts
PUSHOVER_TOKEN no Pushover API token for alerts
LODGIFY_API_KEY no Lodgify public API key (enables /lodgify/* and /guest)
SESSION_SECRET for /guest, /admin Long random string for signing login cookies
ADMIN_PASSWORD for /admin Admin activity page password
EVENT_DB_PATH recommended /data/events.db (see volume below)
SPA_TEMP_MIN / SPA_TEMP_MAX no Guest spa set-point range, default 90–102
POOL_TEMP_MIN / POOL_TEMP_MAX no Guest pool set-point range, default 82–90
PROPERTY_TIMEZONE no Default America/New_York

Do not commit a .env file to the repo.

Event log volume: Railway's filesystem is wiped on every deploy. To keep the admin activity history, add a volume to the service (right-click the service → Attach Volume) mounted at /data, and set EVENT_DB_PATH=/data/events.db.

Generate a SESSION_SECRET with:

python -c "import secrets; print(secrets.token_urlsafe(48))"

6. Python version

The .python-version file in the repo pins Python 3.14. Railway's Nixpacks builder reads this file automatically — no additional configuration needed.

7. Deploy

Railway triggers a deploy on every push to the selected branch. Once deployed, your public URL will be available in the Railway dashboard.


Environment Variable Reference

Variable Required Default Description
AQUALINK_EMAIL yes — iAquaLink account email
AQUALINK_PASSWORD yes — iAquaLink account password
AQUALINK_SPACONTROL_API_KEY yes — API key required on all requests (X-API-Key header)
AQUALINK_SERIAL no first system Controller serial number
TEMP_OVERHEAT_THRESHOLD no 105 Overheat alarm threshold in °F
TEMP_UNDERHEAT_THRESHOLD no 80 Underheat/freeze alarm threshold in °F
TEMP_MONITOR_INTERVAL no 600 Watchdog poll interval (seconds)
VALVE_COOLDOWN no 60 Lockout between valve commands (seconds)
PUSHOVER_USER no — Pushover user key
PUSHOVER_TOKEN no — Pushover API token
LODGIFY_API_KEY no — Lodgify public API key (X-ApiKey); enables /lodgify/* and the guest portal
SESSION_SECRET for /guest, /admin — Secret for signing login cookies (32+ random chars)
ADMIN_PASSWORD for /admin — Admin activity page password
SPA_TEMP_MIN no 90 Lowest spa set point a guest can choose (°F)
SPA_TEMP_MAX no 102 Highest spa set point a guest can choose — must be ≤ 104 and below TEMP_OVERHEAT_THRESHOLD
POOL_TEMP_MIN no 82 Lowest pool set point a guest can choose (°F)
POOL_TEMP_MAX no 90 Highest pool set point a guest can choose — same limits as spa
PROPERTY_TIMEZONE no America/New_York Timezone for arrival/checkout times
EVENT_DB_PATH no ./data/events.db SQLite activity log location
BOOKING_SITE_URL no https://sunshinevilladeerfield.com/ "Book direct" link on the guest page
COOKIE_SECURE no true Set false only for local testing over plain http
BASE_URL no http://127.0.0.1:8000 Target URL for test_tool.py

Safety Notes

  • Overheat protection — the watchdog monitors spa_temp and pool_temp every TEMP_MONITOR_INTERVAL seconds. If either reads ≥ TEMP_OVERHEAT_THRESHOLD°F, set_onetouch_1 (All OFF) is triggered and a Pushover alert is sent. If the scene call fails, pumps are shut off individually as a fallback.
  • Freeze/underheat protection — if either sensor reads ≤ TEMP_UNDERHEAT_THRESHOLD°F, set_onetouch_4 (Pool Heat) is triggered and a Pushover alert is sent.
  • Mechanical cooldown — VALVE_COOLDOWN seconds must elapse between valve-switching commands to protect the diverter.
  • Credentials are never logged or exposed through any API endpoint.

iAquaLink library — available commands

Read commands

Command Description
get_home Home-screen state (online status, temps, heaters, pumps)
get_devices Auxiliary device state (lights, aux switches)
get_onetouch Saved one-touch scenes

Write commands

Command Triggered by Description
set_spa_pump spa_pump.turn_on/off() Toggles spa pump / spa mode
set_pool_pump pool_pump.turn_on/off() Toggles pool pump
set_spa_heater spa_heater.turn_on/off() Toggles spa heater
set_pool_heater pool_heater.turn_on/off() Toggles pool heater
set_solar_heater solar device Toggles solar heater
set_temps thermostat.set_temperature(n) Sets spa/pool target temperature
set_aux aux_switch.turn_on/off() Toggles an auxiliary switch or light
set_light light.set_brightness/effect() Sets brightness or color effect
set_onetouch_N /all-off, /spa-mode Executes one-touch scene N

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages