Metadata-Version: 2.4
Name: python-duco-client
Version: 0.6.1
Summary: Async Python client for the Duco ventilation API
Author: Ronald van der Meer
License-Expression: MIT
Project-URL: Homepage, https://github.com/ronaldvdmeer/python-duco-client
Project-URL: Repository, https://github.com/ronaldvdmeer/python-duco-client
Project-URL: Issues, https://github.com/ronaldvdmeer/python-duco-client/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Home Automation
Classifier: Typing :: Typed
Classifier: Framework :: AsyncIO
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.9.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: aioresponses>=0.7; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Requires-Dist: ruff>=0.3; extra == "dev"
Requires-Dist: bandit>=1.7; extra == "dev"
Requires-Dist: pip-audit>=2.7; extra == "dev"
Dynamic: license-file

# python-duco-client

Async Python client for the DUCO ventilation box local REST API.

The client uses the unauthenticated Connectivity Board API surface. On some
firmware versions this means optional fields like node temperature,
`reported_api_version`, endpoint inventory details, and extended diagnostics
may be unavailable.

## Installation

```bash
pip install python-duco-client
```

## Quick start

```python
import asyncio
import aiohttp
from duco import DucoClient

async def main():
    async with aiohttp.ClientSession() as session:
        client = DucoClient(session=session, host="192.168.1.100")

        board = await client.async_get_board_info()
        print(f"Box: {board.box_name} ({board.box_sub_type_name})")

        nodes = await client.async_get_nodes()
        for node in nodes:
            print(f"Node {node.node_id}: {node.general.node_type}")
            if node.sensor and node.sensor.co2 is not None:
                print(f"  CO2: {node.sensor.co2} ppm")

        await client.async_set_ventilation_state(1, "MAN2")

asyncio.run(main())
```

## CLI

```bash
duco --host 192.168.1.100 info
duco --host 192.168.1.100 nodes
duco --host 192.168.1.100 set 1 MAN2
```

## Documentation

See the [docs/](docs/) folder for the full documentation:

- [Quickstart](docs/quickstart.md)
- [API Reference](docs/api-reference.md)
- [CLI](docs/cli.md)
- [Error Handling](docs/error-handling.md)
- [Acknowledgements](https://github.com/ronaldvdmeer/python-duco-client/blob/main/ACKNOWLEDGEMENTS.md)

## License

MIT. See `LICENSE` for details.

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.6.1] - 2026-05-09

### Documentation

- Add `ACKNOWLEDGEMENTS.md` and link it from `README.md` to document the
  historical attribution for the removed API key generation
  implementation.

## [0.6.0] - 2026-05-08

### Removed

- Remove API key generation and authenticated request support from
  `DucoClient`. The library now uses the unauthenticated Connectivity Board
  API subset only.
- Remove `DucoAuthenticationError` from the public exception surface.

### Changed

- Document that some firmware versions return a reduced unauthenticated
  dataset. In practice this can affect optional node temperature values,
  extended diagnostics, `reported_api_version`, and endpoint inventory data.

## [0.5.0] - 2026-05-08

### Added

- `async_detect_board_family(host, session, ssl_context, timeout)` — standalone
  async helper that detects the board family of a Duco box without requiring a
  `DucoClient` instance. Probes HTTPS first (`/info?module=General&submodule=Board`)
  and falls back to HTTP (`/nodeinfoget?node=1`) when HTTPS fails with a
  transport or protocol error.
- `BoardFamily` StrEnum (`CONNECTIVITY_BOARD`, `COMMUNICATION_PRINT`) returned
  by the detection helper. Both are exported from the top-level `duco` package.
- Both symbols are documented in `docs/api-reference.md`.

### Changed

- When the HTTPS probe returns any HTTP response (including 404), the host is
  considered reachable. A subsequent HTTP transport failure in that case now
  raises `DucoError` instead of `DucoConnectionError`, so callers can correctly
  distinguish "host unreachable" from "host reachable but board type unrecognised".

## [0.4.2] - 2026-05-07

### Added

- `DucoClient` now enforces a per-request timeout via a new `request_timeout`
  constructor parameter (default `10.0` seconds). A request that exceeds the
  timeout is raised as `DucoConnectionError`, consistent with other connection
  failures. Callers no longer need to wrap individual calls in their own
  `asyncio.timeout()`.

## [0.4.1] - 2026-05-07

### Fixed

- Add `WI` (Wi-Fi) to `NetworkType` enum so Duco nodes connected over Wi-Fi no
  longer raise `ValueError: 'WI' is not a valid NetworkType`. Any future
  unrecognised network type values now fall back to `NetworkType.UNKNOWN`
  instead of crashing.

## [0.4.0] - 2026-05-04

### Added

- Expose typed API metadata via `ApiEndpointInfo` and the expanded `ApiInfo`
  model. `/api` responses now include `public_api_version`, optional
  `reported_api_version`, and typed endpoint inventory data.

### Enhanced

- Extend `BoardInfo` with optional `public_api_version` and `software_version`
  fields.
- Parse optional version metadata defensively so older and newer firmware
  variants remain compatible.
- Expand unit and focused live integration coverage for API and board metadata.
- Update the published package description so PyPI includes both `README.md`
  and `CHANGELOG.md`.

## [0.3.10] - 2026-05-03

### Fixed

- `_ensure_api_key`: `DucoConnectionError` is no longer wrapped as
  `DucoAuthenticationError`. Previously, a connection failure during API key
  generation was caught by the generic `except DucoError` handler and re-raised
  as `DucoAuthenticationError`, bypassing callers that correctly handle
  `DucoConnectionError`. The exception is now re-raised unchanged; only genuine
  API failures are wrapped as `DucoAuthenticationError`.

### Enhanced

- Fixed ruff code quality warnings across `src/` and `tests/`: sorted `__all__`
  (RUF022), removed unused `noqa` directive (RUF100), combined `elif` branches
  (SIM114), replaced EN dashes with hyphens in docstrings/comments (RUF002,
  RUF003), used `next(iter(...))` instead of list slice (RUF015), combined
  nested `with` statements (SIM117).
- Fixed ruff docstring style (D413, COM812) across `src/duco/`. Added
  per-file-ignore for T201 in `cli.py` — `print()` is intentional CLI output.

## [0.3.9] - 2026-04-26

### Added

- `build_ssl_context()` is now part of the public API (exported from `duco`).
- `DucoClient.__init__` accepts an optional `ssl_context` parameter.  When
  provided the caller controls when the context is built (e.g. in an executor
  so blocking I/O stays off the asyncio event loop).  When omitted the
  behaviour is identical to previous releases.
- `build_ssl_context()` now caches its result so repeated calls are free of
  blocking I/O.

## [0.3.8] - 2026-04-26

### Changed

- `DucoClient.__init__`: default `scheme` changed from `"http"` to `"https"`.
  All Duco Connectivity Board 2.0 boxes use HTTPS, and since v0.3.7 the client
  automatically constructs a valid SSL context using the bundled Duco CA
  certificate chain. Callers that need plain HTTP must now pass `scheme="http"`
  explicitly.

### Breaking change

Code that instantiates `DucoClient` without a `scheme=` argument and expects
HTTP behaviour must now pass `scheme="http"` explicitly.

## [0.3.7] - 2026-04-25

### Added

- Bundle the Duco device CA certificate chain so HTTPS connections are verified
  without requiring `verify_ssl=False` in callers. The bundled chain contains
  ServerDeviceCert + Duco Intermediate COM CA + Duco Root CA.
- `build_ssl_context()` helper in `duco._ssl` builds a ready-to-use
  `ssl.SSLContext` with certificate verification enabled and hostname
  verification disabled (device cert SAN contains factory IP `192.168.4.1`,
  not the user-assigned IP).
- `DucoClient` automatically uses the SSL context for HTTPS connections and
  passes `ssl=True` (default behaviour) for plain HTTP connections.
- CLI: add `--https` flag (and `DUCO_HTTPS=1` env var) to select HTTPS; add
  `temp` and `RH` columns to `nodes` output; require `--host` (removed
  hardcoded default IP).

### Fixed

- Fix missing `if __name__ == "__main__":` guard in `cli.py`.

### Internal

- Add `tests/test_ssl.py` with 7 unit tests covering SSL context construction
  and wiring in `DucoClient`.

## [0.3.6] - 2026-04-25

### Added

- Add temperature reading support for room nodes (`NodeTemperatureData`).
- New `DucoClient.get_node_temperature()` method.

## [0.3.5] - 2026-04-21

### Added

- Initial release with basic node info retrieval and action control.

[Unreleased]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.6.1...HEAD
[0.6.1]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.4.2...v0.5.0
[0.4.2]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.4.1...v0.4.2
[0.4.1]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.3.10...v0.4.0
[0.3.10]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.3.9...v0.3.10
[0.3.9]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.3.8...v0.3.9
[0.3.8]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.3.7...v0.3.8
[0.3.7]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.3.6...v0.3.7
[0.3.6]: https://github.com/ronaldvdmeer/python-duco-client/compare/v0.3.5...v0.3.6
[0.3.5]: https://github.com/ronaldvdmeer/python-duco-client/releases/tag/v0.3.5
