pycaldera
Python client library for controlling Caldera spas via their cloud API.
Installation
pip install pycaldera
Usage
Asynchronous API
import asyncio
from pycaldera import AsyncCalderaClient, PUMP_OFF, PUMP_LOW, PUMP_HIGH
async def main():
async with AsyncCalderaClient("email@example.com", "password") as spa:
# Get current spa status
status = await spa.get_spa_status()
print(f"Current temperature: {status.water_temperature}°F")
# Get detailed live settings
settings = await spa.get_live_settings()
print(f"Target temperature: {settings.ctrl_head_set_temperature}°F")
# Control the spa
await spa.set_temperature(102) # Set temperature to 102°F
await spa.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
await spa.set_lights(True) # Turn on the lights
asyncio.run(main())
Synchronous API
For simpler use cases, a synchronous wrapper is also available:
from pycaldera import CalderaClient, PUMP_OFF, PUMP_LOW, PUMP_HIGH
with CalderaClient("email@example.com", "password") as spa:
# Get current spa status
status = spa.get_spa_status()
print(f"Current temperature: {status.water_temperature}°F")
# Get detailed live settings
settings = spa.get_live_settings()
print(f"Target temperature: {settings.ctrl_head_set_temperature}°F")
# Control the spa
spa.set_temperature(102) # Set temperature to 102°F
spa.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
spa.set_lights(True) # Turn on the lights
Both clients provide identical functionality, with the synchronous client simply wrapping the async one for convenience.
API Reference
AsyncCalderaClient
The main async client class for interacting with the spa. All operations must be performed within an async context manager:
async with AsyncCalderaClient(
email="email@example.com",
password="password",
timeout=10.0, # Optional: request timeout in seconds
debug=False, # Optional: enable debug logging
) as spa:
# All spa operations must be inside this block
await spa.get_spa_status()
await spa.set_temperature(102)
# etc...
CalderaClient
A synchronous wrapper around AsyncCalderaClient that provides the same functionality without requiring async/await:
with CalderaClient(
email="email@example.com",
password="password",
timeout=10.0, # Optional: request timeout in seconds
debug=False, # Optional: enable debug logging
) as spa:
# All spa operations can be called synchronously
spa.get_spa_status()
spa.set_temperature(102)
# etc...
Error Handling
All operations can raise these base exceptions:
AuthenticationError: When authentication fails or token expiresConnectionError: When network connection fails or API is unreachableSpaControlError: When the API returns an error response
Temperature Control
async with spa as client:
# Set temperature (80-104°F or 26.5-40°C)
try:
# Basic temperature setting
await client.set_temperature(102) # Fahrenheit
await client.set_temperature(39, "C") # Celsius
# Wait for spa to acknowledge the temperature change
await client.set_temperature(102, wait_for_ack=True)
# Control polling behavior when waiting for acknowledgment
await client.set_temperature(
102,
wait_for_ack=True,
polling_interval=5.0, # Check every 5 seconds
polling_timeout=120.0, # Time out after 2 minutes
)
# Manually wait for temperature acknowledgment
settings = await client.wait_for_temperature_ack(
expected_temp=102, # Expected temperature in Fahrenheit
interval=5.0, # Check every 5 seconds
timeout=120.0, # Time out after 2 minutes
)
except InvalidParameterError:
# Raised when temperature is outside valid range
# (80-104°F or 26.5-40°C)
pass
except SpaControlError:
# Raised when polling times out waiting for acknowledgment
pass
Pump Control
async with spa as client:
try:
await client.set_pump(1, PUMP_HIGH) # Set pump 1 to high speed
await client.set_pump(2, PUMP_LOW) # Set pump 2 to low speed
await client.set_pump(3, PUMP_OFF) # Turn off pump 3
except InvalidParameterError:
# Raised when:
# - pump_number is not 1, 2, or 3
# - speed is not PUMP_OFF (0), PUMP_LOW (1), or PUMP_HIGH (2)
pass
Light Control
async with spa as client:
try:
await client.set_lights(True) # Turn lights on
await client.set_lights(False) # Turn lights off
except SpaControlError:
# Raised when light control fails
pass
Status & Settings
async with spa as client:
try:
# Get basic spa status
status = await client.get_spa_status()
print(f"Online: {status.status == 'ONLINE'}")
# Get detailed live settings
settings = await client.get_live_settings()
print(f"Target temp: {settings.ctrl_head_set_temperature}°F")
except ConnectionError:
# Raised when spa is offline or unreachable
pass
except SpaControlError:
# Raised when API returns invalid data
pass
## Development
1. Clone the repository
2. Create a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
- Install development dependencies:
pip install -r requirements-dev.txt
- Install pre-commit hooks:
pre-commit install
The pre-commit hooks will run automatically on git commit, checking:
- Code formatting (Black)
- Import sorting (isort)
- Type checking (mypy)
- Linting (pylint, ruff)
- YAML/TOML syntax
- Trailing whitespace and file endings
License
MIT License - see LICENSE file for details.
Metadata
Release files for pycaldera 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pycaldera-0.1.3.tar.gz | 25.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pycaldera-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.3 kB
Release files / pycaldera-0.1.3.tar.gz
| Download URL | pycaldera-0.1.3.tar.gz |
|---|---|
| Size | 25.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1a64044b969bccc8c33c1fdd7b5513a8c2955c8aab621aa02d44f81a97f25826
|
|
BLAKE2b-256 checksum How to use checksums |
e835eb4d1f2bedd16afdf7b3e7cf20ba9ec186e744debe3f224719569d997158
|
| 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 23, 2026.
Transparency logRelease files / pycaldera-0.1.3-py3-none-any.whl
| Download URL | pycaldera-0.1.3-py3-none-any.whl |
|---|---|
| Size | 16.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7d01f5b22f4918f2690b36c93741f7bb9ccf4655a0d225b201ae0a64f3c4eb99
|
|
BLAKE2b-256 checksum How to use checksums |
3836a5cf32d9b92abe0a370ef043e5b22b00d6d74dc8225ff74016341b244d3c
|
| 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 23, 2026.
Transparency log