The droids we are looking for
This document describes GATT characteristics used to control Zengge smart RGBW BLE bulbs (like on the picture above). All the text below is a result of my own experience of reverse-engineering its protocol. Use it at your own risk, I am not responsible for your hardware.
Looking through the reverse engineered code I figured out that there are potentially two slightly different kinds of bulbs. They differ by names and versions. Bulbs whose names start with LEDBlue or LEDBLE belong to the first group, not subject of this doc. Second group of devices is described here. Also, some commands differ depending on the device version (can be known by querying it's status). The commands below are tested on LEDnet-...-named bulb of third version.
Communication principles
There are three main ways to interact with a bulb:
- Fire and forget. Only writes are supported. No response or acknowledgement.
- Write and listen for notifications. Write a characteristic while subscribed for notifications. As a result of value write, another characteristics may fire a notification.
- Direct read of a characteristic. Read-only access to some parameters.
Most of the communication with a bulb is done via two characteristcs — FFE9 and FFE4 — in a write-and-listen manner: you write a data into FFE9 and listen for FFE4's notifications.
The protocol
Status
Status request is used to query some generic bulb parameters, like power status, device version, color or predefined mode id. Status query is done in "write and listen" manner, so it will not work in non-interactive gatttool mode (you won't be able to see the result).
Request
| Type | Write and listen |
| Write to | FFE9 |
| Notification from | FFE4 |
| Payload |
Constant, [0xEF, 0x01, 0x77]
|
| Notification | See the description below |
Notification description
Resulting notification must be 12 bytes long.
result[0]must be equal to magic constant0x66result[1]: ???result[2]: power status:0x23is for "ON".0x24is for "OFF".
result[3]: mode:0x25-0x38: build-in mode0x41: static color mode
result[4]: ???result[5]: speedresult[6]: red color componentresult[7]: green color componentresult[8]: blue color componentresult[9]: ???result[10]: device versionresult[11]must be equal to magic constant0x99
Examples
| Magic | ??? | Power | Mode | ??? | Speed | R | G | B | ??? | Version | Magic | Description |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x66 |
0x14 |
0x23 |
0x41 |
0x21 |
0x1F |
0xFF |
0x00 |
0x00 |
0x00 |
0x03 |
0x99 |
Static red color |
0x66 |
0x14 |
0x23 |
0x41 |
0x21 |
0x1F |
0x00 |
0xFF |
0x00 |
0x00 |
0x03 |
0x99 |
Static green color |
0x66 |
0x14 |
0x23 |
0x41 |
0x21 |
0x1F |
0x00 |
0x00 |
0xFF |
0x00 |
0x03 |
0x99 |
Static blue color |
0x66 |
0x14 |
0x23 |
0x41 |
0x21 |
0x1F |
0x5A |
0x00 |
0x9D |
0x00 |
0x03 |
0x99 |
Static violet color |
0x66 |
0x14 |
0x23 |
0x27 |
0x21 |
0x1F |
0x00 |
0x00 |
0xFF |
0x00 |
0x03 |
0x99 |
Built‑in mode 0x27 at speed 0x1F (the slowest possible) |
0x66 |
0x14 |
0x23 |
0x34 |
0x21 |
0x10 |
0x00 |
0x00 |
0xFF |
0x00 |
0x03 |
0x99 |
Built‑in mode 0x34 at speed 0x10 (fast) |
0x66 |
0x14 |
0x24 |
0x34 |
0x21 |
0x10 |
0x00 |
0x00 |
0xFE |
0x00 |
0x03 |
0x99 |
Turned off built‑in mode 0x34 at speed 0x10 (fast) |
Power
Query for current status
Power status can be known from FFF3 characteristic.
Request
| Type | Direct read |
| Read from | FFF3 |
| Read result |
1. 0x3B when the bulb is off2. 0xFF when the bulb is on
|
Set current status
Power status can be set via FFF1 and FFF2 characteristics. It's a two step process: first you write 0x04 in FFF1 and then you set power status in FFF2. The requests are done with 200ms interval.
Requests
| Type | Write |
| Write to | FFF1 |
| Payload | 0x04 |
…wait 200ms…
| Type | Write |
| Write to | FFF2 |
| Payload |
1. 0x00 turn off2. 0x3F turn on
|
Example
| Request | Action |
|---|---|
gatttool -b B4:99:4C:2A:0E:4A --char-write-req -a 0x0017 -n 04 && sleep 0.2s && gatttool -b B4:99:4C:2A:0E:4A --char-write-req -a 0x001a -n 00 |
Turn the bulb off |
gatttool -b B4:99:4C:2A:0E:4A --char-write-req -a 0x0017 -n 04 && sleep 0.2s && gatttool -b B4:99:4C:2A:0E:4A --char-write-req -a 0x001a -n FF |
Turn the bulb on |
Static color mode
Static color mode is set via write request to FFE9 characteristic.
Request
| Type | Write |
| Write to | FFE9 |
| Payload | See below |
Payload description
Payload must be 7 bytes long.
payload[0]must be equal to magic constant0x56payload[1]: red color componentpayload[2]: green color componentpayload[3]: blue color componentpayload[4]must be equal to magic constant0x00payload[5]must be equal to magic constant0xF0payload[6]must be equal to magic constant0xAA
Examples
| Magic | R | G | B | Magic | Magic | Magic | Description |
|---|---|---|---|---|---|---|---|
0x56 |
0xFF |
0x00 |
0x00 |
0x00 |
0xF0 |
0xAA |
Static red color |
0x56 |
0x00 |
0xFF |
0x00 |
0x00 |
0xF0 |
0xAA |
Static green color |
0x56 |
0x00 |
0x00 |
0xFF |
0x00 |
0xF0 |
0xAA |
Static blue color |
0x56 |
0x5A |
0x00 |
0x9D |
0x00 |
0xF0 |
0xAA |
Static violet color |
Built-in mode
Built-in mode is set via write request to FFE9 characteristic.
Request
| Type | Write |
| Write to | FFE9 |
| Payload | See below |
Payload description
Payload must be 4 bytes long.
payload[0]must be equal to magic constant0xBBpayload[1]: build-in modepayload[2]: speedpayload[3]must be equal to magic constant0x44
Examples
| Magic | Mode | Speed | Magic | Description |
|---|---|---|---|---|
0xBB |
0x27 |
0x1F |
0x44 |
Built‑in mode 0x27 at speed 0x1F (the slowest possible) |
0xBB |
0x34 |
0x10 |
0x44 |
Built‑in mode 0x34 at speed 0x10 (fast) |
Clock
Query for current clock value
Current clock can be read from FE01 characteristic. Epoch start for the bulb is 2000-01-01 00:00:00; every time you turn the electricity off (on a hardware level, not like described above) the clock is reset to that value.
Request
| Type | Direct read |
| Read from | FE01 |
| Read result | See below |
Response
The response is 7 bytes long.
result[0]: secondsresult[1]: minutesresult[2]: hours (24 hours format)result[3]: day of month, starting from 1result[4]: month (1 is Jan., 2 is Feb., etc)result[5]: lower byte of the yearresult[6]: upper byte of the year
Example
gatttool -b B4:99:4C:2A:0E:4A --char-read -a 0x0086
Characteristic value/descriptor: 08 36 01 01 01 df 07
| Seconds | Minutes | Hours | Date | Month | Lower year | Upper year | Description |
|---|---|---|---|---|---|---|---|
0x08 |
0x36 |
0x01 |
0x01 |
0x01 |
0xDF |
0x07 |
01/01/2015 01:54:08 |
Set clock value
Current clock can be set by writing FE01 characteristic (the same that is used to query clock).
Request
| Type | Write |
| Write to | FE01 |
| Payload | See below |
Payload description
Payload must be 7 bytes long.
result[0]: secondsresult[1]: minutesresult[2]: hours (24 hours format)result[3]: day of month, starting from 1result[4]: month (1 is Jan., 2 is Feb., etc)result[5]: lower byte of the yearresult[6]: upper byte of the year
Example
gatttool -b B4:99:4C:2A:0E:4A --char-write -a 0x0086 -n 3b1f011e01df07
| Seconds | Minutes | Hours | Date | Month | Lower year | Upper year | Description |
|---|---|---|---|---|---|---|---|
0x3b |
0x1F |
0x01 |
0x1E |
0x01 |
0xDF |
0x07 |
01/30/2015 01:31:59 |
Magic constants
Built-in modes
0x25: Seven color cross fade0x26: Red gradual change0x27: Green gradual change0x28: Blue gradual change0x29: Yellow gradual change0x2a: Cyan gradual change0x2b: Purple gradual change0x2c: White gradual change0x2d: Red, Green cross fade0x2e: Red blue cross fade0x2f: Green blue cross fade0x30: Seven color stobe flash0x31: Red strobe flash0x32: Green strobe flash0x33: Blue strobe flash0x34: Yellow strobe flash0x35: Cyan strobe flash0x36: Purple strobe flash0x37: White strobe flash0x38: Seven color jumping change
Speed
Some operational modes take a speed parameter that controls how fast the colors are changed. 0x01 is the fastest, 0x1F is the slowest.
/sample.png)