Quickstart¶
Installation¶
pip install pylocal-akuvox
Connect and Get Device Info¶
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())
Probing capabilities¶
When a connection opens, known device classes are matched against the
built-in device support matrix. For an unfamiliar device, or after a
firmware update, call pylocal_akuvox.AkuvoxDevice.probe_capabilities()
to run the deterministic, non-destructive read probe. The returned
pylocal_akuvox.DeviceCapabilities is also available as
device.capabilities for later calls on the same connection.
Use the probe-then-act pattern so provisioning code only runs operations that are confirmed for the attached device:
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())
UNSUPPORTED operations always fail fast without a device request.
UNKNOWN operations fail fast by default; set
device.attempt_unknown_capability = True only when you intentionally
want to try an unproven device-side operation. See
Device Support Matrix for the live capability matrix.
Note
The remaining examples 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.
Manage Users and PINs¶
import asyncio
from pylocal_akuvox import AkuvoxDevice
async def main():
async with AkuvoxDevice("192.168.1.100") as device:
# Create a user with a PIN
await device.add_user(
name="Alice",
user_id="2001",
web_relay="0",
schedule_relay="1001-1",
lift_floor_num="0",
private_pin="1234",
)
# List all users
users = await device.list_users()
for user in users:
print(f"{user.name} (PIN set: {bool(user.private_pin)})")
# Update a user's PIN
await device.modify_user(id="1", private_pin="5678")
# Delete a user
await device.delete_user(id="1")
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:
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:
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 firmware that returns the hcSingleResult OpenDoor
marker, open_door_http() treats 0 as success, -1 as an Open
Relay Via HTTP credential failure, and other non-zero values as
device-side failures. Responses without that marker continue to use the
HTTP status for compatibility. On an IT83, trigger_relay() raises an
actionable error directing callers to AkuvoxDevice.open_door_http()
instead of sending a credential-less OpenDoor request.
Retrieve Device Status¶
import asyncio
from pylocal_akuvox import AkuvoxDevice
async def main():
async with AkuvoxDevice("192.168.1.100") as device:
status = await device.get_status()
print(f"Uptime: {status.uptime}")
asyncio.run(main())
Manage Schedules¶
import asyncio
from pylocal_akuvox import AkuvoxDevice
async def main():
async with AkuvoxDevice("192.168.1.100") as device:
# Create a weekly schedule
await device.add_schedule(
name="Weekday Access",
schedule_type="1",
week="12345",
daily="08:00-18:00",
)
# List schedules
schedules = await device.list_schedules()
# Delete a schedule
await device.delete_schedule(id="1001")
asyncio.run(main())
Manage Groups¶
import asyncio
from pylocal_akuvox import AkuvoxDevice
async def main():
async with AkuvoxDevice("192.168.1.100") as device:
# List existing groups
groups = await device.list_groups()
for g in groups:
print(f"[{g.id}] {g.name}")
# Create a new group
await device.add_group(name="Residents")
# Rename a group
await device.modify_group(id="1", name="Tenants")
# Delete a group
await device.delete_group(id="1")
asyncio.run(main())
Manage Contacts¶
Door-phone devices expose contact records with a device ID and optional
Group. Apartment-book devices expose read-only records without ID;
their additional apt_name, apt_num, building, and landline
fields come from APTName, APTNum, Building, and Landline.
Empty apartment-book building and landline values are preserved as
"" because the device returned those fields.
import asyncio
from pylocal_akuvox import (
AkuvoxDevice,
AkuvoxUnsupportedError,
Capability,
CapabilityStatus,
)
async def main():
async with AkuvoxDevice("192.168.1.100") as device:
# List all contacts
contacts = await device.list_contacts()
for c in contacts:
if c.id is None:
key = (c.apt_num, c.phone)
print(
f"{c.name}: apt={c.apt_num} phone={c.phone} "
f"building={c.building!r} landline={c.landline!r}"
)
# Use the caller-side key only when both parts are present.
# Fall back to c.name when the pair is incomplete.
if all(key):
print(f"caller key: {key}")
else:
print(f"{c.name} ({c.group}): {c.phone}")
status = device.capabilities.status_of(Capability.CONTACT_ADD)
if status is CapabilityStatus.SUPPORTED:
# Add a contact to a group on door-phone devices
await device.add_contact(
name="Alice",
phone="5551234",
group="Residents",
)
# Move a contact to another group
await device.modify_contact(id="1", group="Staff")
# Delete a contact
await device.delete_contact(id="1")
# Batch delete
await device.delete_contact(id=["2", "3"])
elif status is CapabilityStatus.UNSUPPORTED:
try:
await device.add_contact(name="Alice", phone="5551234")
except AkuvoxUnsupportedError as err:
assert err.reason == "capability_missing"
print("Contact writes are not supported on this device")
else:
print("Contact writes are not confirmed for this device")
asyncio.run(main())
Apartment-book records have no device-assigned ID. The library does not
create a synthetic identifier or guarantee uniqueness. When both fields are
available, use a caller-side (apt_num, phone) composite key; fall back to
name when needed. Two records with the same name remain
distinguishable when their apt_num or phone values differ.
Retrieve Logs¶
import asyncio
from pylocal_akuvox import AkuvoxDevice
async def main():
async with AkuvoxDevice("192.168.1.100") as device:
# Door access logs
door_logs = await device.get_door_logs()
for entry in door_logs:
print(f"{entry.date} {entry.time}: {entry.name} — {entry.status}")
# Call logs
call_logs = await device.get_call_logs()
for entry in call_logs:
print(f"{entry.date} {entry.time}: {entry.call_type} — {entry.name}")
asyncio.run(main())
Authentication Modes¶
import asyncio
from pylocal_akuvox import AkuvoxDevice, AuthConfig, AuthMethod
async def main():
# No auth (default — no credentials needed)
async with AkuvoxDevice("192.168.1.100") as device:
info = await device.get_info()
# 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()
# Digest Auth
auth = AuthConfig(method=AuthMethod.DIGEST, username="admin", password="secret")
async with AkuvoxDevice("192.168.1.100", auth=auth) as device:
info = await device.get_info()
asyncio.run(main())
SSL Connections¶
import asyncio
from pylocal_akuvox import AkuvoxDevice
async def main():
# SSL with certificate verification (recommended for production)
async with AkuvoxDevice("192.168.1.100", use_ssl=True) as device:
info = await device.get_info()
# SSL with self-signed certificate (skip verification)
# WARNING: Only use verify_ssl=False on trusted networks for
# testing. Disabling verification exposes you to MITM attacks.
async with AkuvoxDevice("192.168.1.100", use_ssl=True, verify_ssl=False) as device:
info = await device.get_info()
asyncio.run(main())
Note
When using self-signed certificates, prefer importing the device’s
certificate into your trust store so that you can keep
verify_ssl=True in production deployments.
Warning
Some older Akuvox devices (e.g. the S562 indoor monitor) ship with
legacy 1024-bit Diffie-Hellman parameters that OpenSSL’s default
security level rejects with a dh key too small handshake error.
When verify_ssl=False, this library lowers OpenSSL’s security
level to 0 so these legacy handshakes succeed. This relaxation
is only active in the no-verify path; the verified path
(verify_ssl=True) uses Python’s OpenSSL defaults unchanged.
Because it weakens TLS protections, it is intended for trusted LAN
use only.
Error Handling¶
import asyncio
from pylocal_akuvox import AkuvoxDevice
from pylocal_akuvox.exceptions import (
AkuvoxConnectionError,
AkuvoxAuthenticationError,
AkuvoxValidationError,
)
async def main():
try:
async with AkuvoxDevice("192.168.1.100") as device:
await device.add_user(
name="Bob",
user_id="2002",
web_relay="0",
schedule_relay="1001-1",
lift_floor_num="0",
private_pin="12ab",
)
except AkuvoxConnectionError as e:
print(f"Cannot reach device: {e}")
except AkuvoxAuthenticationError as e:
print(f"Auth failed: {e}")
except AkuvoxValidationError as e:
print(f"Invalid input: {e}")
asyncio.run(main())