Ebb & Flow Hydro Controller

This is a project I created for my brother’s Ebb & Flow hydroponic system. Its a 4x switched outlets & has the ability for 2x digital inputs (they are supplied 5v) and 3x analog inputs (the are supplied 3.3v). Currently there is a DS18B20 for water temp, a DHT22 for ambient temp & humidity, a water level sensor, and a plant moisture sensor. All sensors can disconnect/reconnected easily as I used a 3 wire headphone stereo plug. Thanks @micho111 for that good idea! The system also has two different timers that can be adjusted by sliders on the dashboard. It has a push button on the side of the housing to do manual on/off/timer functions and a green LED in the center that indicates its connection to Cayenne servers. Always gotta have at least one led in a project! lol

This project uses the MKR1000 and a proto shield as the controller. It utilizes the SAMD chip to run multiple loops and the RTC for accurate timing. I had several alarms setup at different times (encase internet is offline) to run the system but ran into problems calling the alarms so need to work on that a little more. I designed it so that it can be expanded with more sensors down the road if needed and easily reprogrammed with a pull out USB cable under the plate.


#include <Arduino.h>
//#define CAYENNE_DEBUG 
//#define CAYENNE_PRINT Serial
// #include <CayenneWiFi101.h>
#include <CayenneMKR1000.h>
#include <SPI.h>
#include <Scheduler.h>
#include <RTCZero.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <DHT.h>

#define relayPin1 6
#define relayPin2 7
#define relayPin3 8
#define relayPin4 9
#define buttonPin 5
#define analog1Pin A0
#define analog2Pin A1
#define analog3Pin A2
#define onlineLED 10

#define myTimezone -5
#define ONE_WIRE_BUS 3  //top port
#define DHTPIN 2
#define DHTTYPE DHT22


DHT dht(DHTPIN, DHTTYPE);
RTCZero rtc;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);


char token[] = "token";
char ssid[] = "wifi";
char password[] = "pass";


unsigned long time1;
unsigned long onlineTime;
unsigned long prevTime;
unsigned long expireTime;
unsigned long expireTime2;
unsigned long slider1time;
unsigned long slider2time;
unsigned long wifiTime;
unsigned long cycle2Dur;
unsigned long cycle2Time;


bool online = false;
bool firstRun = true;
bool buttonPressed = false;

byte disco = 0;
byte relay1;
byte relay2;
byte relay3;
byte relay4;
byte button;
byte timer1;
byte timer2;
byte alarmButton;
byte cycle2;


float tempF;

// const byte seconds = 0;
// const byte minutes = 43;
// const byte hours = 21;
// const byte day = 6;
// const byte month = 6;
// const byte year = 17;

void setup()
{
	pinMode(relayPin1, OUTPUT);
	pinMode(relayPin2, OUTPUT);
	pinMode(relayPin3, OUTPUT);
	pinMode(relayPin4, OUTPUT);
	pinMode(onlineLED, OUTPUT);
	pinMode(buttonPin, INPUT_PULLUP);

	pinMode(analog1Pin, INPUT);
	pinMode(analog2Pin, INPUT);
	pinMode(analog3Pin, INPUT);

	analogReadResolution(12);

	digitalWrite(onlineLED, LOW);
	// Serial.begin(9600);

	rtc.begin();

	dht.begin();

	sensors.begin();
	sensors.setResolution(11);


	Cayenne.begin(token, ssid, password);

	Scheduler.startLoop(loop2);
	Scheduler.startLoop(loop3);
}




void loop()
{
	Cayenne.run();
	time1 = millis();
}



void loop2()
{

	if (relay1 == LOW)
		{
			digitalWrite(relayPin1, LOW);
		}

	else
		{
			digitalWrite(relayPin1, HIGH);
		}


	if (relay2 == LOW)
		{
			digitalWrite(relayPin2, LOW);
		}

	else
		{
			digitalWrite(relayPin2, HIGH);
		}


	if (relay3 == LOW)
		{
			digitalWrite(relayPin3, LOW);
		}

	else
		{
			digitalWrite(relayPin3, HIGH);
		}


	if (relay4 == LOW)
		{
			digitalWrite(relayPin4, LOW);
		}

	else
		{
			digitalWrite(relayPin4, HIGH);
		}

	// Serial.println(rtc.getDay());
	// Serial.println(rtc.getHours());
	// Serial.println(" ");

	yield();
}


