Files
rust_bifrost/doc/hue-zigbee-clusters.md
Beyhan Oğur 427856cd3a first commit
2026-04-26 22:29:38 +03:00

13 KiB

The Color of Magic: Reversing the Hue Zigbee Clusters

This document, which builds on the initial work, aims to compile all available information about the custom Zigbee messages used by Philips Hue devices, and in particular, lights.

The following text refers to commands and attributes on Hue devices. This has been researched using the following units:

"Hue Bulb"

  • "Hue white and color ambiance E27 1100lm"
  • Model LCA006
  • Firmware 1.122.2 (20240902)

"Hue Gradient strip"

  • "Hue Play gradient lightstrip for PC"
  • Model LCX005
  • Firmware 1.122.2 (20240902)

Nomenclature

The following short names are used to refer to zigbee data types and concepts:

Name used here Zigbee meaning
N/S Attribute not supported
u8 Unsigned, 8-bit integer
u16 Unsigned, 16-bit integer
i16 Signed, 16-bit integer
b8 8-bit bitmap value
b32 32-bit bitmap value
e8 8-bit enum value
hex "Octet string" (byte array) in hex notation

Cluster 0xFC00: Hue Button events

Used by hue buttons to report button events and other state changes.

Cluster-specific commands

Command 0: Button Event

These are mostly documented elsewhere, and because they are button events, they are not the main focus of this document.

Cluster 0xFC01: Entertainment

This cluster is used to control "Entertainment Zones", a defining feature of the Hue ecosystem.

Cluster-specific commands

Command 1: Update entertainment zone

This is the major command used to send a "frame" of Hue Entertainment data.

Sending it to a Hue bulb will cause that bulb to repeat it in broadcast mode, for other devices to pick up.

  ┌───────────┬───┬───┬───┬───┬───┬───┬───┬───┐
  │ Byte  Bit ► 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
  ├─▼─────────┼───┴───┴───┴───┴───┴───┴───┴───┤
  │ 0         │ .counter                      │
  │           │                               │
  │ 1         │                               │
  │           │                               │
  │ 2         │                               │
  │           │                               │
  │ 3         │                               │
  ├───────────┼───────────────────────────────┤
  │ 4         │ .smoothing                    │\
  │           │ Defaults to 0x0400            │ } Smoothing factor
  │ 5         │ (encoded as "0004")           │/
  ├───────────┼───────────────────────────────┤
  │ 6         │ Light data block 0            │\
  │           │                               │ \
  │ ..        │                               │  } Repeated for each light
  │           │                               │ /
  │ 12        │                               │/
  ├───────────┼───────────────────────────────┤
  : 13        : Light data block 1            :
  :           :                               :

The "smoothing factor" is a value that controls how agressively the color/brightness will change from the previous frame. A value of 0x0000 is the fastest possible (and generally not very pleasant to look at), while a value of 0x1000 is quite slow, giving very smooth animations, but without any quick changes.

Very high values (e.g. above 0x4000) are so slow that they are unlikely to be useful in most cases.

The existing Hue Entertainment clients all seem to use 0x0400, which is a reasonable starting point. Note that this property does NOT seem to be exposed over any known API, but it is available over Bifrost.

Each "light data block" is a 7-byte packed structure describing the desired state for a light (a bulb, or single segment of a multi-segment light source).

 ┌───────────┬───┬───┬───┬───┬───┬───┬───┬───┐
 │ Byte  Bit ► 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
 ├─▼─────────┼───┴───┴───┴───┴───┴───┴───┴───┤
 │ 0         │ .addr                         │
 │           │ Zigbee address (or alias)     │
 │ 1         │ for the target light          │
 ├───────────┼───────────┬───────────────────┤
 │ 2         │(low 3 bit)│ .mode (5 bit enum)│
 │           │─ ─ ─ ─ ─ ─└───────────────────┤
 │ 3         │ .brightnes (high 8 bits)      │
 ├───────────┼───────────────────────────────┤
 │ 4         │ .color_x (low 8 bits)         │\
 │           ├───────────────┐─ ─ ─ ─ ─ ─ ─ ─│ \
 │ 5         │ (low 4 bits)  │ (high 4 bits) │  same format as for composite updates
 │           │─ ─ ─ ─ ─ ─ ─ ─└───────────────┤ /
 │ 6         │ .color_y (high 8 bits)        │/
 └───────────┴───────────────────────────────┘

The .mode field is an odd one. Only two values have ever been observed:

// the names might change, as we learn more about these bits
enum LightRecordMode {
    Segment = 0b00000,
    Device  = 0b01011,
}

Normal bulbs must be contacted with the LightRecordMode::Device option, while updates for segments on a gradient strip must use the LightRecordMode::Segment mode. Otherwise, the entire segment only lights up in the first color.

Current hypothesis: This values determines if real network addresses or virtual segment addresses are used, but this is currently not tested.

