Get off the 'Can'

Made by Xi Zhang, Manchit Rajani and Mark Davidson

Found in DIoT 2018 2: Ambient Affect

An ambient device to prevent people from spending longer than needed on the toilet.

0

Context

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 causes a lot of inconvenience to others, especially if the bathroom is shared.

Conceptual Design

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. When the light turns red a speaker system starts playing a song which only stops if the user gets off the seat. The device also sends the user a notification on a phone as a warning. This encourages people to vacate the toilet as soon as they are finished using it.   

0

Process

Our initial idea was to use a pressure sensor to detect if someone was sitting on the toilet. But we soon realized an IR distance sensor will be easier to incorporate in the design.

We started with coding for the IR distance sensor and the NeoPixel. When the IR distance sensor detects the user the NeoPixel lights up, and fades when the user moves out of range. If the user stays put for a certain amount of time the NeoPixel will transition from Green to Red. We used the following code as reference. 
(http://tronixstuff.com/2011/06/22/tutorial-arduino-timing-methods-with-millis/)

We then integrated the DF mini player and a speaker to play a sound file on activation. However we met with problems when we were coding for the speaker as it required us to use a library which we were unaware of. We later found a great way to play an audio file. We referred to the following code. 
https://community.particle.io/t/a-great-very-cheap-mp3-sound-module-without-need-for-a-library/20111.   

We also published it to the particle cloud and integrated a notification function using IFTTT. It will send a notification to the users phone to warn him that time is almost up. The issue with using IFTTT for this application is that it could take up to 15 minutes for it 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. We then put the completed product to test in the bathroom and confirm if it actauly worked in the environment and context it was built for

0
//CODE FOR IR DISTANCE SENSOR + NEOPIXEL

#include "neopixel.h"
#include <math.h>
// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN D2
#define PIXEL_COUNT 24
#define PIXEL_TYPE WS2812

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 unsigned long start;
int unsigned long timeElapsed;

void setup() {
  Serial.println( 9600 ); //enable serial monitor
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  pinMode( PIXEL_PIN, OUTPUT );
  pinMode(ledPin, OUTPUT);
  start = millis();
  timeElapsed = 0;
}

void loop() {
  distance = sampleProximity();
  Serial.println( distance );
  if(distance <= 300){
    rainbow(300);
  }

  // int unsigned long finish = millis();
  // timeElapsed = finish - start;
  //
  // if(timeElapsed >= 3000){
  //   rainbow(300);
  // digitalWrite(ledPin, HIGH);
  // delay(500);
  // digitalWrite(ledPin, LOW);
  // delay(500);
  // start = millis();
  // }
  // delay(500);
}

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));
    }
    strip.show();
    delay(wait);
    Particle.connect();
    distance = sampleProximity();
    Serial.println( distance );
    if(distance > 500){
      colorWipe(strip.Color(0, 0, 0), 25);
      break;
    }
  }
  Particle.publish("timeUp");
}
/**
 * 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);
  }
}
Click to Expand
0

Part List

TF Card U Disk Mini MP3 Player x 1

Micro SD Memory Card x 1

Samsung 15318680 8Ω 1.5w+ Speaker x 1

Sharp 2Y0A02 F 69 Proximity Sensor x 1

NeoPixel Ring x 1

Photon x 1

Breadboard x 1

Jump Wires x 13

0
//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);
}
Click to Expand
0
0

Prototype

0

Workflow

When the IR distance sensor detects an object within range the NeoPixel lights up in green. If the object is moved away from the sensor the light switches off again. 

If the object stays within range for a while, the green transitions to red. This happens over a course of 10 minutes. 

Just before 10 minutes elapse the user gets a notification from IFTTT telling him/her to hurry up.When the NeoPixel is completely red the speaker system starts playing a song which is stored in an MMC in the DF Mini player. 

In order to turn the sound off , the object has to move away from range.


0

Storyboard