As computing becomes more ubiquitous in our objects, designers need to be more aware of how to design meaningful interactions into electronically enhanced objects. At the University of Washington, a class of junior Interaction Design majors is exploring this question. These pages chronicle their efforts.

Thursday, December 15, 2022

Buddy Bench

Yeana Song & Jasmine Yuan


UW is a large university with over 30,000 undergrads, making it difficult to meet new people and make lasting connections. The Buddy Bench connects university students through an interactive, experiential approach to meeting new friends. Placed in pairs around the University, a lonely student can sit at one of the chairs to find a buddy!


SLA

Sensors 

- Ultrasonic Distance Sensor


Logic

 - A lonesome student sits at one of the two benches.

- Lights begin blinking simultaneously at both benches to indicate that someone is looking for a buddy.

- A potential buddy sees the blinking lights and sits down at the second bench. Now both benches are occupied!

- A steady light indicates that a buddy match has been made. LCD display provide textual confirmation of match ("We found you a buddy!"). Buddy cards are dispensed.

- Buddies can find each other using the map on the Buddy cards. Suggested activities, restaurants, and other fun 'get-to-know-you-buddy' things are linked on a Linktree.


Actuation

- Responsive blinking and steady lights (Neo pixels)

- LCD display

- Card dispenser (made with a Servo motor)




Code

For this project, we essentially needed two Arduinos to work independently from each other (react and respond to the environment) and report back to each other. As simple as this sounds, it's a LOT harder to code when considering the built-in hierarchy of Arduinos. 

When using I2C communication to connect two Arduinos, one is assigned to be a 'master' and the other is assigned as 'other'. This means that the 'master' Arduino controls the communication by requesting data from the 'other' Arduino. The 'other' Arduino can only respond when requested. This isn't ideal as our project wanted both benches to be able to initiate the blinking. After way too many codes, we were able to make it work by adding complex conditional statements into the 'master' Arduino.

'Master' Arduino code

// Servo
#include <Servo.h>
Servo myservo;
int pos = 0;
// Ultrasonic Sensor
int distance;
const int echoPin = 9;
const int trigPin = 8;
// I2C communication
#include <Wire.h>
#define SLAVE_ADDR 9
#define ANSWERSIZE 25
#define response1 15
#define response2 25
// LED light
const int bluePin = 7;
int ledState = LOW;
unsigned long previousMillis = 100;
const long interval = 100;
//LCD display
#include <LiquidCrystal.h>
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
//neopixel
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define LED_PIN 10
#define LED_COUNT 4
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
unsigned long pixelPrevious = 0; // Previous Pixel Millis
unsigned long patternPrevious = 0; // Previous Pattern Millis
int patternCurrent = 0; // Current Pattern Number
int patternInterval = 5000; // Pattern Interval (ms)
int pixelInterval = 50; // Pixel Interval (ms)
int pixelQueue = 0; // Pattern Pixel Queue
int pixelCycle = 0; // Pattern Pixel Cycle
uint16_t pixelCurrent = 0; // Pattern Current Pixel Number
uint16_t pixelNumber = LED_COUNT;


void setup() {
lcd.begin(16,2);
Wire.begin();
Serial.begin(9600);
Serial.println("I2C Master Demonstration");
pinMode(echoPin, INPUT);
pinMode(trigPin, OUTPUT);
myservo.attach(6);
pinMode (bluePin, OUTPUT);
//neopixel
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
// END of Trinket-specific code.

strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
}

