Metadata-Version: 2.4
Name: senzwifi
Version: 2026.6.2
Summary: Python library for interacting with SenzWifi (Pentair Thermal WiFi) API to control thermostats
Author-email: Jimmy Everling <jimmy@sockless.tech>
License: MIT License
        
        Copyright (c) 2020 Jimmy Everling
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/sockless-coding/python-senz-wifi-module
Project-URL: Repository, https://github.com/sockless-coding/python-senz-wifi-module.git
Project-URL: Bug Tracker, https://github.com/sockless-coding/python-senz-wifi-module/issues
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
