Introduction: Desktop Guardian
Whether you are a gamer, a digital artist, an office worker, a student listening to online lectures while you play video games, a teacher giving said lectures online, or just a sad human being who spends their days behind a desk because you have nothing better to do since there's a lockdown which means you can't go outside and see your friends. We all probably have the same problem sitting behind that godforsaken desk:
You do no hydrate yourself enough
The concept of this project is to ensure that whenever you are stuck behind a desk, you drink enough!
Supplies
Art aspect:
- 3D printer (Prusa mk3s filament printer)
- Autodesk Maya
- Zbrush
- wooden planks
Technical aspect:
- Arduino Uno
- Wires (male-male)
- Flow sensor
- Water pump
- Water bottle
- resistors (3 x 220 Ohm & 1x 1k Ohm)
- 3 LED lights
- Button
0.96 inch OLED Display 128*64 pixels blue
Step 1: Research
I've always had a feeling that I didn't drink enough, since I often experience light headaches and spent so much time behind my desk (especially since the pandemic started) but now I know for a fact I don't because I did the math.
First, I had to ask myself a few questions:
- How much water should I drink a day?
- How much do I actually drink a day?
You've probably heard the general rule that you have to drink around 2 Litres of water a day. However I wanted to be more specific and figure out how much I should drink a day.
So I looked up several equations from different sources and applied my own numbers to them to figure out how much I should drink (see first image above). But I soon figured out there are too many other factors other than body weight that affect your daily water intake, like outside temperature, amount of movement, what kind of foods you eat and that's not even taking physiological factors of your own human body in consideration.
Conclusion: too many factors so just keep it at 2 Litres of water a day
So how much do I actually drink? On average: (813+676+618+976) : 4 ≈ 771 ml a day. Which is 2000 - 771 = 1,229 ml of water that I still need to drink (you should probably do more than 4 measurements to get a more accurate number, the more the better).
Step 2: Research / Ideate
I looked around the internet to see what kind of ways I could measure the water that I was drinking, and I came across 2 ways:
- Weight based measuring
- Using a flow sensor
I weighed the pros and cons of both ways against each other and decided to use the flow sensor, because it seemed like an easier way to measure the water to me at the time.
As for the looks of the project I was very much inspired by gargoyles you would typically find on gothic cathedrals. The first and maybe most obvious reason is because they most often are designed to divert rainwater from the building foundations, which results in water falling out of the gargoyle’s mouth. Furthermore Gargoyles are also seen as protectors, who scare evil away. However for this project I wanted to have a more softer looking guardian since this guardian is here to protect your well-being, not to be scary. So I decided to go with a design that resembles an otter, which has a softer appeal and because they are directly linked to water.
Step 3: 3D-print
Once I have all my measurements, I'll be able to poly model the base of the gargoyle/otter in Maya, add further details in Zbrush and finally load it into Prusa Slicer to prep for printing. Printer used: Prusa mk3s filament printer aka FDM printer.
This was actually a tricky part, I didn't know Zbrush that well and I wasn't a Maya expert either (I've only been working with these programs for a few months) so this step took me an tremendous amount of time. If there is a faster workflow than the one I'm about to describe, let me know.
My workflow:
- Poly model the base of the gargoyle/otter and the volume of the spaces where the tube and the water container are going to sit.
- Export the base model of the gargoyle/otter to Zbrush and add details
- Export the models of the volumes to Zbrush
- Grab both models and use the subtract boolean in Zbrush
- Load the final model in Prusa Slicer to prepare for 3D print
- Start the print
Step 4: Code
#include <br>#include //Using u8g2 library U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE); long lastTotalML = 0; bool goalReached = false; byte interrupt = 0; // 0 = digital pin 2 byte sensorPin = 2; const int bluePin = 13; const int greenPin = 12; const int redPin = 11; const int buttonPin = 3; const int pump = 10; const long target = 20; int reminder; int counter; int callibrate = 2; int buttonState = 0; volatile int pulseCount; //Variables shared between ISR functions and normal functions should be declared "volatile". //This tells the compiler that such variables might change at any time, and thus the compiler must //reload the variable whenever you reference it, //rather than relying upon a copy it might have in a processor register. float flowRate; float flowMilliLitres; long totalMilliLitres; unsigned long refillMilliLitres; unsigned long oldTime; unsigned long oldCounter; ///////////////////////////////////////////////SETUP/////////////////////////////////////////////////////////////////////////////////////////////// void setup() { // Initialize a serial connection for reporting values to the host Serial.begin(9600); // Initialize the LED pin as an output: pinMode(redPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(greenPin, OUTPUT); // Initialize the pushbutton pin as an input: pinMode(buttonPin, INPUT); pinMode(pump,OUTPUT); // Initialize OLED Screen u8x8.begin(); u8x8.setPowerSave(0); u8x8.setFont(u8x8_font_8x13B_1x2_f); u8x8.clear(); u8x8.drawString(0,0,"Starting up"); delay(1000); // Set interrupt pin as input and high pinMode(sensorPin, INPUT); digitalWrite(sensorPin, HIGH); // Write update to screen u8x8.drawString(0,3,"Processing"); delay(1000); // Reset all values pulseCount = 0; flowRate = 0.0; flowMilliLitres = 0; totalMilliLitres = 0; refillMilliLitres = 0; oldTime = 0; // Finalize startup screen u8x8.drawString(0,6,"Complete!"); delay(1000); u8x8.clear(); u8x8.drawString(0,3,"To drink:"); u8x8.setFont(u8x8_font_profont29_2x3_f); delay(500); u8x8.setCursor(0,5); u8x8.print(target); u8x8.print(" "); u8x8.print("ML"); // The Flow-sensor is connected to pin 2 which uses interrupt 0. // Configured to trigger on a FALLING state change attachInterrupt(interrupt, pulseCounter, FALLING); digitalWrite(greenPin, HIGH); } ///////////////////////////////////////////////LOOP/////////////////////////////////////////////////////////////////////////////////////////////// void loop() { // Read the state of the pushbutton value buttonState = digitalRead(buttonPin); // Check if the pushbutton is pressed. If it is, the buttonState is HIGH -> pump goes on if (buttonState == HIGH) { digitalWrite(bluePin, HIGH); digitalWrite(redPin, LOW); digitalWrite(greenPin, LOW); digitalWrite(pump,HIGH); reminder = 0; } // Stop filling when reaching filling threshold if(refillMilliLitres > 20 + callibrate){ digitalWrite(bluePin, LOW); digitalWrite(greenPin, HIGH); digitalWrite(redPin, LOW); digitalWrite(pump, LOW); refillMilliLitres = 0; reminder = 0; } // Reminder that goes off when I haven't filled my cup over an x amount of time if(reminder > 10){ digitalWrite(redPin, HIGH); digitalWrite(greenPin, LOW); } if((millis() - oldTime) > 1000){ // Only process counters once per second // Note the time this processing pass was executed. Note that because we've // disabled interrupts the millis() function won't actually be incrementing right // at this point, but it will still return the value it was set to just before // interrupts went away. oldTime = millis(); reminder++; counter++; flowRate = pulseCount; flowMilliLitres = flowRate / 48; // Because flow sensor gives 48,000 pulses/litre in other words 48 pulses/0.001 ml totalMilliLitres = totalMilliLitres + flowMilliLitres; refillMilliLitres = refillMilliLitres + flowMilliLitres; //print values to serial monitor Serial.print(pulseCount); Serial.print("\t"); Serial.print(flowMilliLitres); Serial.print("mL"); Serial.print("\t"); Serial.print(totalMilliLitres); Serial.print("mL"); Serial.print("\t"); Serial.print(reminder); Serial.print("\t"); Serial.print(counter); Serial.println(); if(goalReached == false) { if(target - totalMilliLitres >= 0) { if(totalMilliLitres != lastTotalML) { u8x8.clearLine(5); u8x8.clearLine(6); u8x8.clearLine(7); u8x8.setCursor(0,5); u8x8.print(target - totalMilliLitres); u8x8.print(" "); u8x8.print("ML"); lastTotalML = totalMilliLitres; } } else { u8x8.clear(); u8x8.drawString(0,1,"Target"); u8x8.drawString(0,5,"Reached!"); //u8x8.setFont(u8x8_font_open_iconic_check_8x8); //u8x8.setCursor(3,7); //u8x8.drawGlyph(3,7,"A"); goalReached = true; } } // Disable the interrupt while resetting counter detachInterrupt(interrupt); // Reset the pulse counter so we can start incrementing again pulseCount = 0; // Enable the interrupt again now that we've finished sending output attachInterrupt(interrupt, pulseCounter, FALLING); } } ///////////////////////////////////////////////Fuctions///////////////////////////////////////////////////////////////////////////////////////// //all functions wil be placed here ///////////////////////////////////////////////Intterupt routine///////////////////////////////////////////////////////////////////////////////// // Routine for INT0 void pulseCounter() { pulseCount++; } ///////////////////////////////////////////////END//////////////////////////////////////////////////////////////////////////////////////////////////////
Sources:
Step 5: Assemble
Once you have everything working it's time to assemble it. I drilled a hole into the bottom of the water reservoir (in my case a nice sturdy water bottle). Then I attached the water tube using a glue gun, this way it also can't leak.
Step 6: Result
Step 7: Reflection
Overall it was a roller-coaster. I ran into so many obstacles cause I didn't think things through enough, granted I've never made anything like this before and therefore I am really proud of myself seeing what I accomplished.
Having said that I feel like there's still room for improvement:
- I would've liked if the water would flow faster, because now you have to wait pretty long for a cup to be filled.
- The box that contains the wiring and such could've look way better if I maybe modelled and printed that as well. A laser cutter would also have been nice using thin wood.
- The model itself while it does look pretty nice due to it's details, I feel like it could've looked more interesting instead of entirely black.
- The original idea was that RGB lights would sit in the eye sockets of the gargoyle, instead of LED lights on the box itself.
- I also intended to add a decibel reader, so that when I make to much loud, a red light would go off. Since I also make a lot of noise when gaming. Due to the complexity of this project (for my standards) it also never made the final cut.
I have learned the following:
- Do a lot of prep work before hand, this will safe you a lot of time. I made the mistake of beginning modelling too early without having a clear vision of how the parts were gonna fit everywhere.
- Plan and make room for making such projects. It really helps to have a designated area for working on a project like this, instead, I had to set up and break down the project over and over again because I have no space. This really brought my motivation down.
Hope you enjoyed, Cheers!