Reverse-engineered from RTL-SDR captures of the OptiBright controller remote. All findings confirmed across 5+ independent captures unless noted otherwise.
- Modulation: OOK (On-Off Keying), 433 MHz
- Observed timing (RTL-SDR captures): ~300 µs (1-bit), ~150 µs (0-bit)
- RMT implementation timing: pulse=508µs, short_gap=508µs, long_gap=1008µs, reset_gap=6000µs
- Burst: each packet transmitted ×
TX_REPEAT(default 5) in rapid succession - Channel commands also append time (HMS) packets when the global time flag is enabled
Byte: [0] [1] [2] [3] [4] [5] [6] [7]
ADDR 0x23 B3 B4 B5 B6 TYPE CRC
ADDR: device address (byte[0]) — a single random byte, generated once and stored in NVS. The fixture learns this address from the first remote it hears after power-up and ignores all other addresses until it is power-cycled.0xD0is the historical example from original captures; actual value is per-device.0x23: byte[1] — constant in all observed captures.- B3–B6: payload bytes — meaning depends on TYPE
- TYPE: encodes the command class. For channel commands, the low nibble is the command sub-type and the high nibble carries channel data (see TYPE 0x06 below). For all other packet types, the full byte is the type identifier.
- CRC: checksum over bytes [0]–[6]
All channel levels use the same scheme across B3, B4, and the high nibble of the TYPE byte:
| Value | Meaning |
|---|---|
0x0 |
Off, level not set |
0x1–0x9 |
Level 1–9 (10%–90%) |
0xa |
Level 10 (100%) |
The high bit of B3/B4/B5 is the on/off flag:
0x80| level = channel on at that level0x00| level = channel off (level remembered for next on)
Level range is confirmed 0x1–0xa (1–10, 10%–100%). Level 0xa = 100% is valid and routinely seen in captures.
The only packet type used to change live channel state. Covers everything from full brightness to all-off — there is no separate all-off command type. Transmitted ×3, optionally followed by ×3 HMS time packets.
TYPE byte encoding:
TYPE byte = (rgb_level << 4) | 0x06
The low nibble 0x06 identifies this as a "set all channels" command.
The high nibble carries the RGB channel's brightness level (0x1–0xa).
When RGB is off, the high nibble carries the dominant active level
(max of white and blue levels, or 0xa as fallback).
Example: White=5, Blue=8, RGB=on@10 → TYPE = 0xa6 (rgb_level=10, cmd=06)
B3 = white channel — 0x80|level (on) or 0x00|level (off)
B4 = blue channel — same encoding
B5 = RGB state — 0x80|color (on) or 0x00|color (off)
B6 = cycle speed — one-hot (see below); 0x00 when RGB off
TYPE = (rgb_level << 4) | 0x06
RGB color presets (B5 low nibble):
| Nibble | Color |
|---|---|
0x1 |
Blue |
0x2 |
Green |
0x3 |
White |
0x4 |
Red |
0x5 |
Orange |
0x6 |
Purple |
0x7 |
Pink |
0x8 |
Yellow |
0x9 |
Rainbow (cycles through colors) |
Cycle speed encoding (B6) — Rainbow only:
| Value | Duration |
|---|---|
0x01 |
Static (no cycle) |
0x02 |
3 seconds per colour |
0x04 |
4 seconds per colour |
0x08 |
5 seconds per colour |
One-hot encoding. B6 = 0x00 when RGB is off. Cycle is only meaningful
when color = Rainbow (0x9); for solid colors, use 0x01.
The only packet type used to sync the device clock. Carries hours, minutes, and seconds. Sent ×3, standalone — never mixed into the schedule burst.
B3 = HH (0–23)
B4 = MM (0–59)
B5 = SS (0–59)
B6 = 0x00
TYPE = 0x01
There is no separate "Time HM" packet type. TYPE 0x01 is the sole time
mechanism; if seconds are not known, send 0x00 for B5.
The schedule is sent as a fixed sequence of 7 types, repeated ×3, with no time packet appended. Sending schedule does not implicitly sync the clock — use a separate time send.
Implementation sequence (×repeatCount):
02 → 03 → 04 → 05 → 07 → 08 → 09
Note on ordering: Early RTL-SDR captures suggested TYPE 02 came last (03→04→05→07→08→09→02). The implementation sends it first. Both orderings have been used; the device appears to accept either.
To disable a slot, set HH = MM = 0x00.
B3 = HH (hour, 0–23; or 0x00 to disable)
B4 = MM (minute, 0–59; or 0x00 to disable)
B5 = state byte (0x00 for W/B slots; rgb_state byte for RGB slots)
B6 = 0x00
White channel turn-off event.
B3=HH, B4=MM, B5=0x00, B6=0x00
White channel turn-on event.
B3=HH, B4=MM, B5=0x00, B6=0x00
Blue channel turn-on event.
B3=HH, B4=MM, B5=0x00, B6=0x00
Sets the target RGB color that the device will use when the schedule fires. Does not represent a timed event — carries no HH/MM.
B3 = target rgb_state byte (same value as TYPE 08 B5 / rgbOn.state)
high nibble: 0x8 = on, 0x0 = off
low nibble: color preset (0x1–0x9)
e.g. 0x86 = purple, 0x81 = blue
B4, B5, B6 = 0x00
TYPE = 0x07
B3 matches the RGB ON slot's stored color (rgbOn.state). TYPE 07 tells the
device which color to arm for the upcoming schedule; TYPE 08/09 B5 carry the
same value as part of the timed ON/OFF events.
RGB channel turn-on event, with frozen color state.
B3=HH, B4=MM
B5 = rgb_state frozen at last schedule save (high nibble: on/off, low nibble: color)
B6 = 0x00
RGB channel turn-off event. B5 carries the frozen color so the device can restore it at the next scheduled ON event.
B3=HH, B4=MM
B5 = rgb_state frozen at last schedule save (same encoding as TYPE 08)
B6 = 0x00
Blue channel turn-off event. Sent first in the current implementation.
B3=HH, B4=MM, B5=0x00, B6=0x00
| Type | Channel | Action | B3 | B5 | Position |
|---|---|---|---|---|---|
| 02 | Blue | OFF | HH | 0x00 |
1 (first) |
| 03 | White | OFF | HH | 0x00 |
1 |
| 04 | White | ON | HH | 0x00 |
2 |
| 05 | Blue | ON | HH | 0x00 |
3 |
| 07 | — | RGB state set | rgbOn.state | 0x00 |
4 |
| 08 | RGB | ON | HH | frozen rgb_state | 5 |
| 09 | RGB | OFF | HH | frozen rgb_state | 6 |
The range 0x1–0xa is confirmed. 0xa = level 10 = 100%. All captures
showing 0x8a in B3/B4 (e.g. the ALL ON packet) are White/Blue ON at 100%.
Confirmed: all live channel changes — brightness, colour, on/off, and all-off — use TYPE 0x06. There is no dedicated all-off command. An all-off packet is simply TYPE 0x06 with the on/off bits clear on all channels, retaining the last-known levels in the low nibbles.
Previously the 0x6 in the low nibble of the channel command TYPE byte was
noted as a "mystery constant". It is the command type identifier: 0x06 =
"set all channels now". The high nibble carries the RGB brightness level.
This is why the TYPE byte for a channel command always has 0x6 in the
low nibble regardless of channel state.
Previously unresolved. Now decoded as a Time Set (HMS) packet:
TYPE = 0x01 (Time Set)
B3 = 0x17 = 23 → HH = 23
B4 = 0x1f = 31 → MM = 31
B5 = 0x21 = 33 → SS = 33
B6 = 0x00
Appeared before a schedule burst because the app always sends a time sync immediately before pushing a schedule. TYPE 0x02 is never used for time — it is exclusively the Blue OFF schedule slot.
-
B6 cycle speed captures for 3s/4s/5s still pending. Values
0x02,0x04,0x08are inferred from app UI one-hot layout; direct packet captures with each speed selected would fully confirm. -
Independent slot behaviour. Whether a schedule can omit TYPE 08 or 09 (e.g. RGB always on, no OFF slot) without causing device issues is untested.
- All captures used an RTL-SDR dongle with URH (Universal Radio Hacker)
- 433.92 MHz, OOK demodulation
- Packet boundaries identified by inter-packet silence gaps
- CRC verified by re-computing over bytes [0]–[6] and matching byte [7]