Your own server in Python
The adaraz library (Python 3.12+) is a complete implementation of the protocol: pairing, sessions, encryption, punch acknowledgements and relay control. You get events.
pip install "adaraz[aiohttp]"The smallest working server:
# Minimal ADARAZ Link server (Python 3.12+): pip install "adaraz[aiohttp]"
# Reader: Settings > Integrations > Home Assistant and other servers,
# server URL http://<this computer>:8765 and the code printed below.
import asyncio
from aiohttp import web
from adaraz import (DeviceConnected, InMemoryStorage, LinkServer, PunchReceived,
generate_private_key)
async def main() -> None:
# Demo only: the key and the pairing live in memory. In production keep both
# (see examples/server.py in ADARAZ-HA): the reader pins the server key at pairing.
server = LinkServer(generate_private_key(), InMemoryStorage(), server="My app", name="Office")
def on_event(event) -> None:
if isinstance(event, DeviceConnected):
print("reader online:", event.device.device_id, event.hello.name, flush=True)
elif isinstance(event, PunchReceived):
p = event.punch
late = " (late)" if p.late else ""
print(f"punch: card {p.uid} at {p.ts} from {p.device_id}{late}", flush=True)
server.subscribe(on_event)
app = web.Application()
app.router.add_get("/api/adaraz/ws", server.handle_aiohttp) # the reader connects here
runner = web.AppRunner(app)
await runner.setup()
await web.TCPSite(runner, "0.0.0.0", 8765).start()
window = server.open_pairing() # one reader, 10 minutes
print(f"pairing code: {window.code} server URL: http://<this computer>:8765", flush=True)
device = await window
print("paired:", device.device_id, flush=True)
# Optional: open the door. Works only if the reader's admin allowed it in the
# reader panel ("Pozwól serwerowi sterować przekaźnikiem").
# result = await server.send_relay(device.device_id, "open")
await asyncio.Event().wait()
asyncio.run(main())Run it and it prints a pairing code:
pairing code: 7QPM-36QP-WNXQ server URL: http://<this computer>:8765Pairing the reader
In the reader's panel: Settings > Integrations > Home Assistant and other servers:
| Field | Value |
|---|---|
| Server address | http://<your computer's IP>:8765 |
| Code | the code printed by the server, e.g. 7QPM-36QP-WNXQ (valid 10 minutes, one reader) |
Click Connect. Letter case, the dashes and the letters O / I / L (read as 0 / 1 / 1) do not matter. Once connected, the panel shows the Server ID: compare it with server.server_id on the server.
Relay
The server can drive the relay only after the reader's administrator enables Let the server control the relay in the same panel section.
result = await server.send_relay(device_id, "open") # pulse like an accepted card
result = await server.send_relay(device_id, "pulse", ms=1500) # 1.5 s pulse
result = await server.send_relay(device_id, "on") # on; "off" turns it off
if not result.ok:
print(result.err) # not_allowed (no consent), invalid, busyBefore production
- Keep the server key and the paired readers. A reader pins the server's public key at pairing: a new key after a restart means pairing every reader again. Implement
LinkStorage(devices, cursor, storing punches) instead ofInMemoryStorage. A file-based version:examples/server.py. - Late punches (
punch.late, arrived more than 5 minutes after the tap): keep them in history but do not use them for live actions such as opening a door. - Every punch arrives once: the server acknowledges a batch only after
store_punchesstored the punches and the cursor. Duplicates (the same card in the same second) are filtered byPunchDeduplicator.
The full library API: library README.
Testing without a reader
The library has a simulated reader (adaraz.device.LinkDevice) that behaves like the real one: pairs with a code, connects, sends punches and executes relay commands.
import asyncio, aiohttp
from adaraz.aiohttp_ws import connect
from adaraz.device import LinkDevice
async def main(code: str) -> None:
async with aiohttp.ClientSession() as http:
reader = LinkDevice(device_id="C00F0202")
await reader.pair(await connect(http, "http://127.0.0.1:8765"), code)
session = asyncio.create_task(reader.run_session(await connect(http, "http://127.0.0.1:8765")))
reader.punch("04A1B2C3")
await reader.wait_delivered()
await reader.stop()
await session
asyncio.run(main("7QPM-36QP-WNXQ"))