⠿ iPixelLab← Back to controls

PROTOTYPE 01 / ENGINEERING NOTES

Small app. Open protocol.

Protocol correction: APK BaseSend.deleteAllData sends 04 00 03 80. This is erase-all, not default display. The previous interpretation was wrong. The controller blocks it in ordinary command batches and removes it from every media/playback action. A separate Erase all action requires typed confirmation tied to the selected panel session. Earlier versions could erase stored content during these actions. Re-upload desired media using the corrected controller. GIF upload and restart persistence are user-verified on 32×32; slot recall after clock mode remains unverified.

iPixel Lab is a buildless HTML, CSS, and JavaScript application. The browser sends commands directly to the nearby LED panel through Web Bluetooth. There is no application backend, vendor account, analytics script, or upload of your Bluetooth log.

Open and connect

Use Chrome on Android, Chrome/Edge on Windows, or Chrome/Chromium on Linux with Web Bluetooth enabled. Open the HTTPS page directly, rather than inside another app’s embedded preview. Power on the panel, close iPixel Color, click Connect panel, then select the panel in the browser’s chooser. The first connection synchronizes local time and reads device information; it does not change brightness, power, or the selected display mode.

For local development, run the static file host from the browser directory:

node serve.mjs
# Open http://localhost:8080

This server only serves files. It does not access Bluetooth or process commands. A phone’s localhost is the phone itself; use the hosted HTTPS page for Android testing.

What this prototype does

Connect/disconnect, local time synchronization, device-information queries, display power, brightness, and nine APK-referenced clock-style choices. Available faces vary with firmware. The canvas is an illustrative local preview, not a screenshot or live readback from your panel. Demo mode sends nothing over Bluetooth and marks all replies as simulated.

Device writes and panel replies are logged separately. A matching reply means a response arrived, not that the requested image or setting has been visually verified. If a write has no reply, the interface labels it unconfirmed. Save the connection journal after a failed hardware test to help diagnose it.

Protocol references

Capture-backed choices

The supplied capture contained 916 writes reassembled into 272 complete iPixel commands. It exposes service 0x00FA, command characteristic FA02, and notification characteristic FA03. The chooser requests access to services 0x00FA and 0xFA01 for firmware variations. Handles are discovered rather than hardcoded.

Commands use a two-byte little-endian total length, a two-byte little-endian command ID, then payload bytes. The clock builder was checked against the captured 0B 00 06 01 05 00 01 1A 0A 19 07 packet. Writes are serialized and conservatively chunked into 20-byte values. GIF validation, resizing, animated preview and upload are implemented. Native text now supports the nine panel effects; custom firmware remains a separate research project.

Device sizes and APK references

The geometry pipeline uses explicit width and height. Model 0x81 maps to the owned 32×32 panel; the documented model 0x80 maps to 64×64. The larger panel has not been hardware-tested. Clock faces, fonts, storage capacity and firmware behavior are separate profile properties.

The user-provided iPixel Color 3.7.9 APK supplies separate nine-face artwork lists for the two square sizes. The controller includes these attributed static layout references, with sample times, rather than substituting a generic centered clock. The images do not animate, display the real panel time or reflect every date/time option. They load from this static host; no vendor service is contacted.

Single-slot playback and ordered playlists use the captured program-selection command after exiting temporary DIY, without the misidentified erase-all command. Drafts and labels stay in this browser; they neither upload content nor confirm that a slot is populated. Temporary editor drawings are not saved to these slots. An accepted playback reply still needs a physical display check. Custom date/time uses the browser’s local zone. Automatic sync pauses when hidden and stops on disconnect.

Temporary DIY drawing

The pixel editor supports pencil, erase, flood fill, a color picker, undo/redo and local exact-size PNG import/export. Edits remain local until Send or opt-in live drawing. Sending enters temporary DIY mode and clears the current display; Exit requests the previous content without saving a slot.

Further static analysis of the supplied APK resolves drawing payloads as effect, RGB and repeated column/row pairs, without an alpha byte. Transparency is composited onto black. Mode entry/exit replies are captured; pixel encoding remains APK-derived and needs a physical corner/RGB test. Live updates keep one in-flight snapshot and one replaceable pending snapshot, and stop on other panel actions or disconnect.

Still-image uploads

PNG, JPEG and still WebP inputs are decoded locally, fitted to the identified panel dimensions, transformed and composited on the chosen background. The exact opaque framebuffer is shown and exported locally as an 8-bit RGB PNG; uploads send raw RGB triplets from that same framebuffer. Client input and upload limits are resource guards, not measured firmware capacities.

Opcode 0x0002/mode 0 carries raw RGB pixels, with total raw-data length/CRC32, destination and 0/2 window markers. The APK bitmap producer and independent raw transmitters establish this distinction. The initial browser version incorrectly sent a PNG container; the panel acknowledged it but did not render it. That payload assumption is corrected, with exact byte-count and pixel-order regression tests. Temporary uploads use address 101. Program-address uploads require an explicit replacement choice, declare a one-slot program and add a local bookmark only after file-complete acknowledgement. The user confirmed corrected temporary raw-image display on the owned 32×32 panel. Capacity and program retention after a power cycle remain unverified.

GIF uploads and limits

