What is in a LIFX message
The LIFX protocol is a binary messaging protocol that is used to communicate with LIFX devices, sending them instructions or querying them for state.
LIFX protocol messages are structured to fit into a single UDP packet. Each LIFX protocol message has two components concatenated together: a header and a payload. The header contains metadata common to all messages and describes the type of payload to follow; the payload provides the data for the specific action being requested. Sometimes the action requires no data, in which case the payload will be 0
bytes long.
LIFX protocol messages are often used in pairs. For querying state, one would send the device a Get
message, and the device would respond to the originating IP address with the corresponding State
message (e.g. LightGet
and LightState
). For changing state, one would send the device a Set
message, and the device would perform the change, optionally respond with a Device
Acknowledgement
message, and respond with a State
message (e.g. DeviceSetPower
, Acknowledgement
, DeviceStatePower
).
Header
The header portion of a LIFX header contains 3 components:
- Frame Header (8 bytes)
- Frame Address (16 bytes)
- Protocol Header (12 bytes)
Any fields named reserved
should be set to all 0
s.
Byte order
Numeric data-type byte-order is little-endian, which means that multi-byte fields will start with the least significant byte.
Frame Header
The Frame header contains information about the following:
- Size of the entire message
- LIFX Protocol number: must be 1024 (decimal)
- Use of the Frame Address
target
field - Source identifier
Field | Bit Size | 0-Indexed Location | Type | Description |
---|---|---|---|---|
size | 16 | Lower octet: Byte 0. Upper octet: Byte 1. | Uint16 | Size of entire message in bytes including this field |
protocol | 12 | Lower octet: Byte 2. Upper 4 bits: Byte 3, Bits 0-3. | Uint16 | Protocol number: must be 1024 (decimal) |
addressable | 1 | Byte 3, Bit 4. | Bool | Message includes a target address: must be one (1) |
tagged | 1 | Byte 3, Bit 5. | Bool | Determines usage of the Frame Address target field |
origin | 2 | Byte 3, Bits 6-7. | Uint8 | Message origin indicator: must be zero (0) |
source | 32 | Lowest octet: Byte 4. Highest octet: Byte 7. | Uint32 | Source identifier: unique value set by the client, used by responses |
The tagged
field is a boolean flag that indicates whether the Frame Address target
field is being used to address an individual device or all devices. When you broadcast a message to the network (i.e. broadcasting a GetService (2) for discovery) you should set this field to 1
and for all other messages you should set this to 0
.
The source
identifier allows each client to provide a unique value, which will be included in the corresponding field in Acknowledgement (45) and State
packets the device sends back to you.
Due to the behavior of some older versions of firmware you should set this value to anything other than zero or one. In some versions of the firmware a source
value of 1
will be ignored. If you set source
to 0
then the device may broadcast the reply on port 56700
which can be received by all clients on the same subnet and may not be the port on which your client is listening for replies.
Frame Address
The Frame Address section contains the following routing information:
- Target address
- A flag to say if an Acknowledgement (45) is required
- A flag to say if a
State
response message is required - Message sequence number
Field | Bits Size | 0-Indexed Location | Type | Description |
---|---|---|---|---|
target | 64 | Bytes 8 - 15. | 8 Uint8 integers | 6 byte device serial number or zero (0) means all devices. The last two bytes should always be 0 bytes. |
reserved | 48 | Bytes 16 - 21. | 6 Reserved bytes | Reserved |
res_required | 1 | Byte 22, Bit 0. | Bool | State message required. However, Get messages will send State messages anyway and State messages to Set messages are usually not useful. |
ack_required | 1 | Byte 22, Bit 1. | Bool | Acknowledgement message required |
reserved | 6 | Byte 22, Bits 2-7. | 6 Reserved bits | Reserved |
sequence | 8 | Byte 23. | Uint8 | Wrap around message sequence number |
The target field represents a device serial number and appears as a hex number of the form d073d5xxxxxx
. When you address a device you should left-justify the first 6 bytes of the target
field with the serial number and then zero-fill the last two bytes. You should set this value to all zero's if you want to broadcast a message to the network.
The replies you get back from devices will always contain the serial number of the device sending the reply. For example, if you are discovering devices, the StateService (3) message will tell you the serial number for each LIFX device on your network.
There are two flags that configure what replies you get from a LIFX device when you send them a message.
ack_required
set to one1
will cause the device to send an Acknowledgement (45) messageres_required
set to one1
will make the device send back one or moreState
messages
It is recommended you set ack_required=1
and res_required=0
when you change the device with a Set
message. When you send a Get
message it is best to set ack_required=0
and res_required=0
, because these messages trigger an implicit State
response. Note that when you ask for a response with a Set
message that changes the visual state of the device, you will get the old values in the State
message sent back.
The sequence
number allows the client to provide a unique value, which will be included by the LIFX device in any message that is sent in response to a message sent by the client. This allows the client to distinguish between different messages sent with the same source
identifier in the Frame Header
. We recommend that your program has one source
value and keeps incrementing sequence
per device for each message you send. Once sequence
reaches the maximum value of 255 for that device, roll it back to 0
and keep incrementing from there.
You can associate replies to their corresponding requests by matching the (source, sequence, target)
values from the request and response packets.
Protocol Header
The Protocol header allows you to specify which message type you are sending to the device.
The type of the message is a unique number assigned to each message type. Corresponding Set/Get/State messages will each have a unique value. This indicates to the client whether there will be a payload and the type of payload.
You may represent the header of a packet with something like:
#pragma pack(push, 1)
typedef struct {
/* frame */
uint16_t size;
uint16_t protocol:12;
uint8_t addressable:1;
uint8_t tagged:1;
uint8_t origin:2;
uint32_t source;
/* frame address */
uint8_t target[8];
uint8_t reserved[6];
uint8_t res_required:1;
uint8_t ack_required:1;
uint8_t :6;
uint8_t sequence;
/* protocol header */
uint64_t :64;
uint16_t type;
uint16_t :16;
/* variable length payload follows */
} lx_protocol_header_t;
#pragma pack(pop)
Payload
The payload will have different values as determined by the type
field in the Protocol Header
. We refer to the entire packet as a LIFX message and each message has a different purpose. You may find what messages are available in the messages section of this documentation.
Updated almost 2 years ago