Badge Explorer

Full-duplex Web Bluetooth console for the DZBJ- LED badge — push images, listen for anything coming back, and sweep the protocol surface.
← the badge connector on cartercole.net
Checking Web Bluetooth support…
disconnected
never connected
On connect, immediately run: the link to this badge does not stay up long enough to click through by hand — arm the work here and Connect will do it in one unattended pass, re-running after every auto-reconnect.
idle
Advertised— Internal name— MAC (ADD)—
Firmware— Storage— time_mode—
Write / Notify— Screen— Packets tx/rx0 / 0

Image → badge

Everything is rasterised to a 368×368 JPEG on this side. The panel is round, so the corners of the square get cropped off by the bezel — the preview shows what survives.

no frame
Storage is additive and there is no delete command over BLE — every send appends a frame to the badge's rotation and the space is never returned. Watch freespace above.

Device settings — type 13

Latent in the stock app (defined, never called). Ranges unconfirmed; the badge answers on this channel, so these are the best guesses from the builder's signature.

Rename changes the internal label only — the advertised BLE name is fixed in firmware and survives a power-cycle.

Inbound capture

The stock app throws away every notification that is not a type-13 status JSON (if(!i || 13!=i.type) return;) — so if the firmware ever does emit anything else, nobody has ever looked at it. This pane keeps every single byte the badge sends, reassembles multi-fragment 0xA0 frames, and scans the running stream for JPEG and IMB signatures.

idle 0 frames · 0 bytes

Read-request candidates

No read-back path is known — the protocol as shipped is write-only, and an earlier sweep found no delete either. These are the plausible shapes for "send me something". Each line is fired as its own packet, then the app waits and keeps whatever arrives. One line per request: type | json-or-hex. Lines starting with # are skipped.

Recovered images

Anything in the inbound stream that parses as a JPEG (SOI…EOI) or an IMB block shows up here. Empty is the expected result — it would be a genuine finding if it is not.

nothing recovered yet

Frame-by-frame

tbytesdecoded

Opcode sweep

Fires a bare {"type":N} at each opcode in range and records what comes back plus any freespace movement. Known map: 1 activation, 2 OTA, 3 boot, 4 dial, 5 animation, 6 album, 7 version, 8 activation-time, 9 lyrics, 12 marquee, 13 settings/status, 14 device-id.

typefree KB answered payload back

Key sweep

Holds the opcode fixed and varies one JSON key, so you can hunt for a verb the firmware recognises without re-walking the whole opcode space.

payloadfree KB repliespayload back

Session report

Everything this session learned, as one block of text — paste it back and it can be folded into badge-protocol.md.

Raw packet composer

Builds one framed packet: C0 | type | total(2) | remaining(2) | len(2) | payload | checksum. Fragmentation kicks in automatically past the chunk size.

on-wire preview:
—

Quick fire

GATT map

Walks every service Chrome will hand over and reads every readable characteristic. Web Bluetooth only exposes services declared up front, so this is a probe list, not a true scan — a service absent here might still exist on the chip.

—

Advertised identities

This badge has been seen advertising as both DZBJ-QZ001 and DZBJ-TV07(BLE) while reporting one MAC. Chrome's device.id is a per-origin hash of the Bluetooth address, so the question is whether the two names share an id. Same id → one address, a pure rename. Different ids → two advertising identities, which would also explain the Unsupported device reconnect failures. Every connect is recorded here automatically and the table survives reloads.

not scanning
advertised namedevice.idseen lastrssi

Log