void loop() {
Wire.requestFrom(SLAVE_ADDR, response1 && response2);
unsigned long currentMillis = millis();
sendPing();
int duration = pulseIn(echoPin, HIGH);
distance = 0.034 * duration / 2;
distance = clamp(distance, 0, 50);

char f = 'X';
String response = "";
while (Wire.available()){
f = Wire.read ();
response += f;
}
if (f == 'F'){
theaterChase(strip.Color(0, 0, 255), 200); // Blue
}

else if (f == 'O') {
digitalWrite(bluePin, LOW);
}

Serial.println(response);

if (distance <= 16){
Wire.beginTransmission(SLAVE_ADDR);
Wire.write('P');
Serial.println ("person");
digitalWrite(bluePin, HIGH);
Wire.endTransmission();
String response = "";

if(currentMillis - pixelPrevious >= pixelInterval) { // Check for expired time
pixelPrevious = currentMillis; // Run current frame
switch (patternCurrent) {
default:
theaterChase(strip.Color(0, 0, 255), 200); // Blue
break;
}
}
}
else if (distance > 16){
Wire.beginTransmission(SLAVE_ADDR);
Wire.write('N');
digitalWrite(bluePin, LOW);
Serial.println ("nobody");
Wire.endTransmission();
// Update current time
if(currentMillis - pixelPrevious >= pixelInterval) { // Check for expired time
pixelPrevious = currentMillis; // Run current frame
switch (patternCurrent) {
case 7:
theaterChaseRainbow(0); // Rainbow-enhanced theaterChase variant
break;
case 6:
rainbow(0); // Flowing rainbow cycle along the whole strip
break;
case 5:
theaterChase(strip.Color(0, 0, 0), 50); // Blue
break;
case 4:
theaterChase(strip.Color(0, 0, 0), 50); // Red
break;
case 3:
theaterChase(strip.Color(0, 0, 0), 50); // White
break;
case 2:
colorWipe(strip.Color(0, 0, 0), 50); // Blue
break;
case 1:
colorWipe(strip.Color(0, 0, 0), 50); // Green
break;
default:
colorWipe(strip.Color(0, 0, 0), 50); // Red
break;
}
}

}
if ((f == 'F')&& (distance <= 16)) {
Wire.beginTransmission(SLAVE_ADDR);
Wire.write('J');
Serial.println('J');
for (pos = 0; pos <= 100; pos += 20) {
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(1); // waits 10 ms for the servo to reach the position
}
for (pos = 100; pos >= 0; pos -= 20) { // goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(1); // waits 10 ms for the servo to reach the position
scroll_left ();
if(currentMillis - pixelPrevious >= pixelInterval) { // Check for expired time
pixelPrevious = currentMillis; // Run current frame
switch (patternCurrent) {
default:
theaterChaseRainbow(50); // Blue
break;
}
}



Serial.println ("DONE");
Wire.endTransmission();
}
}
else{
scroll_right ();
Wire.beginTransmission(SLAVE_ADDR);
Wire.write('S');
Serial.println ("S");
Wire.endTransmission();
}
}

void sendPing(){
digitalWrite(trigPin, LOW);
//delayMicroseconds(50);
digitalWrite(trigPin, HIGH);
//delayMicroseconds(10);
digitalWrite(trigPin, LOW);
}

int clamp(int VAL, int min, int max){
if(VAL <= min){
VAL = min;
return VAL;
}
else if(VAL >= max){
VAL = max;
return VAL;
}
else{
return VAL;
}
}

void scroll_left(){
lcd.begin(16, 2);
lcd.setCursor (0,0);
lcd.print("We found you ");
lcd.setCursor (0,1);
lcd.print("a buddy! ");
delay(50);
for (int positionCounter = 0; positionCounter < 0; positionCounter++) {
lcd.scrollDisplayLeft();
}
}


void scroll_right(){
lcd.begin(16, 2);
lcd.setCursor (0,0);
lcd.print("");
lcd.setCursor (0,1);
lcd.print("");
for (int positionCounter = 0; positionCounter < 0; positionCounter++) {
// scroll one position left:
lcd.scrollDisplayLeft();
// wait a bit:
}
delay(6);
}

void rainbow(int wait) {
// Hue of first pixel runs 5 complete loops through the color wheel.
// Color wheel has a range of 65536 but it's OK if we roll over, so
// just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
// means we'll make 5*65536/256 = 1280 passes through this loop:
for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
// strip.rainbow() can take a single argument (first pixel hue) or
// optionally a few extras: number of rainbow repetitions (default 1),
// saturation and value (brightness) (both 0-255, similar to the
// ColorHSV() function, default 255), and a true/false flag for whether
// to apply gamma correction to provide 'truer' colors (default true).
strip.rainbow(firstPixelHue);
// Above line is equivalent to:
// strip.rainbow(firstPixelHue, 1, 255, 255, true);
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
}
//neo_pixel no delay
void colorWipe(uint32_t color, int wait) {
if(pixelInterval != wait)
pixelInterval = wait; // Update delay time
strip.setPixelColor(pixelCurrent, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
pixelCurrent++; // Advance current pixel
if(pixelCurrent >= pixelNumber) // Loop the pattern from the first LED
pixelCurrent = 0;
}

// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
if(pixelInterval != wait)
pixelInterval = wait; // Update delay time
for(int i = 0; i < pixelNumber; i++) {
strip.setPixelColor(i + pixelQueue, color); // Set pixel's color (in RAM)
}
strip.show(); // Update strip to match
for(int i=0; i < pixelNumber; i+=3) {
strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Set pixel's color (in RAM)
}
pixelQueue++; // Advance current pixel
if(pixelQueue >= 3)
pixelQueue = 0; // Loop the pattern from the first LED
}

// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(uint8_t wait) {
if(pixelInterval != wait)
pixelInterval = wait;
for(uint16_t i=0; i < pixelNumber; i++) {
strip.setPixelColor(i, Wheel((i + pixelCycle) & 255)); // Update delay time
}
strip.show(); // Update strip to match
pixelCycle++; // Advance current cycle
if(pixelCycle >= 256)
pixelCycle = 0; // Loop the cycle back to the begining
}

//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
if(pixelInterval != wait)
pixelInterval = wait; // Update delay time
for(int i=0; i < pixelNumber; i+=3) {
strip.setPixelColor(i + pixelQueue, Wheel((i + pixelCycle) % 255)); // Update delay time
}
strip.show();
for(int i=0; i < pixelNumber; i+=3) {
strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Update delay time
}
pixelQueue++; // Advance current queue
pixelCycle++; // Advance current cycle
if(pixelQueue >= 3)
pixelQueue = 0; // Loop
if(pixelCycle >= 256)
pixelCycle = 0; // Loop
}

// 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) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

'Other' Arduino code

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
#define LED_PIN 10
#define LED_COUNT 4
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
unsigned long pixelPrevious = 0; // Previous Pixel Millis
unsigned long patternPrevious = 0; // Previous Pattern Millis
int patternCurrent = 0; // Current Pattern Number
int patternInterval = 5000; // Pattern Interval (ms)
int pixelInterval = 50; // Pixel Interval (ms)
int pixelQueue = 0; // Pattern Pixel Queue
int pixelCycle = 0; // Pattern Pixel Cycle
uint16_t pixelCurrent = 0; // Pattern Current Pixel Number
uint16_t pixelNumber = LED_COUNT;
#include <LiquidCrystal.h>
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
#include <Servo.h>
Servo myservo;
#include <Wire.h>
#define SLAVE_ADDR 9
#define ANSWERSIZE 5
const int bluePin = 7;
const int yellowPin = 13;
int ledState = LOW;
int distance;
int pos = 0;
const int echoPin = 9;
const int trigPin = 8;
String answer = "Buddy";
//int ledState1 = HIGH;
//pinMode (bluePin, OUTPUT);
unsigned long previousMillis = 100;
const long interval = 100;
char found = 'F';
char nonexistent = 'O';
char x = 'L';
void setup() {
// put your setup code here, to run once:
Wire.begin(SLAVE_ADDR);
Wire.onRequest(requestEvent);
Wire.onReceive(receiveEvent);
Serial.begin(9600);
Serial.println("I2C Slave Demonstration");
pinMode(bluePin, OUTPUT);
pinMode(yellowPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(trigPin, OUTPUT);
myservo.attach(6); // attaches the servo on pin 6 to the servo object
//neopixels
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
lcd.begin(16,2);
}
void requestEvent() {
sendPing();
int duration = pulseIn(echoPin, HIGH);
distance = 0.034 * duration / 2;
distance = clamp(distance, 0, 50);
unsigned long currentMillis = millis();
String response = "";
while (0 < Wire.available()) {
x = Wire.read();
response += x;
}
if (distance <= 16) {
Wire.write(found);
// Print to Serial Monitor
Serial.println(found);
digitalWrite(bluePin, HIGH);
if (currentMillis - pixelPrevious >= pixelInterval){
pixelPrevious = currentMillis;
switch (patternCurrent) {
default:
theaterChase (strip.Color (0,0,255),200);
break;
}
}
} else if (distance > 16) {
// Setup byte variable in the correct size
Wire.write(nonexistent);
Serial.println(nonexistent);
digitalWrite(bluePin, LOW);
if(currentMillis - pixelPrevious >= pixelInterval) { // Check for expired time
pixelPrevious = currentMillis; // Run current frame
switch (patternCurrent) {
case 7:
theaterChaseRainbow(0); // Rainbow-enhanced theaterChase variant
break;
case 6:
rainbow(0); // Flowing rainbow cycle along the whole strip
break;
case 5:
theaterChase(strip.Color(0, 0, 0), 50); // Blue
break;
case 4:
theaterChase(strip.Color(0, 0, 0), 50); // Red
break;
case 3:
theaterChase(strip.Color(0, 0, 0), 50); // White
break;
case 2:
colorWipe(strip.Color(0, 0, 0), 50); // Blue
break;
case 1:
colorWipe(strip.Color(0, 0, 0), 50); // Green
break;
default:
colorWipe(strip.Color(0, 0, 0), 50); // Red
break;
}
}
}
}
void receiveEvent() {
sendPing();
int duration = pulseIn(echoPin, HIGH);
distance = 0.034 * duration / 2;
distance = clamp(distance, 0, 50);
while (0 < Wire.available()) {
x = Wire.read();
}
if (x == 'P') {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
Serial.println("person");
rainbow (10); // Blue
} else {
ledState = LOW;
digitalWrite(bluePin, ledState);
}
}
} else if (x == 'N') {
digitalWrite(bluePin, LOW);
//theaterChase(strip.Color(0, 0, 0), 200);
Serial.println("nobody");
} else if (x == 'J') {
for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(5); // waits 10 ms for the servo to reach the position}
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(5); // waits 10 ms for the servo to reach the position
}
scroll_left();
Serial.println("J");
} else if (x == 'S') {
scroll_right();
Serial.println("S");
}
}

