Using the "Source" and "Target" fields in the LAN protocol

How necessary / advantageous is it to use these fields?

I have had some success with the “target” field set to 0, both in broadcasting packets to all bulbs on my network and in sending packets to a specific bulb’s IP address - but I’m wondering if I would get quicker/more reliable responses if I set the target field properly?

Also, I am not sure how the “source” field is meant to work. Is it supposed to be a purely arbitrary integer - i.e. just pick any number you want? Or is it supposed to be the MAC address or IP address (or something) of the client? If I send a Device::GetService message and the “source” is set to anything other than 0, the bulbs reply with a series of weird messages (half of which are undocumented), instead of with a Device::StateService message. So currently I’m just setting “source” to 0 in all my messages. Am I missing something?

Okay, so the target field is mainly a leftover from an old version of the protocol. If you only want a single bulb to process the command, you can set the mac address in the target field and set the tagged field (in the frame header) to zero. This way you can broadcast the packet and only have one bulb process it (if for example you don’t have time to discover the bulb but you know its MAC). Of course the other way to ensure a single bulb processes a packet is to send that packet unicast to the bulb. You can use both of these methods at the same time if you wish (unicast the message, set the target and set tagged to zero). We have found that on most consumer routers and consumer networks that broadcasts are fairly unreliable and should be avoided where possible.

The source field is just an identifier for your client. I could go into how it actually works but it is used internally to decide where to send packets. You should be setting it to a random number (per client instance, not per message) that is greater than zero so that requests are sent directly to you and not broadcast on the network. This will make gathering responses from your commands more reliable.

I’m not sure why you are getting weird messages back. Would you be able to post a few here? Or if you prefer to keep packet dumps private create a support ticket with us by emailing us at developers@lifx.com and we can help you off forums.

(By the way, this is using my own code for packet creation, parsing, sending and receiving - so it may well be due to some kind of packet creation bug that’s entirely my own fault. The output below is a (slightly cleaned up) dump of what my code prints out into the terminal.)

OK, so this is what happens when I broadcast a packet with source set to 0

Frame header: size 36 origin 0 tagged 1 addressable 1 protocol 1024 source 0
Frame address: target 0 reserved0 0 reserved1 0 ack_required 1 res_required 1 sequence 0
Protocol header: reserved2 0 message_type 2 reserved3 0

Sent message type: 2 (Device::GetService)
To: 192.168.0.255 56700
Payload: {}

Waiting for 1 message(s) of type 3

Received message from: 192.168.0.53 56700
Frame header: size 36 origin 1 tagged 0 addressable 1 protocol 1024 source 0
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437029037422000004 message_type 45 reserved3 0
Message type: 45 (Device::Acknowledgement)
Payload: {}

Received message from: 192.168.0.53 56700
Frame header: size 41 origin 1 tagged 0 addressable 1 protocol 1024 source 0
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437029037431000004 message_type 3 reserved3 0
Message type: 3 (Device::StateService)
Payload: {'port': 56700, 'service': 1}

Which is all present and correct. But if I broadcast the same message with source 12345, I don’t receive either an acknowledgement or a response. If I keep the socket listening for a few minutes instead of timing it out, eventually I get something like this:

Frame header: size 36 origin 0 tagged 1 addressable 1 protocol 1024 source 12345
Frame address: target 0 reserved0 0 reserved1 0 ack_required 1 res_required 1 sequence 0
Protocol header: reserved2 0 message_type 2 reserved3 0

Sent message type: 2 (Device::GetService)
To: 192.168.0.255 56700
Payload: {}

Waiting for 1 message(s) of type 3

Received message from: 192.168.0.53 56700
Frame header: size 100 origin 1 tagged 0 addressable 1 protocol 1024 source 1380667970
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437030738160000004 message_type 59 reserved3 0
Message type: 59 (Device::EchoResponse)
Payload: {'random payload': bytearray(b'LIFXU\xae\xe1c')}

Received message from: 192.168.0.53 56700
Frame header: size 88 origin 1 tagged 0 addressable 1 protocol 1024 source 1380667970
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437030738177000004 message_type 107 reserved3 0
Message type: 107 (Light::State)
Payload: {'hue': 0, 'saturation': 0, 'brightness': 65535, 'kelvin': 3500, 'power_level': 65535, 'reserved1': 0, 'reserved0': 0, 'label': bytearray(b'Downlight 3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')}

