LIFX discovery on iOS with GCDAsyncUdpSocket

Hey guys,

I am starting to implement LIFX bulbs into my home automation app, Home Remote.

I have integrated milight, which also work over UDP so figured this wouldn’t be too hard… Famous last words eh?

I have created a demo project that I figured would work, but I am a little lost with some of the packet documentation.

My class is here: https://gist.github.com/griches/07c921f6998e9148792b

I am not getting anything back from the bulbs, even though I can see UDP packets going out with Wireshark.

I’m guessing either the protocol is wrong or that I should be sending type, although I find the documentation on these unclear at best.

Can anyone see anything obviously wrong?

Thanks.

So I have managed to turn my lights green using the example, so I found out I need to specify type as 2 to send a GetService.

My packet looks like this:

24 00 00 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00

I do not get any responses. It says no payload is required. Is this correct?

Following on again, it seems I maybe do get some responses back. The NSData description when logged out is:

29 00 00 54 00 00 00 00 d0 73 d5 11 97 bd 00 00 4c 49 46 58 56 32 00 00 90 80 27 7a 22 dd 2a 14 03 00 00 00 01 7c dd 00 00

32 00 00 54 42 52 4b 52 d0 73 d5 11 97 bd 00 00 4c 49 46 58 56 32 00 00 90 ae ef 88 22 dd 2a 14 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 3e 11

Neither of these convert to strings to provide a port or service

Sorry for all the self replies, but hopefully this will help others. The address data returned is:

10 02 dd 7c c0 a8 00 41 00 00 00 00 00 00 00 00

I have noticed that the IP address of the bulb is in the address data… although I’m still not sure how to extract this.

EDIT… Ahh, I think I get it, it’s the same set up as the frame I send…

10 is the size (in hex)
02 is the type
DD 7C is the port
c0 a8 00 41 is the IP

Although I’m still not sure how to get this data out and broken up.

Generally I would suggest making a struct for each of the payload the same way you have done for the frame. Then you can extract the data by loading it into the struct.

Another thing I notice you are doing is binding on port 56700. You can actually bind on any port you like, and as long as you set the source field and send using the bound socket then the bulb will return the packets to that port. I suggest you bind on an ephemeral port (check your OS instructions on how to do this).

I’ll list the decodes from my python library for all the packets you posted for you to check against:

1.

24 00 00 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00

lifx_packet(frame_header=frame_header(size=36, origin=0, tagged=1, addressable=1, protocol=1024, source=0), frame_address=frame_address(target=0, reserved1=0, reserved2=0, ack_required=0, res_required=0, sequence=0), protocol_header=protocol_header(reserved1=0, pkt_type=2, reserved2=0), payload=payload_getservice())

2.

29 00 00 54 00 00 00 00 d0 73 d5 11 97 bd 00 00 4c 49 46 58 56 32 00 00 90 80 27 7a 22 dd 2a 14 03 00 00 00 01 7c dd 00 00

lifx_packet(frame_header=frame_header(size=41, origin=1, tagged=0, addressable=1, protocol=1024, source=0), frame_address=frame_address(target=208456536912848, reserved1=55346429577548, reserved2=0, ack_required=0, res_required=0, sequence=0), protocol_header=protocol_header(reserved1=1453216969928442000, pkt_type=3, reserved2=0), payload=payload_stateservice(service=1, port=56700))

3.

32 00 00 54 42 52 4b 52 d0 73 d5 11 97 bd 00 00 4c 49 46 58 56 32 00 00 90 ae ef 88 22 dd 2a 14 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 3e 11

lifx_packet(frame_header=frame_header(size=50, origin=1, tagged=0, addressable=1, protocol=1024, source=1380667970), frame_address=frame_address(target=208456536912848, reserved1=55346429577548, reserved2=0, ack_required=0, res_required=0, sequence=0), protocol_header=protocol_header(reserved1=1453216970176442000, pkt_type=13, reserved2=0), payload=payload_statehostinfo(signal=0, tx=0, rx=0, reserved=4414))

Awesome. Thanks. I have made quite a bit of progress since then, but didn’t realise you could push the data back in to a struct, that’s a huge help.

Thanks for the info about putting it into a struct. That was super helpful.

My bulb’s MAC address is D0 73 D5 11 97 BD but when I log it back from the struct’s target I get:

([0] = ‘\xd0’, [1] = ‘s’, [2] = ‘\xd5’, [3] = ‘\x11’, [4] = ‘\x97’, [5] = ‘\xbd’

Why is there an ‘s’ there instead of 73, everything else is correct.

Might it be because my struct is badly set up?

I’m quite new to C programming but reading the docs for StateService I figured the service and port were the payload. I build a struct like this for it this:

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 reserved_header:16;
/
variable length payload follows */
uint8_t service;
uint32_t port;
} stateService;

But when I check the data size against the struct size it’s incorrect: data size (41) isn’t equal to struct size (44)

‘s’ = 0x73 = 115 - They are all the same thing. Whatever you are using to view the data is showing the ASCII representation when it can. Nothing is wrong there.

Ah man, so my data was fine but my viewer was interpreting it incorrectly. Thanks. Just checked and it’s all good.

Any ideas on the struct question at the end of my post?

Can’t help you there except to confirm that the packet size for the StateService response (device message 3) is 41 bytes which includes a 5 byte payload.

You are running into an issue with memory alignment. Normally when you create a structure your C compiler will add padding in between bytes in order to optimise memory access. However when working with network protocols you usually want to optimise for structure size instead, so you dont want any padding.

Luckily it is fairly easy to tell the compiler to do this. Most compilers will have a preprocessor argument that changes the packing of the structure, for example here is the one for GCC (which is actually inherited from Microsoft compilers). Many other compilers support the same method as GCC, but you should check your compiler documentation. You will want to pack with single byte alignment.

If you want an example the header we list at the bottom of the header description page looks like this:

#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)

Its the #pragma pack(push, 1) and #pragma (pop) lines that are responsible for setting (and unsetting) single byte packing.

Also, I wouldn’t make the full structure again for each message. Instead I would get the first 36 bytes and apply them to the struct with the headers. Then once you check what type it is apply the rest of the bytes to a struct that only contains the payload. That prevents you making many structs all with the same data. Does that make sense?

Thanks. Making a series of payload structs sounds like a good idea… I just need to work out how :slight_smile:

Thanks for all your help Daniel, I now have it sorted and pushing in to the main struct and individual payload structs.

I must say, when I first started working with LIFX I thought the implementation was an absolute ball ache and quite obtuse, but as I’ve carried on and started to understand it a little more I must say it is actually quite nice. It’s lightweight, fast and has all the information you need without any fluff. So well done :slightly_smiling:

I do think the documentation could be a little better… not better… easier to understand, with some basic examples for all the major platforms though, as once you “get it” it’s actually simple (again, famous last words :))

1 Like

No worries, I’m here to help. I can’t take credit for the protocol though, that is the work of our firmware engineers. I’ll forward your feedback on to them.

The documentations is currently a first cut that was written in order to get the protocol in the hands of third party developers as quick as possible. We are currently using the community here as a measure of what parts are missing and where we should focus for future updates. So thanks for your feedback!

Working a treat :slight_smile:

Now to add it in to my Home Automation app!

Hi bro, how did you do that? Can share?

Hey Samho, it is just a case of following their links their documentation. There is a level of understanding required, which you’ll get by following through the entire process.