void loop3()
{
	button = digitalRead(buttonPin);
	if (button == 0)
		{
			delay(500);
			button = digitalRead(buttonPin);
			if (button == 0)
				{
					buttonPressed = true;
					relay1 = LOW;
					relay2 = HIGH;
					Cayenne.virtualWrite(V10, HIGH);
					Cayenne.virtualWrite(V11, HIGH);
					Cayenne.virtualWrite(V15, HIGH);
					expireTime = millis() + slider1time;
					timer1 = 1;
				}
			else
				{
					buttonPressed = false;
				}
		}


	if (timer1 == 1 || buttonPressed == true)
		{
			if (expireTime < millis())
				{
					timer1Done();
				}
		}

	if (timer2 == 1)
		{
			if (expireTime2 < millis())
				{
					timer2Done();
				}
		}

	yield();
}




void timer1Done()
{
	relay1 = HIGH;
	relay2 = LOW;
	Cayenne.virtualWrite(V10, LOW);
	Cayenne.virtualWrite(V11, LOW);
	Cayenne.virtualWrite(V15, LOW);
	timer1 = 0;
	expireTime = 0;
}


void timer2Done()
{
	relay3 = LOW;
	relay4 = LOW;
	Cayenne.virtualWrite(V12, LOW);
	Cayenne.virtualWrite(V13, LOW);
	Cayenne.virtualWrite(V17, LOW);
	timer2 = 0;
	expireTime2 = 0;
}




CAYENNE_CONNECTED()
{
	prevTime = millis();
	digitalWrite(onlineLED, HIGH);
}

CAYENNE_DISCONNECTED()
{
	digitalWrite(onlineLED, LOW);
	prevTime = millis();
	disco++;
}







CAYENNE_OUT(V0) // & V1
{
	if (firstRun == true)
		{
			wifiTime = WiFi.getTime();
			delay(500);
			rtc.setEpoch(wifiTime);

			int timeAdjust = (rtc.getHours() + myTimezone);

			switch (timeAdjust)
				{
				    case -5:
					    timeAdjust = 7;
					    break;
				    case -4:
					    timeAdjust = 8;
					    break;

				    case -3:
					    timeAdjust = 9;
					    break;

				    case -2:
					    timeAdjust = 10;
					    break;

				    case -1:
					    timeAdjust = 11;
					    break;

				    default:
					    break;
				}

			rtc.setHours(timeAdjust);
			firstRun = false;
		}

	Cayenne.virtualWrite(V1, rtc.getMinutes());
	Cayenne.virtualWrite(V0, rtc.getHours());
}


CAYENNE_OUT(V5)
{
	sensors.requestTemperatures();
	delay(350);
	tempF = (sensors.getTempCByIndex(0) * 1.8) + 32;
	Cayenne.virtualWrite(V5, tempF);
	Cayenne.virtualWrite(V14, tempF);
}


CAYENNE_OUT(V6)
{
	int temp1 = dht.readTemperature(true);
	Cayenne.virtualWrite(V6, temp1);
	Cayenne.virtualWrite(V19, tempF);
}


CAYENNE_OUT(V7)
{
	int hum1 = dht.readHumidity();
	Cayenne.virtualWrite(V7, hum1);
}


CAYENNE_OUT(V8)
{
	int analog1 = 0;
	for (int i = 0; i <= 4; i++)
		{
			analog1 = analog1 + analogRead(analog1Pin);
		}
	analog1 = analog1 / 5;
	int y = map(analog1,10,3000,0,100);
	Cayenne.virtualWrite(V8, y);
}


CAYENNE_OUT(V9)
{
	float analog2 = 0;
	for (int i = 0; i <= 4; i++)
		{
			analog2 = analog2 + analogRead(analog2Pin);
		}

	float percent = ((4096 - (analog2 / 5)) / 4096) * 100;
	// int y = map(analog1,10,3000,0,100);
	Cayenne.virtualWrite(V9, percent);
}

CAYENNE_OUT(V20)
{
	if (timer1 == 1 || buttonPressed == true)
		{
			Cayenne.virtualWrite(V20, ((expireTime - time1) / 1000) / 60);
		}

	else
		{
			Cayenne.virtualWrite(V20, 0);
		}
}

