Greenhouse controller v0.4

This is my first project with the arduino, and I know, I jumped right in. This is a pretty big project for a first. I feel like each small part is a hundred sub projects. It's not nearly complete, but I have a working partial version right now, and I figured I might as well put it out there, see if I get any feedback. My documentation is pretty sloppy, but I do put effort into documenting it, so you should be able to see what's going on pretty well. Again, very much a work in progress. At the moment it's fairly limited in capability, here's some stats:

3 outputs (ULN2003 to 12v relay to 110v relay): a water pump, artificial sunlight and a green LED worklight. (Green light does not interrupt the darkness requirement flowering photosensitive plants have)

2 analog inputs: temperature sensors, LM34DZ

2 digital inputs : regular momentary NO pushbuttons

Google Code Archive - Long-term storage for Google Code Project Hosting. <~~ More detailed info

#include "OutletTimer.h"
#include "DigitalInputter.h"
#include "PrintPJRClcd.h"

/*Initialize outlets, OutletTimer(pinNumber, InitialMode, InitialTimeSec) */
OutletTimer MainLightOutletTimer(10,0,300);// Main light
OutletTimer MainVegPumpOutletTimer(9,1,60);//Water pumps
OutletTimer GreenWorkLightOutletTimer(8,4,0);//work lights

/* Initialize digital inputs DigitalInputter(int pinNumber) */
DigitalInputter WorkLightButton(7);
DigitalInputter PumpToggleButton(6);

/* Open LCD library on serial port */
PrintPJRClcd LCD = PrintPJRClcd();

/* Initialize analog inputs */
int AnalogRezTemp = 0;
int AnalogAirTemp = 0;
#define AnalogRezTempPin 0
#define AnalogAirTempPin 1

/* Variables for time watching */
unsigned long previousMillis = 0; // will store last time millis
unsigned long curMillis = 0;
int timeSinceLast = 0;

/* floats for calculating time */
float MainLightTimeFloat;
unsigned char LoopsAround = 0;
void setup()
{

/* Set outlet on and off times */
MainLightOutletTimer.OnDuration = 64800; //main lights, 64800 = 18 hours
MainLightOutletTimer.OffDuration = 21600; // 21600 = 6 hours
MainVegPumpOutletTimer.OnDuration = 60;//veg pump
MainVegPumpOutletTimer.OffDuration = 600;
GreenWorkLightOutletTimer.OnDuration = 0;
GreenWorkLightOutletTimer.OffDuration = 0;
Serial.begin(19200);
delay(1000);
LCD.RedrawScreen();

}

void loop()
{

/* Check inputs */
if (WorkLightButton.CheckPin()) // if work light button has been pushed
{
if (GreenWorkLightOutletTimer.CurrentMode==4) GreenWorkLightOutletTimer.ChangeMode(5); // if current mode is perm off, turn it perm on
else if (GreenWorkLightOutletTimer.CurrentMode==5) GreenWorkLightOutletTimer.ChangeMode(4);
};
if (PumpToggleButton.CheckPin()) //if pump button has been pushed
{
switch(MainVegPumpOutletTimer.CurrentMode) //what's the veg pump at now?
{
case 0: //off, normal operation
MainVegPumpOutletTimer.ChangeMode(1);
break;
case 1: //on, normal operation
MainVegPumpOutletTimer.ChangeMode(4);
break;
case 4: // perm off
MainVegPumpOutletTimer.ChangeMode(5);
break;
case 5: // perm on
MainVegPumpOutletTimer.ChangeMode(0);
break;
};
};

/*measure time past since last loop and increment down */
curMillis = millis();
if (curMillis < previousMillis) previousMillis = 0.0; //roll over check
timeSinceLast = curMillis- previousMillis;
previousMillis = curMillis;

/* Analog inputs */
AnalogRezTemp = analogRead(AnalogRezTempPin);
AnalogAirTemp = analogRead(AnalogAirTempPin);

/* Print info to screen /
//LCD.RedrawScreen();
LCD.PrintData(0,int(AnalogAirTemp/2.1));
LCD.PrintData(1,int(AnalogRezTemp/2.1));
LCD.PrintData(2,MainVegPumpOutletTimer.CurrentMode);
LCD.PrintData(3,(MainVegPumpOutletTimer.TimeLeft/1000));
LCD.PrintData(4, MainLightOutletTimer.CurrentMode);
MainLightTimeFloat = ((long(MainLightOutletTimer.TimeLeft)/1000)/60);
LCD.PrintData(5, int(MainLightTimeFloat));
LCD.PrintData(6, GreenWorkLightOutletTimer.CurrentMode);
/
Pass Time Since Last loop to each outlet */

MainLightOutletTimer.ElapseTime(timeSinceLast);
MainVegPumpOutletTimer.ElapseTime(timeSinceLast);
GreenWorkLightOutletTimer.ElapseTime(timeSinceLast);

LoopsAround++;
if(LoopsAround==42) LCD.RedrawScreen();
}

/*

  • A library for dealing with digital inputs * Created by MulletSoda, 2010
  • With love!
    */

#ifndef DigitalInputter_h
#define DigitalInputter_h

#include "WProgram.h"

class DigitalInputter
{
public**:**
//public variables:
int PinNumber;
int _PinNumber;
unsigned long _currentTime;
unsigned long _deBounceTimeDone;
boolean _deBounce;
void DeBouncer();

//public functions:
DigitalInputter(int _PinNumber);
boolean CheckPin();
private**:**
//private variables
//private functions
};

#endif

#include "WProgram.h"
#include "DigitalInputter.h"

DigitalInputter::DigitalInputter(int PinNumber)
{
//initialize
_PinNumber = PinNumber;
pinMode(_PinNumber, INPUT);
//digitalWrite(_PinNumber, HIGH);//turn on pull up resistor
_deBounceTimeDone = 0;
}

boolean DigitalInputter::CheckPin()
{
_currentTime = millis(); //what is the current time?

//debounce and check
if (!_deBounce) //if deBounce has not been activated
if (digitalRead(_PinNumber)==1){//read pin, if it returns activated then
{
_deBounce = true;
_deBounceTimeDone =500 + _currentTime; // <******************************************* Will this bypass the rollover problem????
//turn on debounce, set time over, return true
return true;
};
};
if (_deBounce) //if deBounce has been activated
{
if (_deBounceTimeDone <= _currentTime) _deBounce = false; //if enough time has passed
};
return false;
}

void DigitalInputter::DeBouncer()
{
}

/*

  • A timer for controlling outlets
  • Created by MulletSoda, 2010
  • With love!
    */

#ifndef OutletTimer_h
#define OutletTimer_h

#include "WProgram.h"

class OutletTimer
{
public**:** //publicly accessable variables and functions

int pinNumber; //0 - 255, pin number of outlet
unsigned long MaintTime; // Maintainence mode time, turns on/off for a set period of time then returns to normal functioning
int CurrentMode; //the current mode of this outlet
unsigned long TimeLeft; //milliseconds until mode change
unsigned long OffDuration; //Time outlet remains off
unsigned long OnDuration; //Time outlet remains on

/* Functions available to the public */

OutletTimer(int _pinNumber, int _InitialMode, unsigned int _InitialTimeSec);
void ElapseTime(int TimeSinceLast); //De-increment time remaining
void ChangeMode(int NewMode); //force a mode change
private**:** //private, localized variables and functions
int _pinNumber;
int _InitialMode;
int _InitialTimeSec;

};

#endif

#include "WProgram.h"
#include "OutletTimer.h"

OutletTimer::OutletTimer(int _pinNumber, int _InitialMode, unsigned int _InitialTimeSec)
{
/* This should do everything needed when the controller first starts up

  • Initialize pin, set pin to whatever, set on time, set off time, set timeleft, set mode,

*/
pinNumber=_pinNumber;
pinMode(pinNumber, OUTPUT);
digitalWrite(pinNumber, _InitialMode);
TimeLeft = _InitialTimeSec * 1000.0; //set TimeLeft
CurrentMode = int(_InitialMode);
}

void OutletTimer::ElapseTime(int TimeSinceLast)
{
//increment time down, this includes check for 0 time and TimeIsUp

if (TimeLeft > 1000.0) TimeLeft-=TimeSinceLast; //time slips away yet again
if (TimeLeft <= 1000.0 && TimeLeft != 0.0) //if TimeIsUp but not disabled
{
switch (CurrentMode) //what's the current mode we're switching from?
{
case (0): //outlet off, normal op: turn mode to on, time to OnTime, turn pin high
ChangeMode(1);
break;
case (1): //outlet on, normal op : mode off, time to OffTime, pin low
ChangeMode(0);
break;
case (2): //maint off time up, return to normal operation: turn mode to on, time to OnTime, turn pin high
ChangeMode(1);
break;
case (3): //maint on time up, return normal op : turn mode off, time to OffTime, pin low
ChangeMode(0);
break;
case (4): //perm off, should NEVER get here
break;
case (5): //perm on, should NEVER get here
break;

}
//for some reason I needed a delay here before, if there are errors, try adding 2ms
}
}

void OutletTimer::ChangeMode(int NewMode)
{
//mode change, newMode can be used to pass button presses, too, by using higher NewMode values
switch (NewMode)
{
case 0: //off
digitalWrite(pinNumber, LOW);
CurrentMode = 0;
TimeLeft = OffDuration * 1000.0;
break;
case 1: //on
digitalWrite(pinNumber, HIGH);
CurrentMode = 1;
TimeLeft = OnDuration * 1000.0;
break;
case 2://off maint
digitalWrite(pinNumber, LOW);
CurrentMode = 2;
TimeLeft=MaintTime * 1000.0;
break;
case 3://on maint
CurrentMode = 3;
digitalWrite(pinNumber, HIGH);
TimeLeft = MaintTime * 1000.0;
break;
case 4://perm off
digitalWrite(pinNumber, LOW);
CurrentMode = 4;
TimeLeft = 0.0;
break;
case 5://perm on
digitalWrite(pinNumber, HIGH);
CurrentMode = 5;
TimeLeft = 0.0;
break;
default: //other
digitalWrite(pinNumber, LOW);
/*

  • This will be used to deal with button presses later. Example :
  • Button0 gets pressed, can be sent as NewMode==50, then a switch (CurrentMode)
  • {case 0: //toggle mode to on; case 1: //toggle mode to off}
    */
    break;

}
}

/*

  • A library for printing to a 8x20 LCD, obtained from PJRC
  • Created by MulletSoda, 2010
  • With love!
    */

#ifndef PrintPJRClcd_h
#define PrintPJRClcd_h

#include "WProgram.h"

class PrintPJRClcd
{

public**:**
/* Variables /
int blank;
/
Functions /
PrintPJRClcd(); //initialize, clear screen, set up all descriptive info
void PrintData(int type,unsigned long datas);//type is set to location, datas is info to be printed
void RedrawScreen();
private
*:**
/* Variables /
int _row; //Cursor row position, 1 - 8
int _column; //Cursor column position, 1 - 20
/
Functions */
void PositionCursor(int _row, int _cursor); //position cursor
};

#endif

#include "WProgram.h"
#include "PrintPJRClcd.h"

PrintPJRClcd::PrintPJRClcd()
{

};

void PrintPJRClcd::RedrawScreen()
{
PositionCursor(1,1);
Serial.print("Air T: F My Robot");
PositionCursor(2,1);
Serial.print("Rez1 T: F Friend");
PositionCursor(4,1);
Serial.print("Pump1: : seconds");
PositionCursor(3,1);
Serial.print(" ");
PositionCursor(5,1);
Serial.print(" ");
PositionCursor(6,1);
Serial.print("Worklight: ");
PositionCursor(7,1);
Serial.print(" Min left ");
PositionCursor(8,1);
Serial.print(" ");

};

void PrintPJRClcd::PositionCursor(int _row, int _column)
{
Serial.write(0x5C);
Serial.write(0x42);
Serial.write(byte(_column+31));
Serial.write(byte(_row+31));
};

void PrintPJRClcd::PrintData(int type, unsigned long datas)
{

switch (type) //where does this data go?
{
case 0: //Air temp
PositionCursor(1,8);
Serial.print(datas);
break;
case 1: //Rez temp
PositionCursor(2,9);
Serial.print(datas);
break;
case 2: //Pump1 on or off
PositionCursor(4,8);
if(datas==0) Serial.print("Off");
else if(datas==1) Serial.print("On ");
case 3: // Minutes left on Pump1
PositionCursor(4,13);
Serial.print(datas);
break;
case 4: //Day or Night
PositionCursor(7,1);
if(datas==0) Serial.print("Night");
else if(datas==1) Serial.print(" Day ");
break;
case 5: // Min left till change
PositionCursor(7,7);
Serial.print(datas);
break;
case 6: //worklight status
PositionCursor(6,12);
if(datas==4) Serial.print("Off ");
if(datas==5) Serial.print("On ");
break;
case 7: //debug
PositionCursor(5,2);
Serial.print(datas);
break;
};
};

Are you growing in soil?

If you're interested I can post the homebrewed moisture sensor I built.

Nope, hydroponic, but post it anyway. Maybe somebody else can use it.

I'm interested in what people think of the code.

Looks pretty good!

I am doing a similar project soon. I may use your code for a little bit of reference... I am new to Arduino.