Skip to content

feat(client): add get_cards() - #96

Open
dariusz-rudzinski wants to merge 1 commit into
roquerodrigo:mainfrom
dariusz-rudzinski:feat/get-cards
Open

dariusz-rudzinski wants to merge 1 commit into
roquerodrigo:mainfrom
dariusz-rudzinski:feat/get-cards

Conversation

@dariusz-rudzinski

@dariusz-rudzinski dariusz-rudzinski commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

What this adds

TTLockClient.get_cards() lists the IC cards (tags) enrolled on a lock, mirroring get_fingerprints(): admin-gated, one indexed BLE query per card over a single connection and handshake, stopping at the lock's own end-of-list response, with a safety cap that raises instead of returning a possibly incomplete list.

  • New CardEntry model (card_id, start_date, end_date, plus is_permanent / has_explicit_start), exported from the top-level package
  • New ttlock get-cards <lock> CLI command
  • README: CLI and API tables updated

Protocol

CMD 0x05 (COMM_IC_MANAGE), sub-op 0x01 (IC_SEARCH):

  • Request: [0x01] + index (2 bytes, big-endian), starting at 0
  • Response data: [battery][op_echo][position:2][card_id][start:5][end:5]
  • End of list: [battery][0x01][0xFF][0xFF], as a SUCCESS response, the same shape as fingerprints
  • Dates use the same 5-byte encoding and sentinels as fingerprints (reused, not redefined)

Differences from fingerprints:

  • CardEntry has no slot. The byte math leaves no room for one: 18 = 1 + 1 + 2 + 4 + 5 + 5.
  • card_id length is variable. The official SDK reads 8 bytes when its own buffer is exactly 20 bytes ([position:2][entries...], which excludes battery and op_echo) and 4 otherwise. In this library's framing that is 22 vs 18 bytes, not the SDK's literal 20. There is a regression test that a 20-byte data raises.
  • position is discarded, as it is for fingerprints. The client uses its own incrementing index.

Verified vs. not verified

Verified on real hardware (an SN484_PV53 lock), via ttlock get-cards:

  • Request/response framing
  • The 4-byte card_id path
  • Multi-card listing: four cards returned in order, then a clean end-of-list
  • The zero-card case: a lock with no cards returns an empty list (index 0 returns the end marker)
  • Date decoding for both sentinel cases (not set / permanent) and real dates
  • The admin handshake is required: an IC_SEARCH sent without it is refused with a clean failure status (tested on one lock model)

Not verified:

  • The 8-byte card_id path (22-byte entries). It is derived from the SDK only and is marked as such in the docstrings and in its test, because no available card produces it.

Known limitation

Like get_fingerprints(), this cannot detect cyclic (day-of-week/time-range) restrictions. That data lives in a separate mechanism (CMD 0x70) that this library doesn't implement for any credential type yet. A card listed as permanent or timed may still be limited to specific days/hours. This is stated in the CardEntry and get_cards() docstrings, in the CLI output, and in the README.

Tests

ruff, mypy and pytest pass (479 tests, 99.24% coverage). Fixtures mirror the real captures, with synthetic card ids substituted for the real ones, since a card UID can be a cloneable credential.

Follow-up

Once this is released, I'd like to add a card hub with per-card child devices to ha-ttlock-ble, following the fingerprint pattern. That depends on a ttlock-ble release containing get_cards().

Lists enrolled IC cards via CMD 0x05 sub-op IC_SEARCH (0x01), mirroring
get_fingerprints(). Adds CardEntry, the `ttlock get-cards` CLI command and
README entries. The 4-byte card id path, multi-card listing, the zero-card
case and the end-of-list response are hardware-verified, and the command is
confirmed admin-gated; only the 8-byte card id path is not. Cannot detect
cyclic restrictions (CMD 0x70 is not implemented for any credential type).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dariusz-rudzinski

Copy link
Copy Markdown
Contributor Author

Squashed the branch into a single commit (d11c078) after opening the PR. The tree is identical to what was there before, so there is no content change. Updated the description to move the zero-card case and the admin-handshake requirement to "verified" (both confirmed on hardware since the first draft).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants