OVERVIEW

This is a smart 4-plant watering machine that caters to the different need of each plant.

Front view

Front view

Back view

Back view

I own a couple unfortunate plants, two small ones and two big ones. They suffer from dehydration whenever I’m away from home. I decided to design a plant watering machine because I want them to stay alive.  Moreover, I want to design a watering machine that caters to the different need of every plant. The idea is that I am able to set a watering cycle such that the machine repetitively waters the plants with different amount of water every set amount of time. For example, I can configure the machine to water the 2 small plants with 1 portion of water and 2 big plants with 3 portions of water everyday.

The final product allows me to set the wait time between each watering cycle with 3 preset buttons (0~3 units of time). Also, for each individual plant, there are 3 preset buttons to set the watering amount (0~3 portions).

Labelled LED Interface

Labelled LED Interface:

plant 1 watering amount = 3 (15 seconds of watering)

plant 2 watering amount = 0

plant 3 watering amount = 2 (10 seconds of watering)

plant 4 watering amount = 1 (5 seconds of watering)

Time between cycle = 2 (20 seconds between each cycle)

In action

In action (disclaimer: the LED doesn’t jitter in reality. The frame rate aliasing makes it to look jittery in the video)

Valve close-up

Valve close-up

Valves

Valves

Tubing

Tubing

PROCESS IMAGES AND REVIEW

One of the hardest problems of this project was designing the valves. It was a mechanical problem and I had zero experience in designing mechanics. I tried to design the valve three times and the last time worked.

First valve design with crossed screws -- not pretty and not really functionalIn the first iteration, I built a valve with two crossing screws such that when the servo rotates, it “pinches” the tube to stop the water from flowing. However, this design was not good, because first of all, the tube was too hard to pinch, and second, the cross shape of the plastic base always trip on the tube and move the tube away.

Soft tube

Soft tube (ordered)

Hard tube

Hard tube (original)

For the second iteration, I ordered softer tubes that made it possible for the servo to stop water from flowing. In the above pictures, you can see the soft tube is much easier to bend.

Final valve design

In the third iteration, I remade the valves with round plastic base and wide apart screws. The crossing screws might cut the tube open. So I designed another version of the valve where the screws won’t pinch the tube, but rather stretch the tube. The soft tube is flexible enough to be stretched. The round plastic base also allows the tube to stay in place when the valve rotates.

Closed vs open valves

Closed vs open valves

This is how the valves look like when they are all open.

Prototype

Prototype

This is the prototype version of this watering machine. In the above image, you can see the first iteration of the valve design.

Considered another pump

I also considered using another pump, but this pump is WAY too powerful for watering purpose.

Tested many servos to find the ones that don't jitter when the pump is on

Tested many servos to find the ones that don’t jitter when the pump is on

Capacitor

Capacitor

One problem I encountered was that the servos jitter really hard when the pump was on. I tested all servos in physical computing lab and found 4 servos that don’t jitter. I also added a capacitor to smooth out the power supply.

Labelled LED Interface

Labelled LED Interface: plant 1 watering amount = 3 (15 seconds of watering) plant 2 watering amount = 0 plant 3 watering amount = 2 (10 seconds of watering) plant 4 watering amount = 1 (5 seconds of watering) Time between cycle = 2 (20 seconds between each cycle)

I also found it challenging to design the interface of the watering machine. I chose the Adafruit Trellis squishy 4×4 LED and button matrix simply because I love the feeling of pressing on them. However, with 16 buttons and LEDs it’s hard to tell the user how to use it and what the setting is. If I want to teach someone else to use the watering machine, I’d love it to be easy to explain and remember. The interface I came up with is the following. The top left button is a red button that execute the setting when it’s pressed. It blinks to confirm the setting. Column 2-4 allow the user to toggle the watering level from 0-3. The first column sets the wait time between each watering cycle from 0-3.

However, without the labels, it is extremely difficult to explain. Therefore, I labeled the buttons and asked someone who has never seen this project to try to interpret it. The only confusion with the labels is the time between cycle setting. The person wondered if it sets the watering frequency during a day or the amount of wait time between each cycle. Currently the time between cycle sets the amount of wait time between each cycle. One unit of time is 10 seconds. So in the above setting, there’s a 20-second wait between each cycle.

DISCUSSION

Response

“The interface might be confusing to some—no labels so it’s easy to get lost!”

Yeah I agree. It’d be great to use different colors to indicate the different functionality of each column. I could have also used a LED screen to show words and numbers. It was a personal decision to use the 4×4 LED and button matrix and I didn’t have enough variety of LED colors to color-code the functionality.

“I found the mechanism interesting, difficult to construct and very useful! Bravo Jeena!!”

Wahhoooo! It was pretty difficult to construct the valves. It took 3 iterations to finally get the valves to tighten properly. In the first iteration, I had two crossing screws close the valve by pinching the tube, but the tube was too hard to actually close up. The second iteration I bought softer tubes, which made it much easier. However, crossing screws were too weak to form a close seal. In the third iteration, I designed a slightly different valve mechanism, where the soft tubes are fixed on both end with zip ties and hot glue, and the valve simply stretch and bend the tube to form a seal.

Self critique

I mostly agree with the comments I received at the critique. I’m happy with how it turned out to be — a functioning watering machine that can water 4 different plants with a sufficient interface (for me). But I’d love it to be smaller, something that can be tucked away in my living room. Right now, it’s a giant open shelf of wires. It’s not pretty enough to occupy that much space. It’d be great to use a smaller clear acrylic box that encloses everything and allows me to see through.

What I learned

Mechanical problems are real.

I didn’t think the valves are hard to design at all. The idea is to water one plant at a time, so I close all valves but one to water only one plant. To switch between valves, I open another valve and close all other valves. I spent most time trying different ways to stop water from flowing while another valve is opened. In the prototype week, I didn’t figure out the mechanical problem. Next time, I should try to figure out the hardest part first.

Also, I gained a ton of soldering skills by soldering 32 LEDs in one go for the button interface, plus many wires later on. I learned how to be very careful and fast at the same time.

I drilled many holes to make the valves work. I drilled holes into the plastic pieces that come with the hobby servos first, then screw the screws in. It was not easy at all, given that the holes are all so tiny. Also, I drilled holes for the zip ties to secure the tubes onto the wooden shelf.

I learned how to pronounce “valve” correctly. It’s “vaaaalv” not “volve”. Ah.

Next steps

I will make a box with a clear acrylic door that can enclose the box.

TECHNICAL INFORMATION

Schematic

Schematic

Schematic

Shelf design

Shelf design

/*
 * Project 2
 * Jeena Yin (qyin)
 * It took two weeks
 *
 * Collaboration:
 * Referenced code in 
 * https://learn.adafruit.com/adafruit-trellis-diy-open-source-led-keypad/connecting
 * https://courses.ideate.cmu.edu/60-223/f2019/tutorials/code-bites#blink-without-blocking
 * 
 * Summary: The code below waters 4 plant with different watering 
 * amount according to the setting
 * 
 * Inputs:
 * Adafruit Trellis LED buttons | PIN A2
 * Valve 0 button               | PIN 4
 * Valve 1 button               | PIN 5
 * Valve 2 button               | PIN 7
 * Valve 3 button               | PIN 8
 * 
 * Outputs:
 * Valve 0 servo | PIN 6 (PWM)
 * Valve 1 servo | PIN 9 (PWM)
 * Valve 2 servo | PIN 10 (PWM)
 * Valve 3 servo | PIN 11 (PWM)
 * Pump motor    | PIN 3
 */

#include <Wire.h>
#include <Servo.h>
#include "Adafruit_Trellis.h"

#define NUMTRELLIS 1
#define numKeys (NUMTRELLIS * 16)
#define INTPIN A2

// Valve 0
const int VALVE0_PIN = 6;        // PWM
const int VALVE0_SWITCH_PIN = 4; // for testing

// Valve 1
const int VALVE1_PIN = 9;        // PWM
const int VALVE1_SWITCH_PIN = 5; // for testing

// Valve 2
const int VALVE2_PIN = 10;       // PWM
const int VALVE2_SWITCH_PIN = 7; // for 

// Valve 3
const int VALVE3_PIN = 11;       // PWM
const int VALVE3_SWITCH_PIN = 8; // for testing

// Pump
const int PUMP_PIN = 3;

const int VALVE_CLOSE_POS = 0;
const int VALVE_OPEN_POS = 140;

// 10 seconds as cycle length: if time set to 2 then water every 20 seconds
const int CYCLELENGTH = 10; 
// 5 seconds as unit for watering amount
const int WATERAMOUNTUNIT = 5; 

// Blink the red LED as feedback confirmation
const int blinkLEDdelay = 70;

// Array that stores the water amount setting
int waterAmount[] = {0, 0, 0, 0};
int waterFrequency = 0;
unsigned long microTimer = 0;
unsigned long macroTimer = 0;
unsigned long quarterMacroTimer = 0; // wait time between watering cycles in second
unsigned long quarterMicroTimer = 0; // wait time between each plant in second
int wateringPlantId = -1; // id of plant being watered

// only water if frequency > 0 and wateramount is > 0 for any plant
bool shouldWater = false; 
bool waitingForNextCycle = false;

Servo valve0;
Servo valve1;
Servo valve2;
Servo valve3;

bool valveStates[] = {false, false, false, false};

Adafruit_Trellis matrix = Adafruit_Trellis();
Adafruit_TrellisSet trellis = Adafruit_TrellisSet(&matrix);

void setup() {
  // put your setup code here, to run once:
  pinMode(PUMP_PIN, OUTPUT);
  pinMode(VALVE0_SWITCH_PIN, INPUT_PULLUP);
  pinMode(VALVE1_SWITCH_PIN, INPUT_PULLUP);
  pinMode(VALVE2_SWITCH_PIN, INPUT_PULLUP);
  pinMode(VALVE3_SWITCH_PIN, INPUT_PULLUP);
  pinMode(INTPIN, INPUT);
  Serial.begin(9600);
  valve0.attach(VALVE0_PIN);
  valve1.attach(VALVE1_PIN);
  valve2.attach(VALVE2_PIN);
  valve3.attach(VALVE3_PIN);
  
  for(int i = 0; i < 4; i++){
    OpenValveForPlant(i);
  }
  OpenAllValves();
  
  digitalWrite(INTPIN, HIGH);
  trellis.begin(0x70);

  // turn on all LEDs
  for (uint8_t i=0; i<numKeys; i++) {
    trellis.setLED(i);
    trellis.writeDisplay();    
    delay(50);
  }
  // then turn them off
  for (uint8_t i=0; i<numKeys; i++) {
    trellis.clrLED(i);
    trellis.writeDisplay();    
    delay(50);
  }
  trellis.setLED(0); // light up the red button only
  trellis.writeDisplay();
}

void loop() {
  if (trellis.readSwitches()) {
    for (uint8_t i=0; i<numKeys; i++) {
      if (i == 0 && trellis.justReleased(i)) {
        BlinkLED(i);
      } 
      if (trellis.justPressed(i)) {
        ButtonPressed(i);
      }
    }
    // tell the trellis to set the LEDs we requested
    trellis.writeDisplay();
  }

  // Watering plants
  if(shouldWater) {
    if(millis()/1000 - microTimer >= quarterMicroTimer) {
      // switch to next plant
      if(wateringPlantId == 3) WaitForNextCycle(); // Cycle ends
      else{
        GetReadyForPlant(wateringPlantId + 1);
      }
    }
  }
  // Waiting for next watering cycle
  else if(waitingForNextCycle) {
    if(millis()/1000 - macroTimer >= quarterMacroTimer) {
      Execute();
      macroTimer = millis()/1000;
    }
  }
  // Only check test switches when it's not watering.
  else{
    CheckSwitches();
  }
  delay(30);
}

void ButtonPressed(int i) { 
  // The red button is pressed
  if(i == 0) {
    Execute();
    // if it was pressed, turn it on
    if (trellis.justPressed(i)) {
      trellis.setLED(i);
    }
    return;
  }
  
  int col = i % 4;
  int row = (int) (i / 4);

  // Set Time
  if(col == 0) { // Interface design silimar to a slider
    if (trellis.isLED(i)) {
      if(row == 3 || !trellis.isLED(i+4)) { // Frequency = 0
        SetFrequency(0);
        for(int k = 1; k <= 3; k++) {
          trellis.clrLED(k*4);
        }
      }
      else {                                // Adjust Frequency
        SetFrequency(row);
        for(int k = 1; k <= row; k++) {
          trellis.setLED(k*4);
        }
        for(int k = row+1; k <= 3; k++) {
          trellis.clrLED(k*4);
        }
      }
    }
    else {                                  // Adjust Frequency 
      SetFrequency(row);
      for(int k = 1; k <= row; k++) {
        trellis.setLED(k*4);
      }
      for(int k = row+1; k <= 3; k++) {
        trellis.clrLED(k*4);
      }
    }
  }
  // Set water level
  else { // Interface design silimar to a slider
    if (trellis.isLED(i)) {
      if(col == 3 || !trellis.isLED(i+1)) { // No water
        SetWater(row, 0);
        for(int k = 1; k <= 3; k++) {
          trellis.clrLED(k+row*4);
        }
      }
      else {                                // Adjust water level
        SetWater(row, col);
        for(int k = 1; k <= col; k++) {
          trellis.setLED(k+row*4);
        }
        for(int k = col+1; k <= 3; k++) {
          trellis.clrLED(k+row*4);
        }
      }
    }
    else {                                  // Adjust water level 
      SetWater(row, col);
      for(int k = 1; k <= col; k++) {
        trellis.setLED(k+row*4);
      }
      for(int k = col+1; k <= 3; k++) {
        trellis.clrLED(k+row*4);
      }
    }
  }
}

// Helper function that starts watering the plant right now
void Execute() {
  // Close all valves
  for(int i = 0; i < 4; i++) {
    CloseValve(i);
  }

  // only water if frequency > 0 and total wateramount is > 0
  shouldWater = (waterFrequency > 0 && WaterAmountNonZero());
  waitingForNextCycle = false;
  
  if(shouldWater) {
    Serial.println("Start watering");
    TurnOnPump();
    // Initialize global variables 
    GetReadyForPlant(0);
  }
  else {
    TurnOffPump();
    OpenAllValves();
    Serial.println("Stop watering");
  }
}

void GetReadyForPlant(int id) {
  Serial.print("Get ready for plant "); Serial.println(id);
  microTimer = millis()/1000;
  quarterMicroTimer = GetWaterTimeForPlant(id);
  Serial.print("Water amount(seconds): "); Serial.println(quarterMicroTimer);
  wateringPlantId = id;
  OpenValveForPlant(id);
}

void WaitForNextCycle() {
  Serial.println("Wait for next cycle... ");
  TurnOffPump();
  OpenAllValves();
  shouldWater = false;
  waitingForNextCycle = true;
  macroTimer = millis()/1000;
  quarterMacroTimer = waterFrequency * CYCLELENGTH;
  Serial.print("Wait time(seconds): "); Serial.println(quarterMacroTimer);
}

int GetWaterTimeForPlant(int id) {
  return waterAmount[id] * WATERAMOUNTUNIT;
}

// Change the global watering frequency per minute
void SetFrequency(int frequency) {
  Serial.print("Set time: "); Serial.println(frequency);
  waterFrequency = frequency;
}

// Set the array of watering amount
void SetWater(int plantId, int water) {
  Serial.print("Set water: "); Serial.print(plantId); Serial.print(" "); Serial.println(water);
  waterAmount[plantId] = water;
}

// Only close one valve
void CloseValve(int valve) {
  // Don't close if is already closed
  if(!valveStates[valve]) return;
  else valveStates[valve] = false;
  Serial.print("Close "); Serial.println(valve);
  switch(valve) {
    case 0:
      valve0.write(VALVE_CLOSE_POS);
      break;
    case 1:
      valve1.write(VALVE_CLOSE_POS);
      break;
    case 2:
      valve2.write(VALVE_CLOSE_POS);
      break;
    case 3:
      valve3.write(VALVE_CLOSE_POS);
      break;
    default:
      break;
  }
  delay(50);
}

void OpenValve(int valve) {
  // Don't open if is already open
  if(valveStates[valve]) return;
  else valveStates[valve] = true;
  Serial.print("Open "); Serial.println(valve);
  switch(valve) {
    case 0:
      valve0.write(VALVE_OPEN_POS);
      break;
    case 1:
      valve1.write(VALVE_OPEN_POS);
      break;
    case 2:
      valve2.write(VALVE_OPEN_POS);
      break;
    case 3:
      valve3.write(VALVE_OPEN_POS);
      break;
    default:
      break;
  }
  delay(50);
}

// Helper function returns true if any plant water amount is > 0
bool WaterAmountNonZero() {
  for(int i = 0; i < 4; i++) {
    if(waterAmount[i] > 0) return true;
  }
  return false;
}

void CheckSwitches() {
//  return;
  if(!digitalRead(VALVE0_SWITCH_PIN)) {
    OpenValveForPlant(0);
  }
  if(!digitalRead(VALVE1_SWITCH_PIN)) {
    OpenValveForPlant(1);
  }
  if(!digitalRead(VALVE2_SWITCH_PIN)) {
    OpenValveForPlant(2);
  }
  if(!digitalRead(VALVE3_SWITCH_PIN)) {
    OpenValveForPlant(3);
  }
}

// Open valve for only plant id, close all other valves
void OpenValveForPlant(int id) {
  for(int i = 0; i < 4; i++) {
    if(i == id) OpenValve(i);
    else CloseValve(i);
  }
  Serial.print("Opening valve for only plant "); Serial.println(id);
}

void TurnOnPump() {
  digitalWrite(PUMP_PIN, HIGH);
  Serial.println("Pump on");
}

void TurnOffPump() {
  digitalWrite(PUMP_PIN, LOW);
  Serial.println("Pump off");
}

// Blink the LED
void BlinkLED(int i) {
  trellis.clrLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.setLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.clrLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.setLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.clrLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.setLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
}

void OpenAllValves() {
  Serial.println("Open all");
  for(int i = 0; i < 4; i++){
    OpenValve(i);
  }
}