Project in progress

Vertical Hydroponic Farm © CC BY-NC-SA

IoT Enabled Hydroponic Farm

  • 30,639 views
  • 14 comments
  • 195 respects

Components and supplies

About this project

We call our project RUFS, for Robotic Urban Farm System

  • A vertical hydroponic garden
    • Allowing for high density yields and shorter growth cycles
    • Reducing resource consumption - water, fertilizer and space
    • Labor saving - no weeds or soil to till
    • Higher consistency of crops with great tasting results

  • Maintained by micro-controllers (Raspberry Pi & Arduino)
    • Watering cycles - monitor and auto refill levels
    • Plant nutrients and pH - monitor and correct
    • Temperature monitoring
    • Air Circulation & Lighting controls (for indoor operation)

  • Smart and Connected
    • Farm Controller App for smartphone, tablet and pc
    • Get notified when the system needs a refill
    • Alerts when something isn't running as expected

  • Year round growing - fresh produce every month of the year
  • Perfect for Urban setting with limited space
  • Reduced operational time and maintenance with automation
  • Eliminate the guesswork of nutrients and watering cycles

Hydroponics is a subset of hydroculture and is a method of growing plants using mineral nutrient solutions, in water, without soil.

The word hydroponics technically means working water, stemming from the Latin words "hydro" meaning water, and "ponos" meaning labor. Many different civilizations from the beginning of time have relied on hydroponics for growing plants, such as the early Mexican and Egyptian civilizations. However, recently growing hydroponically has grown in popularity and use across many different markets.

