{
  "$schema": "brototype.ledgrid.handoff.v1",
  "version": "1.1.0",
  "updated": "2026-04-21",
  "changelog": {
    "1.1.0": "waveloop_pedal: add CMD 0x02 SEVENSEG (digits + DP), CMD 0x03 PIXEL (RGB), CMD 0x04 BRIGHT (per-target), CMD 0x05 SEG_RAW (raw 7-seg bits).",
    "1.0.0": "initial brototype_ledgrid spec: matrix-only over 0xA5 [len] [data]."
  },
  "title": "brototype LED grid — serial ↔ MAX7219 bridge",
  "description": "Host paints an 8-row framebuffer and streams bytes over USB CDC; the Arduino mirrors them onto one or more daisy-chained MAX7219 8×8 panels. Used as a headless status display or generic LED output.",
  "device": {
    "mcu": "ATmega32U4 (SparkFun Pro Micro / Arduino Micro clone)",
    "usb_class": "CDC (virtual serial)",
    "port_hint": {
      "linux": "/dev/ttyACM*",
      "macos": "/dev/cu.usbmodem*",
      "windows": "COM*"
    },
    "baud": 115200,
    "firmware_source": "gitlab.com:elijahlucian/arduino — brototype_ledgrid/brototype_ledgrid.ino"
  },
  "wiring": {
    "MAX7219.VCC": "Pro Micro VCC (5V)",
    "MAX7219.GND": "Pro Micro GND",
    "MAX7219.DIN": "Pro Micro D16 (MOSI)",
    "MAX7219.CLK": "Pro Micro D15 (SCK)",
    "MAX7219.CS":  "Pro Micro D10",
    "chain": "DOUT of panel N → DIN of panel N+1; CS and CLK shared across the chain",
    "power_budget_note": "Single panel at full white ≈ 480mA. Keep intensity ≤ 4/15 when USB-powered; use external 5V + common GND for >2 panels."
  },
  "protocol": {
    "direction": "host → device, one-way",
    "frame": "[0xA5] [N] [N bytes]",
    "sync_byte": "0xA5",
    "length": {
      "type": "uint8",
      "min": 1,
      "max": 64,
      "invariant": "N must equal panels × 8"
    },
    "byte_order": "panel_0 row_0, panel_0 row_1, ..., panel_0 row_7, panel_1 row_0, ..., panel_K-1 row_7",
    "pixel_bit_order": "within a row byte, bit 7 (MSB) = leftmost column on the display",
    "recommended_refresh_hz": 30,
    "max_refresh_hz": 100,
    "framing_loss_behavior": "any byte that isn't 0xA5 while in WAIT_SYNC is silently dropped; length byte of 0 or > 64 drops back to WAIT_SYNC"
  },
  "device_echo": {
    "format": "RX <N>: <hex> <hex> ...\\n",
    "example": "RX 8: 3C 42 42 3C 42 42 42 3C",
    "purpose": "optional verification for the host; safe to ignore"
  },
  "device_behavior": {
    "boot_self_test": "all LEDs on for ~300ms then off, on power-up or reset",
    "rx_led_pin_17": "active-LOW. Slow pulse every 2s while idle (heartbeat); 120ms flash per received frame.",
    "panel_auto_detect": "firmware re-inits MAX7219 chain length automatically whenever an incoming frame length implies a different panel count"
  },
  "example_frames": [
    {
      "name": "clear one panel",
      "hex": "A5 08 00 00 00 00 00 00 00 00"
    },
    {
      "name": "all on, one panel",
      "hex": "A5 08 FF FF FF FF FF FF FF FF"
    },
    {
      "name": "two panels, all on",
      "hex": "A5 10 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF"
    }
  ],
  "pi_integration_recipe": {
    "open_flags": "O_RDWR | O_NOCTTY | O_NONBLOCK",
    "termios": "cfmakeraw; cfsetspeed 115200; 8N1; VMIN=0 VTIME=0",
    "cli_flag_suggestion": "-L /dev/ttyACM0",
    "write_cadence": "~30Hz. Coalesce rapid state changes into a single frame per tick.",
    "thread_placement": "dedicated low-priority thread; must not stall audio thread. Read only atomics; do not take audio locks.",
    "framing_c_pseudocode": [
      "uint8_t out[2 + N];",
      "out[0] = 0xA5;",
      "out[1] = (uint8_t)N;",
      "memcpy(out + 2, frame, N);",
      "write(fd, out, 2 + N);"
    ]
  },
  "prototype_web_ui": {
    "url": "https://brototype.lucianlabs.ca/ledgrid.html",
    "capabilities": [
      "click-toggle / drag-paint a 8×8, 16×8, or 32×8 grid",
      "auto-sends frames over Web Serial (Chrome/Edge) on any change",
      "scripting sandbox: lg.draw(fn), lg.loop(fn, intervalMs), lg.shift(dir), lg.set(r,c,v), lg.fill(), lg.clear(), lg.invert()",
      "per-session state persists in localStorage"
    ],
    "use_case": "use the web UI to validate frame content and protocol handshake before wiring in the host code. Anything that renders correctly in the web sandbox will render identically on hardware."
  }
}