Command 3: Synchronize entertainment zone

This command is used to synchronize the sequence number in an entertainment group. The first two bytes are unknown.

struct {
    x0: u8, // only seen as 0
    x1: u8, // seen as 0 or 1. unknown function
    counter: u32, // frame counter for entertainment group
}

Command 4: Retrieve segment mapping

This command is used to retrieve the segment mapping for a hue multi-segment light.

Request

A single byte is sent. Only observed as 00 (might be an index for highly addressable devices?).

Response

struct Response {
  x0: u8, // unknown
  x1: u8, // unknown
  count: u8, // number of segments
  segments: [Segment], // segment descriptors
}

struct Segment {
  start: u8, // start index for segment
  length: u8, // segment length
}

As an example, the following is a real response from a Hue Gradient light strip:

           ┌───┬───First segment descriptor
           │   │
  00 00 07 00 01 01 01 02 01 03 01 04 01 05 01 06 01
  │      │ │                                       │
  └header┘ └───────Seven segment descriptors───────┘

This tells us the segments are arranged thus:

  • Start at 00, length 01
  • Start at 01, length 01
  • Start at 02, length 01
  • ...

These are all length 1. In other words, the layout is:

0, 1, 2, 3, 4, 5, 6

Command 7: Configure segments for entertainment mode (req/rsp)

Hue Entertainment frames consists of brightness and color data for up to 10 lights, all in a single frame.

Each light is identified by 2 bytes containing its zigbee network (short) address.

For Hue devices that contain multiple lights (such as gradient strips), this presents a problem, since the entire strip only has a single zigbee address!

To solve that problem, this command can be used on multi-segment devices to configure each segment with a virtual address.

Request

struct {
  count: u16,
  addresses: [count x u16],
}

Here is an example of a command that sets seven virtual addresses for a gradient light strip with 7 segments:

        ┌───┬───Segment index 0
        │   │
  00 07 97 d2 98 d2 99 d2 9a d2 9b d2 9c d2 9d d2
  │   │ │                                       │
  └cnt┘ └───────Seven segment indices───────────┘

After this, the segments will respond the these addresses:

  • 0xD297
  • 0xD298
  • 0xD299
  • 0xD29A
  • 0xD29B
  • 0xD29C
  • 0xD29D

Response

struct {
  x0: u16,
}

The only observed response is 0000, which probably indicates success.

Running this command on a Hue device that does not have multiple segments (i.e, a regular Hue bulb) gets a "Command Not Supported" standard Zigbee response, so returning 0000 seems to be a safe way to detect success.

Attributes

Attr Type Desc Strip Bulb Firmware
0000 b8 ? 0F 0B
0001 e8 ? 00 00
0002 u8 Probably max segment count 0A N/S
0003 u8 Probably gradient-related 04 N/S
0004 u8 Probably segment count 07 N/S
0005 u8 Light balance factor FE FE Fails on 1.76.11, works on 1.122.2

Notice that attributes 0002, 0003 and 0004 are not present on the hue bulb. This supports the idea that these attributes are gradient-related.

So far the only attribute known on this cluster is 0x005, which sets the light level balancing for entertainment mode.

This is a feature where lights can be dimmed relatively, so certain lights aren't blindingly bright. Just like regular brightness updates, the valid range is 0x01 to 0xFE. This should always be set to 0xFE, unless you want to dim the light in entertainment mode.

Cluster 0xFC02

Never seen. Maybe they skipped a number?

Cluster 0xFC03: Gradients, Effects, Animations

Cluster-specific commands

Command 0: Write combined state

This is perhaps the single most complicated Hue command. It is used to simultaneously set all supported properties of a Hue bulb.

It has been extensively documented in a separate document.

After setting the state with this command, it can be read back as property 0x0002 (see below).

Attributes

Sample values:

Attr Type Desc Strip Bulb
0001 b32 ? 0000000F 00000007
0002 hex Composite state 0700010a6e01 070001176f01
0010 b16 ? 0001 0001
0011 b64 ? 000000000003FE0E 000000000003FE0E
0012 b32 ? 00000003 00000000
0013 b16 ? 0007 N/S
0031 u16 ? 04E2 N/S
0032 u8 ? 00 N/S
0033 u8 ? 00 N/S
0034 u8 ? 03 N/S
0035 u8 ? FE N/S
0036 u8 ? 4F N/S
0038 u16 ? 0007 N/S

The bulb supports noticably fewer properties, which makes it likely that the missing ones are related to gradient handling.

Cluster 0xFC04

Very rarely observed. Only seen with ZCL: Read Attributes.

Attributes

Attr Type Desc Strip Bulb
0000 b16 ? 1007 1007
0001 b16 ? 0000 0000
0002 b16 ? 0000 0000
0010 u32 ? 00000000 00000000
0011 u32 ? 00000000 00000000
0012 u32 ? 00000000 00000000
0013 u32 ? 00000000 00000000