Friend Finder

Made by Fangzheng Zhu, ziruiliu and akrakora

Found in DIoT - Augmented Objects

An IoT device to enhance your friendship.

0

Outcome

Friend Finder is a pair of augmented clocks that lets you know when your friend is available to hang out.  When one friend presses the button on his clock, it lights up the paired clock, sending a signal that he's available to hang out.  A small character in the corner of the clock also jumps up and down and waves to say "Hi!".  When both friends activate their clocks, the lights mix to represent that both friends are available and they should make plans to hang out.  This project was designed as a reminder to keep up with your best friend, no matter how far apart you are.

0

Inspiration and Context

This project was inspired by Ricky, Fangzheng's friend, and the group's interviewee.  During the interview, he said that he really wanted to know when his friend was available to hang out but didn't want to keep texting him all of the time.  

Our team designed this device to be a gentle reminder that both people could reach out to each other in an unobtrusive way.  When one person is available, the device would send a notification to the other person that could be either responded to or ignored and was also a cute notification system that fits well into the home environment.  We did not want to include screens or text in an effort to make a glance-able and an extension of pre-existing IoT technology. 

0

Discovery

Our team interviewed freshman CMU student, Ricky, who studies architecture. We learned that he likes to hang out with friends, but would like to know if they are available to hang out without texting or bothering them. Sometimes friends miss each other but don’t want to annoy other people by asking them to do an activity too often. Other times, friends are separated by distance and can’t talk to each other in person, but would enjoy a way to communicate without texting or talking on the phone. 

0

Intention

Nowadays, everyone is only a couple of clicks away from each other. Communicating over long distances with text, video, images has never been so easy, and as mobility has become even easier, many of us live apart from our close friends. This makes instant messaging (IM) app valuable actors in our relationships. However, the sensory experience of these messages is the same whether it’s a message from a stranger or a close friend. Receiving an IM becomes a banal experience.

So we want to create an ambient device that is a playful solution to enhance the experience of instant messaging between friends using the possibilities of the Internet of Things (IoT).

0

Prototyping Process

First Prototype

The first prototype was a set of two augmented clocks that send information to each other through hardwired connections.  When one friend presses a button, his led lights up to show that he is sending a signal to his friend's clock.  His friend's clock lights up red, and a character jumps up and down to say "hi!".  If the friend is available, he presses his blue button, and it sends the same signal back to the paired clock.   To turn the signal off, either friend pushes their button.

0
original
Tristyn Zhu - https://youtu.be/tiQROvjlCr8
0
// This #include statement was automatically added by the Particle IDE.
#include <neopixel.h>



#define PIXEL_PIN D2
#define PIXEL_COUNT 30
#define PIXEL_TYPE WS2812

Adafruit_NeoPixel lightstrip = Adafruit_NeoPixel( PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE );
//Designing for IoT 11/15/2019
//Group: Friend Finder
//Function: Turn on leds and solenoids when buttons are pressed.

int solPin = D0;

int times = 1;
int d1 = 100;
int d2 = 1000;
int buttonPin = D4;
int ledPin = D3;
int OnTime = 0;
bool buttonDown=false;//as a trigger
bool onOrNot=false;//if the person is available
int LastClick=0;
bool shouldDoIt = false;


// This value will store the last time we published an event
long lastPublishedAt = 0;
// this is the time delay before we should publish a new event
// from this device
int publishAfter = 1000;

void setup()
{
    lightstrip.begin();
    lightstrip.show();
    
  Serial.begin( 9600 );

  Particle.variable( "times", &times, INT );
  Particle.variable( "d1", &d1, INT );
  Particle.variable( "d2", &d2, INT );
  Particle.function( "setTimes", setTimes );
  Particle.function( "setD1", setDelayOne );
  Particle.function( "setD2", setDelayTwo );
  Particle.function( "test", testSolenoid );

  pinMode(solPin, OUTPUT);
  pinMode( buttonPin , INPUT_PULLUP); // sets pin as input
  pinMode( ledPin , OUTPUT ); // sets pin as output
 
 OnTime = times*(d1+d2);
 
 Particle.subscribe(  "diot/2019/paired/friendfinder/On" , handleSharedEvent );
  Particle.subscribe(  "diot/2019/paired/friendfinder/Off" , handleSharedEvent2 );
}

