Skip to main content

Essl F22 SDK

A synchronous Python SDK for eSSL/ZKTeco F22 attendance and access terminals.

CI Python License

The Essl F22 SDK talks directly to an F22-compatible attendance and access terminal over TCP. It reads the records that already exist on the device, supports selected writes, and keeps a read-only offline mirror in a local file or an external database.

It is a Python library, not a command-line application or a replacement for the vendor's device software. Fingerprint enrollment and matching still happen on the device.

What you can do

  • Read device information, serial number, firmware version, and capacity.
  • Read users, attendance records, fingerprint metadata and templates, groups, timezones, and per-user access assignments.
  • Create, update, and delete users.
  • Write groups, timezones, and user-timezone assignments.
  • Block and unblock a user by changing their access schedule without deleting the user or their fingerprint templates.
  • Sync the device into a read-only mirror backed by SQLite, JSON, memory, PostgreSQL, MySQL, MongoDB, or Redis.
  • Use the SDK on Python 3.12 or newer with no required runtime dependencies beyond the standard library.

Requirements

  • Python 3.12 or newer.
  • Network access to the device on TCP port 4370, or the port configured on the device.
  • The device communication key, if one is configured.
  • An F22-compatible terminal. Compatibility varies by firmware and OEM build; validate a new device before relying on writes.

Optional storage drivers are installed only when needed. The core package and the default test suite do not import them.

Installation

pip install Essl_F22_SDK

With uv:

uv add Essl_F22_SDK

Optional extras:

pip install "Essl_F22_SDK[postgres]"
pip install "Essl_F22_SDK[mysql]"
pip install "Essl_F22_SDK[mongo]"
pip install "Essl_F22_SDK[redis]"
pip install "Essl_F22_SDK[docs]"

The same extras work with uv:

uv add "Essl_F22_SDK[postgres]"

pipx and uvx are application runners. They do not make a library importable in another project. For a one-off run with uvx:

uvx --from Essl_F22_SDK python -c "from essl_f22 import Device; print(Device)"

The distribution name is Essl_F22_SDK. Some package tools display the normalized spelling essl-f22-sdk; that is the same package. The import name is essl_f22.

Quick start

from essl_f22 import Device

with Device("192.168.1.27", comm_key=0) as device:
    info = device.get_device_info()
    print(info.device_name, info.serial_number, info.firmware_version)

    sizes = device.get_free_sizes()
    print("users:", sizes.users, "attendance:", sizes.attendance)

    for user in device.list_users()[:5]:
        print(user.uid, user.user_id, user.name)

Device(...) does not open the connection by itself. The with block calls connect() on entry and close() on exit. The default port is 4370; the default communication key is 0. A key of 0 is a default, not a guarantee that the device accepts it.

Manual lifecycle:

from essl_f22 import Device

device = Device("192.168.1.27", comm_key=0)
try:
    device.connect()
    print(device.get_serial_number())
finally:
    device.close()

Do not share one Device across threads without external locking. The session keeps mutable request and receive-buffer state.

How it works

Concept SDK representation Notes
Device Device TCP client and high-level API.
User User A 72-byte device record keyed by uid.
Attendance AttendanceRecord A 40-byte punch record.
Fingerprint FingerprintTemplate Raw template bytes plus uid, fid, and flag.
Access schedule TimeZone, GroupInfo, UserTimeZone Timezone slots and assignments.
Mirror Mirror Read-only local copy scoped by device serial.
Block state BlockStateStore Saved original assignment needed for unblocking.

The device serial scopes both the mirror and block state. If the hardware changes, create a new Device instance.

Common tasks

Read device information

from essl_f22 import Device

with Device("192.168.1.27", comm_key=0) as device:
    info = device.get_device_info()
    print("platform:", info.platform)
    print("name:", info.device_name)
    print("serial:", info.serial_number)
    print("firmware:", info.firmware_version)

get_serial_number() caches the serial on the Device instance.

Read users

users = device.list_users()
user = device.get_user(295)
same_user = device.get_user_by_user_id("1001")

list_users() reads the full user buffer. Device order is not guaranteed. get_user() and get_user_by_user_id() scan that list and raise UserNotFoundError when no match exists.

Create, update, and delete users

set_user() creates or replaces the user with the same uid. It sends the full 72-byte record, so read an existing user first and change only the fields you intend to write.

from essl_f22 import Device, User

with Device("192.168.1.27", comm_key=0) as device:
    user = device.get_user(295)
    user.name = "Updated Name"
    device.set_user(user)

    test_user = User(uid=296, user_id="sdk-296", name="SDK Test User")
    device.set_user(test_user)
    device.delete_user(test_user.uid)
  • Create and delete have been exercised on hardware.
  • Updating an existing user is implemented but has not been hardware-validated; test it on a non-production device first.
  • delete_user() is destructive and cannot be undone by the SDK.

