Overview
This alarm box utilizes a scale to detect the presence/absence of a contact lens case and sends out different reminders and alarms to promote a more routine and healthy habit of contact lens usage.
The LED’s will light up when the contact case is not in the designated box, assuming that the contact lens are currently being used or not properly stored. LEDs are green when the “usage time” or amount of time the case is not in the box is within the appropriate limit.
LEDs turn red when the contact case has been out of the box for too long or greater than the set appropriate amount of time (3 minutes for the purpose of this demonstration).
The box will trigger an alarm when the contacts case is not in the box and the current time is equal to the set “alarm time” or “A” as controlled by the slider. It will continue to make sounds and flash its LEDs for two hours or until the contact case is back in the box.
Only a small item that is around the weight range of a contact case can be used to stop the alarm. If the object is lighter or heavier than a typical contact case, the alarm will remain activated.
BONUS: The blue button converts the device into a fun night light when pressed.
Process Images & Review
One of the big changes made early in the process was to use a load cell that measures weight to detect the presence/absence of a contact lens case in the device. Originally, the plan was to use an IR Sensor to detect if the case is placed on the device or not. However, I wanted to try something new and I was able to find a small load cell that can measure small weight changes. This type of load cell is a strain gauge, which is set up in a Z formation and measures force from the deformation of the aluminum bar.
After wiring up all of the essential components to make the device, I realized it was a lot of components and that I would need to optimize the wiring/position of the different parts to make a compact device that can fit in a small box.
First, I consolidated all of the wires onto one medium-sized breadboard. Then, I 3D-printed an enclosure which would house the load cell apparatus. I left empty space below the scale so that the breadboard can rest underneath. This stacking helped reduce the overall area of the final box and thus amount of acrylic material used.
The five breadboard-friendly RGB Neopixels did not all fit onto the medium-sized breadboard. I used a lot female-female wires to connect the 5 pixels together and a few male-female wires to connect the first pixel to the breadboard. This introduced more degrees of freedom to how the 5 LED’s could be positioned. When designing the 3D-printed enclosure, I made a small rectangular hole that can hold the 5 Neopixels in place so that it looks like an LED strip. I could have also used actual LED strips, but strips available had too much space in between each LED light.
Discussion
I am overall satisfied with how my project turned out. I was able to incorporate new electrical components and learn how to use them throughout the process. In the end, I was able to save space, carry out all of the different functions I wanted out of the device, and package them in a compact and usable box. Below are some of the responses I received from my peers:
“I liked how it looked, very clean and the lights under the glass container looked very cool. I also liked the thought that way put into it, like how you can’t cheat it. Maybe some labels could help other operate it as well, but overall a great project.“
“Other than the band holding it together, the Contact Lens Reminder looked very appealing. The light up display was beautiful. I liked how it could sense whether or not the contact lens case was in there or another item. Other than the scale not working properly all the time, the project was well done.“
Some of the positive responses that I received was that it looked clean and aesthetic. However, I do agree that this could have been improved. The LCD cutout was slightly too small and the square for the 3D enclosure was too constrained. I did not leave too much buffer on the side, which caused the acrylic to crack. Additionally, I did not leave myself enough time to glue the acrylic pieces together and have the adhesion cure. I do agree that the rubber band is a huge red flag.
I was able to learn new technical skills including working with a load cell and a real time clock. It was definitely hard to get the right second onto the clock, especially since I don’t know how long for sure it takes the computer to load the code to the Arduino. Additionally, I learned that the load cell super variable if you do not set it up right. During my initial testings, I did not screw the wood boards onto the load cell all the way and I only used one point of attachment instead of two. This introduced rotation and caused the load cell to have different read-outs for different positions. I also learned how to use a new Fusion360. It was easy to use to make laser print files for the large box but it was slow to use when I was designing the 3D-printed enclosure. Thus, I switched to Solidworks to do this part, which I was more comfortable with.
I do not expect to build another iteration of this project since it serves the main function I wanted it to. Some of the more crucial improvements also required access to a laser printer, which I would no longer have. However, if I were to do it again, I would carry out the steps earlier and give myself more time to prototype the physical final product. A lot of my difficulties involve not having the right measurements or constraints for the mechanical parts I laser cutted and 3D printed. Although it is easy to figure out which dimensions need to be changed and fixed, I need to further plan out to get more materials and use the machines in the Ideate/TechSpark space. Thus, it is important to build these things early so that I can have enough time to iterate and have better results.
Technical Information
Schematic
Code
/** Title: Alarm Box for Contact Lens Users Author: Gracia Genero Description: This code is used to keep track of time and how long my eye contacts have been used. It determines contact lens usage by the amount of time the contact case is not in the box, assuming that I will always take the contact case out of the scale when I am using my contacts and place the case back on scale when I have taken off the contact lens and stored it in the case. The way it works is as follows: 1. LED lights will remain inactived as long as the case is in the box/on the scale. It determines this by measuring the weight of the object placed directly on the load cell. 2. When the contact case is removed, the LED light will turn green. This indicates that it is withing the appropriate usage time. For a typical contacts lens, it is not recommended to use for more than 8 hours a day. 3. The device continues to keep track of how long the contact case have been out of the box. 4. When the usage time is above the recommended time, the LED light will turn red. 5. The slider sets the alarm time of when the box should send out an alarm if no contact case is detected. Ideally, this should be set at your bed time so that the alarm box can remind you to take it off before you go to sleep. When the real time is between the set alarm time plus 2 hours, the speaker and flashing red LEDs will be triggered. 6. When the alarm is going off, it can be deactivated with the placement of the contact case back onto the scale. If the object placed onto the scale is too light or too heavy compared to a typical contact case, it will not deactivate. 7. The switch turn the device into a fun night light. When pressed, the LED's will flash rainbow colors. _____________________________________________________ Pin mapping: pin | mode | description -------|--------|------------------------------------ A1 | input | slider potentiometer 2 | input | load cell SCK pin 3 | output | load cell DT pin 5 | output | Neopixel RGB Breadboard LEDs 7 | input | 6-pin Tactile Push-button switch 9 | output | speaker _____________________________________________________ Credits: The following functions were obtained or a modified version of prewritten functions from the sample codes of libraries <Adafruit_NeoPixel.h>, <DS3231.h>, and <Volume3.h> : R2D2() whiteOverRainbow() displayTime() **/ const int SLIDER_PIN = A1; const int SPEAKER_PIN = 9; const int LOADCELL_DOUT_PIN = 3; const int LOADCELL_SCK_PIN = 2; const int SWITCH_PIN = 7; const int LED_PIN = 5; const int LED_COUNT = 5; // number of Neopixels in series const int CONTACT_LOW = 20; // weight range of accepted values for when contact case is in the case const int CONTACT_HIGH = 200; const float CONTACT_LIMIT = 0.05; // maximum limit of contact usage in hours int contactLimitCalc = CONTACT_LIMIT * 3600; // convert above time to sec float calibration_factor = 730; // Timer variables const int SECONDWAIT = 1000; unsigned long secondTimer = 0; unsigned long absentTime = 0; unsigned long alarmTime = 0; int absentHour = 0; // will be calculated from absentTime int absentMin = 0; // will be calculated from absentTime int absentS = 0; // Toggle states bool LCDState = HIGH; bool LEDState = HIGH; bool alarmState = LOW; // is the alarm activated? bool ContactState = LOW; // is the contact case in the box? // Variables to store sensor values float weightVal; // load cell returns float numbers int alarm = 120 ; // what minute of the day should the alarm go off int alarm_stop = 360; // when alarm should stop int color; // color of LED lights int interval; int currentMinOfDay; int currentHour; int currentMin; // Libraries used #include <Volume3.h> #include <Wire.h> #include <DS3231.h> #include <Adafruit_NeoPixel.h> #include <LiquidCrystal_I2C.h> #include <HX711.h> // Objects DS3231 Clock; Adafruit_NeoPixel rgb(LED_COUNT, LED_PIN, NEO_RGB + NEO_KHZ800); LiquidCrystal_I2C screen(0x27, 16, 2); HX711 scale; // Clock variables byte Year = 20; byte Month = 03; byte Date = 17; byte Day = 03; byte Hour = 18; byte Minute = 18; byte Second = 35; bool Century=false; bool h12; bool PM; byte ADay, AHour, AMinute, ASecond, ABits; bool ADy, A12h, Apm; void setup() { Serial.begin(9600); Wire.begin(); // Potentiometer slider setup pinMode(SLIDER_PIN, INPUT); // Load cell setup scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0 // RGB Neopixels set up pinMode(LED_PIN, OUTPUT); rgb.begin(); rgb.show(); rgb.setPixelColor(0, 0, 150, 200); // first pixel lights up purple (for validation purposes) rgb.show(); rgb.setBrightness(50); // Set brightness to about 1/5 (max = 255) // set initial time of clock here /* Clock.setClockMode(false); // set to 24h // Clock.setClockMode(true); // set to 12h Clock.setYear(Year); Clock.setMonth(Month); Clock.setDate(Date); Clock.setDoW(Day); Clock.setHour(Hour); Clock.setMinute(Minute); Clock.setSecond(Second); */ // get current clock time currentHour = Clock.getHour(h12, PM); currentMin = Clock.getMinute(); currentMinOfDay = (currentHour * 60) + currentMin; // setup LCD screen screen.init(); screen.backlight(); // turn on the backlight to start LCDtoggle(); delay(2000); // do nothing for 2 seconds // tactile switch setup pinMode(SWITCH_PIN, INPUT); } void loop() { // UPDATE VALUES displayWeight(); // // SLIDER alarm = map(analogRead(SLIDER_PIN), 0, 1023, 0, 143) * 10; alarm_stop = alarm + 120; // alarm is activated for 2 hours unless deactivated through other means // TIMING // if it has at least 1 second since the secondTimer variable last changed: if ( (millis() - secondTimer) >= SECONDWAIT ) { if (alarmState) {LCDState = !LCDState;} // Update and calculate usage time // continue counting the number of seconds since the last time the contacts are in the box. // If the contacts are out of the box, stop counting if (!ContactState) { absentTime++; } else { absentTime = 0; } // Converts usage time (time contacts case is out of the box) to hours, minutes, seconds int absentCalcH = absentTime/3600; int absentCalcM = (absentTime % 3600) / 60; int absentCalcS = (absentTime % 3600) % 60; absentHour = abs(absentCalcH); absentMin = abs(absentCalcM); absentS = abs(absentCalcS); // reset the timer before exiting the function. secondTimer = millis(); // Print data displayTime(); // get and display Time Serial.print("Weight: "); Serial.print(weightVal, 1); Serial.print(" g"); Serial.println(); Serial.print("Contact State = " + String(ContactState)); Serial.println(); Serial.print("Usage Time:" + String(absentHour) + ":" + String(absentMin) + ":" + String(absentS)); Serial.println(); Serial.print("alarm = " + String(alarm) + " alarm_stop = " + String (alarm_stop) + " CurrentMin = " + String(currentMinOfDay)); Serial.println(); Serial.print("switch = " + String(digitalRead(SWITCH_PIN))); Serial.println(); Serial.println(); } // ACTION LCDtoggle(); // toggles the LCD on and off if (digitalRead(SWITCH_PIN)) { whiteOverRainbow(75, 5); } else { // Turn on LED if contacts are not in the box if (ContactState) { LEDState = LOW; alarmState = LOW; LCDState = HIGH; int interval = 0; setRGBcolor(color, interval); } else { LEDState = HIGH; // set off alarm if time is at set alarm time --> no natter what, at time X contacts should be in the box if ((currentMinOfDay >= alarm) and (currentMinOfDay <= alarm_stop)) { alarmState = HIGH; R2D2(); // turn on alarm sound color = 0; interval = 250; } else { alarmState = LOW; LCDState = HIGH; // change LED color based on usage time if (absentTime >= contactLimitCalc) { color = 0; } else { color = 1; } interval = 0; } setRGBcolor(color, interval); } } } /* Below are supporting functions */ void LCDtoggle() { int absMin1 = absentMin % 10; int absMin10 = absentMin / 10; int absHour1 = absentHour % 10; int absHour10 = absentHour / 10; int alaHour1 = (alarm / 60) % 10; int alaHour10 = (alarm / 60) / 10; int alaMin1 = (alarm % 60) % 10; int alaMin10 = (alarm % 60) /10; if (LCDState) { screen.display(); screen.setCursor(1,0); // set cursor to home position screen.print(String(Clock.getMonth(Century)) + "/" + String(Clock.getDate()) + "/" + String(Clock.getYear())); screen.setCursor(1, 1); screen.print("U " + String(absHour10) + String(absHour1) + ":" + String(absMin10) + String(absMin1) + // Time of usage " A " + String(alaHour10) + String(alaHour1) + ":" + String(alaMin10) + String(alaMin1)); // Time that alarm will go off } else {screen.noDisplay();} } void setRGBcolor(int color, int interval) { if (LEDState) { switch (color) { case 0: // red lightUpAll(0, 150, 0, interval); // PIXEL #, GREEN, RED, BLUE break; case 1: // green lightUpAll(150, 0, 0, interval); break; case 2: // blue lightUpAll(0, 0, 150, interval); break; case 3: // purple lightUpAll(0, 150, 200, interval); break; case 4: // yellow lightUpAll(150, 0, 150, interval); break; } } else { rgb.clear(); // turn off all LEDs rgb.show(); } } void lightUpAll(int G,int R,int B, int interval) { for(int i=0; i<rgb.numPixels(); i++) { rgb.setPixelColor(i, G, R, B); // sets the RGB values for pixel number i rgb.show(); delay(interval); } if (interval > 0) { rgb.clear(); rgb.show(); } } void displayWeight() { weightVal = scale.get_units(); // returns a float if ((weightVal >= CONTACT_LOW) and (weightVal <= CONTACT_HIGH)) { ContactState = HIGH; } else { ContactState = LOW; } } // Modified from library <DS3231.h> void displayTime() { currentHour = Clock.getHour(h12, PM); currentMin = Clock.getMinute(); currentMinOfDay = (currentHour * 60) + currentMin; Serial.print("2"); if (Century) { Serial.print("1"); } else { Serial.print("0"); } Serial.print(Clock.getYear(), DEC); Serial.print(' '); // then the month Serial.print(Clock.getMonth(Century), DEC); Serial.print(' '); // then the date Serial.print(Clock.getDate(), DEC); Serial.print(' '); // and the day of the week Serial.print(Clock.getDoW(), DEC); Serial.print(' '); // Finally the hour, minute, and second Serial.print(Clock.getHour(h12, PM), DEC); Serial.print(' '); Serial.print(Clock.getMinute(), DEC); Serial.print(' '); Serial.print(Clock.getSecond(), DEC); // Add AM/PM indicator if (h12) { if (PM) { Serial.print(" PM "); } else { Serial.print(" AM "); } } else { Serial.print(" 24h "); } // Display the temperature Serial.print("T = "); Serial.print(Clock.getTemperature(), 2); // Tell whether the time is (likely to be) valid if (Clock.oscillatorCheck()) { Serial.print(" O + "); } else { Serial.print(" O - "); } // New line on display Serial.print('\n'); int dayOfWeek = Clock.getDoW(); switch(dayOfWeek){ case 1: Serial.println("Sunday"); break; case 2: Serial.println("Monday"); break; case 3: Serial.println("Tuesday"); break; case 4: Serial.println("Wednesday"); break; case 5: Serial.println("Thursday"); break; case 6: Serial.println("Friday"); break; case 7: Serial.println("Saturday"); break; } } // Obtained from library <Volume3.h> void R2D2() { int beeps[] = {1933, 2156, 1863, 1505, 1816, 1933, 1729, 2291}; int buzzVols[] = {144, 180, 216, 252, 252, 252, 252, 216, 180, 144}; int i = 9; while (i >= 0) { vol.tone(SPEAKER_PIN, 1050, buzzVols[i]*4); delayMicroseconds(20*64); vol.tone(SPEAKER_PIN, 1050, buzzVols[i] / 8*4); delayMicroseconds(40*64); i--; } delay(35); i = 0; while (i < 8) { int v = 0; while (v < 250) { // 12.5 mS fade up time vol.tone(SPEAKER_PIN, beeps[i], v*4); v += 10; delayMicroseconds(2*64); } delay(20); v = 250; while (v > 0) { // 12.5 mS fade down time vol.tone(SPEAKER_PIN, beeps[i], v*4); v -= 10; delayMicroseconds(5*64); } vol.noTone(); delay(25); i++; } int f = 2466; while (f < 2825) { vol.tone(SPEAKER_PIN, f, 1023); f += 3; delay(1); } f = 2825; int v = 255; while (f > 2000) { vol.tone(SPEAKER_PIN, f, v*4); f -= 6; v -= 1; delay(1); } vol.noTone(); delay(35); i = 10; while (i > 0) { vol.tone(SPEAKER_PIN, 1050, buzzVols[i]*4); delayMicroseconds(20*64); vol.tone(SPEAKER_PIN, 1050, buzzVols[i] / 8*4); delayMicroseconds(40*64); i--; } vol.noTone(); } // Obtaied from library <Adafruit_NeoPixel.h> void whiteOverRainbow(int whiteSpeed, int whiteLength) { if(whiteLength >= rgb.numPixels()) whiteLength = rgb.numPixels() - 1; int head = whiteLength - 1; int tail = 0; int loops = 3; int loopNum = 0; uint32_t lastTime = millis(); uint32_t firstPixelHue = 0; for(;;) { // Repeat forever (or until a 'break' or 'return') for(int i=0; i<rgb.numPixels(); i++) { // For each pixel in strip... if(((i >= tail) && (i <= head)) || // If between head & tail... ((tail > head) && ((i >= tail) || (i <= head)))) { rgb.setPixelColor(i, rgb.Color(0, 0, 0, 255)); // Set white } else { // else set rainbow int pixelHue = firstPixelHue + (i * 65536L / rgb.numPixels()); rgb.setPixelColor(i, rgb.gamma32(rgb.ColorHSV(pixelHue))); } } rgb.show(); // Update strip with new contents // There's no delay here, it just runs full-tilt until the timer and // counter combination below runs out. firstPixelHue += 40; // Advance just a little along the color wheel if((millis() - lastTime) > whiteSpeed) { // Time to update head/tail? if(++head >= rgb.numPixels()) { // Advance head, wrap around head = 0; if(++loopNum >= loops) return; } if(++tail >= rgb.numPixels()) { // Advance tail, wrap around tail = 0; } lastTime = millis(); // Save time of last movement } } }
Comments are closed.