(from Wikipedia http://en.wikipedia.org/wiki/Hydroponics)

Green and Efficient

Water is recirculated which allows for great efficiency, often more than 90% efficiently on water consumption.

Our vertical design allows for a higher density of plants in a much smaller footprint. With a space less than 5' x 5' we are growing 160 plants.

Automation

The system is maintained with a series of Arduino controllers. Watering cycles, pH levels, nutrient levels, lighting cycles and ventilation fans are all maintained via the Arduino's.

The Arduino's can be networked to a Raspberry Pi using I2C to allow all system parameters to be monitored and updated in real time. The Raspberry Pi is further used to maintain a historical log of all the systems running data and make it available via a set if web services.

A companion smartphone/tablet app interfaces with the Raspberry Pi allowing for configuring and monitoring your entire system from anywhere in the world. The Raspberry Pi communicates with app via a JSON web service.

The steps are outlined here, additional project information including plant suggestions and updates are available on our website at

http://www.bltrobotics.com/

What are we growing?

Vertical hydroponics lends itself very well to leafy green vegetables.I am currently growing successfully lettuces (romaine, boston bib, spring mix) , cabbages (red and green), herbs (basil, cilantro, mint, dill, chives), spinach, kale (dwarf curly variety), broccoli and petunias.

------------

Parts and Pieces (What to buy)

Support Frame

(5) 10' - 2" PVC pipe

(1) 10' - 3" PVC pipe

(4) 2" PVC 90° elbow

(8) 2" PVC tee

(4) 3" to 2" PVC tee

(4) 3" PVC endcaps

Note: All PVC pipe and fittings are Schedule 40 - cellular PVC

Towers/Return

(10) 10' - 2" x 3" PVC downspouts

(1) 10' PVC extruded gutter 4"

(4) PVC gutter end caps (make sure to get 2 left and 2 right)

(2) 1" threaded to 3/4" barbed adapter

(2) 3/4" PVC female threaded connectors

(20) 2" PVC Pipe Hangers ("J" hooks)

(20) #6 Stainless 1 1/2 " machine screws and nuts

Water Supply/Return

(1) 10' - 1"ID potable water tubing

(3) 1" threaded to 3/4" barbed adapter

(1) 1" barbed tee

(5) 3/4" PEX 90° elbow - barbed

(1) 3/4" PEX tee - barbed

(1) 1" to 3/4" PEX reducer - barbed

(2) 10 pack PEX crimp rings

(1) 10 pack 1 1/2" hose clamps

(1) 25' - 3/4" PEX

(20) adjustable 0-10GPH drip emitters

(1) 14 gallon soft plastic bucket

Support Frame (See pictures for steps)

The support frame is constructed from standard Schedule 40 PVC in 2" and 3" OD(outer diameter). The frame was constructed from this material for is modular properties (easy to fit together) and the ease of use for sizing (easy to cut straight with basic tools). This does not preclude the use of other structural material such as wood, plastic and metal as the frame is structural and does not carry water.

Tool note:

PVC pipe for this project is best cut using a mitre saw. These are readily available as an inexpensive hand tool or an electric/power tool. A mitre saw provides good 90° cuts that help add to the stability of the frame.

PVC is glue using a two part glue system.

An Oscillating Multifunction Power Tool was used to cut pockets in vertical towers.

An Electric Heat Gun was used to shape the pockets in the vertical towers.

The support frame is (2) main leg components joined by the (2) "top bar" components and (2) "cross supports".

The length of the "top bar" and "cross supports" dictate the capacity of the towers your system will support.

There are two additional leg extensions made from the 3" PVC that can provide addition support against the elements when used outside. These can be omitted for indoor use.

At 4' we are supporting (10) grow towers spaced at 4.5" on center apart.

Support Frame Steps

Support Top

The top support dictates the size of the grow system and the number of towers the system can support.

Our current plan includes a 4' length of 2" PVC with towers spaced at 4.5" on center. The spacing can be modified to support larger growing areas by increasing spacing between the centers of the towers.

Additional you can decrease the length of pipe to support a smaller number of grow towers.

The support top structure are (2) identically constructed units.

Support Top Cut List

  1. From a 10' section of 2" PVC
    1. Cut (2) sections at 48" - (these are Part #1 - first & second set)

Support Top Assembly Instructions

Dry fit the following:

  1. Fit 2" pipe Part #1 into (2) Part #2 90° elbow

Support Bottom

Like the top support, the bottom cross dictates system size, the piece must be the same width as the support frame top.

Our current plan includes a 4' length.

The support bottom piece are (2) identically 4' sections.

Also included in the bottom of the frame are (2) optional extension legs that are good when the unit is set up outside to provide additional support against wind.

Support Bottom Cut List

  1. From a 10' section of 2" PVC
    1. Cut (2) sections at 48" - (these are Part #1 - first & second set)

  2. From the remaining section of 3" PVC
    1. Cut (4) sections at 12" - (these are Part #2)

Support Top Bottom Instructions

Dry fit the following:

  1. Fit 2" pipe Part #1 into the Part #6 90° tee of the main leg component
    1. Note:You man need an additional person to help hold up the legs when installing bottom cross pieces

  2. Optional: Fit 3" pipe Part #2 into the Part #9 90° tee of the main leg component
  3. Optional: Install cap Part #3 into the 3" pipe Part #2



Grow Towers/Water Return

The towers provide the grow area for the plants in this system.

Or current design supports a very high density of plants by spacing the 3" vertical grow towers 4.5" apart on center (distance from center of tower to neighboring tower), providing approximately 1.5" between towers.

The current 4' length supports (10) towers with (8) slots per tower. With two sides we get (160) plants for our entire system.

There are some limitations to this high density and vertical grow systems to the types of plant you can grow. We are continuing to test the limits of what we can grow and will share our successes and failures in our What to Grow sections of our wiki and user forum.

Towers/Return Overview

The towers are 2" x 3" PVC modified downspouts. The downspouts are generally available in 10' sections.

The availability of 10' sections dictated the 5' height of our design.

The water returns are 4" PVC rain gutters. Also available in 10' sections. They are cut and capped on the ends with gutter caps. Make sure you pick up both left and right versions of the end caps as they are different.

Towers/Return Construction Steps

Part 1: Towers

Towers Cut List

  1. From a 10' sections of 2" x 3" PVC downspouts
    1. Cut (10) sections in half at 60"

Tower Shaping Instructions

We constructed a wood template tool to help form the plant pockets

  1. Divide the (20) towers in to (2) sets of (10).
    1. To improve grow space we offset/stagger the plant slots in the grow towers.

  2. Mark a horizontal line across the front of the first set of (10) towers every 6" starting 6" from the bottom for (8) lines.
    1. A speed square is helpful and getting a straight line across the down spout material.
  3. Mark a horizontal line across the front of the first set of (10) towers every 6" starting 9" from the bottom for (8) lines.
  4. Using a Oscillating Multifunction Power Tool with the straight cut bit, cut a 2" slice on the drawn lines.
  5. Using the Electric Heat Gun soften the plastic for a few seconds (approx. 15-30 seconds depending on wattage of your heat gun) 3" above and 3" below the cut. The PVC will start to pucker or sink and takes on the appearance of wet saggy paper.
  6. Use your wood template to slide into the softened PVC at the cut. Hold in place for approximately 30 seconds.
  7. Repeat for remaining slots in the tower.
  8. Drill a 3/16" hole on the top, back side of the tower to put the screw for the tower hanger.


Water Return

Water Return Cut List

  1. From a 10' sections of 4" PVC gutters
    1. Cut (4) sections at 46"

Water Return Assembly Instructions

  1. Identify the right and left gutter end caps.
  2. Test fit end caps on the sections of gutter.
    1. We roughed up the gutter plastic with some 150 grit sandpaper where the caps overlap the gutter (about 5/8") for a better adhesion.

  3. Spread a liberal bead of silicone PVC adhesive on the inside overlapping edge of the end caps.
  4. Carefully fit the end cap on the gutter making sure glue contacts all around the gutter material.
    1. We used our finger to push excess adhesive around the edge of the gutter and cap to ensure good contact.
  5. Place a piece of masking tape or other easily removable tape on the cap till the recommended dry time.
  6. Repeat for other 3 sides and set aside till dry.
  7. Drill a 1" hole on the back wall of the 4" gutter to insert 1" to 3/4" barbed adapter. This will serve as the connector for the water return to the 14 gallon bucket.
  8. Thread the 1" threaded side of the adapter and thread into the female 1" connector on the inside of the gutter.
  9. Cut a (2) - 12" section of soft 1" vinyl hose for each side of the water return.
  10. Join at the center with the 1" barbed tee.
  11. Cut a section of vinyl tube to length to where you position your 14 gallon water tank.

Water Supply

We choose 3/4" PEX for the main water supply lines for it's potable water safety properties and the ease of installation because we had a PEX crimp installation tool. CPVC would also be an excellent choice if the crimp tool was not available, CPVC is also certified for potable water but requires glue and joint connectors for assembly.

Water Supply/Return Overview

The water supply is laid out so there is a single rise that forks at the center cross piece of the main leg. This design was implemented to reduce the overall height load on the water pump. Instead of (2) 6' rises reducing overall water flow, the rise is slightly more efficient to the pump.

Water Supply/Return Construction Steps

  1. Parts #1 - #6 are 3/4" PEX cut to mirror the main leg dimensions.
    1. These are best sized by taking the measurements of the legs.

  2. Crimp Part #1, Part #2 and Part #3 together with Part #7 - 3/4" barbed PEX tee.
  3. Crimp Part #3 and Part #4 together with Part #8 - 3/4" PEX 90° elbow.
  4. Crimp Part #4 and Part #5 together with Part #9 - 3/4" PEX 90° elbow.
  5. Crimp Part #2 and Part #6 together with Part #10 - 3/4" PEX 90° elbow.
  6. Drill holes in Part #5 and Part #6 for Part #11 - drip emitters.
    1. Starting at elbow end of Part #5/Part #6 - measure from end 4" and mark first hole at the bottom of the PEX pipe.
      1. It is helpful to draw a line along the bottom of the PEX pipe marking the bottom for the remaining marks.
    2. Measure 4.5" from first mark and make a second mark.
    3. Repeat every 4.5", the last mark should be 4" from the opposing leg from the elbow.
    4. Using 3/16 drill bit drill on the mark.
  7. Fit in the the drip emitters - Part #11
    1. The hole is slightly snug for the drip emitters to prevent unwanted dripping, soften the PEX briefly (30 sec.) with the heat gun, this will allow for easier insertion of the drip emitters into the PEX.
  8. Cut a 1" hole in the 14 gallon water bucket.
  9. Thread the 1" to 3/4" barbed adapter through the wall of the water container.
  10. Attach the soft vinyl hose from the return gutters tee to the water bucket.

Electronics

We broke the functionality of the controllers up into separate components.

ClimateBot

Role: Environmental controls for indoor operation.

Used to maintain light cycles based on schedule of on/off intervals and a thermostatically and/or schedule based control of a circulation fan.

Hardware Components

(1) Arduino

(1) 2 Channel Relay Module Board and Shield For Arduino (source: eBay)

(1) Circulation Fan sized to your space

(1) 15 Meter - LED based - 12v Flexible 5050 5:1 Red/Blue (source: eBay)

(1) Waterproof DS18B20 Digital Temperature Sensor Probe for Arduino (source:eBay)

pHarmBot

Role: Water quality control

Used to maintain pH level maintenance and nutrient level maintenance.

Hardware Components

(1) Arduino

(1) 2 Channel Relay Module Board and Shield For Arduino

(1) Analog pH Meter Kit (source: RobotMesh.com)

(1) Arduino Conductivity Sensor (source: eBay)

(1) Waterproof DS18B20 Digital Temperature Sensor Probe for Arduino (source:eBay)

(2) 12V DC Peristaltic Dosing Pump (source: Amazon)

hydroBot

Role: Watering cycles based on schedule of on/off intervals and water reservoir level maintenance

(1) Arduino

(1) 2 Channel Relay Module Board and Shield For Arduino

(2) Side Mounted Water Level Control Float Switch Normal Closed (source:eBay)

(1) 1" Water Flow Meter Counter 1-60L/min (source: Amazon)

(1) 1/2" DC 12V Electric Solenoid Valve Water Inlet Flow Switch Normally Closed (source: eBay)

(1) 620 GPH Submersible Pump (source: Harbor Freight)

Arduino Resources (Sketches and Links):

DS18B20 Digital Temperature Sensor Probe - See attached document (OneWire.txt)

http://playground.arduino.cc/Learning/OneWire

Arduino Conductivity Sensor - See attached document (EC_Sensor.txt)

Analog pH Meter - See attached document (ph_Meter.txt)

analog-ph-meter-kit

Custom parts and enclosures

Schematics

ClimateBot
A1
pHarmBot
A2
hydroBot
A3

Code

farmBot.hC/C++
struct DEBOUNCE {
	int pin;					// number of input pin
	int pinState;      			// the debounced value of input pin
	int pinOldState;				// previous stable state of input pin
	int pinStable;				// HIGH if pinState is stable
	long lastDebounceTime;		// last Reading time
};

typedef DEBOUNCE DEBOUNCE_DEF;
pHarmBot.inoC/C++
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>

/* I2C interface requirements */
#include <Wire.h>

#include <farmBot.h>

/* allowcation of 1024 EEPROM  */
const byte I2CID = 0;  

int number = 0;
int state = 0;
double temp;
/* end I2C interface requirements */

// Pin assignment
const int flowMeterPin = 2;       // water flow meter pin in serial
const int pumpPin = 13;           // circulation pump pin out
const int tankLowPin = 7;         // water low sensor pin in   HIGH means water is Low
const int tankHighPin = 8;        // water high sensor pin in  LOW means water is Full
const int waterFillPin = 6;       // water  fill valve pin out
const int ONE_WIRE_BUS = 3;       // water temperature pin in
const int phDoserPin = 12;
const int ecDoserPin = 4;
const int supDoserPin = 9;
const int phDoserBtnPin = 10;

const int NORMAL = 1;             // processing state
const int FILLTANK = 2;           // processing state
const int FLOWSAMPLERATE = 1000;  // 1 second
const int OFF = 0;                // device mode
const int ON = 1;                 // device mode
const int AUTO = 3;               // device mode

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// Assign the addresses of your 1-Wire temp sensors.
// See the tutorial on how to obtain these addresses:
// http://www.hacktronics.com/Tutorials/arduino-1-wire-address-finder.html

DeviceAddress waterThermometer = { 0x28, 0xFF, 0xA7, 0xF2, 0x05, 0x00, 0x00, 0x69 };
//DeviceAddress outsideThermometer = { 0x28, 0x6B, 0xDF, 0xDF, 0x02, 0x00, 0x00, 0xC0 };
//DeviceAddress dogHouseThermometer = { 0x28, 0x59, 0xBE, 0xDF, 0x02, 0x00, 0x00, 0x9F };

volatile int flowMeterCnt = 10;        // measuring the rising edges of the signal
int systemMode = NORMAL;          // processing state (NORMAL, FILLTANK)
int pumpMode = AUTO;              // pump mode
int pumpState = LOW;              // pump current state
int pumpOnTime = 1;              // minutes that pump is ON
int pumpOffTime = 2;             // minutes that pump is OFF
unsigned long pumpTimer = 0;      // clock time when pump state should toggle

byte waterFillMode = AUTO;         // tank fill valve mode
byte waterFillState = LOW;         // tank fill valve current state
unsigned long fillInterval  = 60000;   // time since last fill one minute

byte waterTempMSB = 80;            // thermometer integer full units ie. 93
byte waterTempLSB = 2;            // thermometer rounded to one decimal place to the right ie. .2 so together 93.2
byte waterTempTarget = 87;
byte waterTempDelta = 5;
int waterTempSampleRate = 3;      // seconds between samples
unsigned long waterTempTimer = 0;

unsigned long flowTimer = 0;      // clock time when flowMeter should be sampled

int phDoserState = LOW;           // off
unsigned long phDoserTimer = 0;
byte phDoseMl = 5;                // number of ml to dose each time

byte cmdRecieved = 0;
int cmdCnt;
int command;

DEBOUNCE_DEF tankLowSensor = { tankLowPin , LOW, LOW, LOW, 0 };       // HIGH = tank empty 
DEBOUNCE_DEF tankHighSensor = { tankHighPin , LOW, LOW, LOW, 0 };     // HIGH = tank not full
DEBOUNCE_DEF phDoserBtn = { phDoserBtnPin , LOW, LOW, LOW, 0 };           // LOW = button pushed

void setup() {
  Serial.begin(9600);
  
  pinMode(pumpPin, OUTPUT);     
  digitalWrite(pumpPin, pumpState);      // set initial pump state 
  
  pinMode(waterFillPin, OUTPUT);   
  digitalWrite(waterFillPin, waterFillState); // set initial water fill value state 

  pinMode(tankLowPin, INPUT);
  digitalWrite(tankLowPin, HIGH);            // turn on pullup resistors; open switch state is HIGH
  
  pinMode(tankHighPin, INPUT);
  digitalWrite(tankHighPin, HIGH);           // turn on pullup resistors; open switch state is HIGH

  pinMode(tankHighPin, INPUT);
  digitalWrite(tankHighPin, HIGH);           // turn on pullup resistors; open switch state is HIGH

  pinMode(phDoserPin, OUTPUT);     
  digitalWrite(phDoserPin, phDoserState);      // set initial pump state 

  i2cSetup();
  oneWireSetup();
//  flowSetup();

} // setup

void loop() {
 
  debouncePin(tankLowSensor); // read tankLow switch  
  debouncePin(tankHighSensor); // read tankHigh switch
  debouncePin(phDoserBtn); // read tankHigh switch
  
  switch (systemMode) {
     case NORMAL: normalMode(); break;
     case FILLTANK: fillTankMode(); break;
     default: normalMode(); break;
  }  
  
} // loop

void normalMode()
{
  if (pumpMode == AUTO) pumpControl();
  if (phDoserState == HIGH || phDoseMl > 0) checkPhDoser();
/*
  if (phDoseMl == 0 && phDoserBtn.pinStable == HIGH && phDoserBtn.pinState == LOW)    // pushed manual dose
  {
    phDoseMl = 3;
    checkPhDoser();
  }
*/ 
} // normalMode

void checkPhDoser()
{
  if (phDoseMl > 0)
  {
    phDoserTimer = setSecondsTimer(phDoseMl);  // one milli liter per second
    phDoserState = HIGH;
    digitalWrite(phDoserPin, phDoserState);  
    Serial.print("Doser is ");
    Serial.print(phDoserState);
    Serial.print(" for ");
    Serial.println(phDoseMl);
    phDoseMl = 0;
  }
  if (checkTimer(phDoserTimer) == HIGH) {          // timer went off
    phDoserState = LOW;                            // turn doser off    
    digitalWrite(phDoserPin, phDoserState);  
    Serial.println("Doser Off");
  }
}

void checkWaterTemp()
{
  if (checkTimer(waterTempTimer) == HIGH) {
    getWaterTemp();
    waterTempTimer = setSecondsTimer(waterTempSampleRate);   
//    Serial.print("sample temp \n");
  }
} //checkWaterTemp

void pumpControl()
{
  if (tankLowSensor.pinStable == HIGH && tankLowSensor.pinState == HIGH) { // low water; turn off pump
    systemMode = FILLTANK;
    pumpState = LOW;
    digitalWrite(pumpPin, pumpState);  
    pumpTimer = millis();
  } else { // water in tank    
      if (checkTimer(pumpTimer) == HIGH) {
        pumpState = !pumpState;
        if (pumpState == HIGH) Serial.println("Pump On");
        if (pumpState == LOW) Serial.println("Pump Off");
        digitalWrite(pumpPin, pumpState);
        if (pumpState == HIGH) {
            pumpTimer = setMinutesTimer(pumpOnTime);
         } else {
            pumpTimer = setMinutesTimer(pumpOffTime);
         }
      }     
    }  
}  // pumpControl

void fillTankMode()
{
  if (tankHighSensor.pinStable == HIGH && tankHighSensor.pinState == LOW) {
    systemMode = NORMAL;
    Serial.print("tank full\n");
    waterFillState = LOW;
    digitalWrite(waterFillPin, waterFillState); //  turn off water fill valve
  } else if (tankLowSensor.pinStable == HIGH && tankLowSensor.pinState == HIGH ) {
    waterFillState = HIGH;
    digitalWrite(waterFillPin, waterFillState); //  turn on water fill valve
    Serial.print("Water low\n");
  } else {
      Serial.print("Filling tank\n");
  }
} // fillTankMode

unsigned long setMinutesTimer(int waitTime)
{ 
  unsigned long endTime;

   endTime = millis() + (waitTime * 60000);  // convert back to milliseconds from minutes
   return endTime;
} // setMinutesTimer

unsigned long setSecondsTimer(int waitTime)
{ 
  unsigned long endTime;

   endTime = millis() + (waitTime * 1000);  // convert back to milliseconds from seconds
   return endTime;
} // setSecondsTimer

int checkTimer(unsigned long timer)
{
   if (millis() > timer) {return HIGH;}
   else {return LOW;}
} // checkTimer

void debouncePin(DEBOUNCE_DEF& target) {
  long debounceDelay = 50;    // the debounce time; increase if the output flickers

   // read the state of the pin into a local variable:
  int reading = digitalRead(target.pin);

  // check to see if pin state has changed and you've waited
  // long enough since the last test to ignore any noise:  
  if (reading != target.pinState) {
    // reset the debouncing timer
    target.lastDebounceTime = millis();
    target.pinStable = LOW;
        if (target.pinOldState == LOW) {target.pinOldState = HIGH;} else {target.pinOldState = LOW;}
 } 
  
  if ((millis() - target.lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:
	  target.pinStable = HIGH;
   }
  target.pinState = reading;
} // debouncePin

/*
  Flow meter functions
*/

void flowMeterRpm ()     //This is the function that the interupt calls
{
  flowMeterCnt++;  //This function measures the rising and falling edge of the hall effect sensors signal
} // flowMeterRpm

void flowSetup() //
{
  pinMode(flowMeterPin, INPUT); //initializes digital pin as an input
  attachInterrupt(0, flowMeterRpm, RISING); //and the interrupt is attached
} // flowSetup

void flowMeter()
{
// reading liquid flow rate using Seeeduino and Water Flow Sensor from Seeedstudio.com
// Code adapted by Charles Gantt from PC Fan RPM code written by Crenn @thebestcasescenario.com
// http:/themakersworkbench.com http://thebestcasescenario.com http://seeedstudio.com

int Calc;                              

  if (checkTimer(flowTimer) == HIGH) {// Wait 1 second
    cli();                            // Disable interrupts
    Calc = (flowMeterCnt * 60 / 7.5); // (Pulse frequency x 60) / 7.5Q, = flow rate in L/hour
    Serial.print (Calc, DEC);         // Prints the number calculated above
    Serial.print (" L/hour\r\n");     // Prints "L/hour" and returns a  new line

    setSecondsTimer(FLOWSAMPLERATE);
    flowMeterCnt = 0;                 // Set NbTops to 0 ready for calculations
    sei();                            // Enables interrupts
  }
  
} // flowMeter

/*
  End Flow Meter Functions
*/

/* IC2 support */


void i2cSetup() {
  EEPROM.write(I2CID, 0x8);
  // set default i2c ID if not yet defined
 // if (EEPROM.read(I2CID)==0) { EEPROM.write(I2CID, 0x4); }
 
 // initialize i2c as slave
 Wire.begin(8);
 
 // define callbacks for i2c communication
 Wire.onReceive(i2cCommand);
 Wire.onRequest(myRequest);
} // i2cSetup

byte i2cResponse[32];
int index = 0;

void myRequest() {    // callback for sending data
  Wire.write(i2cResponse[index]);
  ++index;
}
  
void i2cCommand(int byteCount){ // callback for received data
  unsigned char received;
//  int cmdCnt;
//  int command;
  int output;
 
  cmdCnt = Wire.available();
  if (cmdCnt > 0 ) {
    cmdRecieved++;
    command = Wire.read();      // first byte is the command
    switch (command)
    {
      case 1:    // set Pump Mode (Off =0, On=1, Auto=3)
        if (cmdCnt == 2 ) {     // next byte is the mode
          pumpMode = Wire.read();      
          switch (pumpMode) {
            case ON: if (tankLowSensor.pinStable == HIGH && tankLowSensor.pinState == LOW) {pumpState = HIGH; } break;
            case OFF: pumpState = LOW; break;
           }
           digitalWrite(pumpPin, pumpState);
           Serial.print("Pump state = ");
           Serial.print(pumpState);
           Serial.print("\n");
        }
        break;
      
      case 2:    // set Pump auto ON time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);  // shift to high byte
          received = Wire.read();
          output = output + received; // add in low byte
          pumpOnTime = output;      // ON time in seconds
  Serial.print("Pump auto ON time = ");
  Serial.println(pumpOnTime);
        }
        break;
      
      case 3:    // set Pump auto OFF time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);  // shift to high byte
          received = Wire.read();
          output = output + received; // add in low byte
          pumpOnTime = output;      // OFF time in seconds
  Serial.print("Pump auto OFF time = ");
  Serial.println(pumpOffTime);
       }
        break;
        
      case 4:    // set water Fill Value Mode (Off, On, Auto)
        if (cmdCnt == 2 ) {    // next byte is the mode
          waterFillMode = Wire.read();    
          switch (waterFillMode) {
            case ON: if (tankHighSensor.pinStable == HIGH && tankHighSensor.pinState == HIGH) {waterFillState = HIGH; } break; // only on if not full
            case OFF: waterFillState = LOW;  break;
            case AUTO: if (tankHighSensor.pinStable == HIGH && tankHighSensor.pinState == LOW) {waterFillState = LOW; } break; // turn off if full
          }
          digitalWrite(waterFillPin, waterFillState);
  Serial.print("water Fill Mode = ");
  Serial.println(waterFillMode);
        }
         break;

      case 5:    // turn on phDoser for x seconds
        if (cmdCnt == 2 ) {    // next byte is the number of seconds
          phDoseMl = Wire.read();
          digitalWrite(phDoserPin, HIGH);  
        }
        break;

      case 6:    // turn on phDoser 
        digitalWrite(phDoserPin, HIGH);  
        break;

      case 7:    // turn off phDoser
        digitalWrite(phDoserPin, LOW);  
        break;
              
      case 8:
          i2cRequest();
          index = 0;
          break;
      
       default:
         Serial.print("Command not supported ");
         Serial.println(command);
         break;
   } // end switch 
  } // end if cmdCnt
  
} // ic2Command
 

void i2cRequest() {    // callback for sending data
  byte i2cResponseLen = 0;                 // response length
  unsigned long currentTime = millis();
  unsigned long transitionTime = 0;
  byte pumpStatus = 0;
  byte waterLevel = 0;
  byte refillStatus = 0;
  byte waterTempSettings = 0;
 
    pumpStatus = (pumpMode << 6) | (pumpState << 5);
    if (tankHighSensor.pinState == LOW) { waterLevel = 2; } // Full
    else if (tankLowSensor.pinState == HIGH) { waterLevel = 0;} // Low
    else { waterLevel = 1;}  // Adequate
    waterLevel = waterLevel << 3;
    refillStatus = (waterFillMode << 1) | waterFillState;
    
    i2cResponse[i2cResponseLen] = pumpStatus | waterLevel | refillStatus; 
    i2cResponseLen++;  
   
    if (currentTime < pumpTimer) {transitionTime = pumpTimer - currentTime;} else {transitionTime = 0;}
    transitionTime = transitionTime / 60000;  // convert to minutes
    i2cResponse[i2cResponseLen] = (byte)(transitionTime >> 8); // high byte
    i2cResponseLen++;
    
    i2cResponse[i2cResponseLen] = (byte)transitionTime; // low byte
    i2cResponseLen++;
    
    i2cResponse[i2cResponseLen] = (byte)(pumpOnTime >> 8); // high byte in minutes
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)pumpOnTime; // low byte
    i2cResponseLen++;
    
    i2cResponse[i2cResponseLen] = (byte)(pumpOffTime >> 8); // high byte in minutes
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)pumpOffTime; // low byte
    i2cResponseLen++;
        
    fillInterval = fillInterval / 60000;             // convert to minutes
    i2cResponse[i2cResponseLen] = (byte)(fillInterval >> 8);  // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)fillInterval;  // low byte
    i2cResponseLen++;

    i2cResponse[i2cResponseLen] = (byte)(flowMeterCnt >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)flowMeterCnt; // low byte
    i2cResponseLen++;  

    i2cResponse[i2cResponseLen] = (byte)(waterTempMSB);  // left of decimal byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)(waterTempLSB);  // right if decimal byte
    i2cResponseLen++;
    
    i2cResponse[i2cResponseLen] = waterTempTarget;  // byte
    i2cResponseLen++;  

    i2cResponse[i2cResponseLen] = waterTempDelta;  // byte
    i2cResponseLen++;    // compensate for zero based array 24
 /* 
  Wire.write(i2cResponse, i2cResponseLen);
  */
} // i2cRequest
 
