# pypentairthermalwifi

A Python library for interacting with the Pentair Thermal WiFi API to control thermostats.

## Features

- 🔄 Both sync and async client support
- 🔐 Automatic session management
- 🔁 Retry logic with exponential backoff
- 📝 Full type hints
- 🎯 Simple, intuitive API
- ✅ Comprehensive test coverage

Vibe Coded with Claude Code...

## Installation

```bash
pip install pypentairthermalwifi
```

Or using uv:

```bash
uv add pypentairthermalwifi
```

## Quick Start

### Synchronous Client

```python
from pypentairthermalwifi import PentairThermalWifi

# Create client
client = PentairThermalWifi(
    email="your-email@example.com",
    password="your-password"
)

# Get all thermostats
response = client.get_thermostats()
for group in response.groups:
    for thermostat in group.thermostats:
        print(f"{thermostat.room}: {thermostat.temperature_celsius}°C")

# Get specific thermostat
thermostat = client.get_thermostat("1036918")
print(f"Current temperature: {thermostat.temperature_celsius}°C")

# Set manual temperature
client.set_manual_temperature("1036918", 21.5)

# Close the client when done
client.close()
```

### Asynchronous Client

```python
import asyncio
from pypentairthermalwifi import AsyncPentairThermalWifi

async def main():
    # Create async client
    async with AsyncPentairThermalWifi(
        email="your-email@example.com",
        password="your-password"
    ) as client:
        # Get all thermostats
        response = await client.get_thermostats()
        for group in response.groups:
            for thermostat in group.thermostats:
                print(f"{thermostat.room}: {thermostat.temperature_celsius}°C")

        # Get specific thermostat
        thermostat = await client.get_thermostat("1036918")
        print(f"Current temperature: {thermostat.temperature_celsius}°C")

        # Set manual temperature
        await client.set_manual_temperature("1036918", 21.5)

asyncio.run(main())
```

## Logging

The library includes comprehensive logging at different levels to help you understand what's happening:

- **DEBUG**: Detailed request/response information, parameter values
- **INFO**: High-level operations (authentication, fetching thermostats, updates)
- **WARNING**: Retries, session expiry, non-critical issues
- **ERROR**: Authentication failures, request failures

### Enable Logging

```python
import logging

# Configure logging (do this before creating the client)
logging.basicConfig(
    level=logging.INFO,  # or DEBUG for more detail
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

from pypentairthermalwifi import PentairThermalWifi

client = PentairThermalWifi(email="user@example.com", password="pass")
# You'll now see log messages as operations occur
```

### Demo Applications with Logging

The demo applications support logging via the `LOG_LEVEL` environment variable. When set to `INFO` or `DEBUG`, httpx logging is also enabled to show HTTP requests and responses:

```bash
# Run with INFO level logging (shows library operations + HTTP requests)
export LOG_LEVEL=INFO
uv run python demo.py

# Run with DEBUG level logging (maximum detail including connection events)
export LOG_LEVEL=DEBUG
uv run python demo.py
```

**Example output with `LOG_LEVEL=INFO`:**
```
16:30:45 - pypentairthermalwifi.sync_client - INFO - Authenticating as user@example.com
16:30:45 - httpx - DEBUG - HTTP Request: GET https://www.pentairthermalwifi.com/api/authenticate
16:30:46 - pypentairthermalwifi.sync_client - INFO - Authentication successful, session ID: rm3w7zLJ...
```

## Demo Applications

Two interactive demo applications are included to test the library:

### Synchronous Demo

```bash
# Run with environment variables
export PENTAIR_EMAIL="your-email@example.com"
export PENTAIR_PASSWORD="your-password"
uv run python demo.py

# Or enter credentials interactively
uv run python demo.py
```

### Asynchronous Demo

```bash
# Run with environment variables
export PENTAIR_EMAIL="your-email@example.com"
export PENTAIR_PASSWORD="your-password"
uv run python demo_async.py

# Or enter credentials interactively
uv run python demo_async.py
```

Both demos provide an interactive menu to:
- 📋 List all thermostats with status
- 🔍 View detailed information for a specific thermostat
- 🌡️ Set temperature with confirmation
- ✨ See real-time heating status and online state

## Usage

### Temperature Conversion

The API uses temperatures in 1/100 degrees Celsius (e.g., 2100 = 21.0°C). Helper functions are provided:

```python
from pypentairthermalwifi import temp_to_celsius, celsius_to_temp

# Convert from API format to Celsius
celsius = temp_to_celsius(2100)  # 21.0

# Convert from Celsius to API format
raw = celsius_to_temp(21.0)  # 2100
```

