LIFX Developer Zone

Sending LAN packet using Arduino


#1

Bit of a long shot, but has anyone had any success sending a UDP packet of the LAN Protocol on an Arduino?

I’ve been trying to achieve LIFX lighting control on the Arduino platform but have kept coming up short porting example code to the platform. I can achieve LAN control using both C# and C++ on a Windows platform but Arduino uses a stripped down version of C++ so the code doesn’t port that well at all. Therefore I have resorted to trying to use Arduino’s native UDP library but that also has seemed to fail miserably.

So getting back to my main port, has anyone had any success performing basic LIFX functions on an arduino and, if so, could you possibly point me in the right direction?

Thank you for the help~!


In case anyone was interested, this was the native UDP attempt I have been working on (nothing seems to happen):

#include <SPI.h>        
#include <Ethernet.h>
#include <EthernetUdp.h>

#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 */
} lifx_header;
#pragma pack(pop)

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 177);

bool first = false;
unsigned int localPort = 56700;
char packetBuffer[sizeof(lifx_header)];
EthernetUDP Udp;

void setup() {
  // put your setup code here, to run once:
  Ethernet.begin(mac,ip);
  Serial.begin(19200);
  Udp.begin(localPort);
}

void loop() {
  // put your main code here, to run repeatedly:
   if (first == false)
  {
    int repstatus;
    lifx_header header;
  
    memset(&header, 0, sizeof(lifx_header));

    header.size = sizeof(lifx_header);
    header.origin = 0;
    header.tagged = 1;
    header.addressable = 1;
    header.protocol = 1024;
    header.source = 123;
    header.target[0] = 0;
    header.target[1] = 0;
    header.target[2] = 0;
    header.target[3] = 0;
    header.target[4] = 0;
    header.target[5] = 0;
    header.target[6] = 0;
    header.target[7] = 0;
    header.ack_required = 1;
    header.res_required = 0;
    header.sequence = 100;
    header.type = 21; //SetPower
    
    uint16_t payload = 65534;

    //Send Packet
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());

    Udp.write(header.size);
    Udp.write(header.origin);
    Udp.write(header.tagged);
    Udp.write(header.addressable);
    Udp.write(header.protocol);
    Udp.write(header.source);
    Udp.write(header.target[0]);
    Udp.write(header.target[1]);
    Udp.write(header.target[2]);
    Udp.write(header.target[3]);
    Udp.write(header.target[4]);
    Udp.write(header.target[5]);
    Udp.write(header.target[6]);
    Udp.write(header.target[7]);
    Udp.write(header.ack_required);
    Udp.write(header.res_required);
    Udp.write(header.sequence);
    Udp.write(header.type);
    Udp.write(payload);

    Udp.endPacket();
    delay(10);

    //Print Response to Console
    lifx_header recv_header;
    int packetSize = Udp.parsePacket();
    
    if (packetSize) {
      Udp.read(packetBuffer, sizeof(lifx_header));
      Serial.println("Contents:");
      Serial.println(packetBuffer); //Print response to console
    }
  
    first = true;
  }
}

Need to simulate - day in an hour
#2

You were really close, but you code had a few issues:

  • You were using Udp.remoteIP(), which gives you the last ip address that sent you a packet, so you were sending packets to some random machine on the network.
  • You were sending the header in lots of seperate parts, which makes Arduino turn each into a single byte.
  • The size field needs to be set to the size of the headers, plus the size of the payload
  • You should read the whole packet at once, not just the headers, that way you can read the payload with ((lifx_payload_something*)packetBuffer + sizeof(lifx_header))->field_name.

Anyway, I rewrote the example you gave me, so that it sends a Device::SetPower to the whole network every 5 seconds and has the effect of turning on every bulb. It also listens to the replies and prints the reply type to the serial console as it receives them.

#include <SPI.h>        
#include <Ethernet.h>
#include <EthernetUdp.h>

// The LIFX Header structure
#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 */
} lifx_header;
#pragma pack(pop)

// Device::SetColor Payload
#pragma pack(push, 1)
typedef struct {
  uint16_t level;
} lifx_payload_device_set_power;
#pragma pack(pop)

// Payload types
#define LIFX_DEVICE_SETPOWER 21

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

unsigned int localPort = 8888;  // Port to listen for responses on
unsigned int lifxPort  = 56700; // Port used to send to send to LIFX devices

// Remote IP (In this case we broadcast to the entire subnet)
IPAddress broadcast_ip(255, 255, 255, 255);

// Packet buffer size
#define LIFX_INCOMING_PACKET_BUFFER_LEN 300

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

// Timing data
unsigned long sendInterval = 5000; // 5 seconds

unsigned long lastSend = 0;

void setup() {
  Serial.begin(19200);
  Ethernet.begin(mac);  // Start Ethernet, using DHCP
  Udp.begin(localPort); // Listen for incoming UDP packets
}