GIF87a/89a validation, animated preview, local resizing and prepared-file download use a pinned MIT-licensed omggif codec, bundled with its license and provenance notes. Compatible files at 1× speed retain their original bytes. Resized files are composited onto black and encoded locally; the preview decodes that output. No GIF editor is included. Playback speed changes the prepared file’s frame delays (0.25×–4×); upload again to change the panel speed. Delays use 10 ms units with a 20 ms client minimum and reported bounds. The GIF89a specification defines these delay fields.

The APK sender uses 12 KiB windows for the square models and handles an insufficient-space reply, but the inspected paths expose no numeric total-file maximum. The largest original captured GIF is 200,183 bytes and 250 frames, an observed size rather than maximum capacity. The adjustable 512 KiB default and input/frame/memory limits are client guards. The user confirms browser GIF playback and restart persistence on 32×32. Adjusted timing and independent slot recall after clock mode remain unverified.

The second Android capture confirms program slots 1/2 and cached GIF completion after the first window. Remaining cached-file windows are skipped only for a locally CRC-verified, contiguous matching GIF group. The capture contains no clock transition; selection-only recall after a clock remains unverified.

Static text and explicit erase

Static text uses the raw RGB image path with an original 3×5 bitmap font and included MIT license. Glyph metadata is versioned for Python parity. Preview/export/upload share exact pixels; unsupported characters and clipping are reported. Generated scrolling GIFs and a separate native panel-text mode are available. Native previews are labeled static glyph samples.

Erase all is a separate operation: the page names the selected panel and requires typing ERASE ALL. Normal commands continue rejecting opcode 0x8003. Confirmation expires with a session change; accepted acknowledgement clears only that panel’s local slot list. Missing/rejected replies retain local records and never trigger an automatic retry. The user confirms explicit erase and static text work on the owned 32×32 panel; other firmware remains unverified. All 150 JavaScript tests and the desktop/mobile Chrome checks passed; development exercised simulated erase only.

Remembered connections and editor updates

The controller can reuse a previously permitted device using feature-detected getDevices(), offering a one-click shortcut and one optional automatic attempt on page open. It initializes time/info only and never replays uploads, erase or failed actions. Unsupported or revoked permissions use the explicit browser chooser. Support and persistent permissions vary; see the Web Bluetooth implementation status. Chrome may need Experimental Web Platform features enabled. This reconnects GATT rather than repairing an OS bond; the user now confirms reconnect works in their browser; other platform combinations remain unverified.

Text supports 1.5× and custom fractional scaling with reported whole-pixel glyph dimensions. The editor has a sized square brush/eraser and an in-app color dialog with drawing sampling. Prepared images copy into undoable editor drafts and back without transmitting. The user confirms erase-all and static text on the owned 32×32 panel. All 168 JavaScript tests and desktop/mobile Chrome checks pass for this increment; software checks do not establish native reconnection or 64×64 hardware behavior.

Flood fill

Fill matches a connected four-neighbor region against the original seed color. The editor now compares RGB composited on black, so erased/transparent pixels match opaque black. A tolerance control defaults to 12 (0 for exact matching) to include small source-image color differences. Region membership is computed from the original frame, does not chain through gradients, and preserves barriers/disconnected areas. The UI reports changed/matched counts and Undo restores original RGBA pixels. Tests cover imported near-black noise, erase/draw histories and 32×32/64×64 bounds. Native Chrome verifies a 512-pixel fill with exact Undo restoration. The original reported draft remains a user acceptance check.

Native text effects

The user-supplied APK and two effect captures establish native opcode 0x0100, a base text header and monochrome glyph records. Normal Text effects are Fixed, Left, Right, Up, Down, Blink, Breathe, Snowflake and Laser. Picture Text animations use a separate GIF generator. The browser reproduces all 82 captured native commands byte-for-byte and uses its own MIT bitmap font in fixed cells: 16/32 heights for identified 0x81, plus reference-derived 48/64 for 0x80. Native speed is the app's 0–100 value, not pixels/second. Native previews/PNG downloads show glyph samples; firmware layout and effect motion are not rendered. Physical native behavior, broader color modes and 64×64 hardware remain unverified. All 190 JavaScript tests pass; Chrome confirms the demo TX packet against an independent Python golden and matching sample PNG pixels.

Generated animations and Picture Text

Text includes eight locally generated GIF presets: zoom in/out, spin, side reveal, drop-in, stretch-in, top reveal and character printing. Frame delay and per-page holds are encoded in the file, and the preview plays the actual decoded output. Geometry is adapted from the supplied APK to the bundled bitmap font; Android fonts/antialiasing, gradients and vertical-text variants are not reproduced.

Picture Text snapshots up to six prepared native-text/image/GIF items into one ordered program. The app editor limit is six content items plus an Add placeholder, not a demonstrated firmware maximum. Upload requires explicit consent for the full consecutive slot range and never sends erase-all. Projects download/reopen locally as portable JSON with prepared media; drafts are not autosaved. Selected-item previews do not simulate full firmware program timing, and native effects remain glyph samples.

All 228 JavaScript tests pass. Chrome checks real workers, native GIF frame parity, project reopening and six demo media uploads with matching data, correct addresses, one program declaration and no erase. Independent Pillow verifies 384 frames plus timing/loop data across both square sizes. Physical mixed playback/order/timing/retention and 64×64 hardware remain acceptance checks.

Validation boundary

Packet builders and transport behavior can be checked without a panel. Live browser connectivity and physical display behavior must be tested with the actual device. Closing the page disconnects this controller; it does not provide background automation.

Independent prototype. Not affiliated with VAETTLO, iPixel Color, or their vendors.