loading

This Instructable is about building your very own Smart Lamp! This Instructable will be pretty in depth and we will go over everything you need to be successful in designing and building your very own beautiful Smart Lamp! This Instructable does assume you know quite a bit about Fusion 360 and that your experience in electronics is a bit more advanced but this will be very thorough and include all the STL files needed to just upload to a 3D printer and print out your lamp as well as all the schematics and code is included as well! These should help you build your very own Smart Lamp!

Internet of Things: A "Smarter" Lamp

The Internet of Things is a buzzword in today's tech world. You hear it everywhere but no one can really pin point it's definition. It truly means different things to different people. The largest players in the Tech world themselves, the Googles and Facebooks of the world, can't seem to agree on a definition. But despite this, it seems like every company now has an IoT related internal group or department because stats like '40 Billion connected devices by 2020' and 'Companies will spend 6 Trillion dollars in the next 5 years on connected device solutions' force companies to reassess whether they can afford to miss out on potential opportunities for growth. But this shouldn't discourage anyone from IoT. In fact, IoT is incredible. Never before have we had the opportunity to actually make use of large amounts of data. We can tell so many interesting things that we couldn't have known with precision before, from things like when your car will need to be serviced to which types of plants would flourish in your home garden as a result of your surrounding local weather patterns, soil types, and saturation levels. IoT takes the guess work out of the upkeep of your devices; in short, it can make life a little easier. We have always had data; we just haven't known what to actually do with it until now. IoT is in it's nascent stages; it's growing at a tremendous rate, and as the industry begins to solve some of the growing pains of IoT, we will begin to see truly connected devices. We can see those images of the future where your toaster talks to your fridge and they both talk to your TV. Those visions can't be realized until the issue of networking protocols are solved; i.e, certain companies, like Phillips own the copy rights to very traditional methods of device to device communication, such as I²C (don't worry if you don't know what these are, it won't impact your ability to finish the lamp :)) which, due to the nature of the language used there, has allowed many chip manufacturers to get around this copyright by creating variations of that specific protocol. This happens with all the other major ones as well, like SPI, UART, ANT, etc. This has created massive numbers of permutations on handshaking between devices which has made it impossible for a connected device from a company to speak to another device. In any case, IoT is still in its infancy and has a lot of potential to grow and become useful in your daily lives!

So if you're still reading, awesome! It means you're either passionate about IoT, or you really liked this lamp or both! Either way, I'll break down the Smart Lamp. This is an IoT device which means it's connected to the internet and that data is used for interesting purposes (I'll elaborate more on that below). This Smart Lamp is connected to the internet through a WiFi chip and microcontroller. In addition, a capacitive touch board is used for your touch button interface. Optionally, you can include an ambient light sensor to provide you reading of what sort of lighting your lamp is currently in (The code includes support for this) and you can use that data to decide when to turn on your lamp for example. The Lamp can be configured to be connected to any WPA or WPA2 personal networks (no enterprise support unfortunately) or WEP and the data can be sent to any personal home server you have configured. We have our lamp configured and connected to a remote server from which data is pulled into a game engine. This Lamp has a lot more to offer than just some Internet Connected Lamp.

The Purpose of the Smart Lamp is to have complete access of your lamp (or device) and have interesting information at all times about your system. We have built a virtual model of our lamp in a virtual environment using a game engine. We used networking to connect the two lamps together and have them affect each other. In the virtual model, you can turn on and off your lamp and adjust the brightness, and your physical lamp will reflect these changes instantly and remotely. In the other direction, you can turn on and off your lamp as well as change the brightness, and your model will accurately reflect this. In addition, when your lamp is simply not connected to your network, the lamp enters legacy mode and reverts to a normal working lamp. Building a virtual model of this information in a game engine is COMPLETELY OPTIONAL; you can easily build any front end web client or android or iOS app to act as a place to send the data or have that remote control the game engine model would provide you.

Why would I want a WiFi enabled Lamp?

The true value in this project is not the physical lamp; while it is beautifully designed and a very nice lamp, having the ability to monitor and control a network of connected devices has all of the value. Take for example, a factory. In a factory, there typically exists a lot of machines that do similar tasks. Connecting a network of industrial machinery can help managers operate their plants remotely while collecting data and monitoring the state of their machines. That data you collect is actually very important and can help make conclusions that might otherwise not have been obvious. For example, with a car, you can use IoT sensors that monitor things like engine temperature, force exerted by your car, revolutions per minute, and mileage. These combined can be used to figure out with a degree of certainty the overall health of your car. Instead of needing to know when to change your oil or your tires, the car will already know and tell you through notifications to your phone. In addition, adding a layer of data from a data set containing cars and their average servicing time with respect to mileage can help make that prediction far more accurate. Another example, a factory that uses 3D printers to machine industrial parts would benefit greatly from knowing when over night jobs are completed. Once that happens, a manager would receive a notification on their phone through an app which would allow the manager to turn off all inactive printers saving on utility costs. The convenience of knowing what happens with your machinery, being able to operate smarter and remotely, and saving money throughout that process is why IoT is such a big deal in today's tech world. In other words, Smart Lamps are money savers???

Step 1: Design and General Assembly of the Lamp

Design

When designing anything it is important to start by identifying the constraints and goals. In our case, the interface had to be intuitive enough for anyone to walk up and use. At the end of the day we were still building a lamp and adding technology couldn't detract from that. The buttons had to remain "stateless" because of the user input from the digital lamp. By stateless, I mean we had to steer clear of toggle switches and inputs that had a physical state. If standard switches were used, the state of the switch may not align with the state of the light and would cause confusion for the user.

The organic form was also influenced by the decision to 3D print the body of the lamp. By 3D printing we were able to reduce the time spent machining the body and introduce complexities that would have been impossible through manual fabrication. The drawback was multiple days of clean up and finishing of the surfaces was necessary. The rough surfaces that 3D printing often produces were sanded and filled before they were painted in order to give the lamp a final production-quality finish.

Printing

All the STL files need to print this lamp can be found on GrabCAD.

It is important to understand that while all 3D printers with a large enough envelope can print these pieces, the tolerances and materials available may be different. The body of the lamp was printed on a Stratasys Fortus 450mc. The buttons and diffuser were printed on an Stratasys Objet Connex500.

The orientation is extremely important when printing the body of the lamp because, like wood, 3D prints also have a grain. You want to be sure that the lamp is printed on its side to give the maximum strength up the neck. Reference the attached photos for a visual representation of the print.

Finishing

These next steps will outline my process of refining the surface of a 3D print to the point it no longer looks "printed" and applies to all parts except the diffuser.

While 3D printers still have all sorts of conceptual hype among the maker and consumer communities, it is important to understand that they are primarily a tool. It allows you to get you closer to your physical model faster than ever. Though you gain speed (over most other methods) and reduce costs (compared to injection molding), there can be a fair bit of cleanup.

Step 1: Remove/dissolve all support material.
Step 2: Lightly sand down the print with a medium course (120 grit) sandpaper
Step 3: Glue the top and bottom (not the cap) of the lamp together with epoxy glue compatible with plastics (I recommend a 5 to 10 minute epoxy. Sand down the glue until it is smooth with the rest of the print.
Step 4: Apply a thin layer of Bondo to fill in the ridges created by the layers of the print.
Step 5: Once dry, sand Bondo back down until you see the print. While 120 grit is still fine, you can get it started with 80 grit. Always wear a mask and work in a well ventilated area. Do not sand into your print, we are trying to fill the ridges without modifying the original geometry.
Step 6: Repeat steps 4 and 5 as necessary until perfectly smooth to the touch, then sand with 220 grit.
Step 7: Clean the print with throughly with a damp, lint-free rag.

Paint the Body

The final print was painted in 3 layers: Sandable primer, Enamel paint (any color), and a Matte clear enamel. By separating the priming and painting steps (and not using the 2-in-1 types of paints) it allows you to catch some of the last surface defects before you lay down the paint. I have found matte enamel does not scratch as easily as glossy. Feel free to try either!

Step 1: Tape off internal surfaces (base and where the LEDs lay down in the hood)
Step 2: Position/hang the lamp so you can turn it without touching it. I recommend building a jig that allows it to hang upside-down by the internal tabs. The base cap will get the same paint
Step 3: Apply 2 medium layers of the primer. Let it dry for an hour or more then inspect.
Step 4: Lightly sand all primed surfaces with 220 grit sandpaper. Focus on areas with surface defects, but not too much; we're not trying to sand a pit into it.
Step 5: Re-hang and apply a light layer of primer. If you have any rough areas to fill, applying a slightly heavier coat to these areas can help. Let it dry for an hour or more.
Step 6: For the final sanding 320 grit sandpaper can be used. Focus on keeping sharp edges sharp and sanding evenly.
Step 7: Now we want to apply a thin then a medium layer of the paint enamel. Give at least a full day to dry.
Step 8: Sand it lightly with 400 grit sandpaper and wipe clean with a damp, lit-free rag.
Step 9: Rehang and apply a thin then a medium layer of the clear, matte enamel. Let it dry for at least a day.

Now you are done finishing the body and base cap of the lamp. Next, we will go though a similar process for the buttons. They were modeled with very little tolerance, so find the perfect fit with some 220 grit sandpaper and then reduce it the slightest bit more to account for the thickness of paint.

Paint the Buttons

The steps to paint the buttons are the same as above, minus the primer steps on the power button as light needs to pass though from the LEDs. Focus on not building up too much thickness on the buttons as they will not fit back in the lamp.

Install the Buttons

The buttons have small tabs on them that will help you find the correct depth. Using the same epoxy glue you used for the neck of the lamp, glue each button in place. Be sure to tape off the external area surrounding the base to limit the chance of messing up your beautiful paint job with glue.

Install the Ambient Light Sensor

The ambient light sensor used in this lamp is an Adafruit TSL2561 Light Sensor. It needs to go in before the LEDs are installed. A ribbon cable needs to be installed from the bottom of the sensor board as flat as possible. It is then fed down the neck of the lamp with extra slack. Depending on your printer/solder job, the fit may be a bit tight. A sharp #11 Exacto knife will be the key if you need a bit more room.

Install the LED Strips

The LED strip needs to be cut into 3 four segment (and seen in the photo). There is built in adhesive on the LEDs that will be enough to hold them in place. Install them all in the same orientation as it will make it easier to solder them together. All colors and the 12V+ should be soldered in parallel. Once the strips are installed, feed down the neck and solder four color coded leads to the center strip. Hot glue will add strength to the solder joints. Connect the 12V+ wire to a power supply and ground one of the R, G, or B wires. Once you have confirmed all three light up, move on to the next step.

Install the Diffuser

We are ready to finalize the hood assembly and install the diffuser. Carefully tape off the areas surrounding the LED array so there is no chance of getting glue on the paint. Secure the lamp on its back with the LED cavity level. Using a small stick as an applicator, carefully add glue to the raised surface on either side of the LED cavity. Once glue is evenly on both sides, carefully place the diffuser while monitoring if the glue seeps out. If any does, quickly wipe it away before it has a chance to set.

Step 2: Electronics Rock!

Electronics: Modern Day Wizardry

Technology might seem like magic with the capabilities of our technology today; however, I assure you anyone can learn or do electronics. We break everything down for you guys to be able to replicate this Lamp and hopefully learn a thing or two as well as we explain the underlying principles behind some of the electronics.

Parts List:

Below are all of the things you need to complete the networking and electronics of this Lamp!

Step 1: Connect WIFI breakout to Teensy

Getting Connectivity for this Lamp is 50% of the 'cool' factor and 100% of the IoT portion of this device so it's quite important. The above picture shows you exactly how to connect each pin to the correct pin on the Teensy.

This link is the tutorial from Adafruit and shows you step by step how to connect each pin to the microcontroller. They used an Arduino specifically which has different pin outs. However, I have included two versions of eagle schematic images above that should help any person to properly connect the WiFi board to the Teensy as well as any other boards included in this lamp. In the more "Advanced Schematic", the schematic breaks down the functions of each breakout board and subcircuit, and meets all the proper dimensions in case any advanced makers desire to consolidate the boards into a single board. In the "Basic Schematic", the WiFi board has been abstracted away to be a black box with pins sticking out. Those pins are all labeled with net labels that match the pin it should be connected to on the Teensy with exact naming! This schematic shouldn't be used as the basis of a consolidated board design; it doesn't have the correct dimensions nor the proper circuits on board.

This can be breadboarded if you want to test each portion and verify your connections; this instructable assumes that you are currently working on a Prototyping Board. We have independently done testing and several breadboarding iterations so you can comfortably jump straight into soldering the connection.

This instructable does assume you know how to solder. If you are still learning, Check this link out!

You'll need to solder your header pins to the Teensy first. If you are unsure of how to do that, check this link out!

Once you have soldered your header pins to your teensy, Put the Teensy onto a SparkFun board and align the teensy in this specific orientation: You need to have two vertical columns free on the side of the teensy with the quartz crystal on it (it's a shiny metal rectangle usually etched with a number on it). Learn more here! The opposite side should only fit three vertical columns. This orientation is required because of the peripherals. The Capacitive touch board, WIFI board, and Lux sensor all communicate via SPI communication pins. Because of the size constrains of the lamps base, we can only use the size of prototype boards specified in the parts list. As a result, there are only two orientations that can fit all of the connections for the LED driver circuit (PWM pins from the gate) which is having either 2 or 3 vertical columns on one side and the opposite side would have the number of columns you didn't select from the other side (i.e. 2 on one and 3 on the other or vice versa).

Once you have the proper orientation, you can make the connections permanent and solder the Teensy to the SparkFun Prototype board. You can use the wire clippers to clip the bottoms of the board where the header pins stick out.

After you have soldered the Teensy, Solder header pins to the WIFI board. These should be included in the packaging the WIFI board came in. After you Solder the header pins to the WIFI board, you'll need to solder the WIFI board to a separate prototype board.

To save you time, go ahead and designate which boards you will use for what circuits. Board 1 should only have your teensy on it and nothing else. Board two only has the wifi board. Board 3 will have your LED Driver Circuit, Bi-Directional Switching Regulator, and the connector piece for power from your DC wall power supply to the LEDs/rest of the circuit

Solder the WiFi board to the prototype board upside down (see picture of the three boards connected for reference) and clip the headers. This is done because that orientation allows the three boards to fold up neatly into the base of the lamp.

Last step here is to use the schematic image above to route the connections. Cut and strip a length of stranded core wire thats a little longer than the length of the connection to give it a bit of slack. Then solder the connection on both ends.

General Tip: Color Code your wires and stay consistent throughout the whole circuit! Make your power lines all red stranded core wire and black should be ground. This helps you in your debugging process as well as helps keep things neat and orderly.

LED Driver Circuit & Level Shifter

This Section has multiple portions so we will break it down into smaller portions and walk you through everything here. Let's begin with the power circuitry!

In the Power circuit, we have a connector piece that allows you to connect the positive and negative leads (red and black wires) from the DC power supply (wall brick). We want to solder this piece onto the top side of the board, diagonally in the last spots of the first and second row. This can be seen above in the photo as well. After doing that, let us move our focus towards the MOSFET Driver circuit.

To begin, let us provide some background on MOSFETs for those who do not know what MOSFETs are. MOSFET stands for Metal Oxide Semiconductor Field Effect Transistor. The first part of this just refers to the type of material the transistor is made of (highly simplified as there are implications for the type of material chosen and its resulting properties of the transistor). The second half of this statement is a bit more complicated; Field effect refers to the gate of the transistor being pulled down through induced currents via the charges moving between channels (don't worry if you don't understand but here is more information!). Transistors are basically just switches; you can control the flow of power by applying voltages to gates to make certain portions of the circuit get power. Transistors are pretty complicated; in short, all transistors have a Gate, Drain, and Source. The Drain and Source can be treated as the input and output of the circuit. MOSFETs have lots of different kinds, but we will explain the main ones you'll run into as a maker. NMOS and PMOS stand for Positive and Negative Channel MOSFET. The distinction is important because it will determine what kind of voltage you can apply to the gate to allow current to flow from source to drain or drain to source. NMOS gates require a positive potential voltage between its gate and source. PMOS requires a negative potential between its gate and source. You can learn all about MOSFETs here!

After we have our power jack soldered, we want to place down our MOSFETs. Place them on the same board and each should be one hole space between them. They should all go in a line vertically with each MOSFET facing the same direction. The direction is correct when the side with the writing on it is facing as seen in the picture above. In the picture, the MOSFETs have been folded down so just imagine they aren't if you are having trouble and you should be able to get the positioning correct. Once you have the direction correct, solder in the pins.

In the schematic with the MOSFETs, the Diodes (triangles with horizontal lines) represent the LED light strip. The LED strip has four metal pads (Power, Red, Green, Blue) which you can apply heat to from a soldering iron and connect some more of the stranded core wire (color coded of course) to the pads. Once you have a solid connection, you will supply the strip 12 Volts. This means you will want to connect the power line from the DC power supply to the connector piece you have soldered onto the board. You can easily do this by soldering the wire from the Led strip that represents power to an empty hole on the board in the same row as the power pin of the connector piece. This will give power to the LEDs. You should see your LEDs turn on if you have connected your power correctly at this point. Now it's time to connect the rest of leads from the LED strip. These should be the red, green, and blue control leads. What you will need to do is connect each wire to the row that has the middle pin of the MOSFET connected. You should have three wires connected to three different gates of the MOSFETs. Then you will need three wires (red, green, and blue) to connect from the drain to the digital pin on the Teensy. The drain is the left most pin on the MOSFET and you just need to solder each wire to one of the free holes on that row.

After connecting the LED Strip, we will connect the bi-directional switching regulator. Our LED's operate at 12 volts in order to turn on. However, the rest of the circuits and boards will only work at a maximum of 5 volt logic which means if we don't want to destroy our electronics, we will need to decrease the voltage going to the rest of the circuit. We do this through the switching regulator! The switching regulator is a device that drops (in this case 12 volts to 5 volts) the voltage from some higher voltage to a lower one. Now you might be asking why not simply use a resistor to drop the voltage or a voltage divider circuit using resistors? There are multiple issues with doing this; we highly recommend AGAINST doing this. Why? Resistors will drop the voltage as you want but you will generate a lot of heat! In fact, you could easily melt your circuit as a result of using a resistor or a linear regulator if you are familiar with them. But couldn't I just use a heat sink and funnel the heat away from the circuitry? Yes, you could but then we arise at the issue of energy efficiency! This would be highly inefficient! If you recall some high school physics, Power is the product of Current times Voltage or Current squared times resistance. Power is measured in watts and you'd need a sizable resistor to drop 12 volts to 5 volts. This will cause the power dissipated to be quite large. This would make your lamp highly inefficient. We avoid this problem by using a bi-directional switching regulator! This regulator has an easily achievable 85% efficiency dropping 12 volts to 5 volts. This helps to keep temperatures down and increase the life expectancy of your lamp!

To Connect the switching regulator, see this datasheet! A good skill to have is reading datasheets! Every reputable part has a datasheet that shows you how to use the device and its characteristics and operating ranges. For this regulator, on page 8 of 12, the bottom left hand corner has the table of the pins and their labels. Pin 1 is the left most pin and it's the power input. You should solder a wire from a hole on the same row as the power connector to a hole on the same row as Pin 1. Pin 2 is the ground wire. So from an empty hole on the row of the power connector's ground pin, solder a wire there to an empty hole on the same row as pin 2. Pin 3 is the output. This will serve as line of power for the rest of the circuit. We created a dedicated row of power rails for each board to make it easier for debugging. The power rails on each board are seen in the picture above. A single connection for ground and power from the output of the connector (only for ground) and the output of the switching regulator for power. Each board requires a smaller jump between the large gap in the rows (the rows are not connected directly all the way across the board) and between prototyping board, you will need a soldered connection as well. See the above pictures to get the gist!

Capacitive Touch Breakout Board

This is the last portion of the electronics for those who aren't interested in extending your electronics to include an ambient light sensor. For this portion, you will not need any header pins or to attach the board to a Sparkfun prototyping board. This board will eventually be glued onto the inside of the lamp as close as possible to the buttons to keep the active capacitive touch leads for each button as short as possible. This is to prevent interference and it allows for a clean capacitive touch reading to be received. The board itself has several pins which you can find listed in the "simple" version of the eagle schematic included above. It tells you exactly which pins to connect to the teensy just as in the WIFI module section of this instructable. Solder those connections together and that should provide an SPI communication link to the board as well as power.

The next step is the leads connecting to the button. As explained previously, you want these wires to be as short as possible to make your capacitive touch button as sensitive as possible. To do this, you will want to create a contact pad of sorts, as seen above, with wire. Take some solid core wire and spiral the wire into a dime sized circular, flat coil. The other end of the coil should be the straight part of the wire and be soldered into the pins. You will need 3 button contact wires for each button naturally and these will be soldered into pins 0, 1, 2. In pin 11, you will solder another wire which will be for the power LED. This is a GPIO pin (General Purpose Input/output Pin) and also has PWM (pulse width modulation) capabilities. We will use this pin to control the logic of that LED that becomes the backlight for the power button. The section on programming and code will explain how this is done.

Ambient Light Sensor

This section is dedicated to the ambient light sensor which is a breakout board that takes in light from its surroundings and has on board filtering to provide the user data on the luminosity in the room as well as IR and visible light readings. This breakout is fairly simple and hasn't been included in the eagle schematics however, Adafruit has a very good tutorial linked here! This link will guide you step by step on how to wire your ambient light sensor to the teensy. There is a picture above of the connections; I have included a link that gives you the pinouts of the Teensy and should help you connect the wires together. You will need to connect the power on the breakout board to power anywhere on the board. I would recommend attaching this connection to the power rail that's most convenient. Pictures above include our placement of the power connection. Next, you will need to connect the SDA and SCL pins to the Teensy. Those pins on the teensy are labeled in the eagle schematic and all you need to do is make the connection permanent. Solder a wire from the SDA pin on the teensy to the SDA pin on the light sensor and similarly, connect the wire from the SCL pin on the teensy (pin 13) to the SCL pin on the teensy. After this, the last connection needed to be made is GND. Connect ground from the breakout board to any available ground rail. Again, try and select a place that makes it easier for you to solder and assemble the electronics. See the picture above for our version. All ground and power lines are in black and red respectively.

Step 3: Programming Chops: MQTT Networking

The Software: Making Dumb Objects Smart

You've now made it to the software aspect of this project! If you have made it this far, congratulations! You basically have a smart lamp because we have included all of the code in this instructable and you're ready to upload the code and have a smart lamp! If you are interested in learning more about what the code does, the comments in the code section are detailed and explain everything!

For those who are interested in simply uploading code, there's no need to read any further. This portion of the instructable will talk about how to upload the code to the teensy. For background, the teensy is a microcontroller that needs a manual/automatic bootloader (a program) which switches the processor into programming mode. For those who have previously used an Arduino, when coding a program for an Arduino project, the check mark in the upper right hand corner will verify your sketch for errors and once the code compiles, you can click on the arrow to upload your code. Arduino's IDE automatically switches your Arduino into programming mode and flashes new code to your board. With the Teensy, this capability has been abstracted out to the Teensy bootloader. Every Teensy user needs to download the bootloader and if you haven't already, download the Arduino IDE located here. Once you've completed the installations, download the code we have included above. It will open in your Arduino IDE. There have been lots of libraries included and are required to compile the code. Below, are the links to the libraries you need to download the .zip file and install it into the libraries. If you do not know how to install libraries into an Arduino sketch, here is a link to an instructable on how to do so.

The libraries:

Once you install all of these libraries, your code will compile. After pressing the verify code button (check mark) on the arduino IDE, open the bootloader window for the Teensy. You should see a small window that has a virtual version of the Teensy. Click the auto button and make sure its dented in (as if you had actually pressed a button in...see the teensy bootloader link to see what we mean). After that, press the physical button on the Teensy itself which is on the end of the board opposite the USB. Check the arduino IDE where you have your sketch and click on tools. Make sure that the board says Teensy 3.1/3.2, USB type is set to Serial, CPU speed is set to 96 MHz optimized overclocked and the port is set to "usb followed by some long string of numbers and letters". Once you've done that, press the upload button (sideways arrow) on the arduino. The Teensy should have an LED on the board and it should flash when uploading code. After it's done uploading, you just need to put your electronics into the base of the lamp and you're all done!

If you're interested in what the code does, check the comments included with the code below!

#include <Adafruit_MQTT.h>
#include <Adafruit_MQTT_Client.h>
#include <ArduinoJson.h>
#include <Adafruit_MPR121.h>
#include <MPR121.h>
#include <MPR121_defs.h>
#include <SPI.h>
#include <Adafruit_WINC1500.h>
#include <Adafruit_TSL2591.h><br>#include <Wire.h>
#include <Adafruit_Sensor.h><br>#include <string.h>

#define WINC_CS   10
#define WINC_IRQ  7
#define WINC_RST  4 
#define AIO_SERVER      "mqtt2.seecontrol.com"
#define AIO_SERVERPORT  1883
#define AIO_USERNAME    "IoTLamp"
#define AIO_KEY         "b184b39888dc43fea40fd55e5da0a360"
#define halt(s) { Serial.println(F( s )); while(1);  }
#define LEDPIN 3
#define LED_RED 23
#define LED_GREEN 5
#define LED_BLUE 6
#define touch_pin0 0
#define touch_pin1 1
#define touch_pin2 2
char ssid[] = "";     //  your network SSID (name)
char pass[] = "";    // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;                // your network key Index number (needed only for WEP)
int status = WL_IDLE_STATUS;    //wifi status
const char MQTT_SERVER[] PROGMEM    = AIO_SERVER;
const char MQTT_CLIENTID[] PROGMEM  = __TIME__ AIO_USERNAME;
const char MQTT_USERNAME[] PROGMEM  = AIO_USERNAME;
const char MQTT_PASSWORD[] PROGMEM  = AIO_KEY;
const char IOTLAMP_FEED[] PROGMEM = "/IoTLampDemo"; //publish and subscribe channel name
const char RETURN_FEED[] PROGMEM = "/IoTLampDemo";
uint32_t x = 0; //
uint8_t POWER_STATE = 0;
uint8_t pinNum;
uint8_t led_brightness = 100;
uint8_t redVal = 0;
uint8_t greenVal = 0;
uint8_t blueVal = 0;
uint16_t lux;
bool Network_Mode = false;
bool cap_touch_activated = false;
const int irqpin = 2;  // the number of the pin for activity-indicator      
volatile boolean flagIrq;
struct JSON {
  const char* targetVal;
  const char* codeVal;
  int lamponVal;
  int brightnessVal;
};

/* all declarations */
Adafruit_WINC1500 WiFi(WINC_CS, WINC_IRQ, WINC_RST);
Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591); // light sensor
Adafruit_WINC1500Client client; // wifi chip
Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD); // mqtt declaration
Adafruit_MQTT_Publish IOTLAMP = Adafruit_MQTT_Publish(&mqtt, IOTLAMP_FEED);
Adafruit_MQTT_Subscribe VIRTUAL_LAMP = Adafruit_MQTT_Subscribe(&mqtt, RETURN_FEED);
/*
 * This function is the set up. In the set up, we have initialized all of the pins for the 
 * LEDs as well as the interrupts we are attaching to the buttons. We have called functions that
 * set up the wifi board, the capacitive touch board and a function that will subscribe to the 
 * correct topic path.
 * 
 */
void setup () {
  pinMode(irqpin, INPUT);
  Serial.begin(9600);
  // setup interrupts
  Serial.println("starting program");
  attachInterrupt(irqpin, isrIrqPin, CHANGE); // attach irq pin interrupt & RISING/HIGH/CHANGE/LOW/FALLING
  MPR121.setInterruptPin(irqpin);
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  digitalWrite(LED_RED, LOW);
  digitalWrite(LED_GREEN, LOW);
  digitalWrite(LED_BLUE, LOW);
  capacitiveTouch_Setup();
  wifi_module_config(); //Configure the wifi
  mqtt.subscribe(&VIRTUAL_LAMP);
}
/*
 * This function is the loop. It repeats whatever happens in this function infinitely unless an 
 * interrupt is detected in which case another function is executed while all else is halted.
 * This function checks to see if an interrupt is fired, if so, run the resetButton function.
 * If we are in a networking mode (meaning we have wifi connectivity), then run the networking
 * function. If neither are true, just behave like a regular lamp.
 * 
 */
void loop () {
  if (flagIrq == true) {
    ResetBUTTONFlag(); //If BUTTON interrupt has occurred, reset flag.
  } else {
    if(Network_Mode) {
      networking();
    } 
  }
}
/*
 * This function gets called when the interrupt is fired and then the capacitive button function
 * is then called and the flag boolean gets set to true.
 */
void isrIrqPin () {
  capacitiveTouch_buttons();
  flagIrq = true;
}
/*
 * This function just takes care of states: it only gets called when the interrupt is fired 
 * and the flag boolean gets turned to false and the capacitive touch boolean is set to true.
 */
void ResetBUTTONFlag() {
    //reset BUTTON 1 flag + show led activity-indicator
      flagIrq = false;
      cap_touch_activated = true;
}

// Uncomment this function for expanded lux sensor readout and call it wherever desired.
//void luxSensor_Readout(void) {
//  sensor_t sensor;
//  tsl.getSensor(&sensor);
//  Serial.println("------------------------------------");
//  Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" lux");
//  Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" lux");
//  Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" lux");
//  Serial.println("------------------------------------");
//  Serial.println("");
//  delay(500);
//}
/*
 * This function sets up the board. It begins by providing debugging statements to know if the 
 * capacitive touch board is configured correctly. Then it initializes digital pins on the board
 * itself for the buttons and power button as well. It sets the sensitivity and gets calibration
 * data to begin.Then it turns on the POWER LED to start.
 * 
 */
void capacitiveTouch_Setup() {
  if(!MPR121.begin(0x5A)){ 
    Serial.println("error setting up MPR121");   
    switch(MPR121.getError()){
      case NO_ERROR:
        Serial.println("no error");
        break;  
      case ADDRESS_UNKNOWN:
        Serial.println("incorrect address");
        break;
      case READBACK_FAIL:
        Serial.println("readback failure");
        break;
      case OVERCURRENT_FLAG:
        Serial.println("overcurrent on REXT pin");
        break;      
      case OUT_OF_RANGE:
        Serial.println("electrode out of range");
        break;
      case NOT_INITED:
        Serial.println("not initialised");
        break;
      default:
        Serial.println("unknown error");
        break;      
    }
    while(1);
  }
  Serial.println("MPR121 found!");
  MPR121.setTouchThreshold(2); // default value is 40 for touch
  MPR121.setReleaseThreshold(1);  // default value is 20 for touch; must ALWAYS be smaller than the touch threshold
  MPR121.setNumDigPins(1);       //LED outputs
  MPR121.pinMode(11, OUTPUT);
  MPR121.updateTouchData(); // initial data update
  MPR121.digitalWrite(11, HIGH);
}
/*
 * This is the wifi configuration function.This checks to see if power has been provided to the
 * WIFI module and then sets the networking booleans to true or false depending on if power
 * has been provided. Then if so, print out that you're connected.
 * 
 */
void wifi_module_config() {
  Serial.println(F("Adafruit MQTT demo for WINC1500"));
  Serial.print(F("\nInit the WiFi module..."));  // Initialise the Client
  if (WiFi.status() == WL_NO_SHIELD) { // check for the presence of the breakout
    Serial.println("WINC1500 not present");   // don't continue:
    Network_Mode = false;
    while (true);
  }
  Serial.println("ATWINC OK!");
  Network_Mode = true;
}
/*
 * This function creates a mapping for RGB values. It scales what the maximum tone of white 
 * the lamp will output. Then after creating the mapping for each color Red green and Blue,
 * we call a helper function to set the actual tone of the color of the LEDs
 * 
 */
void map_white(int new_brightness) {
  redVal =    map(new_brightness, 0, 255, 0, 255); //Map red values
  greenVal =  map(new_brightness, 0, 255, 0, 255); //Map green values
  blueVal =   map(new_brightness, 0, 255, 0, 240); //Map blue values
  set_leds(redVal,greenVal,blueVal);
}
/*
 * This function actually sets the LEDs color values and makes the LEDs turn on.
 * 
 */
void set_leds(uint8_t redVal, uint8_t greenVal, uint8_t blueVal) {
  analogWrite(LED_RED, redVal);   // Write current values to LED pins
  analogWrite(LED_GREEN, greenVal);
  analogWrite(LED_BLUE, blueVal);
}
/*
 * this function configures the light sensor. It first checks to see if the sensor is connected
 * to power. once it is, it sets the gain. It amplifies the values it initally gets depending on
 * if the light surrounding the sensor is low, it amplifies to high gain. Afterwards, it sets
 * the integration time which is how long it will take a measurement for. The rest of the function
 * prints out information of your selections in the serial monitor.
 */
void configureSensor(void) {
  Serial.println("Starting Adafruit TSL2591 Test!");
  if (tsl.begin()) {
    Serial.println("Found a TSL2591 sensor");
  } else {
    Serial.println("No sensor found ... check your wiring?");
    while (1);
  }
  // You can change the gain on the fly, to adapt to brighter/dimmer light situations
  //tsl.setGain(TSL2591_GAIN_LOW);    // 1x gain (bright light)
  tsl.setGain(TSL2591_GAIN_MED);      // 25x gain
  // tsl.setGain(TSL2591_GAIN_HIGH);   // 428x gain 

  // Changing the integration time gives you a longer time over which to sense light
  // longer timelines are slower, but are good in very low light situtations!
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);  // shortest integration time (bright light)
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS);
  tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);  // longest integration time (dim light)</p><p>  /* Display the gain and integration time for reference sake */
  Serial.println("------------------------------------");
  Serial.print  ("Gain:         ");
  tsl2591Gain_t gain = tsl.getGain();
  switch (gain)
  {
    case TSL2591_GAIN_LOW:
      Serial.println("1x (Low)");
      break;
    case TSL2591_GAIN_MED:
      Serial.println("25x (Medium)");
      break;
    case TSL2591_GAIN_HIGH:
      Serial.println("428x (High)");
      break;
    case TSL2591_GAIN_MAX:
      Serial.println("9876x (Max)");
      break;
  }
  Serial.print  ("Timing:       ");
  Serial.print((tsl.getTiming() + 1) * 100, DEC);
  Serial.println(" ms");
  Serial.println("------------------------------------");
  Serial.println("");
}
/*
 * This function gets launched once a button is touched. After that happens, it updates the data
 * the capacitive board has stored. Then runs through the available pins (we only go from 0-2 
 * because we only have attached 3 pins to buttons). Then it will return the pin number that has
 * been touched. 
 * 
 */
