LIFX Binary Protocol

To interact with LIFX devices you create packets and send those. A packet is a string of bytes that contains header and payload information. In Photons you create these using the objects listed below. For example DeviceMessages.SetPower can be made by saying:

from photons_messages import DeviceMessages


# An instruction that says to turn the device on
get_power = DeviceMessages.SetPower(level=65535)

The only information in the header that you need to care about are:

target

If you set this then that packet will only be sent to that particular device.

# Send to d073d5000001 regardless of the reference you use
# when you send the message
DeviceMessages.SetPower(level=0, target="d073d5000001")
ack_required

A boolean that tells the device to send back an acknowledgment and for Photons to then expect and wait for one:

# Note that ack_required is True by default
DeviceMessages.GetPower(level=0, ack_required=False)
res_required

A boolean that tells the device to send back a response and for Photons to then expect and wait for one:

# Note that res_required is True by default
DeviceMessages.SetPower(level=0, res_required=False)

The rest of the keyword arguments to the packet are related to the payload of the packet. Many messages have defaults and transformations for these fields.

Note

You can find a definition of the packets in this github repository and explanations on the LAN documentation.

Once you have one of these packet objects you can access fields like it’s a dictionary or like it’s an object:

msg = DeviceMessages.SetPower(level=65535)
assert msg.level == 65535
assert msg["level"] == "65535"

And you can change values the same way:

msg = DeviceMessages.SetPower(level=65535)
msg["level"] = 0
msg.level = 0

Some fields have transformations. For example duration on a LightSetPower is measured in milliseconds on the device, but I find it easier to specify it in seconds instead. And so Photons will take the value you provide and times it by 1000 to make it milliseconds. The value on the object is actually stored using the value that ends up in the binary packet and when you access that value, the opposite transformation is done to turn it back into seconds.

msg = LightMessagse.SetLightPower(level=0, duration=20)
assert msg.duration == 20
assert msg.actual("duration") == 20000

DeviceMessages

DeviceMessages.GetHostInfo

This packet has no fields

DeviceMessages.StateHostInfo

float - signal

This attribute has no transformations

uint32 - tx

This attribute has no transformations

uint32 - rx

This attribute has no transformations

DeviceMessages.GetHostFirmware

This packet has no fields

DeviceMessages.StateHostFirmware

uint64 - build

This attribute has no transformations

uint16 - version_minor

This attribute has no transformations

uint16 - version_major

This attribute has no transformations

DeviceMessages.GetWifiInfo

This packet has no fields

DeviceMessages.StateWifiInfo

float - signal

This attribute has no transformations

uint32 - tx

This attribute has no transformations

uint32 - rx

This attribute has no transformations

DeviceMessages.GetWifiFirmware

This packet has no fields

DeviceMessages.StateWifiFirmware

uint64 - build

This attribute has no transformations

uint16 - version_minor

This attribute has no transformations

uint16 - version_major

This attribute has no transformations

DeviceMessages.GetPower

This packet has no fields

DeviceMessages.SetPower

uint16 - level

This attribute has no transformations

DeviceMessages.StatePower

uint16 - level

This attribute has no transformations

DeviceMessages.GetLabel

This packet has no fields

DeviceMessages.SetLabel

string[32] - label

This attribute has no transformations

DeviceMessages.StateLabel

string[32] - label

This attribute has no transformations

DeviceMessages.GetVersion

This packet has no fields

DeviceMessages.StateVersion

uint32 - vendor

This attribute has no transformations

uint32 - product

This attribute has no transformations

uint32 - version

default: 0

DeviceMessages.GetInfo

This packet has no fields

DeviceMessages.StateInfo

uint64 - time

This attribute has no transformations

float - uptime

transformed: value from the packet is changed for you:

lambda _, v: v / 1e9
float - downtime

transformed: value from the packet is changed for you:

lambda _, v: v / 1e9

DeviceMessages.GetLocation

This packet has no fields

DeviceMessages.SetLocation

bytes[16] - location

This attribute has no transformations

string[32] - label

This attribute has no transformations

uint64 - updated_at

This attribute has no transformations

DeviceMessages.StateLocation

bytes[16] - location

This attribute has no transformations

string[32] - label

This attribute has no transformations

uint64 - updated_at

This attribute has no transformations

DeviceMessages.GetGroup

This packet has no fields

