49713 Designing for the Internet of Things
· 16 members
A hands-on introductory course exploring the Internet of Things and connected product experiences.
An IoT device to enhance your friendship.
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.
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.
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.
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).
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.
// 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", ×, 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
#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", ×, 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
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.
// 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
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.
1. Lab material: https://diotlabs.daraghbyrne.me/docs/controlling-outputs-motors
2. Inspiration: http://diotfall2019.daraghbyrne.me/assignments/augmented-objects/#additional-predents
3. Inspiration: https://www.kickstarter.com/projects/parihug/parihug-hug-loved-ones-from-anywhere-in-the-world
A hands-on introductory course exploring the Internet of Things and connected product experiences.
An IoT device to enhance your friendship.
November 20th, 2019