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.

touch_grass_reminder.mov

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.

touch_grass_reminder_process.mov

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;

}