Threshold of Harmony - Jonathan Zhai/Jingjing Wang - Andy Garcia
While my previous group project (Handcuffs) provided valuable personal experience and growth in terms of the process of brainstorming and prototyping, it ultimately didn't have much relation to my midterm project in terms of concept or design.
One common aspect between the two projects however, was the focus of distance. In Handcuffs, you needed to be physically close enough to the other person in order to "connect" with them. But in my midterm project Threshold of Harmony, the moral was to keep your distance from nature, or else you could harm it.
The significance of our project is that it intends to warn the everyone of how human interference (such as deforestation or overfishing) could destroy the beautiful environment around us. We should all work towards protecting the nature on this Earth.
Jingjing and I intended that users would interact with our artifact by approaching it slowly, and eventually getting too close to it in order to either touch it or get a closer look at the "tree". In response, the tree would shrivel as a result of people not keeping a respectful distance with nature. Distance detection would be handled by ultrasonic sensors. Initially we wanted to put ultrasonic sensors on every angle of the project, and have the branches of the tree shrivel according to which side they were at, but we decided to simplify this aspect due to time constraints, and have all the branches be controlled by one sensor. We also discussed on what kind of motors to use (the selection was between dc motors, servos and stepper motors). Ultimately, we decided on attaching the branches to servos since they would have enough torque to pull on them and were small enough to fit in our project. We also considered adding a photoresistor for additional interactivity, so that we could a function for when the user had touched the tree, but it was quickly scrapped since we felt it would hard to have a stable threshold due to varying light levels from room to room. We had thought about hiding it under the leaves of the tree, but since the branches were meant to fall down, that would be highly unreliable.
Upon building a basic circuit consisting of a single servo being controlled by an ultrasonic sensor (via distance), I found that the ultrasonic readings would fluctuate, since the object being detected had to be directly in front of it. I contacted my professor, who suggested using a motion detector for more stable readings, but we ultimately decided to bare with the ultrasonic sensor since we felt that using a motion detector would stray away from our intentions, since we wanted to focus on "distance with nature".
Here's the test code I used for this circuit:
#include "Servo.h"
Servo myservo;
int distance;
int servoPin = 6;
int triggerPin = 5;
int echoPin = 3;
void setup() {
// put your setup code here, to run once:
myservo.attach(servoPin);
pinMode(triggerPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
// put your main code here, to run repeatedly:
// put your main code here, to run repeatedly:
digitalWrite(triggerPin, LOW);
delayMicroseconds(2);
digitalWrite(triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(triggerPin, LOW);
// pulseIn waits for signal to go from HIGH to LOW,
// timeout according to max range of sensor
long duration = pulseIn(echoPin, HIGH, 17400);
// sound travels roughly 29cm per microsecond so we divide by 29,
// then by 2 since we recorded sound both going forth and back
distance = duration / 29 / 2;
if (distance <= 20) {
myservo.write(180);
}
else {
myservo.write(0);
}
}
While Jingjing was designing and building the exterior of our project (e.g. branches, flowers), and connecting some of the circuits, I focused on problem solving and coding, figuring out how to implement the different stages we wanted (e.g. music playing, and RGB lights blinking while the user kept a distance from the artifact, and the branches dropping when the user got too close). We found that the artifact would "glitch" around when the user was in between a certain distance threshold meant for triggering different stages of the artifact, so I ended up simplifying it to two stages, and utilizing filtering so that the readings would change more consistently.
For decor, I suggested that we would use buzzers to play tunes, and an RGB LED to give our project "a sense of life". But due to time constraints, I didn't have enough time to debug my code, and ultimately settled for the LED to glow green while the user kept a distance, and glow red when the user was too close.
To tidy up my code, I decided to split the various stages the project was meant to display into functions, and control them using ultrasonic sensors within the void loop. This increased the readability of our code a lot.
After adding an extra servo to our project, I found that the servos were moving weirdly in a "glitching" manner. I ended up spending a lot of time debugging my code, only for my professor to tell me that the Arduino didn't have enough power to handle all the motors. I ended up redoing part of the circuit in order to add a 6 volt power source.
Even on the day of user testing, I was debugging the code since the readings from the ultrasonic sensor would cause the project to "glitch" once the tester left the reading distance. But this was finally fixed after our professor recommended constraining the values, and only trigger an event to happen when a person was within a certain distance from the tree.
After some user testing, the tester that I had invited admired our project, but felt that more work could be done on decorating the project, and making it feel more "natural". We also finally found the time to implement the DC motors meant for the flowers, but since we needed more torque to spin the flowers, the final result of the "spinning flowers" was disappointing since they couldn't spin as fast as we had intended.
During the user testing session, Jingjing was inspired by Ivy and Yitong's project, which utilized an atomizer, which we thought could give our project a sense of life.
While I felt that the addition of an atomizer made the project a lot more "fun" and lively (compared to the RGB LED, and motors for the flowers), I feel like one ultrasonic sensor wasn't enough in terms of interactivity, and the project would have been better if we had more time to think about what kind of functions we could have added for interactivity.
While our project was lacking in variety of interaction, it still aligns with my definition of interactivity since the project can "react" to your distance from the tree, and the viewer is in control of how much distance they want to keep. But it's true that our project didn't do that good of a job at guiding the tester how they can interact with the tree. Ultimately, I'm really proud that Jingjing and I got the chance to experiment with so many components in such a short time, design such sophisticated code, and create such a cool and beautiful project, but I'm sure that more thorough planning would have made the project better. For example, a lot of our time was spent on fixing fallen wires since we had put the circuit board onto our artifact at a too early stage, which made it physically difficult to make changes.
Final code:
#include "Servo.h"
Servo servo1;
Servo servo2;
bool was_1 = false;
long interval = 2000;
long previousMilis = 0;
int previous_state;
// controls the servo
int buzzerPin = 2;
int servoPin = 6;
int servoPin2 = 7;
// atomizer
int atomPin = 8;
// controls the rgb
int redPin = 11;
int greenPin = 12;
int bluePin = 13;
// controls the ultrasonic sensor
int triggerPin = 5;
int echoPin = 3;
// controls the dc motors
int dcmotor_1 = 9;
int dcmotor_2 = 10;
// controls the photoresistor
//int resistorPin = A0;
// values
int mapped_servo;
int mapped_rgb;
int mapped_speed;
//int resist_val;
int def_servo = 180;
long distance;
float smoothing = 1;
float smoothed;
int r = 0;
int g = 0;
int b = 0;
int r1 = 255;
int g1 = 255;
int b1 = 0;
void setup() {
Serial.begin(9600);
// put your setup code here, to run once:
// buzzer
pinMode(buzzerPin, OUTPUT);
// servo
servo1.attach(servoPin);
servo2.attach(servoPin2);
// rgb
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
// sensor
pinMode(triggerPin, OUTPUT);
pinMode(echoPin, INPUT);
// dc motors
pinMode(dcmotor_1, OUTPUT);
pinMode(dcmotor_2, OUTPUT);
// photoresistor
//pinMode(resistorPin, INPUT);
pinMode(atomPin, OUTPUT);
digitalWrite(atomPin, LOW);
}
void loop() {
// code for ultrasonic sensor: https://github.com/ima-nyush/Interaction-Lab/tree/main/Tutorials/Ultrasonic%20Distance%20Sensor
// put your main code here, to run repeatedly:
digitalWrite(triggerPin, LOW);
delayMicroseconds(2);
digitalWrite(triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(triggerPin, LOW);
// pulseIn waits for signal to go from HIGH to LOW,
// timeout according to max range of sensor
long duration = pulseIn(echoPin, HIGH, 17400);
// sound travels roughly 29cm per microsecond so we divide by 29,
// then by 2 since we recorded sound both going forth and back
distance = duration / 29 / 2;
// code for smoothing: https://docs.google.com/presentation/d/1GFIeQhnZZoYaiiaeomb2JWSyjVptC_Q__s2Wvs6S0f4/edit#slide=id.g2beb8277a25_0_130
smoothed = smoothed * (1.0 - smoothing) + distance * smoothing;
mapped_servo = map(smoothed, 4, 30, 0, 45);
mapped_servo = constrain(mapped_servo, 0, 15);
mapped_speed = map(smoothed, 4, 30, 0, 500);
mapped_speed = constrain(mapped_speed, 0, 220);
mapped_rgb = map(smoothed, 4, 30, 0, 100);
mapped_rgb = constrain(mapped_rgb, 0, 32);
//resist_val = analogRead(resistorPin);
//for test
Serial.println(distance);
if (smoothed == 0|| smoothed > 80) {
state0();
} else if (smoothed >= 40 && smoothed <= 80) {
state1();
} else if (smoothed < 40) {
state3();
}
//if (smoothed >= 14 && smoothed < 50) {
//setColor(r1, g1, b1);
//state2();
//}
//delay(20);
}
void state0() {
// no ren
def_servo = 0;
servo1.write(def_servo);
servo2.write(def_servo);
//delay(50);
Serial.println(smoothed);
Serial.println("Running STATE 0");
setColor(0, 128, 0); // green
//analogWrite(dcmotor_1, 0);
//analogWrite(dcmotor_2, 0);
birdChirp();
digitalWrite(dcmotor_1, LOW);
digitalWrite(dcmotor_2, LOW);
}
void state1() {
// when the user is not within a certain range of the artifact,
// the RGB light is blinking and music plays, the branches go back up
//birdChirp();
def_servo = 0;
servo1.write(def_servo);
servo2.write(def_servo);
//delay(50);
Serial.println(smoothed);
Serial.println("Running STATE 1");
setColor(0, 128, 0); // green
digitalWrite(dcmotor_1, HIGH);
digitalWrite(dcmotor_2, HIGH);
}
void state2() {
// when the user is within a certain range of the artifact,
// the RGB light starts turning red depending on distance, the branches droop as well music changes
if (previous_state == 3) {
servo1.write(15);
servo2.write(15);
delay(50);
} else {
setColor(r1, g1, b1);
servo1.write(mapped_servo);
servo2.write(mapped_servo);
//analogWrite(dcmotor_1, mapped_speed);
//analogWrite(dcmotor_2, mapped_speed);
Serial.println(smoothed);
Serial.println("Running STATE 2");
previous_state = 2;
analogWrite(dcmotor_1, 150);
analogWrite(dcmotor_2, 150);
}
}
void state3() {
// when the user is too close to the artifact,
// the RGB completely turns red, and the branches fall
def_servo = 180;
servo1.write(def_servo);
servo2.write(def_servo);
//analogWrite(dcmotor_1, 220);
//analogWrite(dcmotor_2, 220);
setColor(255, 0, 0);
Serial.println(smoothed);
Serial.println("Running STATE 3");
hornSound();
digitalWrite(dcmotor_1, LOW);
digitalWrite(dcmotor_2, LOW);
spray();
}
// https://gist.github.com/zachflower/79df9ed5ca398264d3b6
void setColor(int red, int green, int blue) {
while (r != red || g != green || b != blue) {
if (r < red) r += 1;
if (r > red) r -= 1;
if (g < green) g += 1;
if (g > green) g -= 1;
if (b < blue) b += 1;
if (b > blue) b -= 1;
_setColor();
}
}
void _setColor() {
analogWrite(redPin, r);
analogWrite(greenPin, g);
analogWrite(bluePin, b);
}
void birdChirp() {
tone(buzzerPin, 2000, 100); // Start with a high-pitched tone
delay(200); // Short pause to simulate a chirp
tone(buzzerPin, 1500, 150); // Lower the pitch for variation
delay(400);
tone(buzzerPin, 1800, 120); // Another variation in pitch
delay(300);
}
void hornSound() {
tone(buzzerPin, 1000); // Play a tone at 1000 Hz
delay(500); // Sound duration for 500 milliseconds
noTone(buzzerPin); // Stop the tone
delay(300); // Pause between sounds
}
void spray() {
long currentMilis = millis();
if ((currentMilis - previousMilis) >= interval) {
digitalWrite(atomPin, HIGH);
delay(400);
digitalWrite(atomPin, LOW);
}
}
Disassembling the artifact (cardboard was too small to recycle, but wires were returned)