Add pattern chaining support to XBLK format

Pattern entry bytes 108-109 now hold next_pattern (0xFF=loop forever,
0-8=chain to index) and loop_count. MCU will poll LP5562 STATUS register
to detect engine completion and load the successor pattern.

Updated XBLK format spec, Rust Pattern struct (serializer + deserializer),
and React Native app design doc (binary format, data model, resolved
open questions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-05 15:52:33 -08:00
parent a32c242859
commit 2fade7ad8b
3 changed files with 319 additions and 1 deletions

View File

@@ -42,13 +42,27 @@ Offset Size Field
42 32 Engine 2 commands
74 2 Engine 3 command count
76 32 Engine 3 commands
108 4 Reserved
108 1 Next pattern index (0xFF = loop forever, 0-8 = chain to that index)
109 1 Loop count (how many full cycles before chaining, 0 = use next_pattern as-is)
110 2 Reserved
```
Max capacity: `(1024 - 16) / 112 = 9` patterns.
The 16-command-per-engine limit is a hardware constraint of the LP5562 (48 bytes engine SRAM). The `branch` command enables infinite looping, so pattern duration is unlimited.
### Pattern Chaining
A pattern can specify a successor via `next_pattern_index`. When set to anything other than 0xFF, the pattern's engine programs should use finite-count `branch(0, N)` instead of `branch(0, 0)` (infinite loop). When all engines stop (detected by polling LP5562 STATUS register 0x0C for engine interrupt bits), the MCU loads the next pattern.
The LP5562 has no interrupt output pin — the MCU must poll STATUS periodically. During the idle loop, a poll every ~500ms is sufficient and costs negligible power vs. the LED current.
Chain examples:
- `next=0xFF`: Loop forever (default, current behavior)
- `next=1, loops=0`: Play once, then switch to pattern 1
- `next=0, loops=3`: Play 3 full cycles, then switch to pattern 0 (can create A→B→A→B sequences)
- Circular chains (A→B→A) create alternating pattern sequences
## Boot Sequence
1. Init LP5562 (enable, clock, current)

View File

@@ -0,0 +1,284 @@
# xblink LED Pattern Editor — React Native App Design
**Date**: 2026-03-05
**Status**: Draft — to be refined as firmware milestones (M2M6) 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**: 50ms14s 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 | 163 | Per-step, multiplied by prescale |
| SRAM | 256 bytes (64x4B blocks) | Single-packet transfer for most patterns |
| EEPROM (patterns) | ~1024 bytes (blocks 256511) | ~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 (0255)
- **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 0255 → opacity 01)
- 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?)