void loop() {
}
void sendPing(){
digitalWrite(trigPin, LOW);
//delayMicroseconds(50);
digitalWrite(trigPin, HIGH);
//delayMicroseconds(10);
digitalWrite(trigPin, LOW);
}
int clamp(int VAL, int min, int max){
if(VAL <= min){
VAL = min;
return VAL;
}
else if(VAL >= max){
VAL = max;
return VAL;
}
else{
return VAL;
}
}
void scroll_left(){
lcd.begin(16, 2);
lcd.setCursor (0,0);
lcd.print("We found you ");
lcd.setCursor (0,1);
lcd.print("a buddy! ");
delay(50);
for (int positionCounter = 0; positionCounter < 0; positionCounter++) {
// scroll one position left:
lcd.scrollDisplayLeft();
// wait a bit:
}

}
void scroll_right(){
lcd.begin(16, 2);
lcd.setCursor (0,0);
lcd.print("");
lcd.setCursor (0,1);
lcd.print("");
for (int positionCounter = 0; positionCounter < 0; positionCounter++) {
// scroll one position left:
lcd.scrollDisplayLeft();
// wait a bit:
}
delay(6);
}
void colorWipe(uint32_t color, int wait) {
if(pixelInterval != wait)
pixelInterval = wait; // Update delay time
strip.setPixelColor(pixelCurrent, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
pixelCurrent++; // Advance current pixel
if(pixelCurrent >= pixelNumber) // Loop the pattern from the first LED
pixelCurrent = 0;
}
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
if(pixelInterval != wait)
pixelInterval = wait; // Update delay time
for(int i = 0; i < pixelNumber; i++) {
strip.setPixelColor(i + pixelQueue, color); // Set pixel's color (in RAM)
}
strip.show(); // Update strip to match
for(int i=0; i < pixelNumber; i+=3) {
strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Set pixel's color (in RAM)
}
pixelQueue++; // Advance current pixel
if(pixelQueue >= 3)
pixelQueue = 0; // Loop the pattern from the first LED
}
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(uint8_t wait) {
if(pixelInterval != wait)
pixelInterval = wait;
for(uint16_t i=0; i < pixelNumber; i++) {
strip.setPixelColor(i, Wheel((i + pixelCycle) & 255)); // Update delay time
}
strip.show(); // Update strip to match
pixelCycle++; // Advance current cycle
if(pixelCycle >= 256)
pixelCycle = 0; // Loop the cycle back to the begining
}
//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
if(pixelInterval != wait)
pixelInterval = wait; // Update delay time
for(int i=0; i < pixelNumber; i+=3) {
strip.setPixelColor(i + pixelQueue, Wheel((i + pixelCycle) % 255)); // Update delay time
}
strip.show();
for(int i=0; i < pixelNumber; i+=3) {
strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Update delay time
}
pixelQueue++; // Advance current queue
pixelCycle++; // Advance current cycle
if(pixelQueue >= 3)
pixelQueue = 0; // Loop
if(pixelCycle >= 256)
pixelCycle = 0; // Loop
}
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}


Video

https://vimeo.com/781582990

https://vimeo.com/781585698
-working demo


Resources

-many many manyyyyy random Youtube videos on I2C communication, sensors and displays
-https://www.youtube.com/watch?v=PnG4fO5_vU4
-https://dronebotworkshop.com/i2c-arduino-arduino/



Other Pretty Shots of Buddy Bench







No comments:

Post a Comment