DeviceMessages.SetGroup

bytes[16] - group

This attribute has no transformations

string[32] - label

This attribute has no transformations

uint64 - updated_at

This attribute has no transformations

DeviceMessages.StateGroup

bytes[16] - group

This attribute has no transformations

string[32] - label

This attribute has no transformations

uint64 - updated_at

This attribute has no transformations

DeviceMessages.EchoRequest

bytes[64] - echoing

This attribute has no transformations

DeviceMessages.EchoResponse

bytes[64] - echoing

This attribute has no transformations

LightMessages

LightMessages.GetColor

This packet has no fields

LightMessages.SetColor

float - hue

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)) / 360)
float - saturation

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
float - brightness

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
uint16 - kelvin

default: 3500

float - duration

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))

LightMessages.SetWaveform

boolint - transient

default: 0

float - hue

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)) / 360)
float - saturation

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
float - brightness

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
uint16 - kelvin

default: 3500

float - period

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))
float - cycles

default: 1

float - skew_ratio

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v))) - 32768
uint8 - waveform

default: photons_messages.enums.Waveform. SAW

enum: This attribute is a photons_messages.enums.Waveform

LightMessages.LightState

float - hue

This attribute has no transformations

float - saturation

This attribute has no transformations

float - brightness

This attribute has no transformations

uint16 - kelvin

default: 3500

uint16 - power

This attribute has no transformations

string[32] - label

This attribute has no transformations

LightMessages.GetLightPower

This packet has no fields

LightMessages.SetLightPower

uint16 - level

This attribute has no transformations

float - duration

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))

LightMessages.StateLightPower

uint16 - level

This attribute has no transformations

LightMessages.SetWaveformOptional

boolint - transient

default: 0

float - hue

optional: This attribute is optional

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)) / 360)
float - saturation

optional: This attribute is optional

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
float - brightness

optional: This attribute is optional

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
uint16 - kelvin

optional: This attribute is optional

float - period

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))
float - cycles

default: 1

float - skew_ratio

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v))) - 32768
uint8 - waveform

default: photons_messages.enums.Waveform. SAW

enum: This attribute is a photons_messages.enums.Waveform

boolint - set_hue

default: true only if hue is given a value

boolint - set_saturation

default: true only if saturation is given a value

boolint - set_brightness

default: true only if brightness is given a value

boolint - set_kelvin

default: true only if kelvin is given a value

LightMessages.GetInfrared

This packet has no fields

LightMessages.StateInfrared

uint16 - brightness

This attribute has no transformations

LightMessages.SetInfrared

uint16 - brightness

This attribute has no transformations

MultiZoneMessages

MultiZoneMessages.SetColorZones

uint8 - start_index

This attribute has no transformations

uint8 - end_index

This attribute has no transformations

float - hue

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)) / 360)
float - saturation

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
float - brightness

transformed: Value you provide is changed for the binary packet:

lambda _, v: int(65535 * (0 if v is sb.NotSpecified else float(v)))
uint16 - kelvin

default: 3500

float - duration

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))
uint8 - apply

default: photons_messages.enums.MultiZoneApplicationRequest. APPLY

enum: This attribute is a photons_messages.enums.MultiZoneApplicationRequest

MultiZoneMessages.GetColorZones

uint8 - start_index

This attribute has no transformations

uint8 - end_index

This attribute has no transformations

MultiZoneMessages.StateZone

uint8 - zones_count

This attribute has no transformations

uint8 - zone_index

This attribute has no transformations

float - hue

transformed: value from the packet is changed for you:

lambda _, v: float(v) * 360 / 65535
float - saturation

transformed: value from the packet is changed for you:

lambda _, v: float(v) / 65535
float - brightness

transformed: value from the packet is changed for you:

lambda _, v: float(v) / 65535
uint16 - kelvin

default: 3500

MultiZoneMessages.StateMultiZone

uint8 - zones_count

This attribute has no transformations

uint8 - zone_index

This attribute has no transformations

bytes[64] - colors

multiple: This attribute turns into an array of 8 Color objects

MultiZoneMessages.GetMultiZoneEffect

This packet has no fields

MultiZoneMessages.SetMultiZoneEffect

uint32 - instanceid

default: randomly generated number

uint8 - type

default: photons_messages.enums.MultiZoneEffectType. MOVE

