When I caught wind that we were moving from grow-almost-anything England to rocks-are-the-indigenous-life-form New Mexico, I knew that a stereotypical garden wouldn’t work. Well maybe it would, but I don’t feel like going through the hoops of putting one in on a house that we’ll be renting for only a few years. I need to support my chile addiction, and there’s something that’s just not satisfying about buying preserved, picked-weeks-ago store-offered chiles.

I think it was somewhere in Make Magazine (or their website/news) that caught my eye for hydroponics – think: compact, self-contained gardening that you can do on Mars (which some parts of New Mexico look like!). I started doing mad research about it. Is it possible to grow chiles with hydroponics? I don’t know, but according to this guy it is.  So I set about attempting to build a system.

I have an inner-geek: I love tinkering with electronics, computers, drones, model trains… all sorts of stuff that would buy you shares of laughing-stock amongst your macho-infested friends.  So why not attempt to automate it using an Arduino or Raspberry Pi?  Something in line of what Paul Langdon did at the Maker Faire 2015 in NYC.

I then stumbled upon a more compact tower method of building over at Tomorrow’s Garden.  This is the design I want.  Further investigation led me to Mike Walker who has been experimenting with the design since about 2013.  His YouTube site has pretty much become my sole point of research and learning.  Why not build the thing from lessons-learnt that someone else has already discovered? – like don’t use a bucket due to shitty thermal insulation properties (as depicted at right), use a beer cooler; or coat the thing with truckbed liner spraypaint to prevent light in the tower leading to unwanted algae or bacteria growth.

With Mike’s videos as my guide, I set out to building my physical tower:

I used a combination of the instructions on the Tomorrow’s Garden (linked above), as well as a YouTube search on building it – I think Mike Walker has a video on the build as well.  Use those resources, as I won’t go into build details since it has already been documented pretty well elsewhere.  Of note: like Mike, I foam-insulated the top of the cooler to add additional thermal protection.  Also, the 5″ fence-post-made ‘fan enclosure’ is permanently affixed to the cooler, while I left the 4″ fence-post tower as a snug fit to the cooler allowing me to pull it out of the cooler for cleaning and maintenance.  The aluminum Fat Daddio’s pan is just snug-fit (held in place by friction) inside the gothic fence cap.

 

 

Once I had the thing mostly assembled, I started to plan out the automation-side of things.  Things can get confusing as to what goes where and does what, so I had to make a graphic flow chart (below) to keep things straight for me.  So, here’s all what I wanted for my automation:

My initial experimentation was to the tune of an Arduino with much success.  But then I started getting the idea of controlling the tower from my iPhone (the LED strand along the tower specifically) – after all, it will sit prominently on the back patio and will make a descent conversation piece when we have company over.  So I bought a Yun shield (to give the Arduino WiFi capability) and started working with that.  I barely started figuring it out when I stumbled across a little $19 Arduino-like board with WiFi and IoT (read: I can control it from anywhere in the world via internet) called the Particle Photon.  I bought one and tried it on: I was sold!  It’s about ¼ the size of the Arduino with iPhone capability automatically built in.  You just have to not mind opening an account through them (it’s free) in order to program it and propagate it throughout the ‘nets.  The other nice thing is that it uses almost the same coding as the Arduino – I got hung up a little on researching the comparable libraries for what I needed to do (it took a few weeks), but Googling “Spark Photon” (the company used to be “Spark” before they changed their name to “Particle” – you’ll have more success Googling “Spark” vice “Particle”) along with whatever issues you have can help find your answer.

So for the automation parts I ordered:

I put it all together on a 1/2 perma-proto board.  The wiring that I did is below with both Fritzing and actual pictures:

 

The design is simple with pins left to upgrade as required if I feel the need (ie, add in a water pH sensor in the future).  Here is the feature list for now:

  • When the water temperature goes above 24°C (~75°F), the fan turns on to cool the water (acting as a swamp cooler – one of Mike Walker’s ideas after he tried various methods to keep the water cool). The fan then turns off once the water is below 20°C (68°F).  These are the temperatures I’m walking out the door with; after a bunch of research into plant roots, hydroponics and the like, that temperature range seems to be the sweet spot.  Of course, the temperature range is software-controlled and can be amended once the whole thing has been in operation a while.
  • When the water level gets to 50% capacity, the valve will open to refill the reservoir (the Rubbermaid cooler) back to 90% capacity.  Keep in mind the fan will evaporate off the water in order to keep it cool, and I live in an arid desert environment hastening the whole evaporation process.  I bought a Reverse Osmosis filter set to use as the water source.  Overkill?  Perhaps, but I’ll be using it to supply water to two of these towers.  A cheaper option would to buy an in-line charcoal filter and an extra electronically-controlled valve: hook up the two valves in a series (one after the other, the first directing water to the outside somewhere and the second directing water into the reservoir).  When the reservoir needs refilling, the software coding opens the first valve for 60 seconds to dump the initial “charcoaled” black water outside somewhere and then closes it, with the second valve opening (upon the closing of the first valve) to refill the reservoir with the clean water.
  • Via a smartphone, set the watering percentage to keep the roots wet and fed.  Like the Arduino, the Particle Photon doesn’t have a clock (like “water the plants in the evening” or something – a Raspberry Pi would be a better option if this is the goal), it just has a running time of how long it takes to execute its program.  So assuming it takes one second to run the entire sketch, I have a counter, 0 to 99, for each time it runs prior to resetting – 100 seconds total.  So I type in 33 on my smartphone, and for 33 of 100 seconds the water pump will be on providing a rain-shower of water/nutrients to the plants.
  • Via a smartphone, control the LED tower lights as patio mood lighting.  Since it’s outside, I really didn’t intend for the LEDs to act as grow lights.  This feature is more of an entertainment “wow” factor to showcase the plants.

As mentioned above I’ve considered a pH meter, but I can’t think of a way to reliably do anything about it.  What do I mean?  So the Photon measures a 7.0 pH.  I can either build a hugely complicated water drop dispenser system to add drops of “pH Up” and “pH Down” – that’s two dispensers!  …Or I have to adjust the pH by hand.  If I have to physically mess with the Chile Tower by hand to rectify the automated pH reading, then why not just measure the pH by hand as well?  That’s why I decided to spring for the Reverse Osmosis water supply: if I can guarantee a water source of ~7.0 pH, then I can rest easy with fewer pH checks under the assumption that the optimum pH of 5.5 to 6.5 isn’t going to aggressively change with the addition of pH-regulated water.

Below is the code that automates the Tower (click on the file name):

// libraries
    #include <WS2801.h>
    #include <DS18B20.h>
    #include <spark-dallas-temperature.h>
    #include <OneWire.h>
    #include <Arduino.h>


// Pins
    #define DEPTH A1                                // Analog pin 1 to measure the water depth
    //LED Clock (green) = A3                        // Analog Pin 3 to clock the LED strip
    //LED Data (yellow) = A5                        // Analog Pin 5 to send data to the LED strip
    #define ONE_WIRE_BUS D1                         // Digital Pin 1 to read the DS18S20 temperature
    int Valve = D3;                                 // Digital Pin 3 to relay on/off the refill water Valve
    int Pump = D4;                                  // Digital Pin 4 to relay on/off water Pump
    int Fan = D5;                                   // Digital Pin 5 to relay on/off the Fan

// Definitions
    double tempc;                                   // The displayed real-time Temperature
    double resistance;                              // The displayed waterdepth resistance; can comment out once set up
    double waterdepth;                              // The displayed waterdepth in percentage
    double ratio;                                   // how much to water
    int LEDs;
    int iteration;                                  // Integer 0-99 to operate Water Pump based on % time (0-99 loops)
    boolean LEDOp = true;                          // Flag for feedback on whether or not the LED strip is operating
    boolean FanOp = false;                          // Flag for feedback on whether or not the Fan is operating
    boolean PumpOp = false;                         // Flag for feedback on whether or not the Pump is operating
    boolean ValveOp = false;                        // Flag for feedback on whether or not the Valve is operating
    OneWire oneWire(ONE_WIRE_BUS);                  // Setup a oneWire instance to communicate with Thermometer
    DallasTemperature sensors(&oneWire);            // Pass our oneWire reference to Dallas Temperature.
    #define SERIESRESISTOR 560                      // Depth meter resistor
    #define ZERO_VOLUME_RESISTANCE    298.00       // Resistance value (in ohms) when no liquid is present.
    #define CALIBRATION_RESISTANCE    675.00          // Resistance value (in ohms) when liquid is at max line.
    #define CALIBRATION_VOLUME        100.00          // Volume (in any units) when liquid is at max line.
    const int numPixel = 11;                        // Number of LEDs on the Tower
    Adafruit_WS2801 strip = Adafruit_WS2801(numPixel);
 

void setup(void) {
    sensors.begin();                                // IC defaults to 9 bit. If you have trouble consider changing to 12. 
    Serial1.begin(9600);                            // initialize 16x2 LCD Display
        Serial1.write(0xFE);
        Serial1.write(0xD1);
        Serial1.write(16);                            // 16 columns
        Serial1.write(2);                             // 2 rows
      delay(10);
        Serial1.write(0xFE);
        Serial1.write(0x52);                          // autoscroll off
      delay(10);  
        Serial1.write(0xFE);    
        Serial1.write(0x50);
        Serial1.write(200);                           // Contrast
      delay(10);  
        Serial1.write(0xFE);    
        Serial1.write(0x99);
        Serial1.write(255);                           // Brightness
      delay(10);      
      
    Particle.variable("Resistance", &resistance, DOUBLE);// Temp Resistance exported to internet; for set up only, comment out otherwise
    Particle.variable("Depth", &waterdepth, DOUBLE);    // Water Depth exported to internet
    Particle.variable("Celsius", &tempc, DOUBLE);       // Temperature exported to internet  
    Particle.variable("Fan", &FanOp, BOOLEAN);          // Fan Operation exported to internet  
    Particle.variable("Rain Shower", &PumpOp, BOOLEAN); // Pump Operation exported to internet  
    Particle.variable("Refill", &ValveOp, BOOLEAN);     // Valve Operation exported to internet 
    Particle.variable("LEDs", &LEDOp, BOOLEAN);         // LED Operation exported to internet 

    pinMode(LEDs, OUTPUT);
        Particle.function("LEDs",ledToggle);
        
    pinMode(Fan, OUTPUT);
        Particle.function("Fan",fanToggle);
    
    pinMode(Pump, OUTPUT);
        Particle.function("Pump",pumpToggle);
        
    pinMode(Valve, OUTPUT);
        Particle.function("Valve",valveToggle);
    
    strip.begin();
        Particle.function("Color", color);
            color("[000,255,000]");
}                                           


void loop(void) {

// Start loop count for Water Pump On percentage time through 100% (0-99)
    if (iteration < 99) {
        
        
    // Measure the water depth
        float depthvalue = analogRead(DEPTH);       // convert depth reading to resistance
        depthvalue = ( depthvalue / 1023.0) - 1.0;
        resistance = SERIESRESISTOR / depthvalue;
        if ((ZERO_VOLUME_RESISTANCE - CALIBRATION_RESISTANCE) == 0.0) {
            waterdepth = 0.0;
            }
        else { 
            float scale = ((ZERO_VOLUME_RESISTANCE - resistance) / (ZERO_VOLUME_RESISTANCE - CALIBRATION_RESISTANCE));
            waterdepth = (CALIBRATION_VOLUME * scale);
            }
    
    // Measure the temperature of the water
        sensors.requestTemperatures();          // Send the command to get temperatures from all sensors on the one wire bus
        tempc = sensors.getTempCByIndex(0);     // 0 refers to the first IC on the wire
        while (tempc == -127.0) {               // if the anomalous -127C occurs, then get another temp reading without registering the -127
            tempc = sensors.getTempCByIndex(0);
        }
        
    // Display the temp and water level on the LCD Display
        Serial1.print("Temp ");   
        Serial1.print(tempc,1);
        Serial1.println("\337C");
        Serial1.print("Lvl ");   
        Serial1.print(waterdepth,0);
        Serial1.print("%  "); 
        Serial1.print((((waterdepth*0.035) + 4.3)*3.78541),1);
        Serial1.println("L");
        // Serial1.println(iteration);

    
    // Flag the Fan on if temp over 24C, Flag off when below 22C
        if (tempc > 24.0) {
            FanOp = true;
        }    
        if (tempc < 22.0) {
            FanOp = false;
        }

    // Fan operation based off of flag FanOp
        if (FanOp) {
            digitalWrite(Fan, HIGH);
        }
        else {
            digitalWrite(Fan, LOW);
        }

    // Flag the Valve on if Waterlevel under 74% (7gal/26L), Flag off when above 96% (7.6gal/29L)
        if (waterdepth > 96.0) {
            ValveOp = false;
        }
        if (waterdepth < 74.0) {
            ValveOp = true;
        }

    // Valve control based off of flag ValveOp
        if (ValveOp) {
           digitalWrite(Valve, HIGH);
        }
        else {
            digitalWrite(Valve, LOW);
        }
        
    // Flag the Pump on if Iterations are = % AND when Waterlevel is above 5gal/19L (to not cavatate the pump), Flag off when not
        if (iteration < 50 && waterdepth >8.0) {
            PumpOp = true;
        }
        else {
            PumpOp = false;
        }
        
    // Pump control based off of flag PumpOp
        if (PumpOp) {
            digitalWrite(Pump,HIGH);
        }
        else {
            digitalWrite(Pump,LOW);
        }

    // LED control based off of flag LEDOp
        if (LEDOp) {
        // Rainbow lights 
            int i, j;
            for (j=0; j < 256; j++) {     // 25 colors in the wheel
            for (i=0; i < strip.numPixels(); i++) {
          // tricky math! we use each pixel as a fraction of the full 96-color wheel
          // (thats the i / strip.numPixels() part)
          // Then add in j which makes the colors go around per pixel
          // the % 96 is to make the wheel cycle around
                strip.setPixelColor(i, Wheel( ((i * 256 / strip.numPixels()) + j) % 256) );
                Serial.println(j);
            }  
            strip.show();   // write all the pixels out
            delay(20);
            }            
        }
        else {
            color("[000,000,000]");
            delay(1000);
        }


    // Increment the iteration; if the count goes above 99, then reset to zero      
    iteration++;    
    }
    else {
        iteration = 0;
    }
    delay(500);
}



// Internet control of the LEDs
    int ledToggle(String command) {
        if (command=="on") {
            LEDOp = true;
        }
        else if (command=="off") {
            LEDOp = false;
        }
    }
    
// Internet control of the Pump
    int pumpToggle(String command) {
        if (command=="on") {
            PumpOp = true;
        }
        else if (command=="off") {
            PumpOp = false;
        }
    }
    
// Internet control of the Fan
    int fanToggle(String command) {
        if (command=="on") {
            FanOp = true;
        }
        else if (command=="off") {
            FanOp = false;
        }
    }
    
// Internet control of the Valve
    int valveToggle(String command) {
        if (command=="on") {
            ValveOp = true;
        }
        else if (command=="off") {
            ValveOp = false;
        }
    }    
// Internet control of the LED Tower lights
    int color(String command) {
        int red = command.substring(1,4).toInt();
        int green = command.substring(5,8).toInt();
        int blue = command.substring(9,12).toInt();
        int i;
        for (i=0; i < strip.numPixels(); i++) {
            strip.setPixelColor(i, red, green, blue);
            strip.show();
            delay(50);
        }
    }

// Rainbow Cycle for LEDs function
    void rainbowCycle(uint8_t wait) {
        int i, j;
        for (j=0; j < 256 * 5; j++) {     // 5 cycles of all 25 colors in the wheel
        for (i=0; i < strip.numPixels(); i++) {
      // tricky math! we use each pixel as a fraction of the full 96-color wheel
      // (thats the i / strip.numPixels() part)
      // Then add in j which makes the colors go around per pixel
      // the % 96 is to make the wheel cycle around
            strip.setPixelColor(i, Wheel( ((i * 256 / strip.numPixels()) + j) % 256) );
            Serial.println(j);
        }  
        strip.show();   // write all the pixels out
        delay(wait);
        }
    }

    // Color sub-function for Rainbow cycle
        uint32_t Color(byte r, byte g, byte b) {
            uint32_t c;
            c = r;
            c <<= 8;
            c |= g;
            c <<= 8;
            c |= b;
            return c;
        }

    // Wheel sub-function for Rainbow Cycle
        uint32_t Wheel(byte WheelPos) {
            if (WheelPos < 85) {
                return Color(WheelPos * 3, 255 - WheelPos * 3, 0); } 
            else if (WheelPos < 170) {
                WheelPos -= 85;
                return Color(255 - WheelPos * 3, 0, WheelPos * 3); } 
            else {
                WheelPos -= 170; 
                return Color(0, WheelPos * 3, 255 - WheelPos * 3);}
        }

And finally, I leave you with a video of the features in action pre-plants:

I’ll post another post with video once plants start growing.

Tagged with →  
  • Mike Winslow

    You’ve always been one to go “all in” when you get fired up for a project!
    I heard that NASA will be contacting you to start building these things for the Mars Mission. Imagine! Martian Red Peppers processed into rocket fuel.

    • All in -> the only way to go! Now if only I could grow Martian Ghost Peppers… how much would you pay for one of those?!?!