CAYENNE_OUT(V21)
{
	if (timer2 == 1)
		{
			Cayenne.virtualWrite(V21, ((expireTime2 - time1) / 1000) / 60);
		}

	else
		{
			Cayenne.virtualWrite(V21, 0);
		}
}


CAYENNE_OUT(V23)
{
	Cayenne.virtualWrite(V23, disco);
}

CAYENNE_OUT(V24)
{
	float time2 = (time1 / 1000) / 60;
	Cayenne.virtualWrite(V24, time2);
}

CAYENNE_OUT(V25)
{
	onlineTime = ((time1 - prevTime) / 1000) / 60;
	Cayenne.virtualWrite(V25, onlineTime);
}

/////////////////////////////////////////////////////////////

CAYENNE_IN(V1)
{
	cycle2 = getValue.asInt();
	if (cycle2 == 1)
		{
			cycle2Time = cycle2Dur + millis();
		}
	else
		{
			cycle2Time = 0;
		}
}

CAYENNE_IN(V2)
{
	cycle2Dur = getValue.asInt();
	cycle2Dur = (cycle2Dur / 1023) * 60 * 60 * 1000;
}


CAYENNE_IN(V10)
{
	relay1 = getValue.asInt();
}

CAYENNE_IN(V11)
{
	relay2 = getValue.asInt();
}

CAYENNE_IN(V12)
{
	relay3 = getValue.asInt();
}

CAYENNE_IN(V13)
{
	relay4 = getValue.asInt();
}



CAYENNE_IN(V15)
{
	timer1 = getValue.asInt();

	if (timer1 == 1)
		{
			expireTime = millis() + slider1time;
		}

	else
		{
			expireTime = 0;
		}
}

CAYENNE_IN(V16)
{
	unsigned long slider1 = getValue.asInt();
	slider1 = slider1 / 1023;
	slider1time = slider1 * 60 * 1000;
}

CAYENNE_IN(V17)
{
	timer2 = getValue.asInt();

	if (timer2 == 1)
		{
			expireTime2 = millis() + slider2time;
		}

	else
		{
			expireTime2 = 0;
		}
}

CAYENNE_IN(V18)
{
	unsigned long slider2 = getValue.asInt();
	slider2 = slider2 / 1023;
	slider2time = slider2 * 60 * 1000;
}


// CAYENNE_IN(V19)
// {
//      alarmButton = getValue.asInt();
//      if (alarmButton == 1)
//              {
//                      // rtc.setAlarmTime(13, 00, 10);
//                      rtc.setAlarmTime(00, 05, 10);
//                      rtc.attachInterrupt(alarmMatch);
//                      rtc.enableAlarm(rtc.MATCH_HHMMSS);
//
//
//              }
//      else
//              {
//                      rtc.disableAlarm();
//                      // rtc.detachInterrupt();
//              }
// }

// CAYENNE_IN(V22)
// {
//      int sleepButton = getValue.asInt();
//      if (sleepButton == 1)
//              {
//                      WiFi.lowPowerMode();
//              }
//
//      else
//              {
//                      WiFi.noLowPowerMode();
//              }
// }
2 Likes

This is really cool, thanks for sharing. What is he growing with this hydroponic system? Any chance we could get some pictures (assuming they aren’t incriminating :smile:)

You seem to have done well with the standard Arduino Library, but as long as I’m here, this seems like a good place to mention as well that we’re adding SAMD support for our Arduino MQTT Library. Technically it’s already merged into the master branch there, but pending some QA work, so if anyone tries it and sees issues let me know!

2 Likes

LOL yea deff when I get over there tonight or tomorrow & install it. He grows orchids and has tons of them. Very cool collection.

So that’s what the kids are calling it nowadays :wink:

1 Like

nice project thanks for sharing!
BTW thanks for mention me also :blush:

1 Like

I like the headphone jack for sensor connectors. Might have to give that a try on my next project.

I don’t know where you put in the code, but just keep in mind that the soil sensor have corrosion problems. If it is continously loaded with current, the metal layer will corrode in less than one day or two, depending on the humidity of the soil.

To avoid this, you have to put a timer in order to energize when needed and for the time required to make the measure.

nice project I was wondering if I should have done ebb and flow instead of drip… Time will tell. lol