Metadata-Version: 2.4
Name: senzwifi
Version: 0.1.0
Summary: Python library for interacting with SenzWifi (Pentair Thermal WiFi) API to control thermostats
Author: sockless-coding
License: MIT
Keywords: senz,pentair,thermostat,wifi,api,home-automation,iot
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Home Automation
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.28.1
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.25.2; extra == "dev"
Requires-Dist: respx>=0.22.0; extra == "dev"
Requires-Dist: ruff>=0.8.0; extra == "dev"
Requires-Dist: mypy>=1.13.0; extra == "dev"
Dynamic: license-file

# python-senz-wifi-module

Python library for interacting with the SenzWifi (Pentair Thermal WiFi) API to control thermostats.

## Features

- **Async-first** — built on `httpx` for efficient async operations
- **Authentication & session management** — automatic retry on session expiry
- **Thermostat control** — list, read, and update thermostat settings
- **Convenience methods** — set temperature, turn off, boost mode
- **Real-time notifications** — long-polling for thermostat state changes
- **CLI tool** — built-in command-line interface for quick operations
- **Fully typed** — type hints throughout with `py.typed` marker

## Installation

```bash
pip install senzwifi
```

Or install from source:

```bash
git clone https://github.com/sockless-coding/python-senz-wifi-module.git
cd python-senz-wifi-module
pip install -e .
```

## Quick Start

### Using the Library

```python
import asyncio
from senzwifi import AsyncSenzWifi, RegulationMode

async def main():
    # Create client with credentials
    async with AsyncSenzWifi("your@email.com", "your-password") as client:
        # Authenticate
        await client.authenticate()

        # List all thermostats
        response = await client.get_thermostats()
        for group in response.groups:
            print(f"Group: {group.group_name}")
            for t in group.thermostats:
                print(f"  {t.room}: {t.temperature_celsius:.1f}°C (mode: {RegulationMode(t.regulation_mode).name})")

        # Set a thermostat to 21°C manually
        await client.set_manual_temperature("SN123456789", 21.0)

        # Turn off a thermostat
        await client.turn_off("SN123456789")

        # Start boost mode (3 hours by default)
        await client.start_boost("SN123456789")

asyncio.run(main())
```

### Using the CLI

```bash
# List all thermostats
senzwifi --username "your@email.com" --password "your-password" list

# Get a specific thermostat
senzwifi --username "your@email.com" --password "your-password" get SN123456789

# Set temperature to 21°C
senzwifi --username "your@email.com" --password "your-password" set SN123456789 21.0

# Turn off a thermostat
senzwifi --username "your@email.com" --password "your-password" off SN123456789

# Start boost mode
senzwifi --username "your@email.com" --password "your-password" boost SN123456789
```

## API Reference

### AsyncSenzWifi

The main client class. Supports use as an async context manager.

```python
async with AsyncSenzWifi(email, password) as client:
    await client.authenticate()
    # ... use the client
```

#### Constructor

| Parameter | Type | Default | Description |
|---|---|---|---|
| `email` | `str` | — | Account email address |
| `password` | `str` | — | Account password |
| `max_retries` | `int` | `3` | Maximum retry attempts for failed requests |
| `timeout` | `float` | `30.0` | Request timeout in seconds |

#### Methods

##### `authenticate() -> AuthResponse`

Authenticate and obtain a session ID.

```python
auth = await client.authenticate()
if auth.is_success:
    print(f"Logged in as {auth.email}")
```

##### `get_thermostats() -> ThermostatsResponse`

Fetch all thermostats grouped by their group.

```python
response = await client.get_thermostats()
for group in response.groups:
    print(f"{group.group_name}: {len(group.thermostats)} devices")

# Get all thermostats flat
all_thermostats = response.get_all_thermostats()

# Find a specific thermostat
t = response.get_thermostat("SN123456789")
```

##### `get_thermostat(serial_number: str) -> Thermostat`

Get a specific thermostat by serial number.

```python
t = await client.get_thermostat("SN123456789")
print(f"{t.room}: {t.temperature_celsius:.1f}°C")
```

##### `set_manual_temperature(serial_number: str, temperature_celsius: float) -> UpdateThermostatResponse`