uint8_t capacitiveTouch_pinTouched() {
    MPR121.updateTouchData();
    for(pinNum=0; pinNum<=2; pinNum++){
      if(MPR121.isNewTouch(pinNum)){
        return pinNum;
      } 
    }
    pinNum = 4;
    return pinNum;
}
/*
 * Capacitive touch buttons actually holds all of the logic dealing with if a button has been
 * touched. By this point it is assumed a button has been touched and now it will figure out
 * what to do with the specific button that has been touched. It controls all the logic for 
 * the power state and power LED as well as brightness control.
 * 
 */
void capacitiveTouch_buttons() {
  uint8_t touchedPin = capacitiveTouch_pinTouched();
  switch (touchedPin) {
    case 0:
      {
        if (POWER_STATE == 1) {
          if (led_brightness > 230) {
            led_brightness = 255;
            map_white(led_brightness);
          } else {
            led_brightness = led_brightness + 25;
            map_white(led_brightness);
          }
        } else {
          MPR121.digitalWrite(11, LOW);
          map_white(led_brightness);
          POWER_STATE = 1;
        }
      }
      break;
      
    case 1:
      {
        if (POWER_STATE == 1) {
          MPR121.digitalWrite(11, HIGH);
          POWER_STATE = 0;
          map_white(0);
        } else {
          MPR121.digitalWrite(11, LOW);
          POWER_STATE = 1;
          map_white(led_brightness);
        }
        break;
        
      case 2:
        {
          if (POWER_STATE == 1) {
            if (led_brightness <= 30) {
              led_brightness = 25;
              map_white(led_brightness);
            } else {
              led_brightness = led_brightness - 25;
              map_white(led_brightness);
            }
          } else {
            MPR121.digitalWrite(11, LOW);
            map_white(led_brightness);
            POWER_STATE = 1;
          }
        }
        break;
        
      default:
        {
        
        }
         break;
      }
    }
 }
/*
 * This function is the advanced read function for the light sensor. The light sensor will 
 * give you 32 bits representing both Infrared and visible light spectrum. It grabs the 16 bits 
 * of the visible spectrum and returns a calculated lux value for visible spectrum.
 * 
 */
 uint16_t advancedRead(void) {
  uint32_t lum = tsl.getFullLuminosity(); // Read 32 bits with top 16 bits IR, bottom 16 bits full spectrum
  uint16_t ir, full;
  ir = lum >> 16;
  full = lum & 0xFFFF;
  lux = tsl.calculateLux(full, ir);
  return lux;
}
/*
 * This function deals with the transmission of data packets formatted in JSON. It will check 
 * if your channel name is correct and if so, then it will publish the packet to that channel
 */
void mqtt_transmit(char buf[]) {
  //Now we can publish stuff!
  Serial.print(F("\n Sending JSON"));
  Serial.print("...");
  if (!IOTLAMP.publish(buf)) {
    Serial.println(F("Failed"));
  } else {
    Serial.println(F("OK!"));
    Serial.println(buf);
  }
}
/*
 * This function sets up MQTT and sets it up. Has statements to deal with timeout and 
 * after wifi connection has been established, the program attemps to connect to MQTT. Once
 * it connects, it flashes green and turns off to let you know you're connected. If you aren't
 * connected, it will stay red for the duration of the search for wifi process. 
 * 
 */
void MQTT_connect() {
 int8_t ret;
 uint8_t timeout = 10;     // wait 10 seconds for connection:
 bool timedOut = false;
  while (WiFi.status() != WL_CONNECTED && timedOut == false) {  // attempt to connect to Wifi network:
    digitalWrite(LED_RED, HIGH);
    Serial.print("Attempting to connect to SSID: "); Serial.println(ssid);
    status = WiFi.begin(ssid, pass); // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    timeout--;
    delay(10);
    if(timeout == 0) {
      timedOut = true;
      digitalWrite(LED_RED, LOW);
    }
    Network_Mode = false;
  }
  while (timedOut) {
     Network_Mode = false;
     Serial.println("Entered Legacy Mode");
  }
    
  if (mqtt.connected()) { // Stop if already connected.
    Network_Mode = true;
    return;
  }
  
  Serial.print("Connecting to MQTT... ");
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
    Serial.println(mqtt.connectErrorString(ret));
    Serial.println("Retrying MQTT connection in 5 seconds...");
    mqtt.disconnect();
    Network_Mode = false;
    delay(500);  // wait 5 seconds
  }
  Network_Mode = true;
  Serial.println("MQTT Connected!");
  digitalWrite(LED_RED, LOW);
  digitalWrite(LED_GREEN, HIGH);
  delay(1000);
  digitalWrite(LED_GREEN, LOW);
}
/*
 * This is the function that formats JSON. right now it's hard coded to a certain extent
 * which is inflexible however, you can easily change  this to be based on parameters that
 * get passed in. 
 * 
 */
String json_Packet() {
  String pubString = "{\"target\":\"Lamp\", \"code\":\"lamp\",";
  String values = " \"values\": {";
  String onOff = " \"lampon\":"; // ints remained ints instead of having quotes around them for JSON formatting
  String lampState = onOff + POWER_STATE + ",";
  String brightnessState = " \"lampintensity\":";
  String lamp_brightness = brightnessState + led_brightness + ",";
  String lamp_luminosity = "\"luminosity\": ";
  String initial_string = pubString + values + lampState + lamp_brightness + lamp_luminosity;
  return initial_string;
}
/*
 * This function is the logic for the data that comes in from the subscription channel. 
 * It changes turns on and off the LED power backlight depending on the value sent from the
 * subcription channel. It also changes the actual LED brightness depending on that data
 * 
 */
void subscribe_StateChange(JSON &json) {
  POWER_STATE = json.lamponVal;
  led_brightness = json.brightnessVal;
  if (POWER_STATE == 1) {
      MPR121.digitalWrite(11,LOW);
      map_white(led_brightness);
   } else {
      MPR121.digitalWrite(11, HIGH);
      map_white(0);
   }
}
/*
 * This function is the higher level function that deals with all networking. It calls the 
 * function that sets up the MQTT connection. Once that's established, initialize an instance
 * of subscription which is where you'll get data from. Then make an instance of the struct we
 * created earlier that will store the fields we parse from the subscription packet. We create a
 * buffer to store all that subscription data. Then checks to see if a button has been touched
 * which will lead to us reading in light data, packaging the data, then transmitting a packet.
 * If we aren't changing the state of the lamp (touching any buttons), then see if we have any
 * packets to read. If we do, parse it and populate the fields in the JSON struct we created earlier.
 * 
 */