void loop()
{
       // find out if the button is pushed
   // or not by reading from it.
   int buttonState = digitalRead( buttonPin );
    
    
 
  
 // delay( 100 );
  
  
  if( buttonState == LOW )
  {
    // turn the LED On
    digitalWrite( ledPin, HIGH);
    
    if(buttonDown==false){//as long as the button is pushed, the code inside this 'if' will run once
        testSolenoid("1");
        onOrNot=!onOrNot;
        LastClick=millis();
    uint32_t c = lightstrip.Color(255 , 0, 0 );
    for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        lightstrip.setPixelColor( i, c );
        lightstrip.show();
       // delay(100);
    }
   
    
        
        buttonDown=true;
    }
    
  }else{
    // otherwise
    // turn the LED Off
    digitalWrite( ledPin, LOW);
    buttonDown=false;
    
   
    
}

if(onOrNot){
    digitalWrite( ledPin, HIGH);
     publishMyEvent_On();//tell the other board that the friend using this board is available
     doIt( times, d1, d2 );
     
     
    
}else{
    digitalWrite( ledPin, LOW);
     digitalWrite(solPin, LOW);
      publishMyEvent_Off();//tell the other board that the friend using this board is available
     
          uint32_t off = lightstrip.Color( 0, 0, 0 );
    for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        
        lightstrip.setPixelColor( i, off );
        lightstrip.show();
    //    delay( 100); 
    }
 
     
}
  
}

void doIt( int times, int d1, int d2 ){
    
    
   /* 
  for( int i = 0; i < times; i++ )
  {
    digitalWrite(solPin, HIGH);
    delay( d1 ) ;
    digitalWrite(solPin, LOW);
    delay( d2 );
  }
  */
  
  int now=millis();
  if(now==LastClick+d1){
        digitalWrite(solPin, HIGH);
      
  }else if(now==LastClick+d1+d2){
      
        digitalWrite(solPin, LOW);
        LastClick=now;
  }
  
 
  
  
}


int testSolenoid( String command ){
  shouldDoIt = true;
  return 1;
}

int setTimes( String command ){

  Serial.println( "setTimes: " + command );
  int value = command.toInt();
  if( value < 0 ) return -1;
  if( value > 20 ) return -1;
  times = constrain( value, 0, 20 );
  return 1;

}

int setDelayOne( String command ){

  Serial.println( "setDelayOne: " + command );
  int value = command.toInt();
  if( value < 10 ) return -1;
  if( value > 20000 ) return -1;
  d1 = constrain( value, 0, 20000 );
  return 1;

}

int setDelayTwo( String command ){

  Serial.println( "setDelayTwo: " + command );
  int value = command.toInt();
  if( value < 10 ) return -1;
  if( value > 20000 ) return -1;
  d2 = constrain( value, 0, 20000 );
  return 1;

}


void publishMyEvent_On()
{
  // Remember that a device can publish at rate of about 1 event/sec,
  // with bursts of up to 4 allowed in 1 second.
  // Back to back burst of 4 messages will take 4 seconds to recover.
  // So we want to limit the amount of publish events that happen.

  // check that it's been 10 secondds since our last publish
  if( lastPublishedAt + publishAfter < millis() )
  {
      // Remember our subscribe is matching  "db2018/paired/"
      // We'll append the device id to get more specific
      // about where the event came from

      // System.deviceID() provides an easy way to extract the device
      // ID of your device. It returns a String object of the device ID,
      // which is used to identify your device.

      String eventName = "diot/2019/paired/friendfinder/On" + System.deviceID();

      // now we have something like "diot/2019/paired/0123456789abcdef"
      // and that corresponds to this devices info

      // then we share it out
      Particle.publish( eventName, "data goes here" );

      // And this will get shared out to all devices using this code

      // we just pubished so capture this.
      lastPublishedAt = millis();
  }

}

void publishMyEvent_Off()
{
  
  if( lastPublishedAt + publishAfter < millis() )
  {
    

      String eventName = "diot/2019/paired/friendfinder/Off" + System.deviceID();

      Particle.publish( eventName, "data goes here" );

      lastPublishedAt = millis();
  }

}




void handleSharedEvent(const char *event, const char *data)
{
    // Now we're getting ALL events published using "db2018/paired/"
    // This includes events from this device.
    // So we need to ignore any events that we sent.

    // Let's check the event name
    String eventName = String( event ); // convert to a string object
    // This gives us access to a bunch of built in methods
    // Like indexOf()
    // Locates a character or String within another String.
    // By default, searches from the beginning of the String,
    // but can also start from a given index,
    // allowing for the locating of all instances of the character or String.
    // It Returns: The index of val within the String, or -1 if not found.

    // We can use this to check if our event name contains the
    // id of this device

    String deviceID = System.deviceID();

    // device id = 0123456789abcdef
    // event = "diot/2019/paired/0123456789abcdef"

    if( eventName.indexOf( deviceID ) != -1 ){
      // if we get anything other than -1
      // the event came from this device.
      // so stop doing stuff
      return;
    }else{
        
        if(onOrNot){//when this board is told by another board that the friend is availbale and this board is also available, turn on the lightstrip in purple
            uint32_t c = lightstrip.Color( 255, 0, 255 );
    for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        lightstrip.setPixelColor( i, c );
        lightstrip.show();
       // delay(100);
    }
        }
        
        
    }

    // otherwise do your stuff to respond to
    // the paired device here

    //motorOn = true;

}

void handleSharedEvent2(const char *event, const char *data)
{
   
    String eventName = String( event ); // convert to a string object
  

    String deviceID = System.deviceID();

    if( eventName.indexOf( deviceID ) != -1 ){
   
      return;
    }else{
        
        if(onOrNot){//when this board is told by another board that the friend is unavailbale and this board is still available, turn the lightstrip to default(red or blue)
            uint32_t c = lightstrip.Color( 255, 0, 0 );
    for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        lightstrip.setPixelColor( i, c );
        lightstrip.show();
       // delay(100);
    }
        }
        
        
    }


}
Click to Expand
0

Second prototype 

In the second prototype, the team started to experiment with particle pairing capabilities.  When both buttons are pressed, the clocks turn purple to show that both friends are available.  This connection was made through the particle cloud instead of a hard-wired connection.

0
Second prototype
Tristyn Zhu - https://www.youtube.com/watch?v=0wo26eb5cAk
0
#include <neopixel.h>
#define PIXEL_PIN D2
#define PIXEL_COUNT 30
#define PIXEL_TYPE WS2812

Adafruit_NeoPixel lightstrip = Adafruit_NeoPixel( PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE );
//Designing for IoT 11/14/2019
//Group: Friend Finder
//Function: Turn on leds and solenoids when buttons are pressed.  Mix Pixel colors when both buttons are on.

int solPin = D0;

int times = 1;
int d1 = 100;
int d2 = 1000;
int buttonPin = D4;
int ledPin = D3;
int OnTime = 0;
bool buttonDown=false;//as a trigger
bool onOrNot=false;//if the person is available
int LastClick=0;
bool shouldDoIt = false;

void setup()
{
    lightstrip.begin();
    lightstrip.show();
    
  Serial.begin( 9600 );

  Particle.variable( "times", &times, INT );
  Particle.variable( "d1", &d1, INT );
  Particle.variable( "d2", &d2, INT );
  Particle.function( "setTimes", setTimes );
  Particle.function( "setD1", setDelayOne );
  Particle.function( "setD2", setDelayTwo );
  Particle.function( "test", testSolenoid );

  pinMode(solPin, OUTPUT);
  pinMode( buttonPin , INPUT_PULLUP); // sets pin as input
  pinMode( ledPin , OUTPUT ); // sets pin as output
 
 OnTime = times*(d1+d2);
 
}

