Metadata-Version: 2.4
Name: delonghi-comfort
Version: 0.2.0
Summary: Async Python client for De'Longhi 'My Comfort Hub' (Daedalus) connected heaters
Project-URL: Homepage, https://github.com/comfort-hub/delonghi-comfort
Project-URL: Repository, https://github.com/comfort-hub/delonghi-comfort
Project-URL: Issues, https://github.com/comfort-hub/delonghi-comfort/issues
Author-email: Shaun Eccles-Smith <shauneccles@gmail.com>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Keywords: aws-iot,comfort,daedalus,delonghi,heater,home-assistant
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Home Automation
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: aiohttp>=3.9
Requires-Dist: aiomqtt>=2.0
Description-Content-Type: text/markdown

# delonghi-comfort

Async Python client for **De'Longhi "My Comfort Hub"** (Daedalus platform) connected
heaters — e.g. the **Dragon 5 Connect** (`TRD51024WIFI.G`).

It authenticates with Gigya, discovers your appliances, reads live state from the AWS IoT
device shadow, and sends control commands over MQTT. It is transport-friendly for Home
Assistant: bring your own `aiohttp` session and the library stays framework-agnostic.

> ⚠️ Unofficial. Built by reverse-engineering the public app for interoperability with
> hardware you own. Not affiliated with or endorsed by De'Longhi. API keys shipped here are
> app-global public client identifiers (like OAuth client IDs), not user secrets.

## Installation

```bash
pip install delonghi-comfort
```

## Usage

```python
import asyncio
import aiohttp
from delonghi_comfort import DelonghiComfort


async def main() -> None:
    async with aiohttp.ClientSession() as session:
        client = DelonghiComfort(session=session)
        await client.async_login("you@example.com", "password")

        devices = await client.async_get_devices()
        heater = devices[0]
        print(heater.thing_name, heater.model, heater.online)

        await client.async_connect(heater)

        status = await client.async_get_status()
        print("on:", status.is_on, "room:", status.current_temperature,
              "target:", status.target_temperature)

        # live push updates
        client.add_status_listener(lambda s: print("update:", s.raw))

        # control
        await client.async_set_power(True)
        await client.async_set_temperature(21)
        await client.async_set_eco(True)

        await client.async_close()


asyncio.run(main())
```

### Persisting credentials

`async_login` stores a long-lived Gigya session in `client.credentials`. Persist it and
reconstruct the client without a password later:

```python
client = DelonghiComfort(session=session, credentials=saved_credentials)
await client.async_refresh_jwt()      # mint a fresh JWT from the stored session
```

## Command reference

| Method | Message | Reported field |
|---|---|---|
| `async_set_power(bool)` | `SetDeviceStatusRequest` | `DeviceStatus` |
| `async_set_temperature(int)` | `SetRoomTempRequest_degC` | `TempSetPoint` |
| `async_set_eco(bool)` | `SetEcoModeRequest` | `PowerLimit` |
| `async_set_child_lock(bool)` | `SetLockModeRequest` | `KeyLock` |
| `async_set_night_mode(bool)` | `SetNightModeRequest` | `NightModeEnable` |
| `async_set_silent(bool)` | `SetSoundRequest` | `SilentEnable` |
| `async_set_brightness(0-3)` | `SetBrightnessLevelRequest` | `BrightnessLevel` |
| `async_set_schedule_enabled(bool)` | `SetScheduleEnRequest` | `ScheduleEnable` |
| `async_set_temp_unit(TemperatureUnit)` | `SetTempUnitRequest` | `TempUnit` |

Read-only status telemetry (no control command exists): `on_off_timer_minutes`,
`timer_remaining`, `timer_active`, `ota_progress`, `running_partition`. (The shadow's
`PowerLevel` field is deliberately not surfaced — on real hardware it stays at `255`
whether idle, in Eco, or at full power, so it carries no usable information.)

## How it works

- **Auth**: Gigya `accounts.login` → `accounts.getJWT` (OAuth1-signed), auto-probing pools.
- **Devices**: `GET devices` on the AWS API Gateway, authorized by the JWT as a Bearer token.
- **Read**: subscribe + `get` the `MachineStatus` / `MachineCapabilities` named shadows over
  MQTT 5 (TLS:443, ALPN `mqtt`, JWT custom authorizer). A unique client-id is used so the
  physical heater is never evicted.
- **Control**: publish `{"Message": "...", "AppId": "Comfort", "Value": N, "RequestId": "..."}`
  to `<thing>/commands/request` and await the ack on `<thing>/commands/response`.

## Development

```bash
uv sync
uv run pytest
uv run ruff check
uv run mypy delonghi_comfort
```

## License

GPL-3.0-or-later.
