get off the 'can'
Get Off the 'Can' Wiring Diagram
About
With more and more applications and other forms of entertainment available on smartphones today, more and more people are carrying their phones when they go to the bathroom and spend way more time than needed on the toilet. This project was created to remind This causes a lot of inconvenience to others, especially if the bathroom is shared. Get Off the 'Can' is an ambient device that prevents people from spending longer then necessary on the toilet. This was a group project with one other member. My role on this project was building the code and circuitry for the prototype. This project took place over a week long design sprint.
Goal
This device notifies people when they spend more than a specified amount of time on the toilet by using a light and speaker system installed in the bathroom. The light gradually changes color from green to red over a period of 10 minutes, which allows the user to keep track of time. Halfway through, the device sends a notification to the user's phone, reminding them to finish. When the light turns red a speaker system starts playing a song which only stops if the user gets off the seat. This encourages people to vacate the toilet as soon as they are finished using it.
Components
1 x Particle Photon
1 x Adafruit Neopixel Ring 24 x 5050 RGB
1 x DFPlayer Mini (MP3 Player)
1 x Micro SD Memory Card
1 x Samsung 15318680 8Ω 1.5w+ Speaker
1 x Sharp 2Y0A02 F 69 Proximity Sensor
Process
Our initial idea was to use a pressure sensor to detect if someone was sitting on the toilet. Due with placement of the sensor and with the low maximum force the sensor could detect, we opted to use a proximity sensor instead.
I started coding by creating an interface between the IR proximity sensor and the NeoPixel. When the IR distance sensor detects the user the NeoPixel lights up, and fades to off when the user moves out of range. When the user stays within range, the NeoPixel will transition from Green to Red.
I then integrated the DFPlayer Mini and a speaker to play a sound file when triggered from the Particle. This proved to be challenging because the functionality of the player was not documented well on the manufacturer's website. Instead, I referenced a project in the Particle community that utilized the player to understand how to interface with it.
Once the offline functionality was successful, I developed code to publish an event to the Particle cloud halfway through the program's cycle. IFTTT scans the Particle cloud looking for the event. Once the event is found, it sends a push notification to the user's phone reminding them to finish. An issue arose while testing IFTTT integration this application; it could take up to 15 minutes for IFTTT to poll the Particle console. Although the majority of the time it will poll within a minute, it is not guaranteed and thus is not dependable.
After testing to make sure everything worked as intended we then built a casing around the circuit board and its other components. This casing houses the speaker and the IR distance sensor as well. The casing with the proximity sensor and speaker rests on the tank of the toilet, while the light is on a nearby counter within view of the user.
Resources
NeoPixel Library and Code Examples by Adafruit
Code
//FINAL CODE WITH DF MINI PLAYER AND IFTTT INTEGRATION #include "DFRobotDFPlayerMini.h" #include "neopixel.h" #include <math.h> # define Start_Byte 0x7E # define Version_Byte 0xFF # define Command_Length 0x06 # define End_Byte 0xEF # define Acknowledge 0x00 //Returns info with command 0x41 [0x01: info, 0x00: no info] // IMPORTANT: Set pixel COUNT, PIN and TYPE #define PIXEL_PIN D2 #define PIXEL_COUNT 24 #define PIXEL_TYPE WS2812 SYSTEM_THREAD(ENABLED); Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE); int ledPin = D0; int irPin = A0; int irProximity = 0; int transitionTime = 5000; // Tranition Time in ms const int sampleWindow = 50; int distance = 0; // initialize distance variable int done = FALSE ; // the rainbow function has completed int dogs = FALSE ; // music is not playing int unsigned long start; int unsigned long timeElapsed; //DFRobotDFPlayerMini myDFPlayer; //void printDetail(uint8_t type, int value); void setup() { Serial.println( 9600 ); //enable serial monitor Serial1.begin(9600); //enable serial using RX/RX ports execute_CMD(0x3F, 0, 0); // Send request for initialization parameters while (Serial1.available()<10){ // Wait until initialization parameters are received (10 bytes) delay(30); // Pretty long delays between succesive commands needed (not always the same) } setVolume(0); strip.begin(); strip.show(); // Initialize all pixels to 'off' pinMode( PIXEL_PIN, OUTPUT ); pinMode(ledPin, OUTPUT); } void loop() { distance = sampleProximity(); Serial.println( distance ); if(distance <= 300 && done == FALSE){ rainbow(200); } else if(distance <=300 && done == TRUE && dogs == FALSE){ delay(3000); playFirst(); dogs = TRUE; } else if(distance > 500 && done == TRUE){ pause(); colorWipe(strip.Color(0, 0, 0), 25); setVolume(0); done = FALSE; dogs = FALSE; } else{ } } int sampleProximity( ) { unsigned long startMillis = millis(); // Start of sample window int farthest_sample = 0; int closest_sample = 1000; // collect data for 50 mS while (millis() - startMillis < sampleWindow) { int sample = analogRead( irPin ); // invert the range, and convert it to a percent sample = map( sample, 0, 4095, 1000, 0 ); // now see if the sample is the lowest; if ( sample > farthest_sample ){ farthest_sample = sample ; } if ( sample < closest_sample ){ closest_sample = sample; } } Serial.print( "Farthest = " ); Serial.println( farthest_sample ); Serial.print( "Closest = " ); Serial.println( closest_sample ); int proximityAverage = (farthest_sample + closest_sample)/2 ; return proximityAverage; } /* METHODS */ void rainbow(int wait) { uint16_t i, j; for(j=0; j<85; j++) { for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, Wheel((j) & 255)); } if(j == 42){ Particle.publish("timeUp"); } strip.show(); delay(wait); Particle.connect(); distance = sampleProximity(); Serial.println( distance ); if(distance > 500){ colorWipe(strip.Color(0, 0, 0), 25); done = FALSE; break; } done = TRUE; } } /** * Scale a value returned from a trig function to a byte value. * [-1, +1] -> [0, 254] * Note that we ignore the possible value of 255, for efficiency, * and because nobody will be able to differentiate between the * brightness levels of 254 and 255. */ byte trigScale(float val) { val += 1.0; // move range to [0.0, 2.0] val *= 127.0; // move range to [0.0, 254.0] return int(val) & 255; } /** * Map an integer so that [0, striplength] -> [0, 2PI] */ float map2PI(int i) { return M_PI*2.0*float(i) / float(strip.numPixels()); } // Input a value 0 to 255 to get a color value. // The colours are a transition r - g - b - back to r. uint32_t Wheel(byte WheelPos) { if(WheelPos < 85) { return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } else if(WheelPos < 170) { WheelPos -= 85; return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); } else { WheelPos -= 170; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } } void colorWipe(uint32_t c, int wait) { for(uint16_t i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); strip.show(); delay(wait); } } void execute_CMD(byte CMD, byte Par1, byte Par2) // Excecute the command and parameters { // Calculate the checksum (2 bytes) int16_t checksum = -(Version_Byte + Command_Length + CMD + Acknowledge + Par1 + Par2); // Build the command line byte Command_line[10] = { Start_Byte, Version_Byte, Command_Length, CMD, Acknowledge, Par1, Par2, checksum >> 8, checksum & 0xFF, End_Byte}; //Send the command line to the module for (byte k=0; k<10; k++) { Serial1.write( Command_line[k]); } } void playFirst() { execute_CMD(0x3F, 0, 0); delay(500); setVolume(30); delay(500); execute_CMD(0x11,0,1); delay(500); } void pause() { execute_CMD(0x0E,0,0); delay(500); } void setVolume(int volume) { execute_CMD(0x06, 0, volume); // Set the volume (0x00~0x30) delay(2000); }