void loop()
{
       // find out if the button is pushed
   // or not by reading from it.
   int buttonState = digitalRead( buttonPin );
    
    
 
  
 // delay( 100 );
  
  
  if( buttonState == LOW )
  {
    // turn the LED On
    digitalWrite( ledPin, HIGH);
    
    if(buttonDown==false){
        testSolenoid("1");
        onOrNot=!onOrNot;
        LastClick=millis();
    uint32_t c = lightstrip.Color( 0, 0, 255 );
    for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        lightstrip.setPixelColor( i, c );
        lightstrip.show();
       // delay(100);
    }
        
        buttonDown=true;
    }
    
  }else{
    // otherwise
    // turn the LED Off
    digitalWrite( ledPin, LOW);
    buttonDown=false;
    
   
    
}

if(onOrNot){
    digitalWrite( ledPin, HIGH);
     doIt( times, d1, d2 );
     
     
    
}else{
    digitalWrite( ledPin, LOW);
     digitalWrite(solPin, LOW);
     
          uint32_t off = lightstrip.Color( 0, 0, 0 );
    for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        
        lightstrip.setPixelColor( i, off );
        lightstrip.show();
    //    delay( 100); 
    }
 
     
}
  
}

void doIt( int times, int d1, int d2 ){
    
    
   /* 
  for( int i = 0; i < times; i++ )
  {
    digitalWrite(solPin, HIGH);
    delay( d1 ) ;
    digitalWrite(solPin, LOW);
    delay( d2 );
  }
  */
  
  int now=millis();
  if(now==LastClick+d1){
        digitalWrite(solPin, HIGH);
      
  }else if(now==LastClick+d1+d2){
      
        digitalWrite(solPin, LOW);
        LastClick=now;
  }
  
 
  
  
}


int testSolenoid( String command ){
  shouldDoIt = true;
  return 1;
}

int setTimes( String command ){

  Serial.println( "setTimes: " + command );
  int value = command.toInt();
  if( value < 0 ) return -1;
  if( value > 20 ) return -1;
  times = constrain( value, 0, 20 );
  return 1;

}

int setDelayOne( String command ){

  Serial.println( "setDelayOne: " + command );
  int value = command.toInt();
  if( value < 10 ) return -1;
  if( value > 20000 ) return -1;
  d1 = constrain( value, 0, 20000 );
  return 1;

}

int setDelayTwo( String command ){

  Serial.println( "setDelayTwo: " + command );
  int value = command.toInt();
  if( value < 10 ) return -1;
  if( value > 20000 ) return -1;
  d2 = constrain( value, 0, 20000 );
  return 1;

}
Click to Expand
0

Iteration and Refinement

After demonstrating our prototype in class on November 22, our team changed a few things to make our device's purpose and function more clear for the user.

Feedback 1: The paper guy is not clear.  Therefore, we made the character larger and moved it closer to the window.

Feedback 2: We should do some sound design which uses a different sound pattern to show different status.  Therefore, we changed the speed of the solenoid to match different button press conditions.

Feedback 3: We should make the two boxes separate completely.  Therefore, we connected the Particle boards through the cloud.

Feedback 4: We should make the box looks cleaner.  Therefore, we remade one box and fixed a few things on the other box.

0

Final Prototype

Below is a system map for the Friend Finder augmented clocks.

0

Demo

0
0

Code

0
// This #include statement was automatically added by the Particle IDE.
#include <neopixel.h>

#define PIXEL_PIN D2
#define PIXEL_COUNT 30
#define PIXEL_TYPE WS2812

Adafruit_NeoPixel lightstrip = Adafruit_NeoPixel( PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE );

int solPin = D0;
int buttonPin = D4;
int ledPin = D3;
int d1 = 100; //delay time before the solenoid goes up
int d2 = 1500;//delay time before the solenoid goes down
bool buttonDown=false;//works as a valve to make sure some codes only run for once when the button is pushed
bool refreshtimeOrNot=false;//works as another valve to make sure some codes only run for once when the button is pushed
int LastClick=0;//the last time that the solenoid clicked
bool onOrNot=false;//a variable checking if the other friend is available
bool evenOrNot=true;//a variable checking if the person using this board is available
bool buttonState;//if the button is being pushed
int now=0;//a variable showing the time that the doIt function has been running
long lastPublishedAt = 0;// This value will store the last time we published an event
int publishAfter = 1000;// this is the time delay before we should publish a new event from this device

void setup()
{
  lightstrip.begin();
  lightstrip.show();
    
  Serial.begin( 9600 );

  Particle.variable( "d1", &d1, INT );
  Particle.variable( "d2", &d2, INT );
  Particle.variable( "even", evenOrNot);
  Particle.variable( "onOrNot", onOrNot);
  Particle.variable( "buttonState", buttonState);
  Particle.variable( "Last", LastClick);
  Particle.variable( "now", now);
  Particle.function( "setD1", setDelayOne );
  Particle.function( "setD2", setDelayTwo );

  pinMode(solPin, OUTPUT);
  pinMode( buttonPin , INPUT_PULLUP); // sets pin as input
  pinMode( ledPin , OUTPUT ); // sets pin as output
 
  Particle.subscribe(  "diot/2019/paired/friendfinder/On" , handleSharedEvent );
  Particle.subscribe(  "diot/2019/paired/friendfinder/Off" , handleSharedEvent2 );
    
}

void loop()
{
  
   buttonState = digitalRead( buttonPin ); // find out if the button is pushed or not by reading from it.

  
  if( buttonState == false )//when the button is pushed down
  {

    if(buttonDown==false){//as long as the button is pushed, the code inside this 'if' will run once
       if(evenOrNot){
             publishMyEvent_On();//tell the other board that the friend using this board is available
       }else{
           publishMyEvent_Off();//tell the other board that the friend using this board is unavailable
       }
        buttonDown=true;//the valve that makes sure the codes above only run once each time
       }
    
  }else{//when the button is not pushed down
    

    buttonDown=false;//reset the valve for the if part above
    
   
    
}

if(onOrNot){//when the other friend is available

    if(!evenOrNot)
    {//when the person using this board is also available
        d2=300;//speed up the solenoid's clicking to strengthen the signal
        uint32_t c = lightstrip.Color( 255, 0, 255 );// turn the color of the lightstrip to purple
        for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        lightstrip.setPixelColor( i, c );
        lightstrip.show();
        }
    }
        
    else
    {//when the person using this board is not available
        d2=1500;//slow down the solenoid's clicking to make it ambient
        uint32_t c = lightstrip.Color(255, 0, 0);//turn the color of the lightstrip back to red
        for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        lightstrip.setPixelColor( i, c );
        lightstrip.show();
        }
    }
        
        
        
    if(refreshtimeOrNot==false)
    {//if we just switch to this part, run the following code for once
        
        LastClick=millis();//initialize the timer of the doIt function
        refreshtimeOrNot=true;//make sure the codes here only run once each time
    }
    
    doIt( d1, d2 );//turn on the solenoid
   
    }
    else
    {
       refreshtimeOrNot=false;//reset the valve for the if part above
       LastClick=millis();
       digitalWrite(solPin, LOW);//reset the position of the solenoid
       uint32_t off = lightstrip.Color( 0, 0, 0 );//kill the lightstrip
       for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
       lightstrip.setPixelColor( i, off );
       lightstrip.show();
       }
 
     
   }
  
}

void doIt(int d1, int d2 ){//keep the solenoid clicking with intervals d1 and d2
  
  now=millis();//keep the timer running
  if((now>LastClick+d1)&&(now<LastClick+d1+d2)){//wait for d1 before the solenoid goes up 
        digitalWrite(solPin, HIGH);
      
  }else if(now>LastClick+d1+d2){//wait for d2 before the solenoid goes down 
      
        digitalWrite(solPin, LOW);
        LastClick=now;
  }
  
  
}


int setDelayOne( String command ){//make sure the d1 value is sitting within a proper range

  Serial.println( "setDelayOne: " + command );
  int value = command.toInt();
  if( value < 10 ) return -1;
  if( value > 20000 ) return -1;
  d1 = constrain( value, 0, 20000 );
  return 1;

}

int setDelayTwo( String command ){//make sure the d2 value is sitting within a proper range

  Serial.println( "setDelayTwo: " + command );
  int value = command.toInt();
  if( value < 10 ) return -1;
  if( value > 20000 ) return -1;
  d2 = constrain( value, 0, 20000 );
  return 1;

}


