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.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
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
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.SetReboot
This packet has no fields
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(round(0x10000 * (0 if v is sb.NotSpecified else float(v)) / 360)) % 0x10000
- float - saturation
transformed: Value you provide is changed for the binary packet:
lambda _, v: int(round(0xFFFF * (0 if v is sb.NotSpecified else float(v))))
- float - brightness
transformed: Value you provide is changed for the binary packet:
lambda _, v: int(round(0xFFFF * (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(round(0x10000 * (0 if v is sb.NotSpecified else float(v)) / 360)) % 0x10000
- float - saturation
transformed: Value you provide is changed for the binary packet:
lambda _, v: int(round(0xFFFF * (0 if v is sb.NotSpecified else float(v))))
- float - brightness
transformed: Value you provide is changed for the binary packet:
lambda _, v: int(round(0xFFFF * (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
unknown enums: This attribute allows values that aren’t part of the enum
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(round(0x10000 * (0 if v is sb.NotSpecified else float(v)) / 360)) % 0x10000
- float - saturation
optional: This attribute is optional
transformed: Value you provide is changed for the binary packet:
lambda _, v: int(round(0xFFFF * (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(round(0xFFFF * (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
unknown enums: This attribute allows values that aren’t part of the enum
- 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
LightMessages.GetHevCycle
This packet has no fields
LightMessages.SetHevCycle
- boolint - enable
This attribute has no transformations
- uint32 - duration_s
This attribute has no transformations
LightMessages.StateHevCycle
- uint32 - duration_s
This attribute has no transformations
- uint32 - remaining_s
This attribute has no transformations
- boolint - last_power
This attribute has no transformations
LightMessages.GetHevCycleConfiguration
This packet has no fields
LightMessages.SetHevCycleConfiguration
- boolint - indication
This attribute has no transformations
- uint32 - duration_s
This attribute has no transformations
LightMessages.StateHevCycleConfiguration
- boolint - indication
This attribute has no transformations
- uint32 - duration_s
This attribute has no transformations
LightMessages.GetLastHevCycleResult
This packet has no fields
LightMessages.StateLastHevCycleResult
- uint8 - result
enum: This attribute is a photons_messages.enums.LightLastHevCycleResult
unknown enums: This attribute allows values that aren’t part of the enum
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(round(0x10000 * (0 if v is sb.NotSpecified else float(v)) / 360)) % 0x10000
- float - saturation
transformed: Value you provide is changed for the binary packet:
lambda _, v: int(round(0xFFFF * (0 if v is sb.NotSpecified else float(v))))
- float - brightness
transformed: Value you provide is changed for the binary packet:
lambda _, v: int(round(0xFFFF * (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
unknown enums: This attribute allows values that aren’t part of the enum
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: round(float(v) * 360 / 0x10000, 2)
- float - saturation
transformed: value from the packet is changed for you:
lambda _, v: round(float(v) / 0xFFFF, 4)
- float - brightness
transformed: value from the packet is changed for you:
lambda _, v: round(float(v) / 0xFFFF, 4)
- 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
unknown enums: This attribute allows values that aren’t part of the enum
- 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 (f"parameter{i + 2}", T.Reserved(32)) else: for i in range(8): yield (f"parameter{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 ifparameters
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
unknown enums: This attribute allows values that aren’t part of the enum
- 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 (f"parameter{i + 2}", T.Reserved(32)) else: for i in range(8): yield (f"parameter{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 ifparameters
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
unknown enums: This attribute allows values that aren’t part of the enum
- 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
RelayMessages
RelayMessages.GetRPower
- uint8 - relay_index
This attribute has no transformations
RelayMessages.SetRPower
- uint8 - relay_index
This attribute has no transformations
- uint16 - level
This attribute has no transformations
RelayMessages.StateRPower
- uint8 - relay_index
This attribute has no transformations
- uint16 - level
This attribute has no transformations
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
unknown enums: This attribute allows values that aren’t part of the enum
- 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): if typ is enums.TileEffectType.SKY: yield ( "sky_type", T.Uint8.enum(enums.TileEffectSkyType).default(enums.TileEffectSkyType.CLOUDS), ) yield ("parameter2", T.Reserved(24)) yield ("cloud_saturation_min", T.Uint8.default(51)) yield ("parameter4", T.Reserved(24)) yield ("cloud_saturation_max", T.Uint8.default(178)) yield ("parameter6", T.Reserved(184)) else: for i in range(8): yield (f"parameter{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 ifparameters
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
unknown enums: This attribute allows values that aren’t part of the enum
- 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): if typ is enums.TileEffectType.SKY: yield ( "sky_type", T.Uint8.enum(enums.TileEffectSkyType).default(enums.TileEffectSkyType.CLOUDS), ) yield ("parameter2", T.Reserved(24)) yield ("cloud_saturation_min", T.Uint8.default(51)) yield ("parameter4", T.Reserved(24)) yield ("cloud_saturation_max", T.Uint8.default(178)) yield ("parameter6", T.Reserved(184)) else: for i in range(8): yield (f"parameter{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 ifparameters
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
- 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.LightLastHevCycleResult
0 : SUCCESS
1 : BUSY
2 : INTERRUPTED_BY_RESET
3 : INTERRUPTED_BY_HOMEKIT
4 : INTERRUPTED_BY_LAN
5 : INTERRUPTED_BY_CLOUD
255: NONE
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.TileEffectSkyPalette
0 : CLOUDS_SKY
1 : NIGHT_SKY
2 : DAWN_SKY
3 : DAWN_SUN
4 : FULL_SUN
5 : FINAL_SUN
6 : NUM_COLOURS
photons_messages.enums.TileEffectSkyType
0 : SUNRISE
1 : SUNSET
2 : CLOUDS
photons_messages.enums.TileEffectType
0 : OFF
2 : MORPH
3 : FLAME
5 : SKY
photons_messages.enums.Waveform
0 : SAW
1 : SINE
2 : HALF_SINE
3 : TRIANGLE
4 : PULSE