Open Hydroponics evandavey
open-hydroponics / firmware / arduino_aeroponics_firmware / arduino_aeroponics_firmware.pde
/* Hydroponics System
-- Aeroponics controller
-- Author: Evan Davey, evan.j.davey@gmail.com
--V1.1
--- adds string array for state labels
--- converts data string to json from csv
--- implements a newState variable to allow state change events
--- sends data string on state changes
--- adds a name variable to identify devices
Cycles a pump for use in a hyrdoponics system
Implemented as a state machine
X-valid serial command
*menu -> send main menu
*data -> send data
**rst -> state 3
0-idle
Outputs: pumpCtrl=Low,statusLed=Blink,reset=Low
off timer ends -> state 1
waterSensor=High -> state 2
1-pumping
Outputs: pumpCtrl=High,statusLed=Low,reset=Low
on timer ends -> state 1
waterSensor=High -> state 2
2-error,water low
Outputs: pumpCtrl=Low,statusLed=fast blink,reset=Low
requires reset to leave this state
3-reset
Outputs: pumpCtrl=Low,statusLed=Low,reset=High
Waits delay_time before resetting
*/
// hardware
const int pumpCtrl = 2; //connect via a transistor with back voltage protection
const int statusLed = 13; //connect using a current limiting resistor
const int reset = 12; //connect to reset via a transistor tied to ground
const int waterSensor = 8; //connect to a normally closed float switch
// firmware defaults
const float vers=1.1;
char* name="Aeroponics 1";
const unsigned long default_pump_on_time = 30000; //on 30 seconds
const unsigned long default_pump_off_time = 300000; //off 5 minutes
const long baud_rate=115200; //baud rate for serial
volatile int debug_mode=0; //when high, outputs status data
int pumpCycles = 1; //counter of pump cycles
int reset_delay=4; //time to wait after reset command issued
int start_state=0; //starting state
char* stateStrings[]={"0 - Idle","1 - Pumping","2 - Error, Water too low","3 - Waiting to reset"};
// 0 - idle
// 1 - pumping
// 2 - error, water level
// 3 - waiting for reset
volatile int currentState = -1;
volatile int newState = -1;
// Variables will change:
int ledState = LOW; // ledState used to set the LED
int pumpState = LOW; // ledState used to set the LED
long pumpTimer = 0; // will store last time LED was updated
int waterSensorState = LOW;
volatile char cmd='-1';
long pump_on_time; //on 30 seconds
long pump_off_time; //off 5 minutes
unsigned long previousMillis = 0;
boolean in_menu=false;
void setup() {
pinMode(pumpCtrl, OUTPUT);
pinMode(statusLed, OUTPUT);
pinMode(reset, OUTPUT);
pinMode(waterSensor, INPUT);
pump_off_time=default_pump_off_time;
pump_on_time=default_pump_on_time;
Serial.begin(baud_rate);
//delay(1000);
newState=start_state;
}
void keepTime() {
//keep the time
unsigned long currentMillis = millis();
pumpTimer += currentMillis-previousMillis;
previousMillis = currentMillis;
}
void mainControlLoop() {
keepTime();
//check for errors
errorCheck();
//handle state
handleState();
}
void loop()
{
mainControlLoop();
//in the main menu
if (in_menu) {
menu_main();
}
//process received serial data
//available command *menu
handleSerialIn();
if (debug_mode && !in_menu) {
if (pumpTimer % 5000 == 0) {
Serial.println(":: DBEUG ::");
status_toString();
}
}
}
void handleSerialIn() {
if ( Serial.available() > 0) { // if there are bytes waiting on the serial port
char inByte = Serial.read(); // read a byte
if (inByte == '*') { // if that byte is the desired character
int len = 5; // expected string is 6 bytes long
char inString[len]; // declare string variable
for (int i = 0; i < len; i++) {
inString[i] = Serial.read();
delay(100);
}
//Serial.println(inString);
if ( strstr(inString, "menu") != NULL ) { // check to see if the respose is "reset"
menu_main();
} else if (strstr(inString, "data") != NULL ) {
//send data
data_toString();
} else if (strstr(inString, "*rst") != NULL ) {
Serial.println("Resetting..");
//send data
newState=3;
} else if (strstr(inString, "sone") != NULL ) {
//Serial.println("Changing state to 1");
//send data
pumpTimer=0;
newState=1;
Serial.flush();
} else if (strstr(inString, "stwo") != NULL ) {
//send data
newState=2;
Serial.flush();
} else if (strstr(inString,"idle") != NULL ) {
//send data
newState=0;
pumpTimer=0;
Serial.flush();
}
}
}
}
void state_change() {
data_toString();
}
void handleState() {
if (newState!=currentState) {
currentState=newState;
state_change();
}
switch(currentState) {
//IDLE
case 0:
flashLed(statusLed,1,1000);
digitalWrite(pumpCtrl,LOW);
//pump off timer exceeded, switch to pumping state
if (pumpTimer >= pump_off_time) {
pumpTimer=0;
pumpCycles++;
newState=1;
}
break;
//PUMPING
case 1:
digitalWrite(pumpCtrl,HIGH);
//pump on timer exceeded, switch to idle state
if (pumpTimer >= pump_on_time) {
pumpTimer=0;
newState=0;
}
break;
//ERROR
case 2:
digitalWrite(pumpCtrl,LOW);
flashLed(statusLed,3,100);
break;
//RESET
case 3:
for (int i=reset_delay;i>0;i--) {
Serial.print("Will reset in ");
Serial.print(i);
Serial.println("s");
delay(1000);
}
Serial.println("** Resetting now **");
digitalWrite(reset,HIGH);
newState=0;
break;
default:
break;
}
}
void data_toString() {
//delay(1000);
Serial.print("{");
Serial.print("\"State\"");
Serial.print(":");
Serial.print(currentState);
Serial.print(",");
Serial.print("\"State Description\"");
Serial.print(":");
Serial.print(stateStrings[currentState]);
Serial.print(",");
Serial.print("\"Pump Timer\"");
Serial.print(":");
Serial.print(pumpTimer/1000);
Serial.print(",");
Serial.print("\"Pump Off Time\"");
Serial.print(":");
Serial.print(pump_off_time);
Serial.print(",");
Serial.print("\"Pump On Time\"");
Serial.print(":");
Serial.print(pump_on_time);
Serial.print(",");
Serial.print("\"Pump Cycles\"");
Serial.print(":");
Serial.print(pumpCycles);
Serial.print(",");
Serial.print("\"Water Sensor\"");
Serial.print(":");
Serial.print(waterSensorState);
Serial.print(",");
Serial.print("\"Name\"");
Serial.print(":");
Serial.print("\"");
Serial.print(name);
Serial.print("\"");
Serial.print(",");
Serial.print("}");
Serial.println("");
}
void errorCheck() {
// water level too low, enter error state
if (!checkWater()) {
newState=2;
return;
}
}
//checks the water sensor and returns true if water is present
boolean checkWater() {
waterSensorState=digitalRead(waterSensor);
if (waterSensorState == HIGH) {
return false;
}
else {
return true;
}
}
void state_toString(int state) {
Serial.println(stateStrings[state]);
}
void status_toString() {
Serial.print("Version: ");
Serial.println(vers);
Serial.print("Debug Mode: ");
Serial.println(debug_mode);
Serial.print("State: ");
state_toString(currentState);
Serial.print("Pump Timer: ");
Serial.println(pumpTimer/1000);
Serial.print("Pump Cycles: ");
Serial.println(pumpCycles);
Serial.print("Pump Off Time: ");
Serial.println(pump_off_time/1000);
Serial.print("Pump On Time: ");
Serial.println(pump_on_time/1000);
Serial.print("Water Sensor: ");
Serial.println(waterSensorState);
}
void menu_show() {
Serial.println("::::: HYDRO SYSTEM :::::");
status_toString();
delay(500);
Serial.println("--------------------------------");
Serial.println("(1) - exit menu");
Serial.println("(2) - reset");
Serial.println("(3) - toggle debug");
Serial.println("(4) - force state");
Serial.println("(5) - set pump on time");
Serial.println("(6) - set pump off time");
}
void menu_main() {
//first time in, show the menu
if (!in_menu) {
delay(700);
Serial.flush();
menu_show();
in_menu=true;
}
//wait for command
if (in_menu) {
if ( Serial.available() > 0) { // if there are bytes waiting on the serial port
cmd = Serial.read();
switch (cmd) {
case '1':
Serial.println("exiting menu");
in_menu=false;
break;
case '2':
in_menu=false;
newState=3;
break;
case '3':
in_menu=false;
debug_mode=!debug_mode;
Serial.print("Debug mode=");
Serial.println(debug_mode);
break;
case '4':
Serial.println("States-");
for (int i=0;i<=3;i++) {
state_toString(i);
}
Serial.print("State Number (0-3): ");
Serial.flush();
while (true) {
//ensure operation continues while waiting
mainControlLoop();
if ( Serial.available() > 0) { // if there are bytes waiting on the serial port
char * cmd_c="0";
cmd_c[0]=Serial.read();
int cmd_i=atoi(cmd_c);
if (cmd_i<=3) {
Serial.print("Setting state to: ");
Serial.println(cmd_i);
pumpTimer=0;
newState=cmd_i;
in_menu=false;
break;
}
else {
Serial.print("State Number (0-3): ");
Serial.flush();
}
}
}
break;
case '5':
Serial.println("(1) 10 seconds");
Serial.println("(2) 20 seconds");
Serial.println("(3) 30 seconds");
Serial.println("(4) 60 seconds");
Serial.println("(5) 120 seconds");
Serial.flush();
while (true) {
//ensure operation continues while waiting
mainControlLoop();
int t;
if ( Serial.available() > 0) {
cmd=Serial.read();
switch(cmd) {
case '1':
t=10;
break;
case '2':
t=20;
break;
case '3':
t=30;
break;
case '4':
t=60;
break;
case '5':
t=120;
break;
default:
t=30;
}
pump_on_time=t*1000;
Serial.print("new pump time:") ;
Serial.println(t);
in_menu=false;
break;
}
}
break;
case '6':
Serial.println("(1) 60 seconds");
Serial.println("(2) 2 minutes");
Serial.println("(3) 5 minutes");
Serial.println("(4) 10 minutes");
Serial.println("(5) 15 minutes");
Serial.flush();
while (true) {
//ensure operation continues while waiting
mainControlLoop();
long t;
if ( Serial.available() > 0) {
cmd=Serial.read();
switch(cmd) {
case '1':
t=60;
break;
case '2':
t=120;
break;
case '3':
t=300;
break;
case '4':
t=600;
break;
case '5':
t=900;
break;
default:
t=300;
}
pump_off_time=t*1000;
Serial.print("new pump time:") ;
Serial.println(t);
in_menu=false;
break;
}
}
break;
default:
Serial.println("illegal command");
Serial.flush();
menu_show();
}
}
}
}
//blinks a Led pin numBlinks at the blinkRate
void flashLed(int ledPin,int numBlinks,int blinkRate) {
digitalWrite(ledPin,LOW);
for (int i=1;i<=numBlinks;i++) {
digitalWrite(ledPin,HIGH);
delay(blinkRate);
digitalWrite(ledPin,LOW);
delay(blinkRate);
}
}