void publishMyEvent_On()//publish event when telling the other friend the person using this board is available
{
  // Remember that a device can publish at rate of about 1 event/sec,
  // with bursts of up to 4 allowed in 1 second.
  // Back to back burst of 4 messages will take 4 seconds to recover.
  // So we want to limit the amount of publish events that happen.

  // check that it's been 10 seconds since our last publish
  if( lastPublishedAt + publishAfter < millis() )
  {
      // Remember our subscribe is matching  "db2018/paired/"
      // We'll append the device id to get more specific
      // about where the event came from

      // System.deviceID() provides an easy way to extract the device
      // ID of your device. It returns a String object of the device ID,
      // which is used to identify your device.

      String eventName = "diot/2019/paired/friendfinder/On" + System.deviceID();

      // now we have something like "diot/2019/paired/0123456789abcdef"
      // and that corresponds to this devices info

      // then we share it out
      Particle.publish( eventName, "data goes here" );
      evenOrNot=! evenOrNot;
      digitalWrite( ledPin, HIGH);
      // And this will get shared out to all devices using this code

      // we just published so capture this.
      lastPublishedAt = millis();
  }

}

void publishMyEvent_Off()//publish event when telling the other friend the person using this board is not available
{
  
  if( lastPublishedAt + publishAfter < millis() )
  {
    

      String eventName = "diot/2019/paired/friendfinder/Off" + System.deviceID();

      Particle.publish( eventName, "data goes there" );
      evenOrNot=! evenOrNot;
 digitalWrite( ledPin, LOW);
      lastPublishedAt = millis();
  }

}




void handleSharedEvent(const char *event, const char *data)//when receiving function that the the other friend is available, execute the following codes
{
    // Now we're getting ALL events published using "db2018/paired/"
    // This includes events from this device.
    // So we need to ignore any events that we sent.

    // Let's check the event name
    String eventName = String( event ); // convert to a string object
    // This gives us access to a bunch of built in methods
    // Like indexOf()
    // Locates a character or String within another String.
    // By default, searches from the beginning of the String,
    // but can also start from a given index,
    // allowing for the locating of all instances of the character or String.
    // It Returns: The index of val within the String, or -1 if not found.

    // We can use this to check if our event name contains the
    // id of this device

    String deviceID = System.deviceID();

    // device id = 0123456789abcdef
    // event = "diot/2019/paired/0123456789abcdef"

    if( eventName.indexOf( deviceID ) != -1 ){
      // if we get anything other than -1
      // the event came from this device.
      // so stop doing stuff
      return;
    }else{
        
        onOrNot=true;
           
     
        
        
    }

    // otherwise do your stuff to respond to
    // the paired device here

    //motorOn = true;

}

void handleSharedEvent2(const char *event, const char *data)//when receiving function that the the other friend is not available, execute the following codes
{
   
    String eventName = String( event ); // convert to a string object
  

    String deviceID = System.deviceID();

    if( eventName.indexOf( deviceID ) != -1 ){
   
      return;
    }else{
         
         onOrNot=false;
        
        if(!evenOrNot){//when this board is told by another board that the friend is unavailbale and this board is still available, turn the lightstrip to default(red or blue)
            uint32_t c = lightstrip.Color( 0, 0, 0 );
    for( int i = 0 ; i < lightstrip.numPixels(); i++ ){
        lightstrip.setPixelColor( i, c );
        lightstrip.show();
       // delay(100);
    }
        }
        
        
    }


}
Click to Expand
0

Circuit Diagram

0

Next Steps

Here are things that our team would like to improve in future work:

1. Experiment with a different motor to change how the person waves its hands or notifies that the friend is available to hang out.

2. Experiment with other form factors such as a bracelet, necklace, or key chain.  These objects are more mobile.  

3. Consider making the clocks smaller and use batteries as their power sources. Also, the sound can be more ambient so that it won't be too disturbing. We can also connect them to an app if possible.

4. If we take these ideas forward, we would need to consider how different motors work, how to scale down the circuit.  

x
Share this Project

Courses

49713 Designing for the Internet of Things

· 16 members

A hands-on introductory course exploring the Internet of Things and connected product experiences.


Focused on
About

An IoT device to enhance your friendship.

Created

November 20th, 2019