enum: This attribute is a photons_messages.enums.MultiZoneEffectType

float - speed

default: 5

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))
float - duration

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1e9 * float(value))
bytes[32] - parameters

dynamic: This attribute turns into a packet like object with these fields:

def multizone_effect_parameters_for(typ):
    if typ is enums.MultiZoneEffectType.MOVE:
        yield ("parameter1", T.Reserved(32))
        yield ("speed_direction", T.Uint32.enum(enums.Direction).default(enums.Direction.RIGHT))
        for i in range(6):
            yield ("parameter{0}".format(i + 2), T.Reserved(32))
    else:
        for i in range(8):
            yield ("parameter{0}".format(i), T.Reserved(32))

Using transformer:

lambda pkt: multizone_effect_parameters_for(pkt.type)))

You set parameters with a dictionary of fields and you can access the additional fields on parameters as if parameters is it’s own packet with those fields

MultiZoneMessages.StateMultiZoneEffect

uint32 - instanceid

default: randomly generated number

uint8 - type

default: photons_messages.enums.MultiZoneEffectType. MOVE

enum: This attribute is a photons_messages.enums.MultiZoneEffectType

float - speed

default: 5

transformed: value from the packet is changed for you:

lambda _, value: float(value) / 1000
float - duration

default: 0

transformed: value from the packet is changed for you:

lambda _, value: float(value) / 1e9
bytes[32] - parameters

dynamic: This attribute turns into a packet like object with these fields:

def multizone_effect_parameters_for(typ):
    if typ is enums.MultiZoneEffectType.MOVE:
        yield ("parameter1", T.Reserved(32))
        yield ("speed_direction", T.Uint32.enum(enums.Direction).default(enums.Direction.RIGHT))
        for i in range(6):
            yield ("parameter{0}".format(i + 2), T.Reserved(32))
    else:
        for i in range(8):
            yield ("parameter{0}".format(i), T.Reserved(32))

Using transformer:

lambda pkt: multizone_effect_parameters_for(pkt.type)))

You set parameters with a dictionary of fields and you can access the additional fields on parameters as if parameters is it’s own packet with those fields

MultiZoneMessages.SetExtendedColorZones

float - duration

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))
uint8 - apply

default: photons_messages.enums.MultiZoneExtendedApplicationRequest. APPLY

enum: This attribute is a photons_messages.enums.MultiZoneExtendedApplicationRequest

uint16 - zone_index

This attribute has no transformations

uint8 - colors_count

This attribute has no transformations

bytes[656] - colors

multiple: This attribute turns into an array of 82 Color objects

MultiZoneMessages.GetExtendedColorZones

This packet has no fields

MultiZoneMessages.StateExtendedColorZones

uint16 - zones_count

This attribute has no transformations

uint16 - zone_index

This attribute has no transformations

uint8 - colors_count

This attribute has no transformations

bytes[656] - colors

multiple: This attribute turns into an array of 82 Color objects

TileMessages

TileMessages.GetDeviceChain

This packet has no fields

TileMessages.StateDeviceChain

uint8 - start_index

This attribute has no transformations

bytes[880] - tile_devices

multiple: This attribute turns into an array of 16 Tile objects

uint8 - tile_devices_count

This attribute has no transformations

TileMessages.SetUserPosition

uint8 - tile_index

This attribute has no transformations

float - user_x

This attribute has no transformations

float - user_y

This attribute has no transformations

TileMessages.Get64

uint8 - tile_index

This attribute has no transformations

uint8 - length

This attribute has no transformations

uint8 - x

This attribute has no transformations

uint8 - y

This attribute has no transformations

uint8 - width

This attribute has no transformations

TileMessages.State64

uint8 - tile_index

This attribute has no transformations

uint8 - x

This attribute has no transformations

uint8 - y

This attribute has no transformations

uint8 - width

This attribute has no transformations

bytes[512] - colors

multiple: This attribute turns into an array of 64 Color objects

TileMessages.Set64

uint8 - tile_index

This attribute has no transformations

uint8 - length

This attribute has no transformations

uint8 - x

This attribute has no transformations

uint8 - y

This attribute has no transformations

uint8 - width

This attribute has no transformations

float - duration

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))
bytes[512] - colors

multiple: This attribute turns into an array of 64 Color objects

TileMessages.GetTileEffect

This packet has no fields

TileMessages.SetTileEffect

uint32 - instanceid

default: randomly generated number

uint8 - type

default: photons_messages.enums.TileEffectType. OFF

enum: This attribute is a photons_messages.enums.TileEffectType

float - speed

default: 5

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1000 * float(value))
float - duration

default: 0

transformed: Value you provide is changed for the binary packet:

lambda _, value: int(1e9 * float(value))
bytes[32] - parameters

dynamic: This attribute turns into a packet like object with these fields:

def tile_effect_parameters_for(typ):
    for i in range(8):
        yield ("parameter{0}".format(i), T.Reserved(32))

Using transformer:

lambda pkt: tile_effect_parameters_for(pkt.type)))

You set parameters with a dictionary of fields and you can access the additional fields on parameters as if parameters is it’s own packet with those fields

uint8 - palette_count

This attribute has no transformations

bytes[128] - palette

multiple: This attribute turns into an array of 16 Color objects

TileMessages.StateTileEffect

uint32 - instanceid

default: randomly generated number

uint8 - type

default: photons_messages.enums.TileEffectType. OFF

enum: This attribute is a photons_messages.enums.TileEffectType

float - speed

default: 5

transformed: value from the packet is changed for you:

lambda _, value: float(value) / 1000
float - duration

default: 0

transformed: value from the packet is changed for you:

lambda _, value: float(value) / 1e9
bytes[32] - parameters

dynamic: This attribute turns into a packet like object with these fields:

def tile_effect_parameters_for(typ):
    for i in range(8):
        yield ("parameter{0}".format(i), T.Reserved(32))

Using transformer:

lambda pkt: tile_effect_parameters_for(pkt.type)))

You set parameters with a dictionary of fields and you can access the additional fields on parameters as if parameters is it’s own packet with those fields

uint8 - palette_count

This attribute has no transformations

bytes[128] - palette

multiple: This attribute turns into an array of 16 Color objects

Message Objects

photons_messages.fields.Color

float - hue

This attribute has no transformations

float - saturation

This attribute has no transformations

float - brightness

This attribute has no transformations

uint16 - kelvin

default: 3500

photons_messages.fields.Tile

int16 - accel_meas_x

This attribute has no transformations

int16 - accel_meas_y

This attribute has no transformations

int16 - accel_meas_z

This attribute has no transformations

float - user_x

This attribute has no transformations

float - user_y

This attribute has no transformations

uint8 - width

This attribute has no transformations

uint8 - height

This attribute has no transformations

uint32 - device_version_vendor

This attribute has no transformations

uint32 - device_version_product

This attribute has no transformations

uint32 - device_version_version

default: 0

uint64 - firmware_build

This attribute has no transformations

uint16 - firmware_version_minor

This attribute has no transformations

uint16 - firmware_version_major

This attribute has no transformations

Enums

Some fields are enum values. This means the value on the binary packet is a number, but Photons lets you interact with them as Enum values. This means whenever you access an attribute that is an enum, you’ll get back the enum value rather than the number.

When you set a enum value you can use either the enum value itself, the number that enum represents, or the name of the value. For example all of the following do the same thing.

msg = TileMessages.SetTileEffect(...)

# These are all valid ways of setting an enum value.
msg.type = TileEffectType.MORPH
msg.type = "MORPH"
msg.type = 2

# The value photons gives back to you is always an enum value
assert msg.type is TileEffectType.MORPH

Photons will complain if the value you set is not a valid value for that type.

photons_messages.enums.Services

  • 1 : UDP

photons_messages.enums.Direction

  • 0 : RIGHT

  • 1 : LEFT

photons_messages.enums.MultiZoneApplicationRequest

  • 0 : NO_APPLY

  • 1 : APPLY

  • 2 : APPLY_ONLY

photons_messages.enums.MultiZoneEffectType

  • 0 : OFF

  • 1 : MOVE

photons_messages.enums.MultiZoneExtendedApplicationRequest

  • 0 : NO_APPLY

  • 1 : APPLY

  • 2 : APPLY_ONLY

photons_messages.enums.TileEffectType

  • 0 : OFF

  • 2 : MORPH

  • 3 : FLAME

photons_messages.enums.Waveform

  • 0 : SAW

  • 1 : SINE

  • 2 : HALF_SINE

  • 3 : TRIANGLE

  • 4 : PULSE