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:
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.
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.