Introduction: Hue Magic

About: Parent of two & product owner who likes to run and enjoys tinkering with electronics (hard- and software) in his scarse spare time. Likes tech and gadgets, has a background in psychology and photographic d…

Welcome wizards!

A couple of months ago I created a small magic box with wand for my 3 year old son. When he touches the box with the wand, a changing colored light will start emitting from the box. When he sees a color he particularly likes, he can point the wand towards the table lamp (with a Philips Hue bulb inside), cast a spell and the color from the box will magically jump to the lamp! Lamp and light from the box suddenly have the same color...

After a few seconds, the color fades away and the table lamp returns to its state before the spell. Until a new spell is cast...

Step 1: What You Need to Create This Project

To create this project, you'll need the following materials:

    • 1 (or more) Philips Hue color bulb and a Hue Bridge
    • 1 Wemos D1 mini or similar esp8266-based microcontroller
    • 1 (Arduino) touch sensor (e.g. TTP223R)
    • 1 (Arduino) momentary button
    • 1 10uF capacitor
    • 1 RGB led (common anode type)
    • 5 resistors (10, 22 and 47 Ohm, 2x 10K Ohm)
    • 2 small prototyping PCBs (2x3 inch or about 5x7 cm should be big enough)
    • some (jumper) wires
    • a soldering iron
    • a magic wand (can be bought as a ready-made in a toy store, or you can make it yourself)
    • a small box made of cardboard or wood (can be a existing box, but you can also build a box from scratch of course)
    • some tape
    • some glue and/or nuts and bolts to mount the PCBs in the box.
    • optional: wrapping paper for the box

NB: A little experience in reading circuit diagrams is helpful when going through this instructable. My diagram isn't overly complex: if you can differentiate a capacitor from a resistor, you'll probably be fine.

Some experience with Arduino programming using the Arduino IDE is also helpful. Rather basic experience should be enough, as I'll provide the complete code for you to copy/paste. You'll need to adapt a few things though, to make it work in your particular set-up (e.g. your network settings and some details from your Hue configuration). If this sounds a little intimidating, don't worry, I'll help you to retrieve all the information you need.

Step 2: The Box & Wand

The first steps are normally the hardest, but not in this instructable! For an easy start, you can just buy a magic wand from a toy store and for the box you could simply re-use an existing small box that you've already lying around. Just make sure the box isn't made of metal, as this will block the wifi signals and we need those for the magic ;-).

When you re-purpose an existing box, the only thing you have to do is to make two holes in the top of the box: 1 small hole (size 5mm = 0.2") for the RGB led and a bigger hole (around 12-14mm or about 0.5") for the touch sensor.

The exact placement of the holes is not critical, just place them according to your sense of aesthetics but keep a few things in mind:

  • Keep some distance between both holes to make sure the components that will be mounted beneath the holes (the RGB led and the touch sensor) are both allowed to occupy enough space for mounting and wiring.
  • The biggest hole is for the touch sensor. This sensor will be mounted just beneath the hole, in such a way that it can be touched (and even slightly pressed) by the wand. So make sure the wand you buy is not too thick!

Optionally you can use (spray) paint or wrapping paper and cover plastic to make your box a little prettier and to protect it from food spills and dirty hands.

If this first step is a little too unambitious to your liking, please go ahead and create a box and wand entirely from scratch! There are severalinstructables out there that will help you to create a beautiful wand

Whatever path you choose, it's now time to explore the inside of the box.

Step 3: The Hardware Inside

Use a soldering iron to connect the electronic components according to the circuit diagram above. There are a couple of things to pay special attention to:

  • The wires between the Wemos D1 Mini and the RGB led should be long enough so the RGB led can be mounted in the hole you've made in the lid of the box.
  • The same counts for the wires attached to the momentary switch and touch sensor as these should be accessible via the other hole in the lid.
  • The button of the momentary switch should be glued to the underside (the non sensitive side) of the touch sensor, in such a way that you can put the button back on the momentary switch with the touch sensor glued on top (see picture). The touch sensor is mounted on top of the momentary switch to detect button presses done by a finger, in which case the button press will be ignored. Only when the button is pressed by the magic wand (which should be non-conductive, so plastics and wood are fine), the magic cycle will begin.
  • Mount the momentary button with touch sensor on top not too deep below the hole in the lid, because it needs to be reachable by the magic wand to set the magic in motion.
  • Make sure to observe the polarity of the capacitor when soldering it. If you reverse the positive and negative leads, the capacitor will likely emit some magic smoke and put your circuit in an everlasting sleep.
  • Glue, tape and or screw the battery holder and PCB(s) in place. It doesn't have to be neat, as it won't be in sight. It should just be drop proof.

On to the software!

Step 4: The Software

Make sure you have the latest (free) Arduino software editor, which can be downloaded at https://www.arduino.cc/en/Main/Software. To add support for the Wemos D1 mini and other ESP8266-based boards, take the following steps:

  • After installation, start the Arduino software and open the Preferences window.
  • Enter http://arduino.esp8266.com/stable/package_esp8266com_index.json into the "Additional Board Manager URLs" field. You can add multiple URLs, separating them with commas.
  • Open Boards Manager from Tools > Board menu and install esp8266 platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation. "LOLIN(WEMOS) D1 R2 & mini" works best for Wemos D1 mini v2 and v3 boards.

If you need more help installing Arduino and setting up the drivers you can take a look at https://www.instructables.com/id/Wemos-ESP8266-Getting-Started-Guide-Wemos-101/

In the Arduino editor, open a new file (File>New) and copy/paste the code below in the window that just opened. Just overwrite the lines that are already present in the new window (void setup and void loop).

Now you are almost ready, but you'll have to adapt a few pieces of the code for your particular set-up.

First thing to do is to change the ip address on line 34 (in the Arduino editor the lines of code are numbered) into the ip address of your Hue bridge. If you don't know your Hue Bridge IP address, visit https://discovery.meethue.com/ and the right IP address it will appear right away in your browser. The ip address is the dotted number that is preceded by "internalipaddress".

To communicate with the Hue lights, you'll have to create an Hue API user for the Wemos D1 mini, so the Wemos can communicate to the Hue light via the Hue API. To do that, follow the instructions at https://developers.meethue.com/develop/get-started-2/ and copy/paste the generated (pretty long) username in the Arduino code window. Just replace every "YOUR HUE API USERNAME" with the generated API username.

Then you need to select the right Hue light to change color. In the Hue API every light has a number, so you need to find out the number that corresponds with the light that you want to use for this project. One of the easiest ways to find out which number a particular light has, is to download the Hue Viewer App for Android or iOS. Replace the text "YOUR LIGHT NUMBER" with the correct number everywhere in the Arduino code window.

Last thing to do is to setup the Wemos to connect to your wifi network. This is done by uploading the code to the Wemos and on your laptop switch to another wifi network: to "AutoConnectAP". Your browser will then display a page where you can add the SSID (name) and password of your wifi network that the Wemos controller will use to connect to your wifi network (and to the Hue bridge).

NB: If uploading the code to our Wemos D1 mini via USB does not work, you may need to download a driver for the USB chip on the Wemos. A driver for your platform (Windows, Mac) can be downloaded at https://sparks.gogo.co.nz/ch340.html

Now you are all set to test your creation!


// ESP8266 Hue Magic Wand<br>// Richard van Kampen - 2018
// This code is tested on a Wemos D1 mini, but will probably also work on other ESP8266-based development boards 
// To add support for Wemos D1 mini and other ESP8266 boards to Arduino editor, take the following steps: 
// - Start Arduino and open Preferences window. 
// - Enter http://arduino.esp8266.com/stable/package_esp8266com_index.json into the Additional Board Manager URLs field. You can add multiple URLs, separating them with commas. 
// - Open Boards Manager from Tools > Board menu and install esp8266 platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation). 
// used libraries:
#include "ESP8266WiFi.h" // ESP8266 Core WiFi Library 
#include "DNSServer.h" //  Local DNS Server used for redirecting all requests to the WiFiManager configuration portal if no WIFI settings (SSID, password) has not been set yet.
#include "ESP8266WebServer.h" //Local WebServer used to serve the WiFiManager configuration portal
#include "WiFiManager.h" // WiFi Configuration Magic library, if not installed yet please refer to https://github.com/tzapu/WiFiManager#install-through-library-manager
#include "RestClient.h" // https://github.com/fabianofranca/ESP8266RestClient, needed for using the Philips Hue API (see https://developers.meethue.com/develop/hue-api/).
#include "ArduinoJson.h" // https://github.com/bblanchon/ArduinoJson, needed to analyze Hue API response, please install version 5.x via the library manager in Arduino (Menu "Sketch" > Include Library > Manage Libraries > search for ArduinoJson and change version to latest 5.x). Version 6 (currently in beta) throws an error.
// variables and init:
String response;
const int redPin = 13;//on Wemos this is d7
const int greenPin = 12;//on Wemos this is d6
const int bluePin = 14;//on Wemos this is d5
const int touchSensor = 5;//on Wemos this is d1
const int activationPin = 4;//on Wemos this is d2
bool activation = HIGH;
bool touch = LOW;
const char* aan_restore;
int bri_restore;
double x_restore;
double y_restore;
double x_magic;
double y_magic;
bool first = true;
unsigned long startMillis;
unsigned long currentMillis;
unsigned long durationMillis;
RestClient client = RestClient("192.168.178.23"); // "your Hue Bridge IP address"
// If you don't know your Hue Bridge IP address, visit https://discovery.meethue.com and it will appear right away in your browser. The ip address is the dotted number that is preceded by "internalipaddress"
void setup() {
  analogWriteRange(255);
  Serial.begin(9600);
  // Start with the LED off.
  pinMode(activationPin, INPUT_PULLUP);
  pinMode(touchSensor, INPUT);
  startMillis = millis();
  checkWand();
}
void loop() {
  // nothing to do here, leave empty...
}
void checkWand() {
  int rgbColour[3];
  // RGB colour code by James Harton, https://gist.github.com/jamesotron/766994
  // Start with red.
  rgbColour[0] = 255;
  rgbColour[1] = 0;
  rgbColour[2] = 0;
  activation = digitalRead(activationPin);// LOW means wand is being used.
  touch = digitalRead(touchSensor);// HIGH means finger is used instead of wand, which should not be the case.
  while (activation == LOW && touch == LOW) {
    // Choose the colours to increment and decrement.
    for (int decColour = 0; decColour < 3; decColour += 1) {
      int incColour = decColour == 2 ? 0 : decColour + 1; //= ternary operator, means: int incColour;if (decColour == 2) {incColour = 0;} else {incColour = decColour +1;}
      // cross-fade the two colours.
      for (int i = 0; i < 255; i += 1) {
        rgbColour[decColour] -= 1;
        rgbColour[incColour] += 1;
        // as our RGB led has a common anode instead of cathode (so we need to connect to +3.3V instead of ground), we need inverse values for RGB:
        int red = 255 - rgbColour[0];
        int green = 255 - rgbColour[1];
        int blue = 255 - rgbColour[2];
        analogWrite(redPin, red);
        analogWrite(greenPin, green);
        analogWrite(bluePin, blue);
        delay(8);
        activation = digitalRead(activationPin);
        if (activation == HIGH) { // HIGH means wand is lifted.
          goto stopColorCycling;
        }
      }
    }
  }
stopColorCycling:
  currentMillis = millis();
  durationMillis = (currentMillis - startMillis);
  if (durationMillis > 1000) {
    RGBtoxy(rgbColour[0], rgbColour[1], rgbColour[2]);
  }
  else {
    //put Wemos into sleep mode:
    ESP.deepSleep(0);
  }
}
void RGBtoxy(int red, int green, int blue) {
  //see https://developers.meethue.com/documentation/color-conversions-rgb-xy
  double R = map(red, 0, 255, 0, 1000); R /= 1000;
  double G = map(green, 0, 255, 0, 1000); G /= 1000;
  double B = map(blue, 0, 255, 0, 1000); B /= 1000;
  R = (R > 0.04045f) ? pow((R + 0.055f) / (1.0f + 0.055f), 2.4f) : (R / 12.92f);
  G = (G > 0.04045f) ? pow((G + 0.055f) / (1.0f + 0.055f), 2.4f) : (G / 12.92f);
  B = (B > 0.04045f) ? pow((B + 0.055f) / (1.0f + 0.055f), 2.4f) : (B / 12.92f);
  double X = R * 0.649926f + G * 0.103455f + B * 0.197109f;
  double Y = R * 0.234327f + G * 0.743075f + B * 0.022598f;
  double Z = R * 0.0000000f + G * 0.053077f + B * 1.035763f;
  double x = X / (X + Y + Z);
  double y = Y / (X + Y + Z);
  // conversion not fully completed, but probably good enough for what we want to achieve, so leave it at this and send XY values to lamp: 
  sendtoHue(x, y);
}
void sendtoHue(double a, double b) {// actual color change from wand magic
if (first) {
    //first pass: get current lamp state
    getCurrentValues();
  }// then send magic wand colors:
  // wait for spell:
  long wait;
  x_magic = a;
  y_magic = b;
  // lamp on in magic wand color:
  response = "";
  int temp = random (2, 9);
  const char* state = "true";
  for (int i = 1 ; i <= temp; i++) {
    // create char array to send to bridge:
    String temp_body1 = "{\"on\": " + String(state) + ",\"bri\": 220,\"xy\": [" + String(x_magic) + "," + String(y_magic) + "],\"transitiontime\": 1}";
    int str_len1 = temp_body1.length() + 1;
    char post_body1[str_len1];
    temp_body1.toCharArray(post_body1, str_len1);// now we have post_body1 as char array;
    // make rest call:
    int statusCodePut1 = client.put("/api/YOUR HUE API USERNAME/lights/YOUR LIGHT NUMBER/state", post_body1, &response);
    wait = random (100, 600);
    delay(wait);
    if (state == "true") {
      state = "false";
    } else {
      state = "true";
    }
  }
  // reduce brightness...:
  response = "";
  temp = random (4, 17);
  // create char array to send to bridge:
  String temp_body2 = "{\"on\": true,\"bri\": 154,\"transitiontime\": " + String(temp) + "}";
  int str_len2 = temp_body2.length() + 1;
  char post_body2[str_len2];
  temp_body2.toCharArray(post_body2, str_len2);// now we have post_body2 as char array;
  // make rest call:
  int statusCodePut2 = client.put("/api/YOUR HUE API USERNAME/lights/YOUR LIGHT NUMBER/state", post_body2, &response);
  wait = random (1000, 2500);
  delay(wait); 
  // ..and make brighter again:
  response = "";
  temp = random (4, 17);
  // create char array to send to bridge:
  String temp_body3 = "{\"bri_inc\": 100,\"transitiontime\": }";
  int str_len3 = temp_body3.length() + 1;
  char post_body3[str_len3];
  temp_body3.toCharArray(post_body3, str_len3);// now we have post_body3 as char array;
  // make rest call:
  int statusCodePut3 = client.put("/api/YOUR HUE API USERNAME/lights/YOUR LIGHT NUMBER/state", post_body3, &response);
  wait = random (2500, 5000); // wait 2-5 seconds
  delay(wait);//and fade back to old value:
  response = "";
  // create char array to send to bridge:
  String temp_body4 = "{\"on\": " + String(aan_restore) + ",\"bri\": " + String(bri_restore) + ",\"xy\": [" + String(x_restore) + "," + String(y_restore) + "],\"transitiontime\": " + String(20) + "}";
  int str_len4 = temp_body4.length() + 1;
  char post_body4[str_len4];
  temp_body4.toCharArray(post_body4, str_len4);// now we have post_body4 as char array;
  // make rest call:
  int statusCodePut4 = client.put("/api/YOUR HUE API USERNAME/lights/YOUR LIGHT NUMBER/state", post_body4, &response);
ESP.deepSleep(0);
  //going to sleep again....
}
unsigned int getCurrentValues() {
connectWifi();// first connect to Wifi
  response = "";
  // make rest call:
  int statusCodeGet = client.get("/api/YOUR HUE API USERNAME/lights/YOUR LIGHT NUMBER", &response);
  Serial.print("Status code from server after GET: ");
  Serial.println(statusCodeGet);
  Serial.print("Response body from server: ");
  Serial.println(response);
  StaticJsonBuffer<2400> jsonBuffer;
  // Parsing Json response
  // Root of the object tree.
  //
  // It's a reference to the JsonObject, the actual bytes are inside the
  // jsonBuffer with all the other nodes of the object tree.
  // Memory is freed when jsonBuffer goes out of scope. 
JsonObject& root = jsonBuffer.parseObject(response);
  JsonObject& state = root["state"];// Test if parsing succeeds.
  if (!root.success()) {
    Serial.println("parseObject() failed");
  }
// Fetch values.
  aan_restore = state["on"]; Serial.println(aan_restore);
  bri_restore = state["bri"];
  x_restore = state["xy"][0];
  y_restore = state["xy"][1];
  first = false;}
void connectWifi() {
  //Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wifiManager;
  //reset settings - for testing:
  //wifiManager.resetSettings();
//set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
  wifiManager.setAPCallback(configModeCallback);
  //fetches ssid and pass and tries to connect
  //if it does not connect it starts an access point with the specified name
  //here "AutoConnectAP"
  //and goes into a blocking loop awaiting configuration
  if (!wifiManager.autoConnect()) {
    Serial.println("failed to connect and hit timeout");
    //reset and try again, or maybe put it to deep sleep
    ESP.reset();
    delay(1000);
  }
 //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");
  Serial.print("Connected to: ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //IP address assigned to your ESP (Wemos)
  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.println(rssi);
}
void configModeCallback (WiFiManager * myWiFiManager) {
  Serial.println("Entered config mode");
  Serial.println(WiFi.softAPIP());
  //if you used auto generated SSID, print it
  Serial.println(myWiFiManager->getConfigPortalSSID());
}
Toys Contest

Participated in the
Toys Contest