Part 1. CONCEPTION AND DESIGN:
In this project, Mia and I designed a device that can urge people to drink water and keep healthy. This project was divided into two parts: physical part and visual/sound effect. Interaction mainly exists in the physical part. Users press the button attached near the water tap and the water would just come out for 25 seconds (about 250 mL).
According to my understanding of interaction and advice from Eric, we made some changes compared to our original plan. We didn’t include the water bottle and the water adding device in our preliminary design. However, the interaction was too simple and there was almost no interaction. So we add a water bottle with a water tap attached to it and when the user press the button, the water would come out. In this way, the interaction would be more diverse and complicated.
Also, according to our setup, users would receive vocal reminding every two hours to drink water (no reminding from 10 pm to 6 am) and the data would refresh every day.
During user testing session, we mainly got these suggestions. The first one was the change between scenes was too abrupt. When user did’t have enough water they will see drooping tree like this:
And when they drink enough water (8 cups), the scene would suddenly change into this:
Users advised us to make this change more smooth so I add 8 different cases and the alpha of each scene would change gradually like this:
And we could see the raining weather when adding water:
The second thing we got from user testing session was add some decoration and this was the final decoration we’ve done:
Another advice we’ve got was changing the position of the water tap. At that time our problem was that our water bottle was full of water and if we want to install the water tap, we need to pour all the water out and this was too wasting. But we later found an empty bottle so we needn’t fix with this problem.
Part 2. FABRICATION AND PRODUCTION
I think the most significant part in this project was “automatic tap” because we need to attach the water tap ourselves and use a servo to pull it instead of pressing it by hand. We bought the water tap on Taobao and attached it by ourselves. We firstly drill a whole on the water bottle.
However, the biggest drilling bit in the kit could not drill a hole that was big enough for our water tap so we use another bit following Andy’s advice.
We did have a bigger hole using this but it is hard to handle so that we accidentally made the hole too big. This brought us a lot of trouble later on. We tried to use water-proof glue to glue tap onto the bottle but the material of the bucket didn’t make the glue stick well.
When we pressed the tap for a little bit, the tap would just fall down. Then I used some cloth and hot glue.
In this way, the water tap would not fall down when pressing, but water would just leak from the cloth part. So we covered some water-proof glue on top of the cloth and it finally stopped leaking. (It actually leaked again during our presentation. We guess it was because when we pressed the water tap for so many times and there was gap again. We just put more glue and it just stopped leaking.)
Then we drilled a hole on the water tap and used servo motor to pull the tap. When testing, we found that water could come out in the angle we set up. However, during presentation, water just couldn’t come out. It was because there was no enough water in the water bottle so the water pressure was not enough. We later on added more water and set a bigger angle of servo, the water could come out again.
We got the the inspiration of pulling the water tap with servo motor from here: https://www.youtube.com/watch?v=PHeWMVXG2gM
We actually also want to use distance sensor as in this video. But when we set up, we found that the distance sensor was easy to work unmorally due to some interruption so that the water would come out even though there was no cup under the tap. So we just used a button instead.
This was our sketch:
As for the division of our works, Mia and I made the physical part together. She is also responsible for the decoration part, making sound effect and making the poster. I mainly made the processing part (visual effect and set up sound effect) and draw the tree in our project. Our tree is actually made up by many leaves and I only draw leaves on iPad and put them together and made some effects in processing.
Part 3. CONCLUSIONS
I think our project basically accomplish our preliminary design. Using visual effect to indicate how much water the user drink for one day and have voice reminding every 2 hours. So at least they won’t forget drinking water or just drink only when they feel thirsty. Also, inspired by the feedback we got during presentation and the project of Coco’s group, we could not only load the program in the computer, but also attach a screen to the water bottle part so it looks more real and is more convenient for users. Also, as leaking sometimes happens, we can have a water tank under the tap so that leaking water won’t mess everything up.
And for the reason of leaking, I must say that we should be very careful in every step because if we didn’t make the hole too big, we would not have this problem. And we should consider every part very carefully to handle with unexpected cases like the water couldn’t come out during our presentation. One thing is because the time that servo hold was not long enough and we didn’t have enough water in the bottle. This is the thing that we should have consider when making this device.
I put the video of how our project works here:
Part 4.TECHNICAL DOCUMENTATION
Code in Arduino:
#include <Servo.h> Servo myservo; #define NUM_OF_VALUES_FROM_PROCESSING 2 int tempValue = 0; int valueIndex = 0; int values[NUM_OF_VALUES_FROM_PROCESSING]; boolean check = false; int startButton, state; int pre_state = 0; int sensor1 = 0; int sensor2; void setup() { Serial.begin(9600); myservo.attach(9); myservo.write(90); } void loop() { getSerialData(); Serial.println(analogRead(A1)); Check(); if (values[0] == 1){ sensor1 = 0; } Serial.print(sensor1); Serial.print(","); Serial.print(sensor2); Serial.println(); } void Check() { startButton = analogRead(A1); if (startButton > 1000) { state = 1; } else { state = 0; } if (state == 1 && !state == pre_state) { check = true; myservo.write(290); sensor1 = sensor1 + 1; sensor2 = 1; } else { check = false; delay(25000); myservo.write(90); sensor2 = 0; } pre_state = state; } void getSerialData() { while (Serial.available()) { char c = Serial.read(); //switch - case checks the value of the variable in the switch function //in this case, the char c, then runs one of the cases that fit the value of the variable //for more information, visit the reference page: https://www.arduino.cc/en/Reference/SwitchCase switch (c) { //if the char c from Processing is a number between 0 and 9 case '0'...'9': //save the value of char c to tempValue //but simultaneously rearrange the existing values saved in tempValue //for the digits received through char c to remain coherent //if this does not make sense and would like to know more, send an email to me! tempValue = tempValue * 10 + c - '0'; break; //if the char c from Processing is a comma //indicating that the following values of char c is for the next element in the values array case ',': values[valueIndex] = tempValue; //reset tempValue value tempValue = 0; //increment valuesIndex by 1 valueIndex++; break; //if the char c from Processing is character 'n' //which signals that it is the end of data case '\n': //save the tempValue //this will b the last element in the values array values[valueIndex] = tempValue; //reset tempValue and valueIndex values //to clear out the values array for the next round of readings from Processing tempValue = 0; valueIndex = 0; break; } } }
Code in Processing:
import processing.serial.*; import processing.sound.*; int NUM_OF_VALUES_FROM_PROCESSING = 2; int NUM_OF_VALUES_FROM_ARDUINO = 2; Serial myPort; String myString; int values[]; int sensorValues[]; PImage tree, gleaf1, gleaf2, gleaf3, gleaf4; PImage yleaf1, yleaf2, yleaf3, yleaf4, yleaf5, yleaf6; PImage wleaf1, wleaf2, wleaf3; PImage sbg, dbg, rbg; PImage ins; SoundFile rain; SoundFile notice; boolean check1 = true; int x = 0; int time = 1; char letter; int y1 = 204, y2 = 120, y3 = 213; float a1, a2, a3; boolean check = true; int nb=750; int maxDrops =1000; int minDrops=500; int h, h1; int []posX; int []posY; int []spd; int []len; int []a; float []b; int []px; int []py; float []scl; int pre_d = day(); int pre_h = hour(); boolean playSound = true; void setup() { size(500, 500); setupSerial(); tree = loadImage("tree.png"); gleaf1 = loadImage("gleaf1.png"); gleaf2 = loadImage("gleaf2.png"); gleaf3 = loadImage("gleaf3.png"); gleaf4 = loadImage("gleaf4.png"); yleaf1 = loadImage("yleaf1.png"); yleaf2 = loadImage("yleaf2.png"); yleaf3 = loadImage("yleaf3.png"); yleaf4 = loadImage("yleaf4.png"); yleaf5 = loadImage("yleaf5.png"); yleaf6 = loadImage("yleaf6.png"); wleaf1 = loadImage("wleaf1.png"); wleaf2 = loadImage("wleaf2.png"); wleaf3 = loadImage("wleaf3.png"); sbg = loadImage("sbg.jpeg"); dbg = loadImage("dbg.jpeg"); rbg = loadImage("rbg.jpg"); ins = loadImage("instruction.png"); rain = new SoundFile(this, "raining1.mp3"); notice = new SoundFile(this,"notice.mp3"); //generating rain drops posX = new int[50]; posY = new int[50]; spd = new int[50]; len = new int[50]; //generating ripples scl = new float[50]; a = new int[50]; b = new float[50]; px = new int[50]; py = new int[50]; for (int i=0; i<50; i++) { posX[i] = int(random(0, width)); posY[i] = int(random(-50, 0)); spd[i] = int(random(8, 15)); len[i] = int(random(10, 40)); scl[i] = random(1, 1.5); a[i] = int(random(10, 20)); b[i] = a[i]/2; px[i] = int(random(0, width)); py[i] = int(random(400, 500)); } } void draw() { //get yyyy/mm/dd/hh/mm/ss int d = day(); int m = month(); int y = year(); int h = hour(); int ms = minute(); int s = second(); getSerialData(); background(255); imageMode(CENTER); x = x+time; if (x > 3 || x<-3) { time = -time; } //receive the times of users' getting water from Arduino if (sensorValues[1] == 0) { print(sensorValues[0]); if (sensorValues[0] == 0) { letter = 'A'; } if (sensorValues[0] == 1) { letter = 'B'; } if (sensorValues[0] == 2) { letter = 'C'; } if (sensorValues[0] == 3) { letter = 'D'; } if (sensorValues[0] == 4) { letter = 'E'; } if (sensorValues[0] == 5) { letter = 'F'; } if (sensorValues[0] == 6) { letter = 'G'; } if (sensorValues[0] >= 7) { letter = 'H'; } print(sensorValues[0]); } else { letter = 'I'; //When the button is pressed, last for 25s } //show different scenes for different cases switch(letter) { case'A': push(); tint(255, map(1, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); break; case'B': push(); tint(255, map(2, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); push(); tint(255, map(2, 1, 8, 0, 255)); image(sbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); gLeaf(); pop(); break; case'C': push(); tint(255, map(3, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); push(); tint(255, map(3, 1, 8, 0, 255)); image(sbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); gLeaf(); pop(); break; case'D': push(); tint(255, map(4, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); push(); tint(255, map(4, 1, 8, 0, 255)); image(sbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); gLeaf(); pop(); break; case'E': push(); tint(255, map(5, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); push(); tint(255, map(5, 1, 8, 0, 255)); image(sbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); gLeaf(); pop(); break; case'F': push(); tint(255, map(6, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); push(); tint(255, map(6, 1, 8, 0, 255)); image(sbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); gLeaf(); pop(); break; case'G': push(); tint(255, map(7, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); push(); tint(255, map(7, 1, 8, 0, 255)); image(sbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); gLeaf(); pop(); break; case'H': push(); tint(255, map(8, 1, 8, 255, 0)); image(dbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); yLeaf(); pop(); push(); tint(255, map(8, 1, 8, 0, 255)); image(sbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); gLeaf(); pop(); break; case'I': show_wleaf(); if(playSound == true) { rain.play(); playSound = false; }else { playSound = true; } break; } push(); textSize(20); text(y+"."+m+"."+d, 375, 35); text(h+":"+ms+":"+s, 375, 60); pop(); noFill(); strokeWeight(2); stroke(255); rect(25, 25, 75, 30); textSize(12); text("instruction", 33, 43); sendSerialData(); //instruction button if (mouseX>25 && mouseX<100 && mouseY>25 && mouseY<55) { image(ins, width/2, height/2, 500, 500); } //timer for voice remider (no reminder from 10pm to 6am) if((h-pre_h == 2 || h-pre_h <0) && h>6 && h<22){ pre_h = h; notice.play(); } //update the data when a new day comes if(!(d-pre_d ==0)){ values[0] = 1; pre_d = d; }else{ values[0] = 0; } } void gLeaf() { push(); //tint(255,alpha); image(gleaf1, 215+x, 41); image(gleaf1, 133, 127); image(gleaf1, 132, 224); image(gleaf1, 271+x, 195); image(gleaf1, 400, 219); image(gleaf1, 347, 312); image(gleaf1, 309+x, 297); image(gleaf1, 190, 257); image(gleaf1, 236+x, 161); image(gleaf1, 229, 80); image(gleaf1, 306, 123); image(gleaf1, 149, 288); image(gleaf3, 190, 257); image(gleaf3, 236, 161); image(gleaf3, 229+x, 80); image(gleaf4, 234, 152, 306, 306); image(gleaf4, 305+x, 261, 306, 306); pop(); } void yLeaf() { push(); //tint(255,alpha); image(yleaf1, 131, 126); image(yleaf1, 342, 217); image(yleaf1, 220, 97); image(yleaf1, 136, 227); image(yleaf2, 190, 257); image(yleaf2, 236, 161); image(yleaf2, 229, 80); image(yleaf3, 234, 152, 306, 306); //image(yleaf3,305,261,306,306); a1 = map(500-y1, 0, 296, 0, 255); a2 = map(500-y2, 0, 380, 0, 255); a3 = map(500-y3, 0, 287, 0, 255); //effect of falling leaves push(); tint(255, a1); image(yleaf4, 147+x, y1, 130, 130); image(yleaf4, 276-x, y1, 130, 130); tint(255, a2); image(yleaf5, 347+x, y2, 130, 130); image(yleaf5, 256+x*0.2, y2, 130, 130); tint(255, a3); image(yleaf6, 300+x, y3, 130, 130); image(yleaf6, 120-x*0.5, y3, 130, 130); pop(); if (y1<height) { y1 += 6; } else { y1 = 204; } if (y2<height) { y2 += 4; } else { y2 = 120; } if (y3<height) { y3 += 5; } else { y3 = 213; } pop(); } void wLeaf() { image(wleaf3, 234, 152, 306, 306); image(wleaf3, 305, 261, 306, 306); image(wleaf2, 215, 41); image(wleaf2, 133, 127); image(wleaf2, 132, 224); image(wleaf2, 271, 195); image(wleaf2, 400, 219); image(wleaf2, 347, 312); image(wleaf2, 309, 297); image(wleaf2, 190, 257); image(wleaf2, 236, 161); image(wleaf1, 306, 123); image(wleaf1, 229, 80); image(wleaf1, 149, 288); image(wleaf1, 190, 257); image(wleaf1, 236, 161); image(wleaf1, 229, 80); } void show_wleaf() { int trans = 255; if (check == true) { push(); tint(255, trans); image(rbg, width/2, height/2, width, height); image(tree, width/2, height/2, 500, 500); wLeaf(); //effect of rain for (int i=0; i<50; i++) { strokeWeight(0.3); stroke(255, map(constrain(posY[i], 400, 500), 400, 500, 255, 0)); line(posX[i], posY[i], posX[i], posY[i]+len[i]); posY[i] = posY[i] + spd[i]; if (posY[i] >= 500) { posY[i] = 0; } push(); shapeMode(CENTER); noFill(); stroke(255, map(scl[i], 1, 2, 255, 0)); ellipse(px[i], py[i], a[i]*scl[i], b[i]*scl[i]); scl[i] = scl[i]+0.03; if (scl[i]>=2) { scl[i] = 1; } pop(); } trans = trans-1; if (trans == 0) { check = false; } pop(); } } void setupSerial() { printArray(Serial.list()); myPort = new Serial(this, Serial.list()[3], 9600); myPort.clear(); // Throw out the first reading, // in case we started reading in the middle of a string from the sender. myString = myPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII myString = null; values = new int[NUM_OF_VALUES_FROM_PROCESSING]; sensorValues = new int[NUM_OF_VALUES_FROM_ARDUINO]; } void getSerialData() { while (myPort.available() > 0) { myString = myPort.readStringUntil( 10 ); // 10 = '\n' Linefeed in ASCII if (myString != null) { print("from arduino: "+ myString); String[] serialInArray = split(trim(myString), ","); if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) { for (int i=0; i<serialInArray.length; i++) { sensorValues[i] = int(serialInArray[i]); } } } } } void sendSerialData() { String data = ""; for (int i=0; i<values.length; i++) { data += values[i]; //if i is less than the index number of the last element in the values array if (i < values.length-1) { data += ","; // add splitter character "," between each values element } //if it is the last element in the values array else { data += "\n"; // add the end of data character "n" } } //write to Arduino myPort.write(data); print("to arduino: "+ data); // this prints to the console the values going to arduino }
Graph of our circuit:
Some of the process when drawing leaves of the tree:
Sources from Internet:
Tree trunk: http://www.soutu123.com/png/2885486.html
Grass ground background: https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E5%9B%BE%E7%89%87&tn=84053098_3_dg&ie=utf-8
Dry background:http://www.serengeseba.com/zonepic.php?wd=干枯的大地封面大图&img=http://i.serengeseba.com/uploads/i_3_3237675286x3314876271_26.jpg&tip=0
Raning background:https://www.photophoto.cn/pic/27742324.html