# xblink LED Pattern Editor — React Native App Design **Date**: 2026-03-05 **Status**: Draft — to be refined as firmware milestones (M2–M6) progress ## Context The xblink implant needs a companion phone app for creating and transferring LED animation patterns. The hardware constraints are tight: LP5562 has 3 engines x 16 steps each, NTAG5Link has 256-byte SRAM (transfer buffer) and ~1KB usable EEPROM (after NDEF). This design translates those constraints into an intuitive "hybrid timeline" editor — like a step sequencer for light brightness — with animated preview, preset patterns, and NFC transfer. ## Decisions Made - **UI model**: Hybrid timeline (per-channel, tap to add set/ramp/wait steps) - **Timing range**: 50ms–14s full LP5562 capability - **Transfer**: NTAG5 SRAM as 256-byte mailbox, EEPROM as persistent storage - **Storage**: ~1KB EEPROM for patterns (~8 slots), MCU SRAM as runtime cache - **Magnet toggle**: Tap-to-cycle through patterns (flashlight-style) - **Preview**: Animated RGBW circles in-app, real-time as you edit - **Stack**: Expo + react-native-nfc-manager + react-native-dt-theme + react-native-reanimated ## Hardware Constraints Reference | Constraint | Value | UI Impact | |-----------|-------|-----------| | Engines | 3 (16 steps each) | R/G/B get timelines, W is static-only | | Max user steps/engine | 14 (2 reserved for branch+end) | Step counter per channel | | Increment max | 127 PWM steps/ramp | Large ramps consume 2 engine slots | | Prescale | Fast 0.49ms, Slow 15.6ms | Per-step selector in editor | | Step time | 1–63 | Per-step, multiplied by prescale | | SRAM | 256 bytes (64x4B blocks) | Single-packet transfer for most patterns | | EEPROM (patterns) | ~1024 bytes (blocks 256–511) | ~8 patterns at 122B each | | Pattern size | 112B fixed-size entry (3x34B engines + 6B header + 4B chain/reserved) | Fits in one SRAM packet | | White channel | Direct I2C PWM only, no engine | Static brightness slider, no timeline | ## App Screens ### 1. Pattern Library (Home) Ordered list of up to 8 patterns. Order = magnet cycle order. ``` PATTERN LIBRARY [+ Add] "Tap magnet to cycle" [drag] 1. Breathing [mini-preview] R:wave G:wave B:-- W:50% Duration: 2.4s | 6/14 steps [drag] 2. Heartbeat [mini-preview] R:pulse G:-- B:-- W:0% Duration: 1.0s | 10/14 steps [drag] 3. (empty slot) Memory: 244/1024 bytes [========= Sync to Implant =========] ``` - **Cards**: DTCard per pattern, drag handle for reorder, swipe-left to delete - **Add button**: "Create New" or "From Preset" bottom sheet. Disabled at 8 patterns - **Memory bar**: DTProgressBar, warning at 80%, error at 95% - **Sync button**: Opens NFC transfer flow - **Tap card**: Opens pattern editor ### 2. Pattern Editor The core UI. 3 engine timelines (R, G, B) + static W slider + animated preview. ``` [< Back] Breathing [Save] PREVIEW (R) (G) (B) (W) (MIXED) [> Play] [1x] [loop: on] COMBINED COLOR STRIP |====gradient showing mixed RGBW over time====| 0ms 2400ms R 6/14 steps |[SET 0]|===RAMP UP===|===RAMP DN===|[BR->0]| G 6/14 steps |[SET 0]|===RAMP UP===|===RAMP DN===|[BR->0]| B 0/14 steps (tap to add steps) W Static: [=======|===] 128 (no engine — direct PWM only) ``` **Timeline blocks**: - **Set PWM**: Narrow bar, shows brightness value (0–255) - **Ramp**: Wide bar proportional to duration, sloped fill (up/down) - **Wait**: Wide bar, flat, hatched fill - **Branch**: Arrow icon pointing to target step, shows loop count or infinity **Interactions**: - Tap empty space → Add Step sheet (Set / Ramp / Wait) - Tap existing step → Step Editor modal - Long press → Delete confirmation - Step counter: DTChip, warning at 12+, error at 14 **Step Editor Modal**: ``` Edit Step: Ramp Up [Delete] Type: [Set PWM] [Ramp] [Wait] Target Brightness: [====|===] 200 (0-255) Prescale: [Fast 0.49ms] [Slow 15.6ms] Step Time: [--] 32 [++] (1-63) Duration: 502ms Note: This ramp uses 2 engine slots (delta > 127) [Cancel] [Apply] ``` - For ramps, user sets absolute target brightness; app calculates increment + direction - If delta > 127, show warning and deduct 2 from step budget - Duration displayed as calculated value, updates live **White channel**: Single slider. No timeline. Label explains constraint. **Preview area**: - 5 circles: R, G, B, W (individual) + MIXED (additive blend) - Animated via react-native-reanimated (PWM 0–255 → opacity 0–1) - Play/pause, speed selector (0.25x/0.5x/1x/2x), loop toggle - Playback cursor synced across all timelines ### 3. Preset Browser Built-in patterns users can add to their library: | Preset | Engines Used | Steps/Engine | Duration | Description | |--------|-------------|-------------|----------|-------------| | Breathing | 1 (all RGB) | 6 | ~4.0s | Slow ramp 0→255→0, all channels | | Heartbeat | 1 (R only) | 10 | ~1.0s | Double pulse, red | | Color Cycle | 3 (R+G+B) | 7 each | ~3.0s | Sequential fade with trigger sync | | Strobe | 1 (all RGB) | 5 | ~100ms | Fast on/off, 10Hz | | Candle | 2 (R+G) | 8 each | ~200ms | Warm flicker, pseudo-random ramps | | Solid Color | 0 (all direct) | 0 | N/A | Static RGBW, color picker UI | Each preset card shows animated preview circles. ### 4. NFC Transfer Flow 4-phase modal: 1. **Pre-flight**: List patterns to sync, total bytes, confirm 2. **Hold**: Pulsing NFC icon, "Hold phone against implant" 3. **Transferring**: Progress bar, per-pattern status, "DO NOT MOVE" 4. **Complete/Error**: Success with magnet-cycle reminder, or retry ## Data Model ```typescript interface PatternLibrary { version: 1; patterns: Pattern[]; // Ordered, index = magnet cycle position globalCurrent: number; // LED current 0-255 } interface Pattern { id: string; name: string; // Max 32 chars presetId: string | null; engines: [EngineProgram, EngineProgram, EngineProgram]; channelAssignment: { red: EngineRef; green: EngineRef; blue: EngineRef; white: 'direct' }; whiteLevel: number; // 0-255 nextPattern: number; // 0xFF = loop forever, 0-8 = chain to index loopCount: number; // Cycles before chaining (0 = chain on stop) } interface EngineProgram { steps: EngineStep[]; // Max 14 user steps } type EngineStep = | { type: 'set_pwm'; brightness: number } | { type: 'ramp'; targetBrightness: number; prescale: 'fast' | 'slow'; stepTime: number } | { type: 'wait'; prescale: 'fast' | 'slow'; stepTime: number }; type EngineRef = 'engine1' | 'engine2' | 'engine3' | 'direct'; ``` **Step-to-LP5562 mapping**: - `set_pwm` → `EngineCommand::set_pwm(value)` — 1 slot - `ramp` → compute delta from current PWM; if <= 127: 1 `ramp_wait` (1 slot), if > 127: 2 commands (2 slots) - `wait` → `EngineCommand::wait(prescale, stepTime)` — 1 slot - Auto-appended: `branch(0, 0)` for infinite loop, or `branch(0, N)` for chained patterns — 1 reserved slot ## SRAM Transfer Protocol **Packet format** (256 bytes max): ``` Byte Field 0 command (0x01=SYNC_START, 0x02=PATTERN_DATA, 0x03=SYNC_END) 1 sequence number (0-255) 2 flags (bit0=ACK_REQ, bit1=LAST_CHUNK, bit2=ERASE_FIRST) 3 total_patterns 4 pattern_index 5 chunk_index 6 chunk_count 7 reserved 8-9 payload_length (u16 LE) 10-11 crc16 (over header + payload) 12-255 payload (up to 244 bytes) ``` A single 3-engine pattern (122 bytes) fits in one packet. Transfer flow: 1. SYNC_START (global settings) 2. PATTERN_DATA x N (one per pattern, usually single-chunk) 3. SYNC_END (MCU commits to EEPROM, activates pattern 0) **MCU response** (SRAM byte 0 = 0xFF): ``` 0: 0xFF (response marker), 1: echo seq, 2: status (0=OK), 3: active_pattern ``` ## Pattern Binary Format Matches firmware spec `docs/plans/2026-03-05-eeprom-pattern-format.md`: ``` Header (16 bytes, blocks 256-259): [0-3] Magic "XBLK" [4] Version 0x01 [5] Pattern count (1-9) [6] Active pattern index (0-based, wraps at count) [7] LED current (0-255, 0.1mA/step) [8] LED mode (0x00=RGBW, 0x01=Mono3) [9-13] Reserved [14-15] CRC-16 over bytes 0-13 + all pattern data Pattern Entry (112 bytes each, fixed-size): [0] Engine count (0-3) [1] LED_MAP register byte [2-5] Direct PWM [B, G, R, W] [6-7] Engine 1 command count (BE) [8-39] Engine 1 commands (16x2 bytes BE) [40-41] Engine 2 command count [42-73] Engine 2 commands [74-75] Engine 3 command count [76-107] Engine 3 commands [108] Next pattern index (0xFF=loop forever, 0-8=chain to that index) [109] Loop count (cycles before chaining, 0=chain on engine stop) [110-111] Reserved ``` Max capacity: 9 patterns in 1024 bytes (upper 1K EEPROM). ## Reusable Code from Sibling Projects | File | What to reuse | |------|--------------| | `dangerous-things-nfc-identifier/src/services/nfc/commands.ts` | `parseApduResponse`, `hexToBytes`, `bytesToHex`, NTAG5 command patterns | | `dangerous-things-nfc-identifier/src/services/nfc/NFCManager.ts` | NFC session management | | `dangerous-things-nfc-identifier/src/services/detection/ntag5sensor.ts` | SRAM read/write, energy harvesting control | | `react-native-dt-theme/src/components/` | DTCard, DTButton, DTChip, DTModal, DTDrawer, DTProgressBar, DTQuantityStepper, DTSwitch, DTTextInput | | `react-native-dt-theme/src/theme/` | Colors, typography, DTThemeProvider | ## Implementation Phases 1. **Project scaffold + data model** — Expo init, types, serialization, presets 2. **Pattern Library screen** — List, reorder, add/delete, memory bar 3. **Timeline Editor** — Channel timelines, step editor modal, W slider, step budget 4. **Animated Preview** — Reanimated circles, color strip, playback controls 5. **Binary Serialization** — XBLK encoder, CRC-16, SRAM packet framing 6. **NFC Transfer** — react-native-nfc-manager, ISO15693, transfer flow UI 7. **Polish** — Undo/redo, export/import, settings, onboarding ## Resolved Questions - **EEPROM layout**: Lower 1K (blocks 0-255) = NFC/NDEF, upper 1K (blocks 256-511) = XBLK pattern library. Decided in M5. - **Magnet cycle index**: Persists in EEPROM header byte [6] (active_pattern_index). Single block write on switch (~5ms). - **Non-looping patterns**: Supported via pattern chaining (next_pattern field). Engine programs use finite `branch(0, N)` instead of infinite `branch(0, 0)`. MCU polls LP5562 STATUS register to detect engine completion, then loads the next pattern. No INT pin on LP5562 — polling only. ## Open Questions - Trigger command exposure in UI (v1 hides it; presets use it internally) - Pattern chaining UI: how to visualize A→B→C chains in the library screen (arrows? numbered sequence?)