#include <DHT.h>
// #include <SoftwareSerial.h>
#include <LiquidCrystal.h> // LCD display library
#include <SPI.h> // Serial Peripheral Interface library
#include <SD.h> // SD memory card library (SPI is required)
// Digital PINs
#define LCD_DISPLAY_TX_PIN 2
#define DHTPIN_1 20 // what pin we're connected to
#define DHTTYPE_1 DHT22 // DHT 22 (AM2302)
#define DHTPIN_2 21 // what pin we're connected to
#define DHTTYPE_2 DHT22 // DHT 22 (AM2302)
// SD card
#define CS_PIN 10
// LCD
// LiquidCrystal lcd(19, 18, 17, 16, 15, 14);
LiquidCrystal lcd(18, 19, 14, 15, 16, 17);
#define actuatorPositionPin 3 // High - close
#define actuatorActionPin 2 // Low - moving
#define valvePositionPin 5 // High -open
#define valveActionPin 4 // Low - moving
#define LCD_CONNECTED false // set to to true when LCD is hooked up
const int upPin = 6; // Pin # connected to up arrow button
const int downPin = 7; // Pin # connected to down arrow button
const int selectPin = 8; // Pin # connected to select / start button
DHT dht1(DHTPIN_1, DHTTYPE_1);
DHT dht2(DHTPIN_2, DHTTYPE_2);
const int numDhts = 2;
const int numSoil = 4;
const long readInterval = 2000;
const unsigned long actuatorMoveTime = 10000;
const unsigned long valveMoveTime = 10000;
bool actuatorState = false; // false-closed, true-open
bool actuatorMoving = true; // false-idle, true-moving
bool valveState = false; // false-closed, true-open
bool valveMoving = true; // false-idle, true-moving
bool waterLowState = false; // false-closed, true-open (instant action)
const int MoistAirValue = 925;
const int MoistWaterValue = 300;
// temperature schedule
const float openTemp = 30.0; // SD value
const float closeTemp = 25.0; // SD value
const int controlTempSensorID = 0; // Indoor temp sensor ID
// watering schedule
int waterFillRatio[5] = {20, 40, 60, 80, 100}; // in %, SD value
int waterTempRange[5] = {10, 15, 20, 25, 30}; // first value is lowest threshold to start watering, SD value
const int waterCycleSeconds = 60; //SD
const float minWaterMoist = 30; //SD
const float maxWaterMoist = 90; //SD
// timers and counters
unsigned long previousMillis = 0;
unsigned long actuatorTimeCounter = 0;
unsigned long valveTimeCounter = 0;
unsigned long previousCmdStart = 0; // water
// other
int screenNum = 1;
bool editMode = false;
int menuChoice = 0;
// sensor values, parsed and normalized
float airHum[numDhts];
float temp[numDhts];
float soilMoist[numSoil];
struct Command{
bool actuatorCmdReceived; // true - command to move actuator
bool actuatorCmd; // true - open
int actuatorMode; // 0-CLOS, 1-OPEN, 2-AUTO
int actuatorMenuChoice; // same as above, just holds cursor position
bool waterHighCmdReceived;
bool waterHighCmd;
int waterHighMode;
int waterHighMenuChoice;
bool waterLowCmdReceived;
bool waterLowCmd;
int waterLowMode;
int waterLowMenuChoice;
};
Command cmd;
void setup() {
Serial.begin(115200);
lcd.begin(20,4);
dht1.begin();
dht2.begin();
cmd.actuatorCmdReceived = false;
cmd.actuatorMode = 2;
cmd.actuatorMenuChoice = 2;
cmd.waterHighCmdReceived = false;
cmd.waterHighMode = 2;
cmd.waterHighMenuChoice = 2;
cmd.waterLowCmdReceived = false;
cmd.waterLowMode = 2;
cmd.waterLowMenuChoice = 2;
lcd.clear();
P("Welcome!");
lcd.setCursor(0, 2);
P("Greenhouse Control.");
lcd.setCursor(0, 3);
P("version 1.0");
lcd.clear();
// lcd.print(MOSI); //11
// lcd.print(MISO); //13
// lcd.print(SCK); //12
// lcd.print(SS); //10
//Setup SD card
if (SD.begin(CS_PIN) == false) {
lcd.clear();
lcd.print(F(" ERROR:"));
lcd.setCursor(0, 2);
lcd.print(F("Can't setup SD card"));
lcd.setCursor(0, 3);
lcd.print(F("System was shut down"));
} else {
lcd.clear();
lcd.print(F("SD card OK"));
if (readConfig(SD, "/sd.txt")) {
lcd.print(F("read OK"));
} else {
lcd.print(F("read Fail"));
}
lcd.setCursor(0, 1);
for (int i=0; i<=4; i++) {
lcd.print(waterFillRatio[i]);
lcd.print(" ");
}
lcd.setCursor(0, 2);
for (int j=0; j<=4; j++) {
lcd.print(waterTempRange[j]);
lcd.print(" ");
}
}
}
bool readConfig(fs::FS &fs, const char * path){
int col = 1; // Column number (of text file). First column is one.
int row = 1; // Row number (of text file). First row is one.
char tempChar; // Temporary character holder (read one at a time from file)
char tempLine[21]; // Temporary character array holder
int tempLoc = 0; // Current location of next character to place in tempLine array
// Clear the arrays
memset(waterFillRatio, 0, sizeof(waterFillRatio));
memset(waterTempRange, 0, sizeof(waterTempRange));
File file = fs.open(path);
if(!file){
return false;
}
while(file.available() > 0){
// Read a single character
tempChar = file.read();
if (tempChar == 13) { // Carriage return: Read another char (it is always a line feed / 10). Add null to end.
file.read();
tempLine[tempLoc] = '\0';
}
else if (tempChar == 44) { // Comma: Add null to end.
tempLine[tempLoc] = '\0';
}
else if (tempLoc <= 19) { // Add it to the temp line array
tempLine[tempLoc] = tempChar;
tempLoc = tempLoc + 1;
}
if (row == 1 && tempChar == 44) {
waterFillRatio[col - 1] = atoi(tempLine);
}
if (row == 2 && tempChar == 44) {
waterTempRange[col - 1] = atoi(tempLine);
}
if (tempChar == 10) { // End of line. Reset everything and goto next line
memset(tempLine, 0, 21);
tempLoc = 0;
row = row + 1;
col = 1;
}
if (tempChar == 44) { // Comma. Reset everything and goto 1st column
memset(tempLine, 0, 21);
tempLoc = 0;
col = col + 1;
}
}
file.close();
return true;
}
void dataReaders()
{
airHum[0] = dht1.readHumidity();
temp[0]= dht1.readTemperature();
airHum[1] = dht2.readHumidity();
temp[1]= dht2.readTemperature();
int a0 = analogRead(A0);
int a1 = analogRead(A1);
int a2 = analogRead(A2);
int a3 = analogRead(A3);
soilMoist[0] = map(a0, MoistAirValue, MoistWaterValue, 0, 100);
soilMoist[1] = map(a1, MoistAirValue, MoistWaterValue, 0, 100);
soilMoist[2] = map(a2, MoistAirValue, MoistWaterValue, 0, 100);
soilMoist[3] = map(a3, MoistAirValue, MoistWaterValue, 0, 100);
}
void controlActuators(Command *cmd)
{
unsigned long currentMillis = millis();
// done moving
if ((currentMillis - actuatorTimeCounter >= actuatorMoveTime) && actuatorMoving == true) {
actuatorTimeCounter = currentMillis;
actuatorMoving = false;
digitalWrite(actuatorActionPin, HIGH);
Serial.println("Done actuator moving");
}
// start opening
if (actuatorState == false && cmd->actuatorCmd == true && cmd->actuatorCmdReceived == true) {
actuatorState = true; // open it
actuatorMoving = true;
digitalWrite(actuatorPositionPin, LOW);
digitalWrite(actuatorActionPin, LOW);
cmd->actuatorCmdReceived = false;
actuatorTimeCounter = currentMillis;
Serial.println("Starting opening actuators");
// start closing
} else if (actuatorState == true && cmd->actuatorCmd == false && cmd->actuatorCmdReceived == true) {
actuatorState = false; // close it
actuatorMoving = true;
digitalWrite(actuatorPositionPin, HIGH);
digitalWrite(actuatorActionPin, LOW);
actuatorTimeCounter = currentMillis;
Serial.println("Starting closing actuators");
cmd->actuatorCmdReceived = false;
}
}
void controlValve(Command *cmd)
{
unsigned long currentMillis = millis();
if ((currentMillis - valveTimeCounter >= valveMoveTime) && valveMoving == true) {
valveTimeCounter = currentMillis;
valveMoving = false;
digitalWrite(valveActionPin, HIGH);
Serial.println("Done valve moving");
}
if (valveState == false && cmd->waterHighCmd == true && cmd->waterHighCmdReceived) {
valveState = true; // open it
valveMoving = true;
digitalWrite(valvePositionPin, LOW);
digitalWrite(valveActionPin, LOW);
valveTimeCounter = currentMillis;
cmd->waterHighCmdReceived = false;
Serial.println("Starting opening valve");
} else if (valveState == true && cmd->waterHighCmd == false && cmd->waterHighCmdReceived) {
valveState = false; // close it
valveMoving = true;
digitalWrite(valvePositionPin, HIGH);
digitalWrite(valveActionPin, LOW);
valveTimeCounter = currentMillis;
cmd->waterHighCmdReceived = false;
Serial.println("Starting closing valve");
}
}
void setCommand(Command *cmd)
{
unsigned long currentMillis = millis();
// actuators
if (cmd->actuatorMode == 2) {
if (cmd->actuatorCmd == true && temp[controlTempSensorID] < closeTemp ) { // if open and below lower, close
cmd->actuatorCmd = false;
cmd->actuatorCmdReceived = true;
} else if (cmd->actuatorCmd == false && temp[controlTempSensorID] > openTemp) { // if closed and above high, open
cmd->actuatorCmd = true;
cmd->actuatorCmdReceived = true;
}
// actuator manual overrides
} else if (cmd->actuatorMode == 0) {
cmd->actuatorCmd = false;
cmd->actuatorCmdReceived = true;
} else if (cmd->actuatorMode == 1) {
cmd->actuatorCmd = true;
cmd->actuatorCmdReceived = true;
}
// watering
// manual modes
if (cmd->waterHighMode == 0) {
cmd->waterHighCmd = false;
cmd->waterHighCmdReceived = true;
return;
}
if (cmd->waterHighMode == 1) {
cmd->waterHighCmd = true;
cmd->waterHighCmdReceived = true;
return;
}
// automode:
// check the hard limits
if (soilMoist[0] < minWaterMoist) {
cmd->waterHighCmd = true;
cmd->waterHighCmdReceived = true;
return;
}
if (soilMoist[0] > maxWaterMoist) {
cmd->waterHighCmd = false;
cmd->waterHighCmdReceived = true;
return;
}
// min temp to water
if (temp[0] < waterTempRange[0]) {
cmd->waterHighCmd = false;
cmd->waterHighCmdReceived = true;
return;
}
// find the temp range
for (int i= 0; i<=sizeof(waterTempRange); i++) {
if (temp[0] < waterTempRange[i+1]) {
//float ratio = waterTempRange[i];
if (currentMillis - previousCmdStart >= waterCycleSeconds) {
previousCmdStart = currentMillis;
}
if ((currentMillis - previousCmdStart)*100/waterCycleSeconds < waterTempRange[i]) { // offset / cycle < ratio
cmd->waterHighCmd = true;
} else {
cmd->waterHighCmd = false;
}
cmd->waterHighCmdReceived = true;
return;
}
}
// handle what happens here?
}
String printSign(float number) {
String sign = "+";
if (number < 0) {
sign = "";
}
return sign;
}
String printOpenClosed(bool state, bool move) {
if (state == true && move == false) {
return "OPEN";
} else if (state == false && move == false) {
return "CLOSED";
} else if (state == true && move == true) {
return "OPENING";
} else {
return "CLOSING";
}
}
String printMoveProgress(unsigned long elapsedT, unsigned long totalT) {
unsigned long progress = elapsedT*100/totalT;
return String(progress) + "%";
}
void printMenu(int currentSelected) {
String cursor = "*";
if (editMode == true) {
cursor = "?";
}
if (currentSelected == 0) {
P(cursor + "CLOS OPEN AUTO");
} else if (currentSelected == 1) {
P("CLOS " + cursor + "OPEN AUTO");
} else if (currentSelected == 2) {
P("CLOS OPEN " + cursor + "AUTO");
}
}
// generic string print
void P(String data) {
lcd.print(data);
}
// generic float print with decimal places
void PN(float value, int precision) {
lcd.print(value, precision);
}
// returns number of chars occupied by value and precision
int NumChars(float value, int precision) {
int d = 0;
if (value <= -100 ) {
d = 4;
} else if (value <= 10) {
d = 3;
} else if (value < 0) {
d = 2;
} else if (value < 10) {
d = 1;
} else if (value < 100) {
d = 2;
} else {
d = 3;
}
if (precision == 0) {
return d;
} else {
return d+precision+1;
}
}
void updateLCD() {
lcd.clear();
// indoor sensors
if (screenNum == 1) {
P("Inside:");
lcd.setCursor(0, 1);
P("Temp: ");
lcd.setCursor(18-NumChars(temp[0], 1), 1);
P(printSign(temp[0]));
PN(temp[0], 1);
P("C");
lcd.setCursor(0, 2);
P("Humidity:");
lcd.setCursor(18-NumChars(airHum[0], 0), 2);
PN(airHum[0], 0);
P("%");
lcd.setCursor(0, 3);
P("Soil: ");
lcd.setCursor(18-NumChars(soilMoist[0], 0), 3);
PN(soilMoist[0], 0);
P("%");
}
// indoor water control
if (screenNum == 2) {
P("IN: Water: ");
P(printOpenClosed(waterLowState, false));
lcd.setCursor(0, 1);
printMenu(cmd.waterLowMenuChoice);
}
// indoor window control
if (screenNum == 3) {
P("IN: Window: ");
lcd.setCursor(0, 1);
P(printOpenClosed(actuatorState, actuatorMoving));
P(" ");
if (actuatorMoving) {
P(printMoveProgress(millis()-actuatorTimeCounter, actuatorMoveTime));
}
lcd.setCursor(0, 2);
printMenu(cmd.actuatorMenuChoice);
}
// outdoor sensors
if (screenNum == 4) {
P("OUT: ");
P(printSign(temp[1]));
PN(temp[1], 1);
P(" ");
PN(airHum[1], 0);
P("%");
lcd.setCursor(0, 1);
P("Soil: ");
PN(soilMoist[1], 0);
P("%");
}
// outdoor water control
if (screenNum == 5) {
P("OUT: Water: ");
lcd.setCursor(0, 1);
P(printOpenClosed(valveState, valveMoving));
P(" ");
if (valveMoving) {
P(printMoveProgress(millis()-valveTimeCounter, valveMoveTime));
}
lcd.setCursor(0, 2);
printMenu(cmd.waterHighMenuChoice);
}
}
void btnBounce(int btnPin) {
while (digitalRead(btnPin) == LOW);
delay(200);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= readInterval) {
previousMillis = currentMillis;
dataReaders();
setCommand(&cmd);
controlActuators(&cmd);
controlValve(&cmd);
updateLCD();
}
// Up arrow button
if (digitalRead(upPin) == LOW) {
if (screenNum > 1 && editMode == false) {
screenNum = screenNum - 1;
}
if (editMode == true) {
if (screenNum == 2) {
cmd.waterLowMenuChoice--;
if (cmd.waterLowMenuChoice < 0) {
cmd.waterLowMenuChoice = 2;
}
} else if (screenNum == 3) {
cmd.actuatorMenuChoice--;
if (cmd.actuatorMenuChoice < 0) {
cmd.actuatorMenuChoice = 2;
}
} else if (screenNum == 5) {
cmd.waterHighMenuChoice--;
if (cmd.waterHighMenuChoice < 0) {
cmd.waterHighMenuChoice = 2;
}
}
}
updateLCD();
btnBounce(upPin);
}
// Down arrow button
if (digitalRead(downPin) == LOW) {
if (screenNum < 5 && editMode == false) {
screenNum = screenNum + 1;
}
if (editMode == true) {
if (screenNum == 2) {
cmd.waterLowMenuChoice++;
if (cmd.waterLowMenuChoice >= 3) {
cmd.waterLowMenuChoice = 0;
}
} else if (screenNum == 3) {
cmd.actuatorMenuChoice++;
if (cmd.actuatorMenuChoice >= 3) {
cmd.actuatorMenuChoice = 0;
}
} else if (screenNum == 5) {
cmd.waterHighMenuChoice++;
if (cmd.waterHighMenuChoice >= 3) {
cmd.waterHighMenuChoice = 0;
}
}
}
updateLCD();
btnBounce(downPin);
}
// Select button
if (digitalRead(selectPin) == LOW) {
if (screenNum == 2 || screenNum == 3 || screenNum == 5) {
if (editMode == false) {
editMode = true;
} else {
editMode = false;
if (screenNum == 2) {
cmd.waterLowMode = cmd.waterLowMenuChoice;
cmd.waterLowCmdReceived = true;
} else if (screenNum == 3) {
cmd.actuatorMode = cmd.actuatorMenuChoice;
cmd.actuatorCmdReceived = true;
} else if(screenNum == 5) {
cmd.waterHighMode = cmd.waterHighMenuChoice;
cmd.waterHighCmdReceived = true;
}
}
updateLCD();
btnBounce(selectPin);
}
}
}