Pet the Plant: D.I.Y Christmas Special Edition

https://editor.p5js.org/Minjae0216/sketches/Qyv31CZ2_

Concept:

Through “Pet the Plant”, I wanted to make an interactive project that allows you to experience the satisfaction of growing your plant friend without the real responsibilities or risks. In this game, there are three different plants that you can choose to grow and make your own Christmas tree.

I was inspired by my friend who failed to grow her plants. By using an Arduino and different sensors, users can go through the physical motions of caring for a plant, allowing the digital plant to grow without any accidents or loss. The virtual experience is coded in p5.js and designed with Canva.

The reason for <Special Edition: D.I.Y Christmas Tree> is for the environment and the Christmas season. People buy Christmas trees in UAE to enjoy the winter. However, I thought buying Christmas trees was not environmentally friendly. Buying a Christmas tree is often considered environmentally unfriendly due to concerns like deforestation, transportation emissions, and the use of chemicals on tree farms. So, I think of other solutions that can help people to not purchase Christmas trees every winter. Making your own Christmas tree virtually will help the environment and save you money!

Hardware Image:

P5.js Screen Image:

User Testing:

IM Showcase:

IMG_9979

Many people came to see the IM showcase! My project was very popular for kids because it was pretty clear to understand through my screen and the instructions were clear. They liked my interactive project with plants. However, there was some problem in giving water. I had to put the max value to the soil moisture sensor, but I thought it would be fine. People gave too much water to plants, so sometimes virtual plants went so big. It was still an amazing day to experience!

Implementation:

<Arduino Code>

// Arduino code

// Input:
// A0 - photocell sensor
// A1 - force resistor sensor
// A2 - soil moisture sensor

int photocellPin = A0; //photocell sensor
int forceSensorPin = A1; //force resistor sensor
int soil = A2; //soil moisture sensor
int forceSensorValue = 0;

void setup() {
  Serial.begin(9600);
  pinMode(photocellPin, INPUT);
  pinMode(forceSensorPin,INPUT);
  pinMode(soil, INPUT);
}