Set a thermostat to a specific temperature in manual mode.

```python
result = await client.set_manual_temperature("SN123456789", 21.5)
print(f"Success: {result.success}")
```

##### `turn_off(serial_number: str) -> UpdateThermostatResponse`

Turn off a thermostat.

```python
result = await client.turn_off("SN123456789")
```

##### `start_boost(serial_number: str, end_time: datetime | None = None) -> UpdateThermostatResponse`

Start boost (comfort) mode. Defaults to 3 hours from now if no end time is specified.

```python
from datetime import datetime, timedelta, timezone

# Boost for 3 hours (default)
await client.start_boost("SN123456789")

# Boost until a specific time
end = datetime.now(timezone.utc) + timedelta(hours=1)
await client.start_boost("SN123456789", end_time=end)
```

##### `update_thermostat(serial_number: str, thermostat: Thermostat) -> UpdateThermostatResponse`

Update a thermostat with custom settings.

```python
t = await client.get_thermostat("SN123456789")
t.regulation_mode = RegulationMode.SCHEDULE
t.manual_temperature = 2000  # 20.0°C in raw format
result = await client.update_thermostat("SN123456789", t)
```

##### `wait_for_notification(timeout: float = 300.0) -> Notification | None`

Long-poll for thermostat state change notifications.

```python
notification = await client.wait_for_notification(timeout=60.0)
if notification:
    print(f"Change detected: {notification.action} on {notification.thermostat.room}")
```

### Models

#### `RegulationMode`

```python
RegulationMode.SCHEDULE   # 1 — Use the configured schedule
RegulationMode.BOOST      # 2 — Boost / comfort temp mode
RegulationMode.MANUAL     # 3 — Constant temp mode
RegulationMode.OFF        # 5 — Off
```

#### `Thermostat`

Key properties (all temperatures available in both raw and Celsius):

| Property | Type | Description |
|---|---|---|
| `serial_number` | `str` | Device serial number |
| `room` | `str` | Room name |
| `group_name` | `str` | Group name |
| `temperature_celsius` | `float` | Current temperature |
| `regulation_mode` | `int` | Current regulation mode |
| `online` | `bool` | Whether the device is online |
| `heating` | `bool` | Whether heating is active |
| `manual_temperature_celsius` | `float` | Manual setpoint temperature |
| `comfort_temperature_celsius` | `float` | Comfort/boost temperature |
| `min_temp_celsius` | `float` | Minimum temperature |
| `max_temp_celsius` | `float` | Maximum temperature |
| `schedules` | `list[Schedule]` | Available schedules |

#### Temperature Conversion Utilities

```python
from senzwifi import temp_to_celsius, celsius_to_temp

# Raw API value (1/100 °C) → Celsius
temp_to_celsius(2100)  # 21.0

# Celsius → raw API value
celsius_to_temp(21.0)  # 2100
```

### Exceptions

| Exception | Description |
|---|---|
| `SenzWifiError` | Base exception for all errors |
| `AuthenticationError` | Authentication failed |
| `SessionExpiredError` | Session has expired |
| `DeviceNotFoundError` | Thermostat not found |
| `APIError` | API request failed |

## Example: Monitoring Thermostats

See [`examples/monitor.py`](examples/monitor.py) for a complete example that monitors thermostat changes in real time.

```python
import asyncio
from senzwifi import AsyncSenzWifi, RegulationMode

async def monitor():
    async with AsyncSenzWifi("your@email.com", "your-password") as client:
        await client.authenticate()

        # Show initial state
        response = await client.get_thermostats()
        for t in response.get_all_thermostats():
            mode = RegulationMode(t.regulation_mode).name
            print(f"[INIT] {t.room}: {t.temperature_celsius:.1f}°C ({mode})")

        # Watch for changes
        while True:
            notification = await client.wait_for_notification()
            if notification:
                t = notification.thermostat
                mode = RegulationMode(t.regulation_mode).name
                print(f"[CHANGE] {t.room}: {t.temperature_celsius:.1f}°C ({mode})")

asyncio.run(monitor())
```

## Development

```bash
# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run linter
ruff check .

# Run type checker
mypy senzwifi
```

## License

MIT