All thermostat models have convenient properties for temperature access:

```python
thermostat = client.get_thermostat("1036918")

# Access temperatures in Celsius
print(thermostat.temperature_celsius)
print(thermostat.manual_temperature_celsius)
print(thermostat.comfort_temperature_celsius)
print(thermostat.min_temp_celsius)
print(thermostat.max_temp_celsius)
```

### Advanced Usage

#### Custom Thermostat Update

```python
# Get the current thermostat state
thermostat = client.get_thermostat("1036918")

# Modify settings
thermostat.regulation_mode = 5  # Auto mode
thermostat.selected_schedule = 2  # Use program 3

# Update the thermostat
response = client.update_thermostat("1036918", thermostat)
print(f"Update successful: {response.success}")
```

#### Error Handling

```python
from pypentairthermalwifi import (
    PentairThermalWifi,
    AuthenticationError,
    ThermostatNotFoundError,
    SessionExpiredError,
    APIError
)

try:
    client = PentairThermalWifi(email="user@example.com", password="wrong")
    client.authenticate()
except AuthenticationError as e:
    print(f"Authentication failed: {e}")

try:
    thermostat = client.get_thermostat("invalid-serial")
except ThermostatNotFoundError as e:
    print(f"Thermostat not found: {e}")

try:
    response = client.get_thermostats()
except SessionExpiredError as e:
    print(f"Session expired: {e}")
    # Client will automatically re-authenticate on next request

except APIError as e:
    print(f"API error: {e}")
```

#### Using Context Managers

```python
# Sync client
with PentairThermalWifi(email="user@example.com", password="pass") as client:
    thermostats = client.get_thermostats()
    # Client automatically closes when exiting the context

# Async client
async with AsyncPentairThermalWifi(email="user@example.com", password="pass") as client:
    thermostats = await client.get_thermostats()
    # Client automatically closes when exiting the context
```

## API Reference

### PentairThermalWifi / AsyncPentairThermalWifi

Both clients support the same methods (async versions return coroutines).

#### Constructor

```python
client = PentairThermalWifi(
    email: str,
    password: str,
    max_retries: int = 3,
    timeout: float = 30.0
)
```

#### Methods

- **`authenticate() -> AuthResponse`**: Manually authenticate and get session ID
- **`get_thermostats() -> ThermostatsResponse`**: Get all thermostats grouped by groups
- **`get_thermostat(serial_number: str) -> Thermostat`**: Get a specific thermostat by serial number
- **`update_thermostat(serial_number: str, thermostat: Thermostat) -> UpdateThermostatResponse`**: Update thermostat settings
- **`set_manual_temperature(serial_number: str, temperature_celsius: float) -> UpdateThermostatResponse`**: Set manual temperature for a thermostat
- **`close()`**: Close the HTTP client (async: `await client.close()`)

### Models

- **`Thermostat`**: Complete thermostat data with temperature properties
- **`Group`**: Thermostat group containing multiple thermostats
- **`ThermostatsResponse`**: Response containing all groups
- **`Schedule`**: Thermostat program/schedule
- **`AuthResponse`**: Authentication response

### Exceptions

- **`PentairThermalWifiError`**: Base exception for all library errors
- **`AuthenticationError`**: Raised when authentication fails
- **`SessionExpiredError`**: Raised when session expires (auto-recovery enabled)
- **`ThermostatNotFoundError`**: Raised when thermostat is not found
- **`APIError`**: Raised on API errors

## Development Setup

This project uses [uv](https://docs.astral.sh/uv/) for dependency management and packaging.

### Installation

```bash
# Install dependencies
uv sync --all-extras
```

### Running Tests

```bash
# Run tests with coverage
uv run pytest

# Run tests with verbose output
uv run pytest -v

# Run tests with coverage report
uv run pytest --cov-report=html
```

### Code Quality

```bash
# Run linting
uv run ruff check .

# Run linting with auto-fix
uv run ruff check --fix .

# Format code
uv run ruff format .

# Type checking
uv run mypy src/
```

### All Checks

Run all quality checks before committing:

```bash
uv run ruff check . && uv run ruff format --check . && uv run mypy src/ && uv run pytest
```

## Building

```bash
# Build package
uv build
```

## Project Structure

```
pypentairthermalwifi/
├── src/
│   └── pypentairthermalwifi/
│       ├── __init__.py
│       └── py.typed
├── tests/
│   ├── __init__.py
│   └── test_pypentairthermalwifi.py
├── pyproject.toml
└── README.md
```

## License

Add your license here.