void loop() {
  unsigned long currentTime = millis();
  
  // If 5 seconds have passed, send another packet
  if (currentTime - lastSend >= sendInterval) {
    lastSend = currentTime;
    
    lifx_header header;
    lifx_payload_device_set_power payload;
    
    // Initialise both structures
    memset(&header, 0, sizeof(header));
    memset(&payload, 0, sizeof(payload));
    
    // Setup the header
    header.size = sizeof(lifx_header) + sizeof(payload); // Size of header + payload
    header.tagged = 1;
    header.addressable = 1;
    header.protocol = 1024;
    header.source = 123;
    header.ack_required = 1;
    header.sequence = 100;
    header.type = LIFX_DEVICE_SETPOWER;
    
    // Setup payload
    payload.level = 65535;
    
    // Send a packet on startup
    Udp.beginPacket(broadcast_ip, 56700);
    Udp.write((char *) &header, sizeof(lifx_header)); // Treat the structures like byte arrays
    Udp.write((char *) &payload, sizeof(payload));    // Which makes the data on wire correct
    Udp.endPacket();
  }
  
  // Check for incoming packets
  int packetLen = Udp.parsePacket();
  byte packetBuffer[LIFX_INCOMING_PACKET_BUFFER_LEN];
  if (packetLen && packetLen < LIFX_INCOMING_PACKET_BUFFER_LEN) {
    Udp.read(packetBuffer, sizeof(packetBuffer));
    Serial.print("Received Packet type: ");
    Serial.println(((lifx_header *)packetBuffer)->type);
  }
}

#3

Thanks Daniel! That has been a huge help and I have been able to get messages to my lights now based on the example you gave.

Based on your provided code, I am trying to create a function to return a light’s power level. I seem to be having some interesting issues with this as I cannot seem to get the packet to stay in a variable. What I mean by that is I set the variable from the received packet but the value stays at a random value no matter what the lights actually respond. However when I print the variable from within the function, it returns just fine.

It’s sort of hard to explain what’s happening because I get very different results when I try and change my code to further debug the issue. Any insight would be greatly appreciated. >.<

uint16_t GetPower(uint8_t *dest)
{

uint16_t power = 0;

while (transmit == false)
{
transmit = true;
lifx_header header;

// Initialise structures
memset(&header, 0, sizeof(header));

int tag = 0;
if (dest[0] == 0x00 && dest[5] == 0x00)
{
tag = 1;
}

// Setup the header
header.size = sizeof(lifx_header); // Size of header + payload
header.tagged = tag;
header.addressable = 1;
header.protocol = 1024;
header.source = 123;
header.target[0] = dest[0];
header.target[1] = dest[1];
header.target[2] = dest[2];
header.target[3] = dest[3];
header.target[4] = dest[4];
header.target[5] = dest[5];
header.target[6] = 0x00;
header.target[7] = 0x00;
header.res_required = 1;
header.ack_required = 0;
header.sequence = 100;
header.type = LIFX_DEVICE_GETPOWER; //Added correct structures to code

// Send a packet on startup
Udp.beginPacket(broadcast_ip, 56700);
Udp.write((char *) &header, sizeof(lifx_header)); // Treat the structures like byte arrays
Udp.endPacket();

// Check for response packet
int packetLen = Udp.parsePacket();
byte packetBuffer[LIFX_INCOMING_PACKET_BUFFER_LEN];
if (packetLen && packetLen < LIFX_INCOMING_PACKET_BUFFER_LEN) {
  Udp.read(packetBuffer, sizeof(packetBuffer));
}
delay(1);

power = ((lifx_payload_device_power *)packetBuffer + sizeof(lifx_header))->level;
Serial.println(power); //returns 65535
}

transmit = false;
return power; //returns random (but consistent) int
}

#4

I haven’t been able to get your code going either, here’s one I wrote myself:

#include <SPI.h>        
#include <Ethernet.h>
#include <EthernetUdp.h>

// ---- Network Settings ----

// Our MAC address
uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

// Port we listen to
unsigned int localPort = 8888;

// Port for talking to LIFX devices
unsigned int lifxPort  = 56700;

// Remote IP (In this case we broadcast to the entire subnet)
IPAddress broadcast_ip(255, 255, 255, 255);

// --- LIFX Protocol ---

// The LIFX Header structure
#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 */
} lifx_header;
#pragma pack(pop)

// Device::SetPower Payload
#pragma pack(push, 1)
typedef struct {
  uint16_t level;
} lifx_payload_device_set_power;
#pragma pack(pop)

// Device::StatePower Payload
#pragma pack(push, 1)
typedef struct {
  uint16_t level;
} lifx_payload_device_state_power;
#pragma pack(pop)

