# LEDDMX-03 / family protocol — full opcode map

Extracted from `com.ledlamp` v1.x via `baksmali 2.5.2` of `classes.dex` and `classes2.dex`. Source class: `Lcom/home/net/NetConnectBle;`. Lines refer to the unzipped smali file before any method restructuring.

## Frame structure

All commands are 9-byte frames wrapped in family `7B...BF` (LEDDMX/LEDBLE/LEDCAR/LEDPHO) or `7E...EF` (LEDLIGHT/LEDSTAGE) or `70...0F` (LEDLIKE) or `7A...AF` (LEDSUN) or `7D...DF` (LEDSMART). The middle 7 bytes encode the opcode + parameters.

Family prefix is determined by the BLE name of the device (e.g. `LEDDMX-03-5A5B` → family `7B`, suffix `BF`).

## Method → opcode matrix

| Method (smali) | LEDDMX / LEDBLE / LEDPHO | LEDSUN | LEDLIKE | LEDSMART | LEDLIGHT / LEDSTAGE |
|---|---|---|---|---|---|
| **turnOn** | `7B 04 04 01 FF FF FF FF BF` | `7A 01 01 FF FF FF FF FF AF` | `70 01 01 FF FF FF FF FF 0F` | `7D 01 01 01 FF FF FF FF DF` | `7E FF 04 01 FF FF FF FF EF` |
| **turnOff** | `7B 04 04 00 FF FF FF FF BF` | (same with `01`→`00`) | (same) | (same) | (same with `01`→`00`) |
| **setRgb(R,G,B)** | `7B FF 07 R G B FF FF BF` | n/a (single color) | n/a | n/a | `7E FF 05 03 R G B FF EF` |
| **setBrightness(L)** | `7B 01 L 01 FF FF FF FF BF` | n/a | n/a | n/a | `7E 04 02 L FF FF FF FF FF EF` |
| **setMode(M)** | `7B FF 13 M FF FF FF FF BF` | (single mode) | (single mode) | (single mode) | `7E 04 07 M FF FF FF FF FF EF` |
| **setSubarea(N)** | `7B FF 14 N FF FF FF FF BF` | n/a | n/a | n/a | (uses 4-zone model, different) |
| **setZoneRgb(R,G,B,Z)** | `7B 07 R G B Z FF FF BF` | n/a | n/a | n/a | (4-byte payload, different) |

## Notes by opcode

- **04 04 01/00** — `turnOn/turnOff` for 7B family. The trailing `01`/`00` is the on/off flag; the `FF FF FF FF` in between is padding. Some LEDDMX-01/04 variants put the on/off flag in position [2] instead of [3]; the `if-eqz p3` branch in smali distinguishes them.
- **04 04 01 00** — for `7E` family. Same flag layout.
- **FF 07 R G B FF FF** — LEDDMX setRgb: opcode `07`, then RGB, then 2 padding FF. Do NOT use 4 padding FF here (some attempts with `7B 07 R G B FF FF FF FF BF` were wrong).
- **01 L 01** — setBrightness for LEDDMX (9 bytes total). Earlier reverse-engineering guessed `FF 01 01 L FF FF FF FF BF` (10 bytes) — that was WRONG and the slider was a no-op until fixed. Correct smali (line ~4330-4355, default `setBrightness` LEDDMX-03 branch): `const/16 v5, 0x7b; const/4 v6, 0x1; move v7, v0; ... filled-new-array/range {v5..v13}, [I` → registers v5..v13 = `[0x7B, 0x01, v0=level, flag, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF]`. The `flag` byte is set to 1 for an explicit set, 0 for an incremental set. For LEDDMX-04 the smali scales L as `v0*32/100` (effectively a /3.125 scale).
- **FF 13 M** — setMode for LEDDMX. M is 1-128. The `FF FF FF` in the middle is padding, not a parameter.
- **FF 14 N** — setSubarea for LEDDMX. N = number of sub-zones (typically 2-8). Must be called once before `setZoneRgb` to be effective.
- **07 R G B Z** — setZoneRgb for LEDDMX. Z = 0-indexed zone number (0 to N-1). The single `FF FF` is padding, then family suffix `BF`.

## Model-family name prefixes (from `com.ledlamp`'s string constants)