Read attendance

from essl_f22 import Device

with Device("192.168.1.27", comm_key=0) as device:
    records = device.list_attendance()
    for record in records[:10]:
        print(
            record.uid,
            record.user_id,
            record.timestamp,
            record.status,
            record.punch,
        )

The SDK reads the full attendance buffer. There is no device-side date filter or pagination. Filter in Python after the read, or use the mirror for repeated queries.

Read fingerprints

from essl_f22 import Device

with Device("192.168.1.27", comm_key=0) as device:
    templates = device.get_user_fingerprints(295)
    for item in templates:
        print(item.uid, item.fid, item.flag, item.size)

list_fingerprints() returns raw template bytes. Treat them like passwords or identity documents. Do not log them, commit them, or copy them into a mirror. The built-in mirror stores only fingerprint metadata.

There is no public enrollment or template-write API.

Work with groups and timezones

from essl_f22 import Device

with Device("192.168.1.27", comm_key=0) as device:
    groups = device.list_groups()
    timezones = device.list_timezones()
    assignment = device.get_user_timezone(295)

    print("groups:", [(group.group_id, group.tz1) for group in groups])
    print("timezone slots:", sorted(timezones))
    print("personal:", assignment.use_personal, assignment.tz1)

list_groups() and list_timezones() scan their configured ranges and skip the device's not-found response. set_group(), set_timezone(), and set_user_timezone() are real device writes.

Block and unblock

Blocking assigns a deny-all timezone. It does not delete the user or their fingerprint templates. The original assignment is saved in the state store and restored by unblock_user().

from essl_f22 import Device

with Device(
    "192.168.1.27",
    comm_key=0,
    storage_backend="sqlite",
    storage_path="access-state.db",
) as device:
    blocked = device.block_user(295)
    print(blocked.already_blocked, blocked.block_timezone_slot)
    print("blocked:", device.is_user_blocked(295))

    restored = device.unblock_user(295)
    print(restored.state_found, restored.restored)

Use an explicit, durable state path. If the state store is lost, the SDK cannot restore the exact original schedule automatically. Blocking and unblocking are unit-tested with fake transports but have not been validated against a physical door or production access policy.

Mirror and sync

from essl_f22 import Device

with Device(
    "192.168.1.27",
    comm_key=0,
    storage_backend="sqlite",
    storage_path="mirror.db",
) as device:
    result = device.sync("full")
    mirror = device.mirror()

    print(result.mode, result.users, result.attendance)
    print(mirror.get_user(295))
    print(mirror.list_attendance(uid=295, limit=10))
Mode Behaviour
full Replaces every mirrored collection and attendance record.
incremental Replaces non-attendance collections, inserts new attendance keys, and replaces attendance if the device count dropped.

The mirror is read-only. It cannot restore users, attendance, or templates to a device. It stores full user records, including the device password field, so protect it like a credential store. Fingerprint template bytes are not copied.

Storage is created lazily on the first blocking or mirror operation. Without an explicit path, SQLite and JSON use platform-specific default paths available as default_state_path() and default_mirror_path().

Storage backends

Backend Extra Connection value
sqlite none storage_path (default state.db)
json none storage_path (default mirror.json)
memory none none
postgres postgres storage_options={"dsn": ...}
mysql mysql storage_options={"dsn": ...}
mongo mongo storage_options={"uri": ...}
redis redis storage_options={"url": ...}

storage_path can also carry the DSN, URI, or URL. storage_options takes precedence when both are supplied.

from essl_f22 import Device

device = Device(
    "192.168.1.27",
    comm_key=0,
    storage_backend="postgres",
    storage_options={"dsn": "postgresql://user:pass@db/app"},
)

An injected storage= object wins over backend selection. A Device closes only storage it created; injected storage remains owned by the caller.

from essl_f22 import MemoryStorage, Device

storage = MemoryStorage()
device = Device("192.168.1.27", comm_key=0, storage=storage)

create_storage() is available when you need a backend without a Device:

from essl_f22 import create_storage

storage = create_storage("sqlite", path="data/essl.db")

Close storage you create yourself.

Error handling

All SDK-defined exceptions inherit from ESSL22Error.

from essl_f22 import Device, ESSL22Error, UserNotFoundError

with Device("192.168.1.27", comm_key=0) as device:
    try:
        user = device.get_user(295)
    except UserNotFoundError:
        print("no such user")
    except ESSL22Error as exc:
        print(type(exc).__name__, exc)

Catch specific errors when the recovery action differs. CommandError carries the device status code. DeviceTimeoutError is ambiguous for writes: the device may have applied the command before the response was lost, so read the current state before retrying.

See the error reference for the full hierarchy.

Safety and limits

Writes are real. set_user(), delete_user(), set_timezone(), set_group(), set_user_timezone(), and set_time() change the live device. Test on a non-production device first and keep a copy of the data you are about to change.

Blocking depends on local state. block_user() changes the user's access schedule. unblock_user() restores the saved schedule only while the state store still contains it. Back up the state store before relying on it.

The mirror is not a restore button. It is a read-only copy for reporting and comparison. It cannot write data back to the device.

The SDK does not enroll fingerprints, match fingerprints in Python, manage firmware or network settings, provide a GUI or CLI, or encrypt the TCP connection. Use a trusted network or a protected tunnel.

Compatibility

The package declares Python >=3.12 and advertises Python 3.12, 3.13, and 3.14. CI runs the quality gates and package build on Python 3.14.

The project has been exercised against an F22-class terminal reporting platform ZLM60_TFT and firmware in the 6.60 line. Create/read/delete of a test user, attendance reads, group and timezone reads, and mirror sync have been validated. Updating an existing user and block/unblock against a production access policy have not.

The hardware test is read-only and opt-in:

ESSL_F22_HOST=192.168.1.27 \
ESSL_F22_PORT=4370 \
ESSL_F22_COMM_KEY=0 \
uv run pytest -m hardware

It is skipped when ESSL_F22_HOST is unset. Read the compatibility guide before deploying against a new firmware or OEM build.

Documentation

The full handbook starts at docs/index.md. It covers:

  • installation and first connection;
  • the device record model and access control;
  • user management, blocking, mirroring, and storage;
  • exact API, model, error, and protocol references;
  • troubleshooting, compatibility, and release work.

The handbook is built with MkDocs Material:

uv sync --extra docs
uv run mkdocs serve

Validate it with:

uv run mkdocs build --strict

Development

uv sync

Run the quality gates before opening a pull request:

uv run ruff check .
uv run ruff format --check .
uv run mypy src/
uv run pytest
uv run pre-commit run --all-files

Build and check the package:

uv build
uvx twine check dist/*

Tests

The default suite does not require a physical device:

uv run pytest

External storage contract tests are opt-in:

ESSL_F22_POSTGRES_DSN=postgresql://user:pass@db/app \
ESSL_F22_MYSQL_DSN=mysql://user:pass@db/app \
ESSL_F22_MONGO_URI=mongodb://localhost/app \
ESSL_F22_REDIS_URL=redis://localhost/0 \
uv run pytest tests/test_storage_contract.py

Do not commit test credentials or .env files.

Project layout

src/essl_f22/
  access.py       # non-destructive block/unblock manager
  device.py       # high-level device read/write API
  mirror.py       # read-only offline mirror and sync
  models.py       # dataclasses for device records
  protocol.py     # command constants and binary codecs
  session.py      # request/response session
  storage/        # document storage, built-ins, and external adapters
  transport.py    # TCP socket transport
docs/             # MkDocs handbook
tests/            # unit tests and opt-in hardware/storage tests

Versioning

The project is pre-1.0. Treat the public API as usable but still subject to change. Pin the version in your lock file, read CHANGELOG.md, and test upgrades on a non-production device.

License

The Essl F22 SDK is distributed under the terms of the Apache License 2.0. See LICENSE for details.

Metadata

Release files for Essl-F22-SDK 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for Essl-F22-SDK 0.1.0
File Size Uploaded Provenance Actions
essl_f22_sdk-0.1.0.tar.gz 212.8 kB With provenance Details for essl_f22_sdk-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for Essl-F22-SDK 0.1.0
File Interpreter ABI Platform Actions
essl_f22_sdk-0.1.0-py3-none-any.whl Python 3 none any Details for essl_f22_sdk-0.1.0-py3-none-any.whl

Total release size: 257.0 kB

Release files / essl_f22_sdk-0.1.0.tar.gz

Download URL essl_f22_sdk-0.1.0.tar.gz
Size 212.8 kB
Tags Source
SHA-256 checksum
How to use checksums
7de32a095963519aec15529357aec73459c7af6becac90fbd9222778959505d9
BLAKE2b-256 checksum
How to use checksums
4d3565111e3b2ffe670a8bbed2880f52cefa17e14f19b8e05f4c04059722c723
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / essl_f22_sdk-0.1.0-py3-none-any.whl

Download URL essl_f22_sdk-0.1.0-py3-none-any.whl
Size 44.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3352df83a5fba6973fded492db4ed2b17eaf1c00f21e3cbb571ffd4b60aaf8dd
BLAKE2b-256 checksum
How to use checksums
aa664821f12be75069a5dfe6407991b7421a90d2d4a74d06b7f79d8e6ec648c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for Essl-F22-SDK 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for essl_f22_sdk-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page