49713 Designing for the Internet of Things
· 16 members
A hands-on introductory course exploring the Internet of Things and connected product experiences.
Found in DioT 2019: Connected Communities
Jump! is a fun and competitive game where you compete with rival dorms for bragging rights. Located in the common room of an undergraduate dorm, it has a matching pair in the neighboring rival dorm. When you're walking through the common room and see your Jump! display flashing, it means that one of your competitors is doing jumping jacks on the paired device and is racking up points. Hop on your Jump! pad to complete more jumping jacks and beat the competition.
CMU is known to have a diverse, hardworking student body and an intense atmosphere. In some situations, this can lead to students feeling isolated because they focus so much on their coursework. People know that active study breaks can help to do more productive work over longer periods of time, but students often need more motivation to leave their seats and take a break. Our team’s goal is to great a connected device that can encourage students to take quick, fun, and active study breaks that will both reduce their stress and connect to their community. The team will focus on the setting of undergraduate dorm first-floor common areas because this is a semi-public and multi-functional space used for studying, hanging out, eating, and waiting for friends. This space also sees a large quantity of foot traffic because students must pass through the common area to reach their individual rooms. Undergraduate dorms are also known for the rivalries between different buildings, and so the team will focus on creating a device that will add to the competition between dorms and enhance the rivalry.
This team is defining the community as CMU undergraduates who live in dorms. This community is rather large and often looking to connect with others while adjusting to their new living spaces or filling the time between classes. There are about 7000 undergraduates at CMU who come from all over the world (1). About 65%, or 4500, of these students live in university housing (2). The first year of living in a dorm is a big adjustment for most people because they are busy, multi-use spaces without much privacy. Common rooms on the first floors of buildings are gathering places were people study, hang out, play video games, watch movies, or wait for others. People often pass through this space looking for something to do between other activities.
CMU is also known for its intense atmosphere and focus on academic success. Students seem to need to be reminded to take breaks and take care of themselves. While on-campus resources provide some reminders to be active, there’s no consistent activity for undergrads to take a quick, active study break in their dorm. This project will begin looking at the conceptualization and prototyping of an internet-connected device that encourages quick, active breaks while encouraging connections between students living in dorms.
The first precedent is a subway station in Moscow that allows passengers to pay for the ride in squats (1). It started during the Sochi Olympic games as a way to bring sports to more people. To get a free ticket, a passenger has to complete 30 squats in two minutes in front of the ticket machine, which counts the squats for the passenger. In videos of crowds using this device, the exercise brought smiles to the passengers doing the exercise and also to onlookers. Friends challenged each other to do the squats and some passengers upped the ante by doing one-legged squats or more squats in less time (2). Generally, the interaction was met with enthusiasm by users and onlookers who seemed to enjoy the novelty and silliness of the interaction. Not only did the device encourage passengers to take a quick break with exercise, but it also created a fun scenario with smiles and laughter that encouraged interactions with others in the community. Our team will build off of this precedent by also making a small station for exercising in place that is located in a public area to create a funny moment for the participants and onlookers. The sensing method will be different to accommodate the technology available in class, and we will connect matching devices through the Particle cloud in order to encourage competition between different parts of the undergraduate community.
The second precedent is the Underarmour online fitness tracker app (3). Many technologies exist that allow users to connect and compete with friends through an app. The apps collect and share fitness data between friend groups and can motivate users to keep active for some time, but the novelty of the experience quickly wears off. The app cannot display data in physical space because it is only displayed on phone screens. This precedent shows one way of displaying fitness data and competitions that can motivate people to exercise more but does not have a direct physical link to the activity. The app can send push notifications to remind someone to exercise, but that is the limit to the interaction. The team would like to build off of this idea of sharing fitness data between people through an internet connection while building this connection through a physical game space and physical display. A physical display of information will act as the attention-grabbing motivation for passerbyers to stop and play the game. It will add an element of entertainment to the interaction to keep players and their friends interested.
The third precedent is an arcade game called Jumping Jackpot which is an arcade game where players try to match their jumps to flashing lights in order to win points. The game is not a connected device, but it is an example of how arcade games draw the attention of players and encourage them to complete a task. We will build off of the style of the game but change it to fit the environment of a dorm. Our device must be interesting enough to draw the attention of students looking for something to do while also not irritation people who do not want to play the game. This balance will be important to the user interaction and if they accept or decline the invitation to play the game.
The team used a human-centered design process to generate ideas and decide on a concept. We began by researching the needs of the community and prior work done on this topic. With the user's needs in mind, we began brainstorming different ways to solve the problem. We narrowed down the project's focus by eliminating options that we could not execute in the timeframe of the project and with the materials provided by the course. One challenge the team faced was figuring out how to display scores and player status in a physical, interactive, and robust way. We went with a combination of leds and an arrow controlled by a servo in order to make the score both eye-catching and easy to understand. Another challenge was how to ensure a clear connection between the user and their community. Our approach to this challenge was to intentionally place the devices in semi-public areas where there would be enough people to interact with but not enough to prevent use for fear of embarrassment. We also chose the location to capitalize on existing connections and rivalries between groups of students.
Jump! is a fun and competitive game designed to encourage CMU students to take quick and active study breaks, reducing their stress and encouraging connections with others in their community. It's an internet-connected, life-size, interactive device that allows students to play games against people across campus with real-time, physical feedback on the status of both players.
Located in the common room of an undergraduate dorm, it grabs students' attention as they walk through the space. Flashing lights on the Jump! display means that your competitor in a rival dorm is doing jumping jacks and racking up points on their device. The player then hops on their Jump! pad to complete more jumping jacks and beat the competition.
Below is an illustration of this concept.
In this device, a force sensor in the floor mat detects whether or not the player has completed a jump. The display, which shows if you or the other player has more jumps, shows the score with a combination of lights and an arrow. When you jump, your display lights up and the arrow moves towards your side of the display. If the other player is winning, the arrow moves towards their side of the display. You win by completing more jumping jacks than your competitor and moving the arrow all the way towards your side of the scoreboard.
The first step in prototyping was to create a small scale version of the device to make sure that the force sensor and servo could interact with each other as the team intended.
We then worked on prototyping better ways to display the score. This is when we included the led strip and different types of arrows.
The particles were then connected through the particle cloud so that the displays show which player has the most recorded jumps.
Finally, all of the different components of the system were combined for a final round of testing and debugging.
//Jumper code(applicable for both particle boards)
//Designing for IoT 12/12/2019
// Source for light strip code: https://learn.adafruit.com/rgb-led-strips/arduino-code
#define REDPIN A3
#define GREENPIN A4
#define BLUEPIN A2
#define FADESPEED 5 // make this higher to slow down
//variables for controlling the light strip
int lastFade = 0;//the variable recording the time entering the light pattern function
int timeElapsed=0;//record the current time
int colorValue = 0; //the color we're going to assign to the light strip
int redValue = 0;
int blueValue = 0;
bool ifNotify=false;//controlling if the reminder is on
//these 10 variables are the time intervals for the light pattern when the player wins
int timerDuration =500;
int timerDuration2 =1000;
int timerDuration3 = 1500;
int timerDuration4 = 2000;
int timerDuration5 = 2500;
int timerDuration6 = 3000;
int timerDuration7 = 3500;
int timerDuration8 = 4000;
int timerDuration9 = 4500;
int timerDuration10 = 5000;
// Define a pin that we'll place the FSR on
// Remember to add a 10K Ohm pull-down resistor too.
int fsrPin = A0;
int fsrReading = 0;
int countChannel=0;//this variable is part of the counting logic
// Create a variable to store the LED brightness.
int ledBrightness = 0;
int d1 = 1000;//time interval for updating the servo
int highReading=1000;//thresholds of couting the pressure as one jump
int lowReading=100;
int jumpCount=0;//jump count for this player
String JC=" ";//medium to store received data from the other player
int jumpCount2=0;//jump count for the other player
int servoPin = D6;
Servo myServo;
int servoPos = 90;//initial position of the servo(arrow)
int LastClick=0;//the variable recording the time entering the servo update function
int Subtraction=0;//updated angle of the servo
bool refreshtimeOrNot=false;//part of the logic to make sure cloud event is published once for each time
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
int now=0;//variable recording current time
bool otherSideOn=false;//variable indicating if the other player is there
bool updateOrNot=true;//controlling if we want to update the servo
//(part of the logic to prevent the outdated cloud data from affecting the servo)
int lastUpdate=0;//last time we enable servo update
int updateDelay=3000;//the time span we want to lock the update
void setup() {
// Set up the LED for output
pinMode(REDPIN, OUTPUT);
pinMode(GREENPIN, OUTPUT);
pinMode(BLUEPIN, OUTPUT);
Particle.variable("force", &fsrReading, INT);
Particle.variable("count", &jumpCount, INT);
Particle.variable("count2", &jumpCount2, INT);
Particle.variable("Subtraction", &Subtraction, INT);
Particle.variable( "Last", LastClick);
Particle.variable( "now", now);
Particle.variable( "ledBrightness", ledBrightness);
Particle.variable( "otherSideOn", otherSideOn);
// attaches the servo on the D6 pin to the servo object
myServo.attach(servoPin);
//cloud function for testing
Particle.function("servo", servoControl);
Particle.function("updateservo", outerUpdate2);
Particle.function("resetCount", resetCount);
Particle.variable( "servoPos" , &servoPos , INT );// Keep a cloud variable for the current position
Particle.subscribe( "diot/2019/paired/jumper/update" , handleSharedEvent );//receiving jumpcount data from the other player
Particle.function( "beginRemind", reminder);//a function that enables light strip pattern when this player wins
}
void loop() {
// Use analogRead to read the photo cell reading
// This gives us a value from 0 to 4095
fsrReading = analogRead(fsrPin);
// Map this value into the PWM range (0-255)
// and store as the led brightness
ledBrightness = map(fsrReading, 200, 4095, 0, 100);
if(fsrReading<200){
ledBrightness=0;//prevent the light strip from blinking when there is low pressure on FSR
}
if(ifNotify==false){//when we don't want the light strip to show the winning pattern
lastFade=millis();
if(otherSideOn){//when the other player is there, show the mixed color on the light strip
analogWrite(GREENPIN, ledBrightness);
analogWrite(REDPIN, ledBrightness);
analogWrite(BLUEPIN, ledBrightness);
}else{//otherwise we only show green light(or one of the RGB color)
analogWrite(GREENPIN, ledBrightness);
analogWrite(REDPIN, 0);
analogWrite(BLUEPIN,0);
}
}else{//when we need the light strip to show the winning pattern
reminder("");//turn on the winning pattern
}
//part of the jump counting logic, we need to detect a high to low pressure fluctuation to count as one jump
if(countChannel==0){
Channel0();//enter this function when detecting high pressure
}else if(countChannel==1){
Channel1();//enter this function when detecting low pressure
}
//final calculated angle for the servo representing the substraction between the jump counts of 2 players
Subtraction=90-(jumpCount-jumpCount2)*4;
// update the servo
outerUpdate();
if(Subtraction<10){//when this player is winning
updateOrNot=false;//stop updating jump count data from the other player to prevent servo's angle reset failure
resetCount(" ");//reset the servo
ifNotify=true;//enable the winning pattern of the light strip
}else if(Subtraction>170){//when this player loses
resetCount(" ");//reset the servo
}
if(updateOrNot==false){//when not updating the servo
lockUpdate();
}else{
lastUpdate=millis();//update the timer
}
}
void Channel0(){
if(fsrReading>highReading){//when detecting high pressure
countChannel=1;//entering next channel
}
}
void Channel1(){
if(fsrReading<lowReading){//when detecting low pressure after previous high reading
countChannel=0;//switch back to the previous channel
jumpCount++;//add up to the jump count
}
}
void outerUpdate(){//when we want to update the servo
now=millis();
if(refreshtimeOrNot==false)
{//if it's false, run the following code for once
LastClick=millis();//initialize the timer of the update function
refreshtimeOrNot=true;//make sure the codes here only run once each time
}
if(now>LastClick+d1){//run these lines every d1 milliseconds
publishMyEvent();//send the jumpcount of this player to the other player
outerUpdate2(" ");//update the servo
LastClick=now;//record the last update time
}
}
int outerUpdate2(String command){//function for servo update
servoControl( String(Subtraction));
return 1;
}
int servoControl(String command)//function for servo update
{
// Convert
int newPos = command.toInt();
// Make sure it is in the right range
// And set the position
servoPos = constrain( newPos, 9 , 171);
// Set the servo
myServo.write( servoPos );
// done
return 1;
}
void publishMyEvent()//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/jumper/update" + System.deviceID();
// now we have something like "diot/2019/paired/0123456789abcdef"
// and that corresponds to this devices info
// then we share the jump count of this player out
Particle.publish( eventName, String(jumpCount));
// And this will get shared out to all devices using this code
// we just published so capture this.
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{ // otherwise do your stuff to respond to
// the paired device here
JC=data;//use JC to transfer the received data to string
if(JC!="reset"){ //if not informed to reset angle
if(updateOrNot==true){//and when the update is not locked
jumpCount2=JC.toInt();//accept the other player's jump count
}
if(jumpCount2>=1 ){//when there is another player out there
otherSideOn=true;
}
}else{//when this board is told by the other board to reset
//reset the servo
JC="0";
jumpCount=0;
jumpCount2=0;
updateOrNot=false;//do not receive other oudated data to prevent reset failure
}
}
}
int resetCount(String command)
{
//reset this servo
JC="0";
jumpCount=0;
jumpCount2=0;
String eventName = "diot/2019/paired/jumper/update" + System.deviceID();
Particle.publish( eventName, "reset");//tell the other board to reset as well
return 1;
}
void lockUpdate(){//lock the servo from updating
if (lastUpdate<millis()-updateDelay){//the lock will continue for a while
updateOrNot=true;
}
}
int reminder(String anystr){
//enbale winning pattern for the light strip(not a smart way to do it but a quick and dirty way to get the job done)
timeElapsed = millis() -lastFade; //let the timer begin from 0
if( timeElapsed < timerDuration ){//First 500 millisec: turn blue
int colorValue = map( timeElapsed, 0, timerDuration, 0 , 255 );
int blueValue = colorValue;
analogWrite(BLUEPIN, blueValue);
}else if(timeElapsed < timerDuration2){//Middle 10 minutes: It should slowly transition to red over the course of the next 500 millisec
colorValue = map( timeElapsed, timerDuration, timerDuration2, 0 , 255 );
int redValue = colorValue;
blueValue = 255 - colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
} else if(timeElapsed < timerDuration3){//repeat the process
colorValue = map( timeElapsed, timerDuration2, timerDuration3, 0 , 255 );
blueValue = colorValue;
redValue = 255-colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
}else if(timeElapsed < timerDuration4){//repeat the process
colorValue = map( timeElapsed, timerDuration3, timerDuration4, 0 , 255 );
redValue = colorValue;
blueValue = 255 - colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
}else if(timeElapsed < timerDuration5){//repeat the process
colorValue = map( timeElapsed, timerDuration4, timerDuration5, 0 , 255 );
blueValue = colorValue;
redValue = 255-colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
}else if(timeElapsed < timerDuration6){//repeat the process
colorValue = map( timeElapsed, timerDuration5, timerDuration6, 0 , 255 );
redValue = colorValue;
blueValue = 255 - colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
}else if(timeElapsed < timerDuration7){//repeat the process
colorValue = map( timeElapsed, timerDuration6, timerDuration7, 0 , 255 );
blueValue = colorValue;
redValue = 255-colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
}else if(timeElapsed < timerDuration8){//repeat the process
colorValue = map( timeElapsed, timerDuration7, timerDuration8, 0 , 255 );
redValue = colorValue;
blueValue = 255 - colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
}else if(timeElapsed < timerDuration9){//repeat the process
colorValue = map( timeElapsed, timerDuration8, timerDuration9, 0 , 255 );
blueValue = colorValue;
redValue = 255-colorValue;
analogWrite(BLUEPIN, blueValue);
analogWrite(REDPIN, redValue);
}else if(timeElapsed < timerDuration10){// fade out to off.
colorValue = map( timeElapsed, timerDuration9, timerDuration10, 0 , 255 );
blueValue = 255-colorValue;
analogWrite(BLUEPIN, blueValue);
}
else{//turn off the lights when the routine finishes
analogWrite(REDPIN, 0);
analogWrite(GREENPIN, 0);
analogWrite(BLUEPIN, 0);
ifNotify=false;//end the winning pattern
otherSideOn=false;//refresh the other player's state,assume they leave next round(if they do not, this boolean will be true again)
}
return 0;
}
Click to Expand
1. Test with users in context and incorporate feedback.
3. Conceal wires and circuits. Correct current draw issue where activating the servo dims the lights. Finalize scorekeeping code and game reset function.
4. Make a stable podium or installation for each unit. Make clear signage so the user understands the game at a glance.
5. Adapt the game to include more than two players and connect a greater community.
Source for light strip code: https://learn.adafruit.com/rgb-led-strips/arduino-code
All other references are cited in that topic's section.
A hands-on introductory course exploring the Internet of Things and connected product experiences.
~
December 5th, 2019