T-Dongle Display Media
Home > 3rd-Party Integration > T-Dongle Display Media
T-Dongle Display Media
The T-Dongle C5 exposes an optional BLE service for assigning a built-in logo, firmware-rendered status text, or an SD-card image/GIF to its Idle, Scanning, and Attacking display contexts. The service is absent on every other Biscuit model and is discovered after connection rather than advertised.
GATT service
| Role | UUID | Properties | Security |
|---|---|---|---|
| Service | fb1e5001-54ae-4a28-9f74-dfccb248601d |
Service | — |
| Info | fb1e5002-54ae-4a28-9f74-dfccb248601d |
Read | Open |
| Control | fb1e5003-54ae-4a28-9f74-dfccb248601d |
Write, Notify | Encrypted |
| Data RX | fb1e5004-54ae-4a28-9f74-dfccb248601d |
Write, Write Without Response | Encrypted |
Read Info before enabling Control notifications. Info does not require an encrypted link and returns a compact UTF-8 JSON object:
{"v":1,"sd":true,"width":160,"height":80,"maxBytes":1048576,"maxFrames":80,"maxFiles":32,"maxFps":10,"maxDurationMs":15000}
Only subscribe to Control when the user opens display management. Its CCCD and all Control/Data writes require an encrypted link. A bonded client should honor the standard Service Changed indication and rediscover services after a firmware update.
Control protocol
Each Control request is one compact UTF-8 JSON object in a single write and
contains protocol version v:1, a positive integer request id, and op.
Each logical response or event is also one JSON object, but its UTF-8 bytes may
span sequential notifications at the negotiated ATT payload limit. Reassemble
one complete object before parsing it; objects are never interleaved. A direct
response echoes id and contains either ok:true or ok:false plus error.
| Operation | Request fields | Result |
|---|---|---|
get_config |
— | enabled and the three assignments |
list |
— | Zero or more asset events followed by list_end |
set_enabled |
enabled boolean |
Updates the backlight preference |
set_assignment |
context, kind, optional file |
Validates/caches, commits, and returns canonical file for an asset |
begin_upload |
name, replace, size, sha256 |
session, resumeOffset, windowBytes, canonical name, optional committed |
commit_upload |
session |
Validates and atomically installs staging data; recent matching commits are idempotent |
cancel_upload |
session |
Removes staging data |
delete_asset |
name |
Resets affected assignments, removes the asset, and returns canonical name plus reset contexts |
context is idle, scanning, or attacking. Assignment kind is logo,
status, or asset; an asset assignment also contains file.
{"v":1,"id":1,"op":"config","ok":true,"enabled":true,"idle":{"kind":"logo"},"scanning":{"kind":"status"},"attacking":{"kind":"asset","file":"warning.bma"}}
The list is metadata-only. The T-Dongle never sends media or thumbnails back to a client.
{"v":1,"id":2,"event":"asset","name":"warning.bma","animated":true,"size":48321,"frames":24,"durationMs":2400}
{"v":1,"id":2,"event":"list_end","ok":true}
Stable error codes include busy, not_encrypted, no_sd, sd_full,
storage_error, duplicate, invalid_request, invalid_asset,
hash_mismatch, offset_mismatch, not_found, no_memory, internal,
timeout, and library_full.
no_sd is reserved for a card that was not mounted, and it restricts the media
library only: list, begin_upload, and delete_asset return it, and
set_assignment to an asset returns not_found. get_config, set_enabled,
and set_assignment to logo or status stay available, so keep the screen
on/off control reachable when sd is false. sd_full reports known
insufficient space, while storage_error reports a filesystem failure on an
otherwise mounted card. A storage failure may include an additive stage value
of mkdir, open, write, rename, or delete. Clients should branch on the
stable error code and may use stage for diagnostics. Cryptographic failures
return internal with stage:"sha"; no_memory denotes an allocation failure.
Upload transport
begin_upload supplies the canonical .bma filename, replacement choice,
complete byte count, and lowercase 64-character SHA-256 digest. Only one bulk
session can be active. A matching interrupted upload can resume during the
same boot for five minutes.
Every Data RX value starts with this little-endian header:
| Offset | Type | Meaning |
|---|---|---|
| 0 | uint32 |
Session ID |
| 4 | uint32 |
Absolute file offset |
| 8 | bytes | BMA payload |
Use Write Without Response with the platform’s backpressure signal when it is available. Otherwise, send sequential writes with response. Derive payload size from the negotiated maximum write length minus eight bytes. Stop at each 8 KiB window and wait for a cumulative acknowledgement:
{"v":1,"event":"ack","session":305419896,"nextOffset":8192}
A gap produces a NACK whose expectedOffset is the next byte the device will
accept. commit_upload verifies received length, SHA-256, all BMA bounds, and
CRC32 before replacing the destination. Scans, attacks, wardrives, portals,
other SD bulk activity, and OTA are mutually exclusive with media transfer.
The most recent successful commit remains replayable for five minutes during
the same boot. A matching begin_upload then returns committed:true and a
resumeOffset equal to the file size; repeat commit_upload with that session
to recover a lost response without retransmitting the file.
Biscuit Media Asset (.bma)
All integers are little-endian. Files are at most 1 MiB and encode a 160×80 RGB565 canvas.
Header (16 bytes)
| Field | Type | Constraint |
|---|---|---|
| Magic | 4 bytes | ASCII BMA1 |
| Width | uint16 |
160 |
| Height | uint16 |
80 |
| Frame count | uint16 |
1 through 80 |
| Flags | uint16 |
Bit 0 animated; all other bits zero |
| Total duration | uint32 |
0 static; at most 15000 ms animated |
Frame record
Each frame has a 15-byte header followed by its payload:
| Field | Type | Constraint |
|---|---|---|
| Delay | uint16 |
0 static; at least 100 ms animated |
| X, Y | two uint16 |
Changed-rectangle origin |
| Width, height | two uint16 |
Non-zero rectangle inside 160×80 |
| Encoding | uint8 |
0 raw, 1 RLE |
| Payload length | uint32 |
Exact following byte count |
Frame zero covers the full canvas. Subsequent animated records use the
smallest changed rectangle from already-composited frames. Raw encoding is
row-major little-endian RGB565 and has exactly width * height * 2 bytes. RLE
is a sequence of (runLength:uint16, pixel:uint16) pairs with non-zero runs
that cover the rectangle exactly. Choose the smaller encoding per frame.
The final four bytes are the little-endian IEEE CRC32 of every preceding byte. The sum of frame delays equals the header duration. This is a valid all-black static fixture:
424d4131a00050000100000000000000000000000000a0005000010400000000320000c7ec2d1f
Storage and naming
Assets live under /display/assets/; staging data uses /display/.upload/.
Filenames are case-insensitively unique and use a .bma extension. The ASCII
basename is 1–48 bytes and may contain letters, numbers, spaces, _, and -.
The library contains at most 32 valid files.
The device stores only display-enabled state and the three assignments in NVS. Missing SD, missing/corrupt media, and allocation failure always fall back to the compiled Biscuit logo.