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 via the XRF data module

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

// 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.

// 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(", tempC"));
file.print(F(", tempF"));

// 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.

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

void error_P(const char* msg) {

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


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';
} else {
error("Can't create file name");
if (!file.open(fileName, O_CREAT | O_WRITE | O_EXCL)) error("file.open");
do {
} while (Serial.read() >= 0);

// Write data header.


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.
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();...

Read more »