void loop() {
  // Read the value from three sensors
  int lightValue = analogRead(photocellPin);
  int forceSensorValue = analogRead(forceSensorPin);
  int soilValue = analogRead(soil);

  // Send the value to p5.js
    Serial.print(soilValue);
    Serial.print(",");
    Serial.print(lightValue);
    Serial.print(",");
    Serial.print(forceSensorValue);
    Serial.println(); // Print a newline to indicate the end of the data

My Arduino code establishes input connections for a photocell sensor, force resistor sensor, and soil moisture sensor on analog pins A0, A1, and A2. In the setup function, the serial communication is initiated at a baud rate of 9600, and the pinMode is configured for the three pins. Within the main loop, analogRead captures values from the photocell, force sensor, and soil sensor, and these readings are sent to the serial port.

<P5.js Code>

// Define global variables for images, page tracking, and sensor data
let backgroundImage;
let choicebackgroundImage;
let resultbackgroundImage;
let merrychristmasImage;
let cactusImage;
let greenplantImage;
let palmtreeImage;
let currentPage = 1;
let serial;
let latestData = "waiting for data";
let plantSize = 80;
let plantX, plantY;
let xOffset = 0;
let yOffset = 0;
let userChoice = ''

//Sensor Values
let soilMoistureValue = 0;
let photocellValue = 0;
let forceSensorValue = 0;

//Ornament Variables
let ornaments = ['https://i.imgur.com/FPuVyGI.png', 'https://i.imgur.com/fJxVZe3.png', 'https://i.imgur.com/rGZS7Yx.png']; 
let currentOrnament = '';
let ornamentsList = [] ;
let hasOrnament = false;
let isAddingOrnament = false;


//Preload images before setup
function preload() {
  //loadimages
  backgroundImage = loadImage('https://i.imgur.com/lGiEbaQ.png');
  choicebackgroundImage = loadImage('https://i.imgur.com/tmRSqeN.png')
  cactusImage = loadImage('https://i.imgur.com/kIxRg6H.png')
  palmtreeImage = loadImage('https://i.imgur.com/A8tjpQM.png')
  greenplantImage = loadImage('https://i.imgur.com/yMYwr8D.png'); 
  resultbackgroundImage = loadImage('https://i.imgur.com/1IHM8SH.png')
  merrychristmasImage = loadImage ('https://i.imgur.com/MoUEmwD.png')
  
  //Load Ornament Images
   for (let i = 0; i < ornaments.length; i++) {
    ornaments[i] = loadImage(ornaments[i]);
  }
}

// Setup function, called once at the beginning
function setup() {
  // Create canvas fit to the background image
  createCanvas(700,500);
    // Connect to the p5.serialport server
  serial = new p5.SerialPort();
 // Set up serial communication
 serial.list();
 serial.open('/dev/tty.usbmodem101');

 serial.on('connected', serverConnected);

 serial.on('list', gotList);

 serial.on('data', gotData);

 serial.on('error', gotError);

 serial.on('open', gotOpen);

 serial.on('close', gotClose);
 
}

// Callback function when connected to the serial server
function serverConnected() {
 print("Connected to Server");
}

// Callback function when a list of serial ports is received
function gotList(thelist) {
 print("List of Serial Ports:");

 for (let i = 0; i < thelist.length; i++) {
  print(i + " " + thelist[i]);
 }
}

// Callback function when the serial port is opened
function gotOpen() {
 print("Serial Port is Open");
}

// Callback function when the serial port is closed
function gotClose(){
 print("Serial Port is Closed");
 latestData = "Serial Port is Closed";
}

// Callback function when an error occurs in serial communication
function gotError(theerror) {
 print(theerror);
}

// Callback function when new data is received from the serial port
function gotData() {
 
  let currentString = serial.readLine();
  console.log('serial received: ' + currentString);
  trim(currentString);
  if (!currentString) return;
  console.log(currentString);
  latestData = currentString;

  // Split the received data into values
  let dataValues = split(latestData, ',');

  // Assign the values to the corresponding variables
  console.log(dataValues);
  if (dataValues.length === 3) {
    console.log('got 3 values');
    soilMoistureValue = int(dataValues[0]);
    console.log('soil: ' + soilMoistureValue);
    photocellValue = int(dataValues[1]);
    console.log('photocell: ' + photocellValue);
    forceSensorValue = int(dataValues[2]);
    console.log('force: ' + forceSensorValue);
  
}

}


//fullscreen
function keyPressed() {
  if (key === 'f') {
    toggleFullscreen();
  }
}

// Draw function, called continuously
function draw() {
  if (currentPage === 1) {
    drawStartPage();
  } else if (currentPage === 2) {
    drawChoicePage();
  } else if (currentPage === 3) {
    drawResultPage();
  }

}

// Draw function for the start page
function drawStartPage() {
  background(backgroundImage);

  if (mouseIsPressed) {
    currentPage = 2; // Move to the next page when clicked
  }
}

// Draw function for the choice page (next page)
function drawChoicePage() {
  background(choicebackgroundImage);

  textSize(24);
  fill(0);
  

  if (keyIsPressed) {
    // Check the pressed key and move to the corresponding page
    if (key === '1') {
      userChoice = 'cactus';
      currentPage = 3; // Go to the third page with cactus
    } else if (key === '2') {
      userChoice = 'palmTree';
      currentPage = 3; // Go to the third page with green plant
    } else if (key === '3') {
      userChoice = 'greenPlant';
      currentPage = 3; // Go to the third page with palm tree
    }
  }
}


// Draw function for the result page (last page)
function drawResultPage() {
  background(resultbackgroundImage);

   // Display selected plant based on user choice
  if (userChoice === 'cactus') {
    plantX = 485 + xOffset;
    plantY = 337 + yOffset;
    image(cactusImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'palmTree') {
    plantX = 489 + xOffset;
    plantY = 341 + yOffset;
    image(palmtreeImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'greenPlant') {
    plantX = 500 + xOffset;
    plantY = 339 + yOffset;
    image(greenplantImage, plantX, plantY, plantSize, plantSize);
  }

  // Check sensor values and simulate plant growth for all plant types
  if (userChoice === 'cactus' || userChoice === 'palmTree' || userChoice === 'greenPlant') {
    // Check soil moisture sensor value
    if (soilMoistureValue > 860) {
      // Increase size when soil moisture is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check photocell sensor value
    if (photocellValue > 70) {
      // Increase size when light is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check force sensor value
    if (forceSensorValue > 1000) {
      // Add ornament when force sensor value is high
      isAddingOrnament = true;
    }
  }

  // Display ornaments
  for (let i = 0; i < ornamentsList.length; i++) {
    let ornament = ornamentsList[i];
    image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
  }

  // If the photocell value is less than 10, switch to a Christmas background
  if (photocellValue < 10) {
    background(merrychristmasImage);
    if (userChoice === 'cactus') {
      plantX = 485 + xOffset;
      plantY = 337 + yOffset;
      image(cactusImage, plantX, plantY, plantSize, plantSize);
      
      // Display the selected plant based on user choice along with ornaments
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'palmTree') {
      plantX = 489 + xOffset;
      plantY = 341 + yOffset;
      image(palmtreeImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'greenPlant') {
      plantX = 500 + xOffset;
      plantY = 339 + yOffset;
      image(greenplantImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  }
    
  }
  
}
// MousePressed function checks if the user is ready to add an ornament and if the click is within the plant area
function mousePressed() {
   // Add ornament at the clicked position
  if (isAddingOrnament && mouseX > plantX && mouseX < plantX + plantSize && mouseY > plantY && mouseY < plantY + plantSize) {
    // Add ornament at the clicked position
    let ornament = {
      image: random(ornaments),
      x: mouseX,
      y: mouseY,
      size: 40, // Adjust the size of the ornament as needed
    };

    ornamentsList.push(ornament);

    // Reset the flag
    isAddingOrnament = false;
  }
}


I’ve created a p5.js sketch that brings a virtual Christmas tree to life, responding to Arduino sensors and user interactions. The code begins by initializing global variables, including images for backgrounds, plants, and ornaments, as well as parameters for page tracking and sensor data.

In the setup function, the canvas is created, and a connection to the p5.serialport server is established for communication with an Arduino. Callback functions are set up to handle various serial events, ensuring a smooth connection between the digital representation and physical sensor inputs.

The draw function renders different pages based on user choices and sensor inputs. Users progress through pages by clicking the mouse or pressing keys to select the type of plant displayed on the last page.

The drawResultPage function, specific to the final page, displays the chosen plant with dynamic growth simulation based on soil moisture, light intensity, and force sensor values. The plant size adjusts, and ornaments are added under specific conditions, creating an interactive and festive atmosphere. The code also intelligently switches to a Christmas-themed background in low-light conditions, enhancing the holiday spirit.

My code handles user interactions, allowing users to enter fullscreen mode with the ‘f’ key and add ornaments to the virtual tree by clicking on it. The integration of visual elements, sensor data, and user interactions results in an engaging experience, turning virtual festivities into a tangible and interactive celebration of the Christmas season.

<Communication between Arduino and p5.js>

function drawResultPage() {
  background(resultbackgroundImage);

   // Display selected plant based on user choice
  if (userChoice === 'cactus') {
    plantX = 485 + xOffset;
    plantY = 337 + yOffset;
    image(cactusImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'palmTree') {
    plantX = 489 + xOffset;
    plantY = 341 + yOffset;
    image(palmtreeImage, plantX, plantY, plantSize, plantSize);
  } else if (userChoice === 'greenPlant') {
    plantX = 500 + xOffset;
    plantY = 339 + yOffset;
    image(greenplantImage, plantX, plantY, plantSize, plantSize);
  }

  // Check sensor values and simulate plant growth for all plant types
  if (userChoice === 'cactus' || userChoice === 'palmTree' || userChoice === 'greenPlant') {
    // Check soil moisture sensor value
    if (soilMoistureValue > 860) {
      // Increase size when soil moisture is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check photocell sensor value
    if (photocellValue > 70) {
      // Increase size when light is high
      plantSize += 1.5;
      xOffset -= 0.8;
      yOffset -= 1.4;
    }

    // Check force sensor value
    if (forceSensorValue > 1000) {
      // Add ornament when force sensor value is high
      isAddingOrnament = true;
    }
  }

  // Display ornaments
  for (let i = 0; i < ornamentsList.length; i++) {
    let ornament = ornamentsList[i];
    image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
  }

  // If the photocell value is less than 10, switch to a Christmas background
  if (photocellValue < 10) {
    background(merrychristmasImage);
    if (userChoice === 'cactus') {
      plantX = 485 + xOffset;
      plantY = 337 + yOffset;
      image(cactusImage, plantX, plantY, plantSize, plantSize);
      
      // Display the selected plant based on user choice along with ornaments
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'palmTree') {
      plantX = 489 + xOffset;
      plantY = 341 + yOffset;
      image(palmtreeImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  } else if (userChoice === 'greenPlant') {
      plantX = 500 + xOffset;
      plantY = 339 + yOffset;
      image(greenplantImage, plantX, plantY, plantSize, plantSize);
    
      if (ornamentsList[0]) {
        for (let i = 0; i < ornamentsList.length; i++) {
          let ornament = ornamentsList[i];
          image(ornament.image, ornament.x, ornament.y, ornament.size, ornament.size);
        }
      }
  }
    
  }
  
}
// MousePressed function checks if the user is ready to add an ornament and if the click is within the plant area
function mousePressed() {
   // Add ornament at the clicked position
  if (isAddingOrnament && mouseX > plantX && mouseX < plantX + plantSize && mouseY > plantY && mouseY < plantY + plantSize) {
    // Add ornament at the clicked position
    let ornament = {
      image: random(ornaments),
      x: mouseX,
      y: mouseY,
      size: 40, // Adjust the size of the ornament as needed
    };

    ornamentsList.push(ornament);

    // Reset the flag
    isAddingOrnament = false;
  }
}

In the drawResultPage function, the code checks the sensor values (soil moisture, photocell, and force sensor) and simulates plant growth based on these values. If conditions related to sensor values are met, the plant size is increased, and offsets are adjusted to simulate growth. If the force sensor value is high, the variable isAddingOrnament is set to true, indicating that an ornament should be added. The code also includes a section for displaying ornaments on the plant based on user interactions. If isAddingOrnament is true, the mousePressed function adds an ornament at the clicked position within the plant area.

In summary, the p5.serialport library is used to establish a connection between the p5.js sketch and the Arduino, enabling real-time communication and interaction based on sensor data. The code continuously reads data from the Arduino, processes it, and updates the visual representation of the Christmas tree accordingly.

Digital Challenges & Future Improvement:

Before working on “Pet the Plant”, my projects mostly involved using a single Arduino sensor. However, for this project, I needed to incorporate three different sensors. Initially, I tackled each sensor’s interaction separately by coding them in different p5.js sketches. The challenge arose when I had to merge all these interactions into one comprehensive project and figure out a way to smoothly transition between them.

For future improvement, I would like to use the real plant to make the design of a virtual plant. For this project, I did not know how to animate a real plant to make a virtual plant, and I used pngs to show the virtual plants to users. However, since it is “Pet the Plant”, it would be much more friendly to users if I used their real plants to be virtual plant like avatars of a human and grow them in the game. This could be achieved through computer vision and image processing techniques to capture and interpret the characteristics of users’ real plants. By using a webcam or other image-capturing devices, the system could analyze the live feed of the real plant, extracting features such as color, size, and overall health. I can enable users to see their real plants reflected in the virtual environment. Also, I might implement interactive features where users can virtually nurture and care for their specific plant avatars.

 

 

 

 

 

Leave a Reply