```
LEDBLE       → 7B...BF (LEDBLE protocol)
LEDPHO       → 7B...BF (LEDPHO protocol)
LEDCAR-00-   → 7B...BF
LEDCAR-01-   → 7B...BF
LEDCAR-02-   → 7B...BF
LEDDMX       → 7B...BF
LEDDMX-01-   → 7B...BF
LEDDMX-02-   → 7B...BF
LEDDMX-03-   → 7B...BF
LEDDMX-04-   → 7B...BF
LEDSUN       → 7A...AF
LEDSMART     → 7D...DF
LEDLIKE      → 70...0F
LEDLIGHT     → 7E...EF
LEDSTAGE     → 7E...EF
```

Note that several family names share the `7B...BF` wrapper but have different opcodes for the same operation. The class `NetConnectBle` has an outer `if/else` chain matching the name prefix, and inside each branch the `filled-new-array` defines the family-specific bytes.

## Smali → byte array decoding recipe

When a smali method shows:

```smali
const/16 v13, 0xbf
const/16 v5, 0x7b
const/16 v6, 0xff
const/4 v7, 0x1
const/16 v11, 0xff
const/16 v12, 0xff
move v9, v0          ; v0 holds the brightness param
move/from16 v10, p5
filled-new-array/range {v5 .. v13}, [I
move-result-object v0
```

The byte array is `[v5, v6, v7, ..., v13]` = `[0x7B, 0xFF, 0x01, 0x01, v0=param, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF]` — setBrightness with the level coming from v0 (a method param).

For RGB methods, the registers `v3, v4, v5` (or `p1, p2, p3`) hold the three channel values and are placed via `move v6, p1; move v7, p2; move v8, p3` (or similar). The byte order in the array is always R, G, B.

## Cross-family helper pattern (in code, not smali)

```java
String m = detectModel(name);  // "LEDDMX-03", "LEDBLE", etc.
if (m.startsWith("LEDDMX") || m.startsWith("LEDBLE") || m.startsWith("LEDPHO") || m.startsWith("LEDCAR")) {
    // 7B...BF
} else if (m.startsWith("LEDSUN")) {
    // 7A...AF
} else if (m.startsWith("LEDSMART")) {
    // 7D...DF
} else if (m.startsWith("LEDLIKE")) {
    // 70...0F
} else {  // LEDLIGHT, LEDSTAGE, default
    // 7E...EF
}
```

## Verified working commands (end-to-end tested with Chicho's LEDDMX-03-5A5B)

| Command | Bytes sent | Tira response |
|---|---|---|
| `cmdPower(true)` | `7B 04 04 01 FF FF FF FF BF` | Turns on |
| `cmdColor(0xFF, 0, 0)` | `7B FF 07 FF 00 00 FF FF BF` | Solid red |
| `cmdColor(0x74, 0xAC, 0xDF)` | `7B FF 07 74 AC DF FF FF BF` | Solid Argentine celeste |
| `cmdBrightLevel(100)` | `7B 01 64 01 FF FF FF FF BF` | Full brightness |
| `cmdBrightLevel(10)` | `7B 01 0A 01 FF FF FF FF BF` | Dim |
| `cmdZoneRgb(R, G, B, 0)` | `7B 07 R G B 00 FF FF BF` | (in flag mode) Zone 0 of 6 — **opcode confirmed, but see subarea caveat below** |
| `cmdSubarea(6)` | `7B FF 14 06 FF FF FF FF BF` | Opcode 14 is in the smali but **did NOT paint zones** on Chicho's LEDDMX-03-5A5B. The strip stayed on the last solid color. The banderazo fallback uses a 0.5s color-alternating loop instead (see SKILL.md). |

## Known-not-working on LEDDMX-03

- `cmdEffect(N)` — `7B FF 13 M FF FF FF FF BF` — Chicho says "los fx no andan". Possible reasons: the mode index in the actual app is not 1-128 but a sub-table per strip. Try empirical: `M = 1, 2, 3, 4, 5, 6, 16, 32, 64, 100, 128`. Or the app sends a second `setMode` immediately after the first with a different value.
- Brightness via the `+`/`-` toggle (without slider) was not reliable. The slider (0-100) works directly.

## Reverse-engineering log: how we got here

1. First attempt (failed): guessed bytes from web search results. PWA used `7B 04 04 01 00 00 00 00 BF` (wrong tail) and `7E FF 04 01 00 FF FF 00 EF` (mixed families).
2. Second attempt (partial): Chicho ran the reference app while I tried to capture BLE. `btsnoop_hci.log` not accessible on stock Pixel 9a — no `sudo`, no `su`, no `btmon`. Vision was down too.
3. Third attempt (worked): `adb shell pm path com.ledlamp` → got the APK path → `adb shell cat <path> > local.apk` → unzipped → baksmali → read `NetConnectBle.smali` directly. Found the actual `filled-new-array` register values. Built the controller from those.