// end I2C support
// git clone http://github.com/jrowberg/i2cdevlib


// Reads DS18B20 "1-Wire" digital temperature sensors.
// Tutorial: http://www.hacktronics.com/Tutorials/arduino-1-wire-tutorial.html

void oneWireSetup()
{
  // Start up the library
  sensors.begin();
  sensors.setResolution(waterThermometer, 10); // set the resolution to 10 bit (good enough?)
//  sensors.setResolution(outsideThermometer, 10);
//  sensors.setResolution(dogHouseThermometer, 10);
} // oneWireSetup

float getTemperature(DeviceAddress deviceAddress)
{
  float tempF = 0;
  float tempC = sensors.getTempC(deviceAddress);
  if (tempC == -127.00) {
    Serial.print("Error getting temperature");
  } else {
    tempF = DallasTemperature::toFahrenheit(tempC);
    Serial.print("C: ");
    Serial.print(tempC);
    Serial.print(" F: ");
    Serial.print(DallasTemperature::toFahrenheit(tempC));
    Serial.print("\n");;
  }
  return tempF;
}

void getWaterTemp()
{ 
  float tempF = 0;
  double leftPart;
  double rightPart;
  double newRight;
  sensors.requestTemperatures();
  tempF = getTemperature(waterThermometer);
 
  rightPart = modf(tempF, &leftPart);
  waterTempMSB = (byte)leftPart;
  newRight = rightPart*10;
  rightPart = modf(newRight, &leftPart);
  waterTempLSB = (byte)leftPart;
}  // getWaterTemp
// end DS18B20 support
climateBot.inoC/C++
#include <OneWire.h>
#include <DallasTemperature.h>
#include <farmBot.h>
#include <EEPROM.h>

/* I2C interface requirements */
#include <Wire.h>
 
/* allowcation of 1024 EEPROM  */
const byte I2CID = 0;  

int number = 0;
int state = 0;
double temp;
/* end I2C interface requirements */

// Pin assignment
const int ONE_WIRE_BUS = 3;        // air temperature pin in
const int lightsPin = 4;          // lights pin out
const int circulationFanPin = 13;  // circulation fan pin out

const int NORMAL = 1;             // processing state
const int SPECIAL = 2;           // processing state
const int OFF = 0;                // device mode
const int ON = 1;                 // device mode
const int AUTO = 3;               // device mode

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// Assign the addresses of your 1-Wire temp sensors.
// See the tutorial on how to obtain these addresses:
// http://www.hacktronics.com/Tutorials/arduino-1-wire-address-finder.html

DeviceAddress airThermometer = { 0x28, 0xFF, 0xA7, 0xF2, 0x05, 0x00, 0x00, 0x69 };
//DeviceAddress outsideThermometer = { 0x28, 0x6B, 0xDF, 0xDF, 0x02, 0x00, 0x00, 0xC0 };
//DeviceAddress dogHouseThermometer = { 0x28, 0x59, 0xBE, 0xDF, 0x02, 0x00, 0x00, 0x9F };

int systemMode = NORMAL;          // processing state (NORMAL, FILLTANK)

int lightsMode = AUTO;            // lights mode
int lightsState = LOW;            // lights current state
int lightsOnTime = 3;          // minutes that lights are ON
int lightsOffTime = 2;         // minutes that lights are OFF
unsigned long lightsTimer = 0;    // clock time when lights state should toggle

int circulationFanMode = AUTO;               // circulation fan mode
int circulationFanState = LOW;               // circulation fan current state
int circulationFanOnTime = 1;             // minutes that fan is ON
int circulationFanOffTime = 2;            // minutes that fan is OFF
unsigned long circulationFanTimer = 5000;       // clock time when fan state should toggle

byte airTempMSB = 83;            // thermometer integer full units ie. 93
byte airTempLSB = 2;            // thermometer rounded to one decimal place to the right ie. .2 so together 93.2
byte airTempTarget = 78;
byte airTempDelta = 3;
int airTempSampleRate = 3;      // seconds between samples
unsigned long airTempTimer = 0;

byte cmdRecieved = 0;
int cmdCnt;
int command;

void setup() {
  Serial.begin(9600);
  
  pinMode(lightsPin, OUTPUT);   
  digitalWrite(lightsPin, lightsState);      // set initial lights state 
  
  pinMode(circulationFanPin, OUTPUT);   
  digitalWrite(circulationFanPin, circulationFanState);            // set initial fan state 

  i2cSetup();
  oneWireSetup();
//  flowSetup();

} // setup

void loop() {
 
  switch (systemMode) {
     case NORMAL: normalMode(); break;
     case SPECIAL: specialMode(); break;
     default: normalMode(); break;
  }  
  
} // loop

void normalMode()
{
  if (circulationFanMode == AUTO) circulationFanControl();
  if (lightsMode == AUTO) lightsControl();
  airTemp();
} // normalMode

void airTemp()
{
  if (checkTimer(airTempTimer) == HIGH) {
    getAirTemp();
    airTempTimer = setSecondsTimer(airTempSampleRate);    // change value to mean seconds
//    Serial.print("sample temp \n");
  }
}  // airTemp

void circulationFanControl()
{
  if (airTempMSB >= airTempTarget)  // turn fan on
  { 
    circulationFanState = HIGH;
  }
  if (airTempMSB < airTempTarget - airTempDelta)  // turn fan off
  { 
    circulationFanState = LOW;
  }
  digitalWrite(circulationFanPin, circulationFanState);  
}  // circulationFanControl

void lightsControl()
{
  if (checkTimer(lightsTimer) == HIGH) {
    lightsState = !lightsState;
    digitalWrite(lightsPin, lightsState);
    if (lightsState == HIGH) {
      lightsTimer = setMinutesTimer(lightsOnTime);
    } else {
     lightsTimer = setMinutesTimer(lightsOffTime);
    }
  }
} // end lightsControl

void specialMode()
{
  systemMode = NORMAL;
} // specialMode

unsigned long setMinutesTimer(int waitTime)
{ 
  unsigned long endTime;

   endTime = millis() + (waitTime * 60000);  // convert back to milliseconds from minutes
   return endTime;
} // setMinutesTimer

unsigned long setSecondsTimer(int waitTime)
{ 
  unsigned long endTime;

   endTime = millis() + (waitTime * 1000);  // convert back to milliseconds from seconds
   return endTime;
} // setSecondsTimer

int checkTimer(unsigned long timer)
{
   if (millis() > timer) {return HIGH;}
   else {return LOW;}
} // checkTimer

void debouncePin(DEBOUNCE_DEF& target) {
  long debounceDelay = 50;    // the debounce time; increase if the output flickers

   // read the state of the pin into a local variable:
  int reading = digitalRead(target.pin);

  // check to see if pin state has changed and you've waited
  // long enough since the last test to ignore any noise:  
  if (reading != target.pinState) {
    // reset the debouncing timer
    target.lastDebounceTime = millis();
    target.pinStable = LOW;
        if (target.pinOldState == LOW) {target.pinOldState = HIGH;} else {target.pinOldState = LOW;}
 } 
  
  if ((millis() - target.lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:
	  target.pinStable = HIGH;
   }
  target.pinState = reading;
} // debouncePin

/* IC2 support */

void i2cSetup() {
  EEPROM.write(I2CID, 0x06);
  // set default i2c ID if not yet defined
 // if (EEPROM.read(I2CID)==0) { EEPROM.write(I2CID, 0x4); }
 
 // initialize i2c as slave
 Wire.begin(EEPROM.read(I2CID));
 
 // define callbacks for i2c communication
 Wire.onReceive(i2cCommand);
 Wire.onRequest(myRequest);
} // i2cSetup

byte i2cResponse[32];
int index = 0;

void myRequest() {    // callback for sending data
  Wire.write(i2cResponse[index]);
  ++index;
}
  
void i2cCommand(int byteCount){ // callback for received data
  unsigned char received;
//  int cmdCnt;
//  int command;
  int output;
 
  cmdCnt = Wire.available();
  if (cmdCnt > 0 ) {
    cmdRecieved++;
    command = Wire.read();      // first byte is the command
    switch (command)
    {              
      case 1:    // set Lights Mode (Off, On, Auto)
        if (cmdCnt == 2 ) {    // next byte is the mode
          lightsMode = Wire.read();      
          switch (lightsMode) {
            case ON: lightsState = HIGH; break;
            case OFF: lightsState = LOW; break;
          }
          digitalWrite(lightsPin, lightsState);
           Serial.print("Lights state = ");
           Serial.println(lightsState);
       }
        break;
        
      case 2:    // set Lights auto ON time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);  // shift to high byte
          received = Wire.read();
          output = output + received; // add in low byte
          lightsOnTime = output;      // ON time in seconds
        }
         break;
        
      case 3:    // set Lights auto OFF time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);    // shift to high byte
          received = Wire.read();
          output = output + received;  // add in low byte
          lightsOffTime = output;      // OFF time in seconds
        }
        break;
        
      case 4:    // set Circulation Fan (Off, On, Auto)
        if (cmdCnt == 2 ) {    // next byte is the mode
          circulationFanMode = Wire.read();      
          switch (circulationFanMode) {
            case ON: circulationFanState = HIGH; digitalWrite(circulationFanPin, circulationFanState); break;
            case OFF: circulationFanState = LOW; digitalWrite(circulationFanPin, circulationFanState); break;
          }
        }
        break;
           
      case 5:    // set Circulation Fan auto ON time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);    // shift to high byte
          received = Wire.read();
          output = output + received;  // add in low byte
          circulationFanOnTime = output;          // ON time in seconds
        }
        break;
      
      case 6:    // set Circulation Fan auto OFF time
       if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);    // shift to high byte
          received = Wire.read();
          output = output + received;  // add in low byte
          circulationFanOffTime = output;         // OFF time in seconds
        }
        break;

      case 8:
          i2cRequest();
          index = 0;
          break;
             
       default:
         Serial.print("Unhandled command # ");
         Serial.print(command);
         Serial.print("\n");;
         break;
   } // end switch 
  } // end if cmdCnt
  
} // ic2Command
 

void i2cRequest() {    // callback for sending data
  byte i2cResponseLen = 0;                 // response length
  unsigned long currentTime = millis();
  unsigned long transitionTime = 0;
  byte lightsStatus = 0;
  byte circulationFanStatus = 0;
    
    i2cResponse[i2cResponseLen] = (byte)(airTempMSB);  // left of decimal byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)(airTempLSB);  // right if decimal byte
    i2cResponseLen++;
    
    i2cResponse[i2cResponseLen] = airTempTarget;  // byte
    i2cResponseLen++;  

    i2cResponse[i2cResponseLen] = airTempDelta;  // byte
    i2cResponseLen++;  

    lightsStatus = (lightsMode << 6) | (lightsState << 5 );
    circulationFanStatus = (circulationFanMode << 3) | circulationFanState << 2;
    i2cResponse[i2cResponseLen] = lightsStatus | circulationFanStatus;      //  lights, circulation fan mode and status
    i2cResponseLen++;

    if (currentTime < lightsTimer) {transitionTime = lightsTimer - currentTime;} else {transitionTime = 0;}
    transitionTime = transitionTime / 60000;  // convert to minutes
    i2cResponse[i2cResponseLen] = (byte)(transitionTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)transitionTime; // low byte
    i2cResponseLen++;
    
    i2cResponse[i2cResponseLen] = (byte)(lightsOnTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)lightsOnTime; // low byte
    i2cResponseLen++;

    i2cResponse[i2cResponseLen] = (byte)(lightsOffTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)lightsOffTime; // low byte 17
    i2cResponseLen++;
    
    if (currentTime < circulationFanTimer) {transitionTime = circulationFanTimer - currentTime;} else {transitionTime = 0;}
    transitionTime = transitionTime / 60000;  // convert to minutes
    i2cResponse[i2cResponseLen] = (byte)(transitionTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)transitionTime; // low byte
    i2cResponseLen++;

    i2cResponse[i2cResponseLen] = (byte)(circulationFanOnTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)circulationFanOnTime; // low byte
    i2cResponseLen++;
    
    i2cResponse[i2cResponseLen] = (byte)(circulationFanOffTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)circulationFanOffTime; // low byte 22
    i2cResponseLen++;    // compensate for zero based array 24
  
} // i2cRequest
 
double GetChipTemp(void)
{  // Get the internal temperature of the arduino
  unsigned int wADC;
  double t;
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN); // enable the ADC
  delay(20); // wait for voltages to become stable.
  ADCSRA |= _BV(ADSC); // Start the ADC
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t);
} // GetChipTemp

// end I2C support
// git clone http://github.com/jrowberg/i2cdevlib

// Reads DS18B20 "1-Wire" digital temperature sensors.
// Tutorial: http://www.hacktronics.com/Tutorials/arduino-1-wire-tutorial.html

void oneWireSetup()
{
  // Start up the library
  sensors.begin();
  sensors.setResolution(airThermometer, 10); // set the resolution to 10 bit (good enough?)
//  sensors.setResolution(outsideThermometer, 10);
//  sensors.setResolution(dogHouseThermometer, 10);
} // oneWireSetup

float getTemperature(DeviceAddress deviceAddress)
{
  float tempF = 70;
  float tempC = sensors.getTempC(deviceAddress);
  if (tempC == -127.00) {
 //   Serial.print("Error getting temperature");
  } else {
    tempF = DallasTemperature::toFahrenheit(tempC);
//    Serial.print("C: ");
//    Serial.print(tempC);
//    Serial.print(" F: ");
//    Serial.print(DallasTemperature::toFahrenheit(tempC));
//    Serial.print("\n");;
  }
  return tempF;
}