// Payload types
#define LIFX_DEVICE_GETPOWER 20
#define LIFX_DEVICE_SETPOWER 21
#define LIFX_DEVICE_STATEPOWER 22

// Packet buffer size
#define LIFX_INCOMING_PACKET_BUFFER_LEN 300

// Length in bytes of serial numbers
#define LIFX_SERIAL_LEN 6

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

// Timing data
unsigned long sendInterval = 30000; // 30 seconds
unsigned long timeoutInterval = 500;

unsigned long lastSend = 0;

void setup() {
  Serial.begin(19200);
  Serial.print("\n\n\n\n");
  Serial.println("Setting up network");
  Ethernet.begin(mac);  // Start Ethernet, using DHCP
  Udp.begin(localPort); // Listen for incoming UDP packets
  Serial.println("Network is set up");
}

void loop() {
  uint8_t dest[] = {0xd0, 0x73, 0xd5, 0x12, 0x0e, 0x95};
  unsigned long currentTime = millis();
  
  // If 30 seconds have passed, send another packet
  if (currentTime - lastSend >= sendInterval) {
    lastSend = currentTime;
    
    Serial.print("Power level: ");
    Serial.println(GetPower(dest));
  }
  
  Ethernet.maintain();
}

uint16_t GetPower(uint8_t *dest) {
  uint16_t power = 0;
  
  lifx_header header;
  
  // Initialise both structures
  memset(&header, 0, sizeof(header));
  
  // Set the target the nice way
  memcpy(header.target, dest, sizeof(uint8_t) * LIFX_SERIAL_LEN);
  
  // Setup the header
  header.size = sizeof(lifx_header); // Size of header + payload
  header.tagged = 0;
  header.addressable = 1;
  header.protocol = 1024;
  header.source = 123;
  header.ack_required = 0;
  header.res_required = 1;
  header.sequence = 100;
  header.type = LIFX_DEVICE_GETPOWER;
  
  // Send a packet on startup
  Udp.beginPacket(broadcast_ip, 56700);
  Udp.write((char *) &header, sizeof(lifx_header));
  Udp.endPacket();
  
  unsigned long started = millis();
  while (millis() - started < timeoutInterval) {
    int packetLen = Udp.parsePacket();
    byte packetBuffer[LIFX_INCOMING_PACKET_BUFFER_LEN];
    if (packetLen && packetLen < LIFX_INCOMING_PACKET_BUFFER_LEN) {
      Udp.read(packetBuffer, sizeof(packetBuffer));
      
      if (((lifx_header *)packetBuffer)->type == LIFX_DEVICE_STATEPOWER) {
        power = ((lifx_payload_device_state_power *)(packetBuffer + sizeof(lifx_header)))->level;
        return power;
      } else {
        Serial.print("Unexpected Packet type: ");
        Serial.println(((lifx_header *)packetBuffer)->type);
      }
    }
  }
  
  return power;
}

Hopefully that helps you spot the issue in your code. Otherwise you could send me the entire code and I’ll try debug it on my end.


#5

Hi there,
I’m working on building an ESP8266 lifx switch. I saw this post and it was incredibly helpful in allowing me to connect with the lifx bulbs directly over my wifi. I have a question though. When you use the following:

and

How are you getting the MAC address for the bulb? I haven’t built a discovery service and was wondering if I can convert the ID of the bulb into such a Hex array? I have currently a 12 character string, how would I convert it to such an array? (I tried simply adding 0x to each pair of numbers in my serial number and didn’t have any luck - is it that simple?)

I can connect using a direct IP and setting the dest variable to {0,0,0,0,0,0} but I’d really like to use the ID, as I can enter it into a web page and capture it that way when setting up the switch. Also, with DHCP, my addresses may change.

EDIT- I’m unable to send from ESP8266 module using 255.255.255.255 as a broadcast ip. I can get the light to respond to the direct IP request, but when addressing it with broadcast - I can see the broadcast on the network, but no action from the bulb. Are there any examples of an arduino client calling for status, sending an action and receiving an update status back? Sort of round trip for a call to the light?

Thank you very much!


#6

Since I posted the questions above and my edit - I’ve been able to connect to the bulb, but only using a broadcast IP of 192.168.1.255. I am sending a destination or mac address in my header - just constructed manually at this time from the bulb ID.
I am now successful at turning the bulb on if off and off if on. I can tell what the power setting is by reading the payload.level value - (0 or 65535).
Here’s how I got it to work: I have to request the power level with the function provided above, which works if I broadcast UDP a message with the mac address in the dest array. I capture the IP from the remote host and feed it back into the next step to send my payload of a 0 or 65535. It won’t work if I have a dest, I have to set it to all 0’s and use the direct IP to get it to work. Is that a change in the protocol? Something I may be doing wrong when sending a packet? It’s the only difference I could discern.

I can provide my code if necessary - just didn’t want to junk up the works here.

