Architecture¶
openzk is layered so each module has one responsibility and can be understood and tested
on its own.
client.py ZKDevice — high-level, read-only API (context manager)
│
session.py connect → AUTH → SDKBuild handshake → capability read (restored on close)
│
transport.py Transport interface · TcpTransport · buffered table read · frame capture
│
framing.py pure wire: checksum, make_commkey, build_packet, parse_frame (no I/O)
opcodes.py generated command-opcode table (factual numbers)
models.py frozen dataclasses (DeviceInfo, User, Attendance, OpLog, *Template, UserPhoto)
codecs.py parse raw device-table blobs → models
errors.py typed exception hierarchy
Wire layer (framing.py)¶
Pure functions, no sockets. Packets are framed as MAGIC(4) + length(4) + payload, where the
payload is cmd(2) + checksum(2) + session_id(2) + reply_id(2) + data. The checksum and the
reply_id increment/wrap behaviour are pinned by known-answer test vectors so the bytes
on the wire are provably correct.
Transport (transport.py)¶
An abstract Transport defines connect/disconnect/capture. TcpTransport implements it
over a socket, reading frame-by-frame (no fixed-size recv that could truncate a
multi-packet reply). capture() returns the first response frame plus any follow-on frames a
command emits. read_buffer() implements the device's buffered table-read flow
(PREPARE_BUFFER → chunked READ_BUFFER). A FakeTransport (in tests) replays scripted
frames so the whole stack is unit-testable without hardware.
Session (session.py)¶
Performs the connect/auth sequence and then the modern SDK handshake (SDKBuild=1) that
gates extended commands on newer firmware — a step legacy clients skip. It reads device
capability flags so the client can fail fast (ZKNotSupported) instead of issuing a doomed
command, and restores the original handshake state on close.
Deriving the command set¶
Opcodes were obtained by reverse-engineering the official SDK for interoperability:
disassembling each exported function and reading the opcode constant it sends. Those numbers
are committed in opcodes.py. Payload shapes were cross-checked against existing open
clients; response parsers (codecs.py) were validated byte-for-byte against a real device
before being relied on. No proprietary code or binary is included — see
Disclaimer.
Design rules¶
- Read-only. No write/control commands in the public API.
- No silent guessing. A parser that hasn't been validated against real hardware is not shipped as public API.
- Typed and dependency-free. Pure stdlib; frozen dataclasses;
py.typed.