void getAirTemp()
{ 
  float tempF = 0;
  double leftPart;
  double rightPart;
  double newRight;
  sensors.requestTemperatures();
  tempF = getTemperature(airThermometer);
 
  rightPart = modf(tempF, &leftPart);
  airTempMSB = (byte)leftPart;
  newRight = rightPart*10;
  rightPart = modf(newRight, &leftPart);
  airTempLSB = (byte)leftPart;
}
// end DS18B20 support
chembot.inoC/C++
#include <DallasTemperature.h>



#include <farmBot.h>

#include <OneWire.h>


#include <EEPROM.h>

/* I2C interface requirements */
#include <Wire.h>
 
/* allowcation of 1024 EEPROM  */
const byte I2CID = 0;  

int number = 0;
int state = 0;
double temp;
/* end I2C interface requirements */

// Pin assignment
const int ONE_WIRE_BUS = 3;        // air temperature pin in
const int lightsPin = 4;          // lights pin out
const int circulationFanPin = 13;  // circulation fan pin out

const int NORMAL = 1;             // processing state
const int SPECIAL = 2;           // processing state
const int OFF = 0;                // device mode
const int ON = 1;                 // device mode
const int AUTO = 3;               // device mode

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// Assign the addresses of your 1-Wire temp sensors.
// See the tutorial on how to obtain these addresses:
// http://www.hacktronics.com/Tutorials/arduino-1-wire-address-finder.html

DeviceAddress airThermometer = { 0x28, 0xFF, 0xA7, 0xF2, 0x05, 0x00, 0x00, 0x69 };
//DeviceAddress outsideThermometer = { 0x28, 0x6B, 0xDF, 0xDF, 0x02, 0x00, 0x00, 0xC0 };
//DeviceAddress dogHouseThermometer = { 0x28, 0x59, 0xBE, 0xDF, 0x02, 0x00, 0x00, 0x9F };

int systemMode = NORMAL;          // processing state (NORMAL, FILLTANK)

int lightsMode = AUTO;            // lights mode
int lightsState = LOW;            // lights current state
int lightsOnTime = 3;          // minutes that lights are ON
int lightsOffTime = 2;         // minutes that lights are OFF
unsigned long lightsTimer = 0;    // clock time when lights state should toggle

int circulationFanMode = AUTO;               // circulation fan mode
int circulationFanState = LOW;               // circulation fan current state
int circulationFanOnTime = 1;             // minutes that fan is ON
int circulationFanOffTime = 2;            // minutes that fan is OFF
unsigned long circulationFanTimer = 5000;       // clock time when fan state should toggle

byte airTempMSB = 80;            // thermometer integer full units ie. 93
byte airTempLSB = 2;            // thermometer rounded to one decimal place to the right ie. .2 so together 93.2
byte airTempTarget = 82;
byte airTempDelta = 5;
int airTempSampleRate = 3;      // seconds between samples
unsigned long airTempTimer = 0;

byte cmdRecieved = 0;
int cmdCnt;
int command;

void setup() {
  Serial.begin(9600);
  
  pinMode(lightsPin, OUTPUT);   
  digitalWrite(lightsPin, lightsState);      // set initial lights state 
  
  pinMode(circulationFanPin, OUTPUT);   
  digitalWrite(circulationFanPin, circulationFanState);            // set initial fan state 

  i2cSetup();
  oneWireSetup();
//  flowSetup();

} // setup

void loop() {
 
  switch (systemMode) {
     case NORMAL: normalMode(); break;
     case SPECIAL: specialMode(); break;
     default: normalMode(); break;
  }  
  
} // loop

void normalMode()
{
  if (circulationFanMode == AUTO) circulationFanControl();
  if (lightsMode == AUTO) lightsControl();
 
} // normalMode

void circulationFanControl()
{
  if (checkTimer(airTempTimer) == HIGH) {
    getAirTemp();
    airTempTimer = setSecondsTimer(airTempSampleRate);    // change value to mean seconds
//    Serial.print("sample temp \n");
  }

  if (airTempMSB >= airTempTarget)  // turn fan on
  { 
    circulationFanState = HIGH;
  }
  if (airTempMSB < airTempTarget - airTempDelta)  // turn fan off
  { 
    circulationFanState = LOW;
  }
  digitalWrite(circulationFanPin, circulationFanState);  
}  // circulationFanControl

void lightsControl()
{
  if (checkTimer(lightsTimer) == HIGH) {
    lightsState = !lightsState;
    digitalWrite(lightsPin, lightsState);
    if (lightsState == HIGH) {
      lightsTimer = setMinutesTimer(lightsOnTime);
    } else {
     lightsTimer = setMinutesTimer(lightsOffTime);
    }
  }
} // end lightsControl

void specialMode()
{
  systemMode = NORMAL;
} // specialMode

unsigned long setMinutesTimer(int waitTime)
{ 
  unsigned long endTime;

   endTime = millis() + (waitTime * 60000);  // convert back to milliseconds from minutes
   return endTime;
} // setMinutesTimer

unsigned long setSecondsTimer(int waitTime)
{ 
  unsigned long endTime;

   endTime = millis() + (waitTime * 1000);  // convert back to milliseconds from seconds
   return endTime;
} // setSecondsTimer

int checkTimer(unsigned long timer)
{
   if (millis() > timer) {return HIGH;}
   else {return LOW;}
} // checkTimer

void debouncePin(DEBOUNCE_DEF& target) {
  long debounceDelay = 50;    // the debounce time; increase if the output flickers

   // read the state of the pin into a local variable:
  int reading = digitalRead(target.pin);

  // check to see if pin state has changed and you've waited
  // long enough since the last test to ignore any noise:  
  if (reading != target.pinState) {
    // reset the debouncing timer
    target.lastDebounceTime = millis();
    target.pinStable = LOW;
        if (target.pinOldState == LOW) {target.pinOldState = HIGH;} else {target.pinOldState = LOW;}
 } 
  
  if ((millis() - target.lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:
	  target.pinStable = HIGH;
   }
  target.pinState = reading;
} // debouncePin

/* IC2 support */

void i2cSetup() {
  EEPROM.write(I2CID, 0x06);
  // set default i2c ID if not yet defined
 // if (EEPROM.read(I2CID)==0) { EEPROM.write(I2CID, 0x4); }
 
 // initialize i2c as slave
 Wire.begin(EEPROM.read(I2CID));
 
 // define callbacks for i2c communication
 Wire.onReceive(i2cCommand);
 Wire.onRequest(i2cRequest);
} // i2cSetup
  
void i2cCommand(int byteCount){ // callback for received data
  unsigned char received;
//  int cmdCnt;
//  int command;
  int output;
 
  cmdCnt = Wire.available();
  if (cmdCnt > 0 ) {
    cmdRecieved++;
    command = Wire.read();      // first byte is the command
    switch (command)
    {              
      case 1:    // set Lights Mode (Off, On, Auto)
        if (cmdCnt == 2 ) {    // next byte is the mode
          lightsMode = Wire.read();      
          switch (lightsMode) {
            case ON: lightsState = HIGH; break;
            case OFF: lightsState = LOW; break;
          }
          digitalWrite(lightsPin, lightsState);
          Serial.print("Lights state = ");
          Serial.println(lightsState);
          Serial.print("Lights Mode = ");
          Serial.println(lightsMode);
       }
        break;
        
      case 2:    // set Lights auto ON time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);  // shift to high byte
          received = Wire.read();
          output = output + received; // add in low byte
          lightsOnTime = output;      // ON time in seconds
        }
         break;
        
      case 3:    // set Lights auto OFF time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);    // shift to high byte
          received = Wire.read();
          output = output + received;  // add in low byte
          lightsOffTime = output;      // OFF time in seconds
        }
        break;
        
      case 4:    // set Circulation Fan (Off, On, Auto)
        if (cmdCnt == 2 ) {    // next byte is the mode
          circulationFanMode = Wire.read();      
          switch (circulationFanMode) {
            case ON: circulationFanState = HIGH; break;
            case OFF: circulationFanState = LOW; break;
          }
            digitalWrite(circulationFanPin, circulationFanState);
            Serial.print("Fan state = ");
            Serial.println(circulationFanState);
        }
        break;
           
      case 5:    // set Circulation Fan auto ON time
        if (cmdCnt == 3 ) {    // next 2 bytes is the time
          received = Wire.read();
          output = (received << 8);    // shift to high byte
          received = Wire.read();
          output = output + received;  // add in low byte
          circulationFanOnTime = output;          // ON time in seconds
        }
        break;
      
      case 6:    // set Circulation Fan auto OFF time
           circulationFanOffTime = 6001;         // OFF time in seconds
//       if (cmdCnt == 3 ) {    // next 2 bytes is the time
//          received = Wire.read();
//          output = (received << 8);    // shift to high byte
//          received = Wire.read();
//          output = output + received;  // add in low byte
//          circulationFanOffTime = 6001;         // OFF time in seconds
//        }
        break;
       
       default:
         Serial.print("Unhandled command # ");
         Serial.print(command);
         Serial.print("\n");;
         break;
   } // end switch 
  } // end if cmdCnt
  
} // ic2Command
 

void i2cRequest() {    // callback for sending data
  byte i2cResponse[32];
  byte i2cResponseLen = 1;                 // response length
  unsigned long currentTime = millis();
  unsigned long transitionTime = 0;
  byte lightsStatus = 0;
  byte circulationFanStatus = 0;
  String outString = "Hello";
  
  
  
/*    
    i2cResponse[i2cResponseLen] = (byte)11;  // left of decimal byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)airTempLSB;  // right if decimal byte
    i2cResponseLen++;
    Serial.print("air temp msb = ");
    Serial.println(airTempLSB);
    
    i2cResponse[i2cResponseLen] = airTempTarget;  // byte
    i2cResponseLen++;  
    Serial.print("air temp Target = ");
    Serial.println(airTempTarget);

    i2cResponse[i2cResponseLen] = airTempDelta;  // byte
    i2cResponseLen++;  
    Serial.print("air temp Delta = ");
    Serial.println(airTempDelta);
 
    lightsStatus = (lightsMode << 6) | (lightsState << 5 );
    circulationFanStatus = (circulationFanMode << 3) | circulationFanState << 2;
    i2cResponse[i2cResponseLen] = lightsStatus | circulationFanStatus;      //  lights, circulation fan mode and status
    i2cResponseLen++;

    if (currentTime < lightsTimer) {transitionTime = lightsTimer - currentTime;} else {transitionTime = 0;}
    transitionTime = transitionTime / 60000;  // convert to minutes
    i2cResponse[i2cResponseLen] = (byte)(transitionTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)transitionTime; // low byte
    i2cResponseLen++;
    Serial.print("lights transitionTime = ");
    Serial.println(transitionTime);
    
    i2cResponse[i2cResponseLen] = (byte)(lightsOnTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)lightsOnTime; // low byte
    i2cResponseLen++;
    Serial.print("lights on duration = ");
    Serial.println(lightsOnTime);

    i2cResponse[i2cResponseLen] = (byte)(lightsOffTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)lightsOffTime; // low byte 17
    i2cResponseLen++;
    Serial.print("lights off duration = ");
    Serial.println(lightsOffTime);
    
    if (currentTime < circulationFanTimer) {transitionTime = circulationFanTimer - currentTime;} else {transitionTime = 0;}
    transitionTime = transitionTime / 60000;  // convert to minutes
    i2cResponse[i2cResponseLen] = (byte)(transitionTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)transitionTime; // low byte
    i2cResponseLen++;
    Serial.print("fan transitionTime = ");
    Serial.println(transitionTime);

    i2cResponse[i2cResponseLen] = (byte)(circulationFanOnTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)circulationFanOnTime; // low byte
    i2cResponseLen++;
    Serial.print("fan on duration = ");
    Serial.println(circulationFanOnTime);
    
    i2cResponse[i2cResponseLen] = (byte)(circulationFanOffTime >> 8); // high byte
    i2cResponseLen++;
    i2cResponse[i2cResponseLen] = (byte)circulationFanOffTime; // low byte 22
    i2cResponseLen++;    // compensate for zero based array 24
    Serial.print("fan off duration = ");
    Serial.println(circulationFanOffTime);
 
    Serial.print("message length = ");
    Serial.println(i2cResponseLen);
    Wire.write(i2cResponse, i2cResponseLen);
 */
  
    Wire.write(outString);
} // i2cRequest
 
double GetChipTemp(void)
{  // Get the internal temperature of the arduino
  unsigned int wADC;
  double t;
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN); // enable the ADC
  delay(20); // wait for voltages to become stable.
  ADCSRA |= _BV(ADSC); // Start the ADC
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t);
} // GetChipTemp

// end I2C support
// git clone http://github.com/jrowberg/i2cdevlib

// Reads DS18B20 "1-Wire" digital temperature sensors.
// Tutorial: http://www.hacktronics.com/Tutorials/arduino-1-wire-tutorial.html

void oneWireSetup()
{
  // Start up the library
  sensors.begin();
  sensors.setResolution(airThermometer, 10); // set the resolution to 10 bit (good enough?)
//  sensors.setResolution(outsideThermometer, 10);
//  sensors.setResolution(dogHouseThermometer, 10);
} // oneWireSetup

float getTemperature(DeviceAddress deviceAddress)
{
  float tempF = 70;
  float tempC = sensors.getTempC(deviceAddress);
  if (tempC == -127.00) {
 //   Serial.print("Error getting temperature");
  } else {
    tempF = DallasTemperature::toFahrenheit(tempC);
//    Serial.print("C: ");
//    Serial.print(tempC);
//    Serial.print(" F: ");
//    Serial.print(DallasTemperature::toFahrenheit(tempC));
//    Serial.print("\n");;
  }
  return tempF;
}

void getAirTemp()
{ 
  float tempF = 0;
  double leftPart;
  double rightPart;
  double newRight;
  sensors.requestTemperatures();
  tempF = getTemperature(airThermometer);
 
  rightPart = modf(tempF, &leftPart);
  airTempMSB = (byte)leftPart;
  newRight = rightPart*10;
  rightPart = modf(newRight, &leftPart);
  airTempLSB = (byte)leftPart;
  Serial.print("Air Temp: ");;
  Serial.println(airTempMSB);

}
// end DS18B20 support

Comments

Similar projects you might like

Direction Indicators for Bikes

Project tutorial by Rosaliew

  • 75 views
  • 0 comments
  • 3 respects

Third Eye for The Blind

Project tutorial by Muhammed Azhar

  • 556 views
  • 0 comments
  • 8 respects

The Trump Button

Project tutorial by Louis Moreau and Aurelien Lequertier

  • 573 views
  • 1 comment
  • 5 respects

Starry Night Prom

Project showcase by user584870303

  • 443 views
  • 1 comment
  • 4 respects

ArduRadio AlarmClock

Project tutorial by Tittiamo

  • 751 views
  • 2 comments
  • 1 respect

One Button Restart of a Stalled Raspberry Pi Zero W

Project tutorial by Ray Burnette

  • 2,072 views
  • 0 comments
  • 15 respects
Add projectSign up / Login