Kerry


#7

Me too! I’m using the Arduino bootloader on a linksprite esp8266 Adruino-compatible board and have added a full-color GUI with touchscreen. Currently I can just change the color using my own color wheel.

@daniel_hall Is there any Arduino library out there for the LAN protocol? I can adapt them to the Esprissif.

-Brian


#8

Hi Guys,

This thread has been incredibly useful. I’ve been trying to control my lights with this app and it works ok, but I have a group that has 3 bulbs and I do the following to send the same command:

Udp.beginPacket(broadcast_ip1, 56700);
Udp.write((char *) &header, sizeof(lifx_header)); // Treat the structures like byte arrays
Udp.write((char *) &payload, sizeof(payload));    // Which makes the data on wire correct
Udp.endPacket();
delay(200);
Udp.beginPacket(broadcast_ip2, 56700);
Udp.write((char *) &header, sizeof(lifx_header)); // Treat the structures like byte arrays
Udp.write((char *) &payload, sizeof(payload));    // Which makes the data on wire correct
Udp.endPacket();
   delay(200);
Udp.beginPacket(broadcast_ip3, 56700);
Udp.write((char *) &header, sizeof(lifx_header)); // Treat the structures like byte arrays
Udp.write((char *) &payload, sizeof(payload));    // Which makes the data on wire correct
Udp.endPacket();

Some messages get right away and some other get there after a few seconds if they even get there (sorry! not checking for ack for now)

Is this the correct approach? Send the same UDP packet three different times? I was expecting to have the group functionality, having to send a command to a group but this doesn’t seem to be right. Any light (pun intended!) that can be share don this will be highly appreciated.

Thanks!


#9

Looking at how other implementations handle groups, it seems looping through the devices is indeed the way to do it. Groups don’t appear to have a callable endpoint.

See @mclark’s LifxLAN for a python example.

Also, assuming the lights have a good connection to the network, there shouldn’t be any noticeable delay. Not sure why you’re seeing that.


#10

I just use micropython and lifxlan by removing the nerifaces component. Works well.


#11

I’ve not quite got frustrated enough to look into putting micropython on the NodeMCU. Got any tips?


#12

Not used that one myself, am using an lolin lite esp32 and still in test mode but micropython is really easy to install and use as it also has touch sensors which has libraries ready to go in micropython.


#13

I’ve now got micropython installed, but I’m not clear on how I’d go about adding the lifxlan library.


#14

RTFM :wink:

There is a lot of detail on the micropython site.
You can use a python library, e.g. LIFXLAN but you can’t install libraries you need to add them and reference them in your python script.
for example with LIFXLAN it uses netifaces to find the correct subnet to broadcast on, you need to extract that part from the script and add your own broadcast address. e.g. [u’255.255.255.255]
You will need to add your dependencies if micropython does not have its own version. e.g. bitstruct is used, if that isn’t in micropython then you add the python file to the script and call it.
As per the doco you add the library in a folder on the device and call it.
Half the fun is working it out as I am still doing.


#15

Thanks. I found your repo and started from there :grin:

Unfortunately it looks like there’s not quite enough room on a NodeMCU v3 for the bitstring library, so I don’t think I can run LifxLAN directly. I might get an ESP32-based board and try again.
However, I might be able to get a little web client running on it, which can then POST to my existing LifxLAN server…


#16

Get a pi W. So much easier.


#17

Yeah, but massively increases my cost per switch…

My current plan once I’ve sorted out the socket issue I’m having is to have standard code on a bunch of boards, and move the switch logic to the server, so it receives “switch 1 gpio 5 pressed/held” and knows what to do with that.

I feel like I should look into MQTT, but don’t have the brain space for another system at the moment!


#18

Why ? One raspberry pi W and use the esp32 or the on you have with a sensor to post a message to mqtt on the pi and then run lifxlan there. Your switches are cheap then and a cheap central computer has the ram etc you need to control your lights.

The esp32 I got for $8.00 US this week and there are cheaper ones. Esp32 has touch sensors so easy to use that.
Setting up mosquitto is easy. You can also use node red on the pi W and create a flow from that. I did that quickly this week


#19

That’s in essence what I’m doing. I thought you were recommending a pi over an Arduino for the sensor processing, not for the server. I have a perfectly capable server that’s already running LifxLAN. I also have a Smartthings system providing most of the automations I need.

I was mainly saying I don’t see the advantage of setting up MQTT over just running a dedicated python httpserver linked to LifxLAN. Perhaps if I was starting from scratch I’d consider it, but I try to keep most of my stuff as off-the-shelf as possible and then just add custom code around the edge cases.


#20

The big advantage I see is the ability to log the MQTT messages to determine what your LIFX bulbs (or any IoT target for that matter) was supposed to do vs what it actually did. :slight_smile: