Inspiration

As college students, most of us end up studying late into the evening; as this is often long after the sun has set, most of us turn to artificial light. However, bright lighting disrupts the production of melatonin, which helps us fall asleep. There are already a range of blue light filters for computers that can reduce blue light exposure in the evening; what if your lamp could do something similar?

What it does

Nite Lite is a solution to this problem. In the course of the hours immediately before bedtime, Nite Lite will gradually dim the lights so that the transition from working to sleeping goes as smoothly as possible. You can use any lamp of your choice with any standard incandescent light bulb, along with a standard light dimmer. In addition, Nite Lite features a multi-color RGB LED that changes color to reflect the current temperature. This way, you know exactly how to dress when you get up in the morning!

How we built it

Nite Lite functions as a controller for your light source, an adjunct to the lighting you already have. The primary component is an Arduino servo motor that gets connected to a light dimmer. The Arduino controls the servo motor, which in turn mechanically adjusts the brightness by moving the slider on the dimmer. Also included is a temperature sensor that reads the temperature. The Arduino processes the temperature and uses a formula to convert the voltage to a temperature, which is then used to adjust the colors emitted by the RGB LED to properly reflect the temperature. Fumbling for a light switch in the dark can be hard. That’s why a motion sensor is included that can automatically turn on the lights. The motion sensor is only used to turn the lights on, not off. If the lights are already on, sensor input is ignored.

Challenges we ran into

Our main initial setback was that we realized that we would not be able to use the Arduino to directly control the voltage. We couldn’t simply use a transistor, since we working with mains electricity, which is AC. Furthermore, since it’s 120 volts, it’s incredibly dangerous (potentially deadly). We had to look for alternate ways of achieving the same goal without being able to directly control voltage levels. We decided to look into mechanically controlling the brightness using an external dimmer, which would make directly connections to the electrical network unnecessary. The primary challenge we faced with the mechanical controller is that it proved finicky and hard to get right. For one, the servo motor did not have a complete 360 degrees of freedom, which restricted the kinds of motions we could utilize in controlling the dimmer. Secondly, it was extremely difficult to reliably keep the servo motor connected to the dimmer slider, which was incredibly slippery. We initially tried hot glue, but this came right off. Twisty ties and rubber bands around the slider came off easily as well. We ended up taping a piece of strong tape on top of the slider and then connecting a string from the servo to the piece of tape. This allowed us to reliably drag the slider in one direction to dim the light source.

Accomplishments that we’re proud of

We’re most proud of being able to control the slider mechanically, as this was not how we initially thought we would approach the project, and we had to go through many different design iterations of figuring out how to connect the servo motor to the slider and then how to control it so the brightness would change as desired.

What we learned

We learned how to coherently integrate multiple sensors into our project in a way that came together to function as part of one product. In particular, we learned a lot about using the servo motor, as this was not something we had used in the labs before, and its behavior was not intuitive and required a lot of debugging and analysis to understand. In some sense, we had to reverse engineer how the servo motor was working before we could control it to do what we wanted, and so we learned a lot about how it operated in the process.

What’s next for Nite Lite

In its current stage, Nite Lite is only capable of dimming a light over time, and can’t increase the brightness back on its own, mostly because of limitations of the servo motor we used. An upgrade to Nite Lite would involve using a servo motor capable of 360 degree movement. This would allow us to better control the slider. We could also opt to push the slider instead of tying directly to it, but this would require 360 degree freedom, as we would need to reverse directions and then move all the way around in order to change whether the light is dimming or brightening.

Promo Video

https://youtu.be/fbX5DTqvV5M

Code

// Include the Servo library 
#include <Servo.h>
// Declare the Servo pin 
int servoPin = 3; 
// Create a servo object 
Servo Servo1; 

//PWM pins for RGB LED
int redPin = 11;
int greenPin = 9;
int bluePin = 10;

// temperature sensor
int tempPin = A5;

// ping sensor
int trigPin = 12;
int echoPin = 13;

// switch
int switchPin = 6;
// initialize light to be white
int r = 255;
int g = 255;
int b = 255;

boolean lightOn = false;
int lightIntensity = 100;
int previousVoltage = 0;

#include <WiFi.h>

char ssid[] = "ESEPROJECT";     //  your network SSID (name) 
char pass[] = "12345678";    // your network password
int status = WL_IDLE_STATUS;     // the Wifi radio's status



#include <SPI.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

unsigned int localPort = 8888;       // local port to listen for UDP packets
const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets




// A UDP instance to let us send and receive packets over UDP
#include <WifiUdp.h>
WiFiUDP Udp;

void setup() { // setup code that runs once
  Serial.println("Setup");
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT); 
  pinMode(switchPin, INPUT);
  Servo1.attach(servoPin);
  Serial.begin(9600);
  // attempt to connect using WPA2 encryption:
  Serial.println("Attempting to connect to WPA network...");
  //status = WiFi.begin(ssid, pass);
  status = 4;
  Serial.println(status);

  // if you're not connected, stop here:
  if ( status != WL_CONNECTED) { 
    Serial.println("Couldn't get a wifi connection");
  } 
  // if you are connected, print out info about the connection:
  else {
    Serial.println("Connected to network");
  }
}

void updateRGB(int temperature) {
  int temp = temperature;
  Serial.print("TEMP is ");
  Serial.println(temp);
  if (temp < 30) {
    r = 0;
    g = 0;
    b = 255;
  } else if (temp < 45) {
    r = 30;
    g = 144;
    b = 255;
  } else if (temp < 60) {
    r = 0;
    g = 255;
    b = 255;
  } else if (temp < 70) {
    r = 0;
    g = 255;
    b = 127;
  } else if (temp < 80) {
    r = 255;
    g = 255;
    b = 0;
  } else if (temp < 95) {
    r = 255;
    g = 140;
    b = 0;
  } else {
    r = 255;
    g = 0;
    b = 0;
  }
}

double Thermister(int RawADC) {  // Function to perform the fancy math of the Steinhart-Hart equation
  double Temp;
  Temp = log(((10240000/RawADC) - 10000));
  Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
  Temp = Temp - 273.15;              // Convert Kelvin to Celsius
  Temp = (Temp * 9.0)/ 5.0 + 32.0; // Celsius to Fahrenheit - comment out this line if you need Celsius
  return Temp;
}

void updateTemperature() {
  int temperatureReading = analogRead(tempPin);
  int temperature = Thermister(temperatureReading);
  updateRGB(temperature);
  analogWrite(redPin, r);
  analogWrite(greenPin, g);
  analogWrite(bluePin, b); 
}

void readMotionSensor() {
  long duration, distance;
  digitalWrite(trigPin, LOW);  // start trig at 0
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH); //The rising edge of trig pulse
  delayMicroseconds(10); // decides duration of trig pulse
  digitalWrite(trigPin, LOW); //falling edge of the trig pulse
  // NOTE: echo pin reads HIGH till it receives the reflected signal
  duration = pulseIn(echoPin, HIGH);  // Reading the duration for which echoPin was HIGH gives        //you the time the sensor receives a reflected signal at the echo pin
  distance = (duration / 2) / 29.1;   // Calculate the distance of the reflecting surface in cm
  Serial.print("MTN AT ");
  Serial.println(distance);
  if (distance < 3  && distance >= 0) {
    if (lightOn == false) {
      lightOn = true;
      updateLight();
    }
  }
}

void toggleLight() {
  if (lightOn == false) {
    lightOn = true;
  } else {
    lightOn = false;
  }
}

void updateLight() { // call this function whenever light settings are changed
  // adjust slider to proper position
  if (lightOn == true) { // set to proper position based on brightness
    int servoPos = getServo(lightIntensity);
    Servo1.write(servoPos); // turn to proper position
  } else { // set to OFF position
    int servoPos = getServo(0); // get position for 0 brightness light
    Servo1.write(servoPos); // turn light to OFF position
  }
}

int getServo(int intensity) { // translates brightness levels to servo positions
  if (lightIntensity > 90) {
    return 45; // all the way on
  } else if (intensity > 80) {
    return 40;
  } else if (intensity > 70) {
    return 35;
  } else if (intensity > 60) {
    return 30;
  } else if (intensity > 50) {
    return 25;
  } else if (intensity > 40) {
    return 20;
  } else if (intensity > 30) {
    return 15;
  } else if (intensity > 20) {
    return 10;
  } else if (intensity > 10) {
    return 5;
  } else if (intensity > 5) {
    return 0;
  } else {
    return -30;
  }
}

void readSwitch() {
  int switchVoltage = digitalRead(switchPin);
  if (previousVoltage == 0 && switchVoltage == 1) {
    Serial.println("VOLTAGE high");
    delay(200); // debounce
    if (switchVoltage == 1) {
      toggleLight();
      updateLight();
      previousVoltage = 1;
      Serial.println("SWITCH pressed");
    }
  } else if (switchVoltage == 0) {
    previousVoltage = 0;
  }
}

// send an NTP request to the time server at the given address
void sendNTPpacket(const char * address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); // NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

void getTime() {
  sendNTPpacket(timeServer); // send an NTP packet to a time server

  // wait to see if a reply is available
  delay(1000);
  // We've received a packet, read the data from it
  Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

  // the timestamp starts at byte 40 of the received packet and is four bytes,
  // or two words, long. First, extract the two words:

  unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
  unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
  // combine the four bytes (two words) into a long integer
  // this is NTP time (seconds since Jan 1 1900):
  unsigned long secsSince1900 = highWord << 16 | lowWord;
  Serial.print("Seconds since Jan 1 1900 = ");
  Serial.println(secsSince1900);

  // now convert NTP time into everyday time:
  Serial.print("Unix time = ");
  // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
  const unsigned long seventyYears = 2208988800UL;
  // subtract seventy years:
  unsigned long epoch = secsSince1900 - seventyYears;
  // print Unix time:
  Serial.println(epoch);

  // print the hour, minute and second:
  Serial.print("The UTC time is ");       // UTC is the time at Greenwich Meridian (GMT)
  Serial.print((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
  Serial.print(':');
  if (((epoch % 3600) / 60) < 10) {
    // In the first 10 minutes of each hour, we'll want a leading '0'
    Serial.print('0');
  }
  Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
  Serial.print(':');
  if ((epoch % 60) < 10) {
    // In the first 10 seconds of each minute, we'll want a leading '0'
    Serial.print('0');
  }
  Serial.println(epoch % 60); // print the second
  // wait 1 seconds before asking for the time again
  delay(1000);
}

void loop() {
  //getTime();
  readSwitch();
  readMotionSensor();
  updateTemperature();
  delay(50);
  Servo1.write(20);
}
Share this project:

Updates

Private user

Private user posted an update

Inspiration

As college students, most of us end up studying late into the evening; as this is often long after the sun has set, most of us turn to artificial light. However, bright lighting disrupts the production of melatonin, which helps us fall asleep. There are already a range of blue light filters for computers that can reduce blue light exposure in the evening; what if your lamp could do something similar?

What it does

Nite Lite is a solution to this problem. In the course of the hours immediately before bedtime, Nite Lite will gradually dim the lights so that the transition from working to sleeping goes as smoothly as possible. You can use any lamp of your choice with any standard incandescent light bulb, along with a standard light dimmer. In addition, Nite Lite features a multi-color RGB LED that changes color to reflect the current temperature. This way, you know exactly how to dress when you get up in the morning!

How we built it

Nite Lite functions as a controller for your light source, an adjunct to the lighting you already have. The primary component is an Arduino servo motor that gets connected to a light dimmer. The Arduino controls the servo motor, which in turn mechanically adjusts the brightness by moving the slider on the dimmer. Also included is a temperature sensor that reads the temperature. The Arduino processes the temperature and uses a formula to convert the voltage to a temperature, which is then used to adjust the colors emitted by the RGB LED to properly reflect the temperature. Fumbling for a light switch in the dark can be hard. That’s why a motion sensor is included that can automatically turn on the lights. The motion sensor is only used to turn the lights on, not off. If the lights are already on, sensor input is ignored.

Challenges we ran into

Our main initial setback was that we realized that we would not be able to use the Arduino to directly control the voltage. We couldn’t simply use a transistor, since we working with mains electricity, which is AC. Furthermore, since it’s 120 volts, it’s incredibly dangerous (potentially deadly). We had to look for alternate ways of achieving the same goal without being able to directly control voltage levels. We decided to look into mechanically controlling the brightness using an external dimmer, which would make directly connections to the electrical network unnecessary. The primary challenge we faced with the mechanical controller is that it proved finicky and hard to get right. For one, the servo motor did not have a complete 360 degrees of freedom, which restricted the kinds of motions we could utilize in controlling the dimmer. Secondly, it was extremely difficult to reliably keep the servo motor connected to the dimmer slider, which was incredibly slippery. We initially tried hot glue, but this came right off. Twisty ties and rubber bands around the slider came off easily as well. We ended up taping a piece of strong tape on top of the slider and then connecting a string from the servo to the piece of tape. This allowed us to reliably drag the slider in one direction to dim the light source.

Accomplishments that we’re proud of

We’re most proud of being able to control the slider mechanically, as this was not how we initially thought we would approach the project, and we had to go through many different design iterations of figuring out how to connect the servo motor to the slider and then how to control it so the brightness would change as desired.

What we learned

We learned how to coherently integrate multiple sensors into our project in a way that came together to function as part of one product. In particular, we learned a lot about using the servo motor, as this was not something we had used in the labs before, and its behavior was not intuitive and required a lot of debugging and analysis to understand. In some sense, we had to reverse engineer how the servo motor was working before we could control it to do what we wanted, and so we learned a lot about how it operated in the process. What’s next for Nite Lite In its current stage, Nite Lite is only capable of dimming a light over time, and can’t increase the brightness back on its own, mostly because of limitations of the servo motor we used. An upgrade to Nite Lite would involve using a servo motor capable of 360 degree movement. This would allow us to better control the slider. We could also opt to push the slider instead of tying directly to it, but this would require 360 degree freedom, as we would need to reverse directions and then move all the way around in order to change whether the light is dimming or brightening.

Promo Video

https://youtu.be/fbX5DTqvV5M

Code

`// Include the Servo library

include

// Declare the Servo pin int servoPin = 3; // Create a servo object Servo Servo1;

//PWM pins for RGB LED int redPin = 11; int greenPin = 9; int bluePin = 10;

// temperature sensor int tempPin = A5;

// ping sensor int trigPin = 12; int echoPin = 13;

// switch int switchPin = 6; // initialize light to be white int r = 255; int g = 255; int b = 255;

boolean lightOn = false; int lightIntensity = 100; int previousVoltage = 0;

include

char ssid[] = "ESEPROJECT"; // your network SSID (name) char pass[] = "12345678"; // your network password int status = WL_IDLE_STATUS; // the Wifi radio's status

include

// Enter a MAC address for your controller below. // Newer Ethernet shields have a MAC address printed on a sticker on the shield byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

unsigned int localPort = 8888; // local port to listen for UDP packets const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP

include

WiFiUDP Udp;

void setup() { // setup code that runs once Serial.println("Setup"); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(switchPin, INPUT); Servo1.attach(servoPin); Serial.begin(9600); // attempt to connect using WPA2 encryption: Serial.println("Attempting to connect to WPA network..."); //status = WiFi.begin(ssid, pass); status = 4; Serial.println(status);

// if you're not connected, stop here: if ( status != WL_CONNECTED) { Serial.println("Couldn't get a wifi connection"); } // if you are connected, print out info about the connection: else { Serial.println("Connected to network"); } }

void updateRGB(int temperature) { int temp = temperature; Serial.print("TEMP is "); Serial.println(temp); if (temp < 30) { r = 0; g = 0; b = 255; } else if (temp < 45) { r = 30; g = 144; b = 255; } else if (temp < 60) { r = 0; g = 255; b = 255; } else if (temp < 70) { r = 0; g = 255; b = 127; } else if (temp < 80) { r = 255; g = 255; b = 0; } else if (temp < 95) { r = 255; g = 140; b = 0; } else { r = 255; g = 0; b = 0; } }

double Thermister(int RawADC) { // Function to perform the fancy math of the Steinhart-Hart equation double Temp; Temp = log(((10240000/RawADC) - 10000)); Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp ); Temp = Temp - 273.15; // Convert Kelvin to Celsius Temp = (Temp * 9.0)/ 5.0 + 32.0; // Celsius to Fahrenheit - comment out this line if you need Celsius return Temp; }

void updateTemperature() { int temperatureReading = analogRead(tempPin); int temperature = Thermister(temperatureReading); updateRGB(temperature); analogWrite(redPin, r); analogWrite(greenPin, g); analogWrite(bluePin, b); }

void readMotionSensor() { long duration, distance; digitalWrite(trigPin, LOW); // start trig at 0 delayMicroseconds(2); digitalWrite(trigPin, HIGH); //The rising edge of trig pulse delayMicroseconds(10); // decides duration of trig pulse digitalWrite(trigPin, LOW); //falling edge of the trig pulse // NOTE: echo pin reads HIGH till it receives the reflected signal duration = pulseIn(echoPin, HIGH); // Reading the duration for which echoPin was HIGH gives //you the time the sensor receives a reflected signal at the echo pin distance = (duration / 2) / 29.1; // Calculate the distance of the reflecting surface in cm Serial.print("MTN AT "); Serial.println(distance); if (distance < 3 && distance >= 0) { if (lightOn == false) { lightOn = true; updateLight(); } } }

void toggleLight() { if (lightOn == false) { lightOn = true; } else { lightOn = false; } }

void updateLight() { // call this function whenever light settings are changed // adjust slider to proper position if (lightOn == true) { // set to proper position based on brightness int servoPos = getServo(lightIntensity); Servo1.write(servoPos); // turn to proper position } else { // set to OFF position int servoPos = getServo(0); // get position for 0 brightness light Servo1.write(servoPos); // turn light to OFF position } }

int getServo(int intensity) { // translates brightness levels to servo positions if (lightIntensity > 90) { return 45; // all the way on } else if (intensity > 80) { return 40; } else if (intensity > 70) { return 35; } else if (intensity > 60) { return 30; } else if (intensity > 50) { return 25; } else if (intensity > 40) { return 20; } else if (intensity > 30) { return 15; } else if (intensity > 20) { return 10; } else if (intensity > 10) { return 5; } else if (intensity > 5) { return 0; } else { return -30; } }

void readSwitch() { int switchVoltage = digitalRead(switchPin); if (previousVoltage == 0 && switchVoltage == 1) { Serial.println("VOLTAGE high"); delay(200); // debounce if (switchVoltage == 1) { toggleLight(); updateLight(); previousVoltage = 1; Serial.println("SWITCH pressed"); } } else if (switchVoltage == 0) { previousVoltage = 0; } }

// send an NTP request to the time server at the given address void sendNTPpacket(const char * address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52;

// all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); // NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); }

void getTime() { sendNTPpacket(timeServer); // send an NTP packet to a time server

// wait to see if a reply is available delay(1000); // We've received a packet, read the data from it Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

// the timestamp starts at byte 40 of the received packet and is four bytes, // or two words, long. First, extract the two words:

unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); // combine the four bytes (two words) into a long integer // this is NTP time (seconds since Jan 1 1900): unsigned long secsSince1900 = highWord << 16 | lowWord; Serial.print("Seconds since Jan 1 1900 = "); Serial.println(secsSince1900);

// now convert NTP time into everyday time: Serial.print("Unix time = "); // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: const unsigned long seventyYears = 2208988800UL; // subtract seventy years: unsigned long epoch = secsSince1900 - seventyYears; // print Unix time: Serial.println(epoch);

// print the hour, minute and second: Serial.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT) Serial.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day) Serial.print(':'); if (((epoch % 3600) / 60) < 10) { // In the first 10 minutes of each hour, we'll want a leading '0' Serial.print('0'); } Serial.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute) Serial.print(':'); if ((epoch % 60) < 10) { // In the first 10 seconds of each minute, we'll want a leading '0' Serial.print('0'); } Serial.println(epoch % 60); // print the second // wait 1 seconds before asking for the time again delay(1000); }

void loop() { //getTime(); readSwitch(); readMotionSensor(); updateTemperature(); delay(50); Servo1.write(20); }`

Log in or sign up for Devpost to join the conversation.