void networking() {
  MQTT_connect();
  Adafruit_MQTT_Subscribe *subscription;
  JSON json;
  String basic_string = json_Packet();
  StaticJsonBuffer<5000> jsonBuffer;
  while(cap_touch_activated) {
        lux  = advancedRead(); // gets lux value and sets it equal to lux
        String finalString = basic_string + lux + "}}";
        char buf[finalString.length() + 1];
        finalString.toCharArray(buf, finalString.length() + 1);
        mqtt_transmit(buf);
        cap_touch_activated = false;
  }
  while ((subscription = mqtt.readSubscription(5000))) {
    if (subscription == &VIRTUAL_LAMP) {
      JsonObject& root = jsonBuffer.parseObject((char*) VIRTUAL_LAMP.lastread);
      if (!root.success()) {
        Serial.println("parseObject() failed");
        return;
      }
       json.targetVal = root["target"];
       json.codeVal = root["code"];
       json.lamponVal = root["values"]["lampon"];
       json.brightnessVal = root["values"]["lampintensity"];
       subscribe_StateChange(json);
    }
  }
}

NETWORKING: WIFI, MQTT & MQTT.fx

This is a section dedicated to explaining what MQTT is and networking in general! Let's get started!

Before we get into the MQTT stuff, you'll need to set up your WIFI networking credentials for this discrete device. Now this lamp cannot connect to WPA2 enterprise networks due to security. The libraries aren't designed to handle username fields. Just a network SSID and password which makes enterprise network support not provided at the moment. There is support for WPA, WPA2 personal, and WEP networks at the moment. You will need to change the SSID and Password for the network credentials you will be using. See the lines below:

<p>char ssid[] = "";     //  your network SSID (name)<br>char pass[] = "";    // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;                // your network key Index number (needed only for WEP)</p>

You will not need to change the KeyIndex value unless you are using a WEP network.

So MQTT...MQTT is a machine to machine connectivity protocol. What does all of that mean? Well for starters MQTT is a way for devices to talk to each other without having to use a lot of power to do so. It has very small data packets so that also makes for a faster transmission as well. MQTT used to be known as MQ Telemetry Transport. In any case, MQTT relies on a publish and subscription model to send and receive data.

MQTT uses publish and subscribe to send data back and forth between devices and to do so, they need a data broker. This means, you need an intermediary place to send data that sets up "topics" or channels that will be dedicated to publishing and subscribing data. This broker handles the logic of sending and receiving data. Of course, you will still need to write functions that specify where specifically in this broker you want to pull data from or send data too but you can monitor the data being received and sent from this broker. Publishing is the same as transmitting; you package your data in the format you want it to be in and then designate a channel name to publish to. Once you have done that, you can use libraries that you downloaded from adafruit to actually send your data to this channel and after you set up your broker, you can see the data being received.

Subscribing is a bit more difficult than publishing with regards to actually receiving data to your device. With publishing, you simply have to transmit. You don't necessarily care that your data gets received or parsed on the other end depending on your use case. However, with subscription, it's on the client side (device manufacturer) to make sure data is properly read from the subscription channel topic, no packets were lost, parse the data, and react accordingly to that data. With subscription, you have to be constantly listening to see if you get data and stop all else from occurring because the state of your device will change shortly thereafter receiving a packet. Subscription works similarly to how publish works; you establish a channel and you read data from it through a function call (subscription*. readSubscription(int timeout)) from the Adafruit MQTT library. You can constantly loop through packets and parse as you get them and immediately change things about the state of your physical lamp as a result. If you are doing something that specifically needs to know that every packet was received and in what quality, there is an additional field for the quality of service with each packet sent. This is used to notify you of what the quality of your transmission was. If you received all of your packets, you will have a 2 returned to you. This means you successfully sent a packet and received a ping packet back. If you get a one, the packet was sent but you can't be sure for certain you've received them all back. A QoS of 0 tells you that you've sent it and there's no additional information about whether it was received or not or whether you received all of the packets.

The broker we used is MQTT.fx which you should download here. MQTT.fx is a client broker that deals with all the data management. To use it, you'll need to set it up. First thing you'll need to do is click on the gear for the settings before you attempt to connect to MQTT.fx. Once you've clicked it, you'll need to give it a URL (broker address) for the server you'll be sending the data to as well as the port number. Provide yourself a made up profile name which to keep things straight for us, we used that profile name and made it our publish channel name and subscribe name as well with a "/" before the profile name though. After you've entered all of that information, generate a random key and keep record of that key. You'll replace the value for AIO_KEY in the code with that new key you've generated. You can also go to the General tab and provide a username and password, although we didn't and it wasn't particularly necessary. After that, you're ready to hit the apply button and OK button then hit the connect button and you should be configured for a working server. Then afterwards, you can type in the publish and subscribe channel name in the drop down tab and you should see your data there as long as you include a backslash in front of the channel name. This is important; its a hardcoded format in MQTT and your data will not appear if you do not have the backslash present in front of the channel name you've designated. After this, you're all set!

Very nice. But its impossible for me to built one. Too complicated, lol.
<p>Thats one beautiful lamp! The &quot;embedded plug&quot; on the bottom is fantastic idea for keeping the flexibility without changing the sleek apearance. I don't think I've seen this before.</p><p>You guys seem pretty chill, so I'll be honest with you: I did not read through most of this, partly because I knew some things and partly because of the of the massive amount:</p><p>Splitting it up into steps would improve readability a lot; it's so much nicer if there is an occasional picture to go along with a portion-sized amount of text. Personally I aim at 2000-4000 chars per step so it can be read with 1x scrolling.</p><p>The way you incorporate links to other pages is fantastic in terms of readability. It provides information, while it de-clutters your text, so keep that!</p><p>While there's still potential in your writing I love the way you combine design &amp; technology. Hopefully I didn't discourage you and see you back here with another project sometime!</p>
The amount of detail and background information available is simply.. beautiful! I enjoyed reading it even though I'm not really planning to build one, I just enjoyed reading a well written instructable. Of course the reading wasn't only enjoyable, but also beneficial and gave me a lot of ideas. Thank you.
<p>We wanted to make an instructable where people could learn a lot of new concepts relating to IoT but aggregate the information in a way that would be easily accessible for everyone (hence all the links). We love getting feedback so we can keep writing more instructables on all the other projects we are working on too! Thank you very much for the feedback! Glad we could help!</p>
I am new to IoT. Thanks for that much details. Very helpful.
Thanks so much! Glad we could help!

About This Instructable

7,147views

181favorites

License:

More by Strategy_ResearchInterns:How to Build a Smart Lamp 
Add instructable to: