I decided to make an irrigation controller for small urban veggie gardens. I am trying my best to keep costs low, due to Christmas and kids etc, so a lot of this stuff I already had (hence the choices)

The BOM is basically:

1 Seeduino Stalker v2.3
1 5V solar panel
1 Waterproof enclosure
1 LiPo battery pack (I have used a 3.7V 1000mAh)
1 Soil Moisture sensor (Sourced from Micro Robotics – about R60 – dfrobot brand)
1 Solenoid valve 20mm inner diameter valves. Sourced from dealextreme (waiting for delivery)
1 Rain sensor (sourced from delextreme – waiting for delivery)
2 Mini JST connectors (hard to get here, but had some lying around)
1 micro SD card (from another project) – 4GB
1 CR2032 Lithium cell for the RTC during the nights
1 wifi connection (not sure which way I’ll go for this yet)
Various bits of wire and solder etc.

Pics below of the stalker and components.

I added the soil sensor to the A0 pin of the stalker (see code)
The solar panel was glued to the inside lid of the enclosure so that it remained protected, while still gathering light to charge the LiPo battery. The battery is sufficient (so far) to keep the system ticking over at night, and the solar panel seems to be highly efficient even on cloudy days.

The SD card is used as a backup data logger, in case the wifi goes down due to power failures or cannot connect for whatever reason.

The data that gets collected so far looks like this (CSV data file on a logrotate)

timestamp, soilmoisture, tempinC, tempinF

Temperature, Time and soil moisture are collected every minute for data plotting etc. The data will be put into a MongoDB database for analysis later (depending on wifi)

The basic idea is that when the soil moisture reaches a certain threshold, it will turn on the irrigation system by opening the solenoid valve. One soil moisture is high enough, it shuts off and saves water.

The code so far (excuse it, it’s a WIP)

#include //http://github.com/JChristensen/DS3232RTC
#include //http://playground.arduino.cc/Code/Time
#include //http://arduino.cc/en/Reference/Wire
#include

// SD chip select pin
const uint8_t chipSelect = 10;

// Size of read/write.
const size_t BUF_SIZE = 512;

// Log file base name. Must be six characters or less.
#define FILE_BASE_NAME “DATA”

//——————————————————————————
// File system object.
SdFat sd;

// Log file.
SdFile file;

//——————————————————————————
// store error strings in flash to save RAM
#define error(s) sd.errorHalt_P(PSTR(s))
//——————————————————————————

const uint8_t ANALOG_COUNT = 1;

// Write data header.
void writeHeader() {
file.print(F(“timestamp”));
file.print(F(“,soil”));
file.print(F(“, tempC”));
file.print(F(“, tempF”));
file.println();
}

// Log a data record.
void logData(time_t time, int soil, float tempc, float tempf) {
// Write data to file. Start with log time in micros.
file.print(time);
file.write(‘,’);
file.print(soil);
file.write(‘,’);
file.print(tempc);
file.write(‘,’);
file.print(tempf);
file.println();
}

// Error messages stored in flash.
#define error(msg) error_P(PSTR(msg))

void error_P(const char* msg) {
sd.errorHalt_P(msg);
}

void setup(void)
{
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) – 1;
char fileName[13] = FILE_BASE_NAME “00.CSV”;

Serial.begin(9600);

setSyncProvider(RTC.get);
if (timeStatus() != timeSet) Serial.print(” FAIL!”);

// Initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
// breadboards. use SPI_FULL_SPEED for better performance.
if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();

// Find an unused file name.
if (BASE_NAME_SIZE > 6) {
error(“FILE_BASE_NAME too long”);
}
while (sd.exists(fileName)) {
if (fileName[BASE_NAME_SIZE + 1] != ‘9’) {
fileName[BASE_NAME_SIZE + 1]++;
} else if (fileName[BASE_NAME_SIZE] != ‘9’) {
fileName[BASE_NAME_SIZE + 1] = ‘0’;
fileName[BASE_NAME_SIZE]++;
} else {
error(“Can’t create file name”);
}
}
if (!file.open(fileName, O_CREAT | O_WRITE | O_EXCL)) error(“file.open”);
do {
delay(10);
} while (Serial.read() >= 0);

// Write data header.
writeHeader();

}

void loop(void)
{
// Force data to SD and update the directory entry to avoid data loss.
if (!file.sync() || file.getWriteError()) error(“write error”);

if (Serial.available()) {
// Close file and stop.
file.close();
Serial.println(F(“Done”));
while(1) {}
}

static time_t tLast;
time_t t = now();
tmElements_t tm;

//check for input to set the RTC, minimum length is 12, i.e. yy,m,d,h,m,s
if (Serial.available() >= 12) {
//note that the tmElements_t Year member is an offset from 1970,
//but the RTC wants the last two digits of the calendar year.
//use the convenience macros from Time.h to do the conversions.
int y = Serial.parseInt();
if (y >= 100 && y < 1000) Serial.print("Error: Year must be two digits or four digits!"); else { if (y >= 1000)
tm.Year = CalendarYrToTm(y);
else //(y < 100) tm.Year = y2kYearToTm(y); tm.Month = Serial.parseInt(); tm.Day = Serial.parseInt(); tm.Hour = Serial.parseInt(); tm.Minute = Serial.parseInt(); tm.Second = Serial.parseInt(); t = makeTime(tm); RTC.set(t); //use the time_t value to ensure correct weekday is set setTime(t); //dump any extraneous input while (Serial.available() > 0) Serial.read();
}
}

t = now();
if (t != tLast) {
tLast = t;
if (minute(t) == 0) {
float c = RTC.temperature() / 4.;
float f = c * 9. / 5. + 32.;
int moisture = analogRead(0);
logData(t, moisture, c, f);
}
}
}

DSC_0001

DSC_0002

DSC_0003

Liked this post? Follow this blog to get more.