Touch Grass Reminder
Emily Lau
Front view of the touch grass reminder, power on, and lid close
component attached to the chair
component placed on studio table
The Touch Grass Reminder is designed to remind me to take breaks from sitting for extended periods in the studio. It has two main components: one attached to the chair and the other placed on the table. The chair component includes a pressure sensor under the cushion, which detects whether I am seated. If the pressure sensor detects continuous sitting for two hours, the device triggers a servo motor in the table box to open its lid, which has a small patch of artificial grass on it. In the box there are snacks and the device also displays a message on the LCD screen, reminding me to take a break. There’s also a button on the side of the device that I press when I'm done with the break, which closes the lid and resets the system to start detecting pressure again.
In this video, I demonstrate how the user interacts with the pressure sensor ( in the real scenario, would be placed under a chair cushion). The user presses the sensor for a few seconds, and the elapsed time is displayed on the LCD screen. Once it reaches 5 seconds, the device's lid opens, revealing a piece of artificial grass, and some snacks.
Process
testing if the transceiver works. (presser->motor movement)
putting the component under the chair cushion in studio to see what pressure value is outputted.
make sure there is enough space for the Arduino and leave hole for the motor, button, and LCD wires.
plan out how the device would look and brainstorm the lid open mechanism
Laser cutting the parts out on 12*24 inches 3mm wood.
I tried using a curved board for the lid, but it didn’t close properly and the dimensions were slightly off, covering some graphics.
Before starting the design of the actual box, I wanted to plan out the mechanism first. I spent some time drafting different ways the lid could be attached and how the opening mechanism would function.
I found it challenging to visualize how the motor should move and how the wooden piece would attach to the lid. I used SolveSpace to create a simple simulation of the lid opening mechanism.
found a perfect length hinge for the lid. Drilled holes in the wood and attached it with screws.
gluing the pieces together, use clamp to hold them in place.
testing to if the Arduino and wires fit in the laser cut case. (transmitter)
testing to if the Arduino and wires fit in the laser cut case. (receiver)
I initially thought the hardest part would be building the physical device since I was using components I was mostly familiar with, except for the transducer. However, working with the transducer turned out to be one of the most difficult and time-consuming aspects of this project. Having two Arduinos and managing two sets of code added complexity and I encountered issues with the receiver not receiving data correctly, so I had to write multiple test codes to check what data, if any, was being transmitted. This required a lot of back-and-forth between code files to debug.I really enjoyed the physical build portion of the project and experimenting with different laser cut techniques such as using figure joints and creating curved surfaces.
One feature I would like to add in the future is accounting for bathroom breaks or brief moments where I leave the chair like adjusting my posture. Currently, any short removal of pressure restarts the timer, making it difficult to reach two hours. I would also consider adding a power switch for my components or using a different power supply, as right now the only way to turn off the device is by unplugging it. During the in-class critique, I received a comment: "Have the box only broadcast when the pressure sensor is sensing something to help save battery." I thought this was a great point because if I forget to turn off one of the devices when leaving the room, they will continue transmitting and receiving data which will drain the batteries quickly. Overall, I’m satisfied with how this project turned out. The device addresses my needs quite well and can be use as a table storage, and I think I will actually use it in the studio.
Technical information
Transmitter Schematic
Receiver Schematic
/*
TRANSMITTER CODE
author: emily lau
This code reads the data from a pressure sensor and sends it to a receiver using nRF24L01.
pin mapping:
Arduino pin | role | description
___________________________________________________________________
A1 input Attached to the output of the pressure sensor
8 output CSN pin for the nRF24L01 module
9 output CE pin for the nRF24L01 module
11 output MOSI (Master Out Slave In) for SPI communication
12 input MISO (Master In Slave Out) for SPI communication
13 output SCK (Serial Clock) for SPI communication with nRF24L01
Some code referenced:
- DFRobot_VL53L0X Library - https://github.com/DFRobot/DFRobot_VL53L0X
*/
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(9, 8);
const byte address[6] = "00001";
const int pressureSensorPin = A1;
void setup() {
Serial.begin(9600);
radio.begin();
radio.openWritingPipe(address);
radio.setPALevel(RF24_PA_LOW);
radio.setChannel(100);
radio.setDataRate(RF24_250KBPS);
radio.stopListening();
}
void loop() {
int pressureSensorReading = analogRead(pressureSensorPin); // Read pressure sensor value
Serial.print("Pressure Sensor Reading = ");
Serial.println(pressureSensorReading);
// Send the pressure sensor reading as int
radio.write(&pressureSensorReading, sizeof(pressureSensorReading));
delay(1000);
}
/*
RECIEVER CODE
author: emily lau
This code receives data from a transmitter using the nRF24L01 module. It controls a servo motor, displays messages on a 16x2 I2C LCD, and monitors a button press. A timer begins when pressure data is received, when the received pressure value exceeds a time threshold, the servo motor moves.
pin mapping:
Arduino pin | role | description
___________________________________________________________________
2 output Controls the servo motor
6 input Button pin with internal pull-up resistor
8 output CSN pin for the nRF24L01 module
9 output CE pin for the nRF24L01 module
A4(SDA) output I2C communication for the LiquidCrystal_I2C display (SDA pin)
A5(SCL) output I2C communication for the LiquidCrystal_I2C display (SCL pin)
11 output MOSI (Master Out Slave In) for SPI communication
12 input MISO (Master In Slave Out) for SPI communication
13 output SCK (Serial Clock) for SPI communication with nRF24L01
Some code referenced and adapted from:
- DFRobot_VL53L0X Library https://github.com/DFRobot/DFRobot_VL53L0X
- Assistance from ChatGPT
*/
#include <Servo.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(9, 8);
Servo myservo;
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Pins
const int motorPin = 2;
const int buttonPin = 6;
const byte address[6] = "00001";
unsigned long pressStartTime = 0;
bool isPressing = false;
const int pressureThreshold = 600;
bool motorAt180 = false;
bool messageDisplayed = false; // track if "you are here!" was displayed already
// Button debounce variables
unsigned long lastButtonPress = 0;
bool buttonPressedState = HIGH; // Tracks the previous button state
const unsigned long debounceDelay = 300;
void setup() {
myservo.attach(motorPin);
myservo.write(60);
// Receiver setup
Serial.begin(9600);
radio.begin();
// Set up the radio for communication
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_LOW);
radio.setChannel(100);
radio.setDataRate(RF24_250KBPS);
radio.startListening();
// Button setup
pinMode(buttonPin, INPUT_PULLUP);
// LCD setup
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(":)"); // Initial message on LCD
delay(500);
}
void loop() {
unsigned long currentMillis = millis(); // Get the current time
// Check if radio has received data
if (radio.available()) {
int pressureValue = 0;
radio.read(&pressureValue, sizeof(pressureValue));
Serial.println(pressureValue);
// Check if the pressure value exceeds threshold
if (pressureValue > pressureThreshold && !motorAt180) {
if (!isPressing) {
pressStartTime = millis();
isPressing = true;
// Display "you are here!" when pressure is first sensed
if (!messageDisplayed) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("you are here!");
messageDisplayed = true; // make sure that the message only display once
}
}
unsigned long elapsedTime = millis() - pressStartTime;
unsigned long seconds = (elapsedTime / 1000) % 60; // Convert to seconds
unsigned long minutes = (elapsedTime / 60000); // Convert to minutes
lcd.setCursor(0, 1); // Move to the second line of the LCD
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) {
lcd.print("0");
}
lcd.print(seconds);
// Check if the pressure has been there for 5 seconds
if (millis() - pressStartTime >= 5000) {
Serial.println("Pressed for 5 seconds");
myservo.write(185);
motorAt180 = true;
// Update LCD message
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Go touch some");
lcd.setCursor(0, 1);
lcd.print("grass!");
}
} else {
// Reset if pressure is released
isPressing = false;
pressStartTime = 0;
}
}
// Button to reset motor to 0
bool currentButtonState = digitalRead(buttonPin); // Read the button state
if (currentButtonState != buttonPressedState && currentButtonState == LOW) {
if (currentMillis - lastButtonPress >= debounceDelay) {
Serial.println("Button pressed! Resetting motor to 0.");
myservo.write(60); // Move servo to 0 degrees
motorAt180 = false; // Reset the flag
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(":) glad you're");
lcd.setCursor(0, 1);
lcd.print("not here");
lastButtonPress = currentMillis;
messageDisplayed = false;
}
}
// Update the button pressed state for the next loop iteration
buttonPressedState = currentButtonState;
}