This guide helps you decode hex packets directly from the logs without needing external tools.
AA | OP | LL LL | [PAYLOAD...]
│ │ │ └─ Variable data (0-n bytes)
│ │ └─ Length in bytes (big-endian, 2 bytes)
│ └─ Opcode (1 byte)
└─ Start byte (always 0xAA)
01- Keep Alive (PING)02- Change Profile03- Sync Profiles04- Set RGB Key (single)05- Set All RGB Keys06- Lock/Unlock Device07- Hello (protocol-version handshake)
81- Keep Alive Reply (PONG)82- Profile Changed83- Button Pressed84- Key Pressed85- Battery Status86- Device Telemetry (reply to Hello)
aa 01 00 00
│ │ │ └─ Length low byte: 0
│ │ └─ Length high byte: 0 (payload = 0 bytes)
│ └─ Opcode: 01 (Keep Alive)
└─ Start: AA ✓
Meaning: Ping to keep connection alive, no data.
aa 82 00 01 02
│ │ │ │ └─ Payload: 02 = Profile index 2 (0-based)
│ │ │ └─ Length low: 01 (1 byte payload)
│ │ └─ Length high: 00
│ └─ Opcode: 82 (Profile Changed)
└─ Start: AA ✓
Meaning: Device switched to profile 2 (3rd profile, since 0-based).
aa 04 00 05 05 ff 00 00 32
│ │ │ │ │ │ │ │ └─ W: 50 (0x32 = 50 brightness)
│ │ │ │ │ │ │ └─ B: 0
│ │ │ │ │ │ └─ G: 0
│ │ │ │ │ └─ R: 255 (0xFF = 255)
│ │ │ │ └─ Key index: 5
│ │ │ └─ Length low: 05 (5 bytes)
│ │ └─ Length high: 00
│ └─ Opcode: 04 (Set RGB Key)
└─ Start: AA ✓
Meaning: Set key 5 to red (255,0,0) at 50% brightness.
aa 03 00 10 02 01 04 54 65 73 74 02 07 44 65 66 61 75 6c 74
│ │ │ │ │ │ │ └──┴──┴──┴──┘ │ └──┴──┴──┴──┴──┴──┴──┘
│ │ │ │ │ │ │ "Test" (UTF-8) │ "Default" (UTF-8)
│ │ │ │ │ │ └─ Name length: 4 └─ Name length: 7
│ │ │ │ │ └─ Profile 1 index: 1 (1-based)
│ │ │ │ │
│ │ │ │ │ (Profile 2 block starts at the second `02`: index=2, name_len=7)
│ │ │ │ └─ Count: 2 profiles
│ │ │ └─ Length: 16 bytes (0x0010)
│ └─ Opcode: 03 (Sync Profiles)
└─ Start: AA ✓
Meaning: Sync 2 profiles:
- Profile at index 1: "Test"
- Profile at index 2: "Default"
Length math: count(1) + idx(1)+nlen(1)+"Test"(4) + idx(1)+nlen(1)+"Default"(7) = 16 bytes → 0x0010.
aa 84 00 02 00 41
│ │ │ │ │ └─ Key: 0x41 = 'A' (ASCII)
│ │ │ │ └─ Profile: 0
│ │ │ └─ Length: 2 bytes
│ │ └─ Length high: 00
│ └─ Opcode: 84 (Key Pressed)
└─ Start: AA ✓
Meaning: Key 'A' was pressed while on profile 0.
aa 06 00 01 01
│ │ │ │ └─ Lock flag: 01 = LOCKED (00 = unlocked)
│ │ │ └─ Length: 1 byte
│ │ └─ Length high: 00
│ └─ Opcode: 06 (Lock Device)
└─ Start: AA ✓
Meaning: Lock the device (workstation locked).
aa 85 00 01 48
│ │ │ │ └─ Payload: 0x48 = 72 (72% battery)
│ │ │ └─ Length low: 01 (1 byte payload)
│ │ └─ Length high: 00
│ └─ Opcode: 85 (Battery Status)
└─ Start: AA ✓
Meaning: Device is at 72% battery.
aa 85 00 01 ff
Meaning: No battery detected - device is running on USB only (0xFF sentinel).
aa 05 00 40 ff 00 00 32 ff 00 00 32 ... (64 bytes total)
│ │ │ │ └──┴──┴──┴──┘ └──┴──┴──┴──┘
│ │ │ │ Key 0: R=255 Key 1: R=255
│ │ │ │ G=0 G=0
│ │ │ │ B=0 B=0
│ │ │ │ W=50 W=50
│ │ │ └─ Length: 64 bytes (0x0040) = 16 keys × 4 bytes
│ │ └─ Length high: 00
│ └─ Opcode: 05 (Set All RGB Keys)
└─ Start: AA ✓
Meaning: Set all 16 keys to red at 50% brightness.
aa 07 00 07 01 05 30 2e 32 2e 33
│ │ │ │ │ │ └──┴──┴──┴──┘
│ │ │ │ │ │ ASCII "0.2.3" (app version)
│ │ │ │ │ └─ App version length: 5
│ │ │ │ └─ Protocol version: 0x01 = 1 (PROTOCOL_VERSION constant)
│ │ │ └─ Length low: 07 (7 bytes)
│ │ └─ Length high: 00
│ └─ Opcode: 07 (Hello)
└─ Start: AA ✓
Meaning: Host (app v0.2.3) announces protocol version 1; firmware should reply with 0x86 DEVICE_TELEMETRY.
Length math: protocol_version(1) + name_len(1) + "0.2.3"(5) = 7 → 0x0007.
aa 86 00 12 01 05 31 2e 32 2e 33 00 00 13 88 01 00 03 0d 40 00 00
│ │ │ │ │ │ └──┴──┴──┴──┘ └──┴──┴──┴──┘ │ └──┴──┴──┴──┘ └──┴──┘
│ │ │ │ │ │ fw "1.2.3" uptime BE u32 │ free_heap BE ble_err
│ │ │ │ │ │ = 0x00001388 │ = 0x00030D40 BE u16=0
│ │ │ │ │ │ = 5000 ms │ = 200000 B
│ │ │ │ │ │ └─ reset_reason: 01 = POWERON
│ │ │ │ │ └─ Firmware version length: 5
│ │ │ │ └─ Protocol version: 1
│ │ │ └─ Length low: 12 (18 bytes)
│ │ └─ Length high: 00
│ └─ Opcode: 86 (Device Telemetry)
└─ Start: AA ✓
Meaning: Firmware v1.2.3 has been up 5 s since a power-on reset, has ~200 KB free heap, and has logged zero BLE errors.
Big-endian breakdown:
uptime_ms=00 00 13 88= (0×2²⁴) + (0×2¹⁶) + (0x13×2⁸) + 0x88 = 4864 + 136 = 5000 msfree_heap=00 03 0d 40= (0x03×2¹⁶) + (0x0D×2⁸) + 0x40 = 196608 + 3328 + 64 = 200000 bytesble_error_count=00 00= 0
Length math: pv(1) + fvlen(1) + "1.2.3"(5) + uptime(4) + reset(1) + heap(4) + ble_err(2) = 18 → 0x0012.
Reset reason byte (subset of esp_reset_reason()):
| Value | Meaning |
|---|---|
00 |
Unknown |
01 |
Power-on |
03 |
Software reset |
05 |
Deep-sleep wake |
06 |
Brownout |
08 |
Task watchdog |
09 |
Interrupt watchdog |
The length is 2 bytes, big-endian:
00 0e → 0x000E = 14 bytes
00 40 → 0x0040 = 64 bytes
01 00 → 0x0100 = 256 bytes
Common key codes:
30-39 → '0'-'9'
41-46 → 'A'-'F'
00 → 0 (off)
80 → 128 (half)
FF → 255 (full)
- In commands: 1-based (1, 2, 3, ...)
- In events: 0-based (0, 1, 2, ...)
1. App → Device: aa 01 00 00 [PING]
2. Device → App: aa 81 00 00 [PONG]
3. App → Device: aa 03 00 0e 02... [SYNC profiles]
4. App → Device: aa 05 00 40 ff... [SET all RGB keys]
1. Device → App: aa 82 00 01 02 [Profile changed to 2]
2. App → Device: aa 05 00 40 ff... [Send new RGB colors]
1. Device → App: aa 84 00 02 00 41 [Key 'A' pressed]
2. App executes the command for key 'A'
When reading logs:
- First byte = AA? ✓ Valid packet start
- Second byte → Look up opcode above
- Bytes 3-4 → Calculate length (big-endian)
- Remaining bytes → Should match length
- Profile indices:
- Commands use 1-based
- Events use 0-based
Log entry:
📁 Synchronizing 2 profiles to device...
Profile 1: 'Main'
Profile 2: 'Gaming'
Packet size: 19 bytes
→ aa03000f0201044d61696e0206 47616d696e67 [SYNC]
Manual decode:
aa Start ✓
03 Sync Profiles
00 0f Length = 15 bytes
02 Count = 2 profiles
01 Profile index 1
04 Name length 4
4d 61 69 6e "Main" (hex to ASCII)
02 Profile index 2
06 Name length 6
47 61 6d 69 6e 67 "Gaming"
Verification: body = count(1) + 1+1+4 + 1+1+6 = 15 bytes → 0x000F. Full packet = 4-byte header + 15 = 19 bytes. ✓
Pro Tip: Use an online hex-to-ASCII converter for quick string decoding:
54657374→ "Test"44656661756c74→ "Default"