Talking to HUE

Finding your HUE Bridge

I'm using the Wifi101 library to handle communication and ArduinoJson library to handle json generation and parsing.

Be sure thatyour WiFi module's firmware and SSL certificates are up to date. See the Adafruit Feather M0 WiFi with ATWINC1500 tutorial for details.

To use the HUE restful interface to control lighting you first have to find out what to talk to.

const char *fetch_hue_ip() {
  if (!client.connectSSL("www.meethue.com", 443)) {
    return NULL;
  } 
  client.println("GET /api/nupnp HTTP/1.1");
  client.println("Host: www.meethue.com");
  client.println("Connection: close");
  if (!client.println()) {
    client.stop();
    return NULL;
  }

  char status[32] = {0};
  client.readBytesUntil('\r', status, sizeof(status));
  if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
    client.stop();
    return NULL;
  }

  char endOfHeaders[] = "\r\n\r\n";
  if (!client.find(endOfHeaders)) {
    client.stop();
    return NULL;
  }

  JsonArray& root = jsonBuffer.parseArray(client);
  client.stop();

  if (!root.success()) {
    return NULL;
  }

  return strdup(root[0][F("internalipaddress")]);
}

It starts by making a secure connection to www.meethue.com doing a GET from /api/nupnp. If the result has a 200 status the response body is parsed and the internalipaddress member is extracted.  That's the IP on the local network of the HUE bridge device.

To access the API on your HUE bridge device, you'll need a user. See the HUE Getting Started guide for details.

Light groups

In addition to managing individual lights, HUE allows you group lights. The idea behind this is to group lights into rooms to allow you to control all the lights in a room at once from the hue smartphone app.

While everything about your local HUE installation is discoverable through the RESTful API, I've simplified things a bit by hardcoding the group id that the device should control. In a full installation each module would need to have its room/group id set somehow. In my final vision, the name of the room the module is located in would be assigned from a central controller.  That's beyond the scope of this guide, though.

hue_ip = fetch_hue_ip();
if (hue_ip == NULL) {
  while (true) {
  }
}
light_numbers = lights_for_group(ROOM_ID);
if (light_numbers == NULL) {
  while (true) {
  }
}

The above code is from setup(). It gets the IP of the local HUE bridge and then asks for the lights in the room that's been set.

Notice that I use the first (i.e the 0th) element in the array of light numbers to stash the number of lights for later iteration.

uint8_t *lights_for_group(const char *group_number)
{
  if (!client.connect(hue_ip, 80)) {
    return NULL;
  } 

  client.print("GET /api/");
  client.print(HUE_USER);
  client.print("/groups/");
  client.print(group_number);
  client.println(" HTTP/1.1");

  client.print("Host: ");
  client.println(hue_ip);
  client.println("Connection: close");
  if (!client.println()) {
    client.stop();
    return NULL;
  }

  char status[32] = {0};
  client.readBytesUntil('\r', status, sizeof(status));
  if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
    client.stop();
    return NULL;
  }

  char endOfHeaders[] = "\r\n\r\n";
  if (!client.find(endOfHeaders)) {
    client.stop();
    return NULL;
  }

  JsonObject& group = jsonBuffer.parseObject(client);
  client.stop();

  if (!group.success()) {
    return NULL;
  }

  JsonArray& lights = group["lights"];
  uint8_t *light_numbers = (uint8_t*)malloc(lights.size() + 1);
  light_numbers[0] = (uint8_t)lights.size();
  for (uint i = 0; i < lights.size(); i++) {
    light_numbers[i+1] = (uint8_t)atoi((const char *)lights[i]);
  }
  return light_numbers;
}

Controlling lights

Once the list of lights is in hand, they can be controlled.

void update_light(uint8_t light_number, boolean on_off, uint8_t brightness)
{
  if (!client.connect(hue_ip, 80)) {
    return;
  } 

  char content[32];
  sprintf(content, "{\"on\":%s,\"bri\":%d}", on_off ? "true" : "false", brightness);

  client.print("PUT /api/");
  client.print(HUE_USER);
  client.print("/lights/");
  client.print(light_number);
  client.println("/state HTTP/1.1");

  client.print("Host: ");
  client.println(hue_ip);

  client.println("Connection: close");

  client.print("Content-Type: ");
  client.println("application/json");
  client.println("User-Agent: FeatherM0Sender");
  client.print("Content-Length: ");
  client.println(strlen(content));
  client.println();

  client.println(content);
  client.stop();
}


void update_all_lights(uint8_t *light_numbers, boolean on_off, uint8_t brightness)
{
  if (light_numbers != NULL) {
    uint8_t num_lights = light_numbers[0];
    for (int i = 0; i < num_lights; i++) {
      update_light(light_numbers[i+1], on_off, brightness);
    }
  }
}

To keep things simple this just fires & forgets, assuming the lights will get set appropriately. You can close the feedback loop to some extent by querying the HUE some time later to verify that the change was made to the lights.

Last updated on Apr 17, 2018 Published on Feb 14, 2018