Received message from: 192.168.0.53 56700
Frame header: size 38 origin 1 tagged 0 addressable 1 protocol 1024 source 1380667970
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437030740160000004 message_type 111 reserved3 0
Message type: 111 (unknown message type)
Payload: {}

Received message from: 192.168.0.53 56700
Frame header: size 50 origin 1 tagged 0 addressable 1 protocol 1024 source 1380667970
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437030742209000004 message_type 13 reserved3 0
Message type: 13 (Device::StateHostInfo)
Payload: {'reserved': 5600, 'signal': 0.0, 'rx': 0, 'tx': 0}

Received message from: 192.168.0.53 56700
Frame header: size 50 origin 1 tagged 0 addressable 1 protocol 1024 source 1380667970
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437030744209000004 message_type 17 reserved3 0
Message type: 17 (Device::StateWifiInfo)
Payload: {'reserved': 0, 'signal': 3.1622775509276835e-07, 'rx': 4633, 'tx': 5786}

Received message from: 192.168.0.53 56700
Frame header: size 40 origin 1 tagged 0 addressable 1 protocol 1024 source 1380667970
Frame address: target 151646752830416 reserved0 55346429577548 reserved1 0 ack_required 0 res_required 0 sequence 0
Protocol header: reserved2 1437030746209000004 message_type 406 reserved3 0
Message type: 406 (unknown message type)
Payload: {}

I’m guessing that maybe these messages aren’t actually a response to my message at all - they’re responding to something else that’s sending messages to my bulbs - like maybe Lifx Cloud is checking in with the bulbs or something?

But regardless, that doesn’t explain why I don’t get an acknowledgment or a StateService message in response if I set the source to 12345 instead of 0 in my GetService message.

You really are on the ball! Those messages you are seeing are indeed the LIFX Cloud monitoring the bulbs to ensure their connection is still live.

One thing you should note is that when you set the source port, the responses are sent with the destination port set to the source port that they came from. Most operating systems will assign a random high numbered port to sockets when they are created, and this is where the packets will be sent back to. In the python SDK I’m building I solve this problem by sending the packets from the same socket that I have bound for listening.

1 Like

Ah, good stuff - using the same socket for both sending and receiving completely solves the problem - now I am getting the response regardless of whether I set the “source” field or not. Thank you!

Perhaps the documentation could be updated slightly to make it clear that the message is unicast to the same port that the client’s message came from.

If the source identifier is a non-zero value, then the LIFX device will send a unicast message back to the same IP address and port that the client used to send the originating message.

If the source identifier is a zero value, then the LIFX device may send a broadcast message that can be received by all clients on the same sub-net. See ack_required and res_required fields in the Frame Address.

(Or perhaps this is totally obvious to non-noobs and doesn’t need to be said!)

Cheers, I’ll go update the documentation now. Next time you check, it’ll be there. :smiley:

One other thing that I think is a little unclear in the documentation:

When a client sends a packet to a device, the packet’s “target” field refers to the MAC address of the device.

But when a device sends a packet to a client, the packet’s “target” field does not refer the MAC address of the client - it refers to the MAC address of the device itself.

In this case, the name “target” is a little confusing, because the field has nothing to do with the target of the device’s message. In fact, it’s really the MAC address of the source of that message.

To make this clearer, maybe we could say something like this in the header documentation:

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. For discovery using Device::GetService the tagged field should be set to one (1) and the target should be all zeroes. The device will then respond with a Device::StateService message, which will include its own MAC address in the target field. In all subsequent messages that the client sends to the device, the target field should be set to the device MAC address, and the tagged field should be set to zero (0).

You are right, it can be a touch confusing. I like to think of the them as always being from the view of the client. So when you get a message from a device the target field specifies which target is responding.

The changes you suggested really help make the situation clear. I’ll go apply them now. Thanks for your contribution.

Thanks! I think my general confusion about these fields stemmed from a number of things:

  1. The fact that, as you say, the fields are always named from the client’s perspective, not the device’s (or sender’s) perspective.
  2. The “source” field being a random number, while the “target” is actually a significant number (a MAC address). I think I was assuming that the “target” field in the messages from the bulbs was just their version of a random “source” number.
  3. My code printing out the “target” field as a decimal rather than a hex value. I think if I’d chosen to print it in hex I would have realised much sooner that the bulbs were sending their own MAC address in that field.

Thanks for updating the docs, hopefully this will help other noobs like myself avoid making similar blunders.