Slight tilted front view of the touch grass device without power plugged in. The grass cut out part is where the arduino, scale, speaker, and fan are. The pot of grass is REAL grass to ensure you get the real outdoor experience.
This self assistive device helps me connect with nature even when I am indoor swamped with my school works. Figuratively, it also reminds me to log off and experience real life.
When you touch the grass, it'll play nature sound. When you haven't touch the grass for a while (maybe 6 hours), there is a fan that will blow on the grass so that the grass will look like it's slightly moving to get your attention. It's a subtle reminder.
I used a load cell to detect weight change when touching the grass, which connects to a speaker and fan.
Top view of touching the grass. You can see the fan and some of the load cell.
You can take the plant up to water it and put it back to the load cell. Part of the reason why I don't want to glue the plant to the load cell.
Close up of the load cell and fan without grass. Fan is taped to the load cell so when it blows it is not visible through the hand cut grass.
The device is quite tall if you compare it to a spray paint can.
Fitting all my electronics into the wooden box. It's quite spacious so I can even store the connector wire between arduino and the laptop and my power source all inside the box
In this video, I show when the grass hasn't been touched for too long, the fan started to blow on it. When you touch it, the fan will stop and start playing nature sound. Based on the pressure you pressed on the grass. the volume of nature sound will increase or decrease (more pressure -> higher volume). There are also three different nature sounds that play randomly.
I just set up the MP3 speaker to play 3 nature sounds and tried to connect them to the scale.
The fan airflow was a lot weaker than I expected so I was trying to find the best angle to blow on the grass. If the fan placement is on the side it'll be too noticeable, so I decided to move it to the bottom.
I finally learned how to properly laser cut a box. I tried to cut the resources I can find online but the finger joints won't fit, so I evetually made my own box in Fusion. Thank you Zar for helping and teaching me how to properly laser cut the box!
Assembling all the parts together. Figuring out the fan placement was a bit challenging. But I managed to tape it to the load cell base.
This was the form I brought to the final critique, and I received so many feedback that I should have disguised the base and load cell with more nature element because it doesn't really connect with the pot of grass. So I added grass cut out afterwards.
The hardest part of the project is to keep the grass alive. It is still thriving now!
Overall I learned a lot from this project. The software part was a lot easier than I expected, and I definitely underestimated how much I can do on the hardware level. I really like the concept of touching grass and I see so much potential in how this project can develop. This is a good first attempt, even though there are many things I wish I did better.
My initial idea was very simple. When you touch the grass it will make nature sound. During the first in-class critique I received feedback like 'Are you going to add different audio options to switch between' and 'Will it only work if pressed hard enough unless I understood it incorrectly'. That made me think if I should make use of the weight change further and incorporate different audio sounds to make it more interesting. So I incorporate the feedback to randomly play a sound from 3 different audio files and how the sound volume will map to how much weight is being added. I also thought of the idea of how grass move in the wind. That eventually became the reminder to touch grass.
As I figured out the function of the device. I didn't put too much thought on how the form should look initially. I think the best way will be designing a load cell that also has big enough room to contain all my wires and speaker. I did find a 3D print file online that was exactly that so I tried to print it and it neither finish printing on time nor fit all my electronics. Eventually I just thought that I could glued my existing load cell frame on top of a base that contains all the electronics. I again started from laser cutting the box from the file I found online. Maybe I didn't set the settings right but the finger joint part didn't fit. So I made a box from Fusion and laser cut it under Zar's help. Another lesson learned: sometimes it's less time consuming to make things on my own.
Then I was able to bring a somewhat functional prototype to the final critique. Turns out I actually got the most feedback on how the form of my device can be improved because my device didn't feel very nature like 'I think it would be really cute to play around with different presentations to further push the illusion of being outside' and 'I think the main thing for me is design, really drilling into the nature theme with more grass, or branches and stuff'. That was the moment I realized the form of my design is just as important as the function. I didn't think too much on it because my focus was mostly on the grass. But since it's a physical computing class, how the form looks is also critical to the audience. That's when I really start to think how else can I improve my device to make it connect to the nature more. As you see in the final photos, I added some grass cut out to hide the base and the load cell. I think it looks so much better with the disguise! My only wish is that I knew that before the critique, but I was meant to learn a lot from the critique and I am glad I did.
I was never really properly trained to design physical objects with 3D software and in a way I was avoiding doing it (I rather code..). I recognize it as my shortcoming and probably the only way to improve is through making. If I were to do it again I'll try to finish all the software and function as soon as possible. Then spend more time on learning to build the form and revising it. I am really happy with how the interaction turns out. It's very smooth and responsive. People love it. But I really think the form can be improved by a lot. It's taking up a lot of space now, and it could have been designed more elegantly.
For the next iteration of the project I actually want to try not using a real grass. I want to make a perfect small square of grass that respond to touches. If it's real then it has to be in some kind of soil or it'll take up more space in general. Maybe that's something I can play with real and fake grass. Or making fake grass feel real. The idea of this is also not needing people to take care of the grass like an indorr oasis. Real grass is kinda needy and dries out quickly especially when you have heater on.
/*
Touch grass device for Intro to physcom project 2
Reads a load cell weight change as input and trigger speaker to play 3 different nature sound randomly. Based on how much weight is being added, the sound volume will increase and decrease. If no weight change has been detected for 5 secs, a fan will start blowing for 20 secs.
Some sources that I used:
- ChatPGT helped me set up threshold for touch and map touch pressure to music volume
pin mapping:
Arduino pin | role | description
___________________________________________________________________
2 input Load cell DT
3 input Load cell SDK
10 input MP3 player TX
11 output MP3 player RX
9 output Fan
Made by Summer Chao, summerc@andrew.cmu.edu
*/
#include "HX711.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
// -------------------- HX711 SETUP --------------------
const int LOADCELL_DOUT_PIN = 2;
const int LOADCELL_SCK_PIN = 3;
HX711 scale;
float calibration_factor = 2280.0; // adjust after calibration
float baseline = 0.0;
bool touched = false;
// ↑ More sensitivity to touch ↓
const float TOUCH_THRESHOLD = 3.0; // grams change to count as a touch
const float RELEASE_RATIO = 0.6;
const float NOISE_BAND = 0.5; // ignore tiny noise
const float MAX_DELTA = 20.0; // lower = louder faster
// Debounce for touch detection
const int TOUCH_COUNT_THRESHOLD = 2;
int touchCounter = 0;
// -------------------- FAN SETUP --------------------
const int FAN_PIN = 9;
bool fanOn = false;
unsigned long lastTouchTime = 0;
unsigned long fanStartTime = 0;
// -------------------- DFPLAYER SETUP --------------------
static const uint8_t PIN_MP3_TX = 10; // Arduino -> DFPlayer RX
static const uint8_t PIN_MP3_RX = 11; // Arduino <- DFPlayer TX
SoftwareSerial softwareSerial(PIN_MP3_RX, PIN_MP3_TX);
DFRobotDFPlayerMini player;
// Smoothed volume
float smoothVol = 0;
// -------------------- FUNCTIONS --------------------
float getSmoothed() {
static float smoothVal = 0;
float raw = scale.get_units(2); // average of 2 readings
const float alpha = 0.3; // faster response
smoothVal = (alpha * raw) + (1 - alpha) * smoothVal;
return smoothVal;
}
// Map delta to volume (10-30)
int deltaToVolume(float delta) {
int vol = (int)(delta / MAX_DELTA * 20) + 10; // start at 10
if (vol > 30) vol = 30;
if (vol < 10) vol = 10;
return vol;
}
// -------------------- SETUP --------------------
void setup() {
Serial.begin(9600);
softwareSerial.begin(9600);
pinMode(FAN_PIN, OUTPUT);
digitalWrite(FAN_PIN, LOW);
Serial.println("Initializing DFPlayer Mini...");
if (player.begin(softwareSerial)) {
Serial.println("DFPlayer Mini connected!");
delay(500);
player.volume(20);
} else {
Serial.println("Connecting to DFPlayer Mini failed!");
}
Serial.println("Initializing load cell...");
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
scale.set_scale(calibration_factor);
delay(500);
scale.tare(10);
delay(200);
baseline = scale.get_units(10);
Serial.println("System ready.");
lastTouchTime = millis();
}
// -------------------- LOOP --------------------
void loop() {
if (!scale.is_ready()) {
Serial.println("HX711 not found.");
delay(500);
return;
}
float smoothed = getSmoothed();
float delta = fabs(smoothed - baseline);
if (delta < NOISE_BAND) delta = 0;
// ---------------- Touch Detection ----------------
if (!touched) {
if (delta > TOUCH_THRESHOLD) {
touchCounter++;
if (touchCounter >= TOUCH_COUNT_THRESHOLD) {
touched = true;
touchCounter = 0;
Serial.println("Touched!");
// Randomly pick 001, 003, or 005
int r = random(1, 4);
int trackNum = (r == 1) ? 1 : (r == 2 ? 3 : 5);
player.play(trackNum);
lastTouchTime = millis(); // reset timer
}
} else {
touchCounter = 0;
}
} else {
// Release detection
if (delta < (TOUCH_THRESHOLD * RELEASE_RATIO)) {
touched = false;
Serial.println("Released.");
player.stop();
lastTouchTime = millis(); // reset timer
} else {
lastTouchTime = millis(); // still touching
}
}
// ---------------- Volume Control ----------------
if (touched) {
int targetVol = deltaToVolume(delta);
smoothVol = 0.4 * targetVol + 0.6 * smoothVol;
player.volume((int)smoothVol);
}
// ---------------- Fan Logic ----------------
unsigned long currentTime = millis();
// 1️⃣ If touched, immediately turn fan off and reset timer
if (touched) {
if (fanOn) {
Serial.println("Touched → Fan OFF");
digitalWrite(FAN_PIN, LOW);
fanOn = false;
}
lastTouchTime = currentTime; // reset inactivity timer
}
// 2️⃣ If not touched for 20s and fan is off → turn it on
if (!touched && !fanOn && (currentTime - lastTouchTime >= 5000)) {
Serial.println("No touch for 20s → Fan ON");
digitalWrite(FAN_PIN, HIGH);
fanOn = true;
fanStartTime = currentTime;
}
// 3️⃣ If fan has been on for 10s → turn it off
if (fanOn && (currentTime - fanStartTime >= 20000)) {
Serial.println("Fan OFF after 10s");
digitalWrite(FAN_PIN, LOW);
fanOn = false;
lastTouchTime = currentTime; // restart inactivity timer
}
// ---------------- Debug ----------------
Serial.print("Weight: ");
Serial.print(smoothed, 2);
Serial.print(" g | Δ=");
Serial.print(delta, 2);
Serial.print(" g | Volume=");
Serial.print((int)smoothVol);
Serial.print(" | Fan=");
Serial.print(fanOn ? "ON" : "OFF");
Serial.print(" | state:");
Serial.println(touched ? "TOUCHED" : "idle");
delay(100);
}