Metadata-Version: 2.4
Name: pylocal-akuvox
Version: 1.1.0
Summary: Async Python library for the Akuvox local HTTP API
Project-URL: Homepage, https://github.com/tykeal/pylocal-akuvox
Project-URL: Documentation, https://pylocal-akuvox.readthedocs.io/
Project-URL: Repository, https://github.com/tykeal/pylocal-akuvox
Project-URL: Issues, https://github.com/tykeal/pylocal-akuvox/issues
Author-email: Andrew Grimberg <tykeal@bardicgrove.org>
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.13.2
Requires-Dist: aiohttp>=3.14.0
Provides-Extra: dev
Requires-Dist: aioresponses>=0.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: furo>=2024.0; extra == 'docs'
Requires-Dist: sphinx-autodoc-typehints>=3.0; extra == 'docs'
Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
Requires-Dist: sphinx>=8.0; extra == 'docs'
Provides-Extra: test
Requires-Dist: aioresponses>=0.7; extra == 'test'
Requires-Dist: pytest-asyncio>=1.0; extra == 'test'
Requires-Dist: pytest-cov>=6.0; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

<!--
SPDX-FileCopyrightText: 2026 Andrew Grimberg <tykeal@bardicgrove.org>
SPDX-License-Identifier: Apache-2.0
-->

# pylocal-akuvox

[![CI](https://github.com/tykeal/pylocal-akuvox/actions/workflows/build-test.yaml/badge.svg)](https://github.com/tykeal/pylocal-akuvox/actions/workflows/build-test.yaml)
[![Documentation](https://readthedocs.org/projects/pylocal-akuvox/badge/?version=latest)](https://pylocal-akuvox.readthedocs.io/en/latest/?badge=latest)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Python](https://img.shields.io/badge/python-3.13.2%2B-blue.svg)](https://www.python.org/downloads/)

Async Python library for the Akuvox local HTTP API.

**pylocal-akuvox** provides a single `AkuvoxDevice` object for
communicating with Akuvox intercoms and access controllers on the LAN.
It supports user/PIN management, relay control, schedule management,
and log retrieval over the device's local HTTP API.

## Features

- **Async-only** — designed for `asyncio` event loops and Home Assistant
- **Single runtime dependency** — only `aiohttp`
- **Full device management** — users, PINs, relays, schedules, and logs
- **Multiple auth modes** — None, Allowlist, Basic, and Digest
- **SSL support** — including self-signed certificate handling
- **Legacy TLS compatibility** — OpenSSL SECLEVEL relaxation for old devices
- **Comprehensive error handling** — typed exception hierarchy
- **Capability-aware API** — built-in device support matrix, safe probing,
  and fail-fast unsupported-operation checks
- **Contact schema fidelity** — door-phone and apartment-book contact records
  preserve their device-specific fields

## Installation

```bash
pip install pylocal-akuvox
```

## Quick Start

```python
import asyncio
from pylocal_akuvox import AkuvoxDevice

async def main():
    async with AkuvoxDevice("192.168.1.100") as device:
        info = await device.get_info()
        print(f"{info.model} — FW {info.firmware_version}")

asyncio.run(main())
```

## Capability-aware API

Known device classes are matched against a built-in capability matrix when
the connection opens. For unfamiliar devices, or after a firmware update, run
the safe read-only probe and then act only on confirmed capabilities:

```python
import asyncio
from pylocal_akuvox import AkuvoxDevice, Capability, CapabilityStatus

async def main():
    async with AkuvoxDevice("192.168.1.100") as device:
        capabilities = await device.probe_capabilities()

        user_add_status = capabilities.status_of(Capability.USER_ADD)
        if user_add_status is CapabilityStatus.SUPPORTED:
            await device.add_user(
                name="Alice",
                user_id="2001",
                web_relay="0",
                schedule_relay="1001-1",
                lift_floor_num="0",
                private_pin="1234",
            )
        else:
            print("User creation is not confirmed for this device")

asyncio.run(main())
```

The probe uses a deterministic, non-destructive read sequence. Operations
whose status is `UNSUPPORTED` always fail fast. Operations whose status is
`UNKNOWN` fail fast by default; set `device.attempt_unknown_capability = True`
only when you intentionally want to try an unproven device-side operation.
The effective profile is available as `device.capabilities` for the current
connection.

The examples below call service methods directly for brevity. They assume the
relevant capability is `SUPPORTED`; for portable code, guard each operation
with the probed or matrix profile as shown above.

### Contact models

Door-phone devices such as X916 and E18C expose contacts with `ID`, `Name`,
`Phone`, and `Group`. Apartment-book devices such as X915S expose contacts with
`Name`, `Phone`, `APTName`, `APTNum`, `Building`, and `Landline`; they have no
device-assigned `ID` or `Group`.

`Contact` exposes apartment-book metadata as `apt_name`, `apt_num`, `building`,
and `landline`. Those fields are `None` on door-phone records. Apartment-book
contacts are read-only over the public HTTP API, so manage them through the
device web UI, provisioning, or another vendor-supported channel.

### Manage Users and PINs

```python
import asyncio
from pylocal_akuvox import AkuvoxDevice

async def main():
    async with AkuvoxDevice("192.168.1.100") as device:
        await device.add_user(
            name="Alice",
            user_id="2001",
            web_relay="0",
            schedule_relay="1001-1",
            lift_floor_num="0",
            private_pin="1234",
        )

        users = await device.list_users()
        for user in users:
            print(f"{user.name} (ID: {user.user_id})")

asyncio.run(main())
```

### Trigger a Door Relay

Door-phone models that support the JSON relay API use `trigger_relay()`,
which sends `/api/relay/trig` with the connection's `AuthConfig`:

```python
import asyncio
from pylocal_akuvox import AkuvoxDevice

async def main():
    async with AkuvoxDevice("192.168.1.100") as device:
        await device.trigger_relay(num=1, delay=5)

asyncio.run(main())
```

IT83-class devices use Akuvox's separate Open Relay Via HTTP setting instead.
Enable **Phone → Relay → Open Relay Via HTTP** on the device and pass those
relay-specific credentials per call:

```python
import asyncio
from pylocal_akuvox import AkuvoxDevice

async def main():
    async with AkuvoxDevice("192.168.1.100") as device:
        await device.open_door_http(
            user="relay-user",
            password="relay-password",
        )

asyncio.run(main())
```

The vendor endpoint carries the OpenDoor password in the URL query string,
so it can appear in proxy or device access logs outside this library. On an
IT83, `trigger_relay()` raises an actionable error directing callers to
`AkuvoxDevice.open_door_http()` instead of sending a credential-less
OpenDoor request.

### Authentication

```python
import asyncio
from pylocal_akuvox import AkuvoxDevice, AuthConfig, AuthMethod

async def main():
    # Basic Auth
    auth = AuthConfig(method=AuthMethod.BASIC, username="admin", password="secret")
    async with AkuvoxDevice("192.168.1.100", auth=auth) as device:
        info = await device.get_info()

asyncio.run(main())
```

## Documentation

Full documentation is available at
[pylocal-akuvox.readthedocs.io](https://pylocal-akuvox.readthedocs.io/).

## Contributing

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

```bash
# Clone and install
git clone https://github.com/tykeal/pylocal-akuvox.git
cd pylocal-akuvox
uv sync --group dev

# Run tests
uv run pytest tests/ -x -q

# Run linting
uv run ruff check src/ tests/

# Build docs locally
uv run --extra docs sphinx-build -b html docs docs/_build/html
```

## License

Apache-2.0 — see [LICENSE](LICENSE) for details.
