{"id":209,"date":"2015-01-31T00:11:19","date_gmt":"2015-01-31T00:11:19","guid":{"rendered":"http:\/\/coffeefueledcreations.com\/blog\/?p=209"},"modified":"2016-09-26T16:59:13","modified_gmt":"2016-09-26T15:59:13","slug":"mr-greens-greenhouse-waterer","status":"publish","type":"post","link":"http:\/\/coffeefueledcreations.com\/blog\/?p=209","title":{"rendered":"Mr Green&#8217;s Greenhouse Waterer"},"content":{"rendered":"<p><a href=\"http:\/\/46.32.240.35\/coffeefueledcreations.com\/blog\/wp-content\/uploads\/2015\/01\/IMG_20150130_235450_713.jpg\"><img loading=\"lazy\" class=\"aligncenter size-large wp-image-225\" src=\"http:\/\/46.32.240.35\/coffeefueledcreations.com\/blog\/wp-content\/uploads\/2015\/01\/IMG_20150130_235450_713-1024x577.jpg\" alt=\"Jpeg\" width=\"960\" height=\"541\" srcset=\"http:\/\/coffeefueledcreations.com\/blog\/wp-content\/uploads\/2015\/01\/IMG_20150130_235450_713-1024x577.jpg 1024w, http:\/\/coffeefueledcreations.com\/blog\/wp-content\/uploads\/2015\/01\/IMG_20150130_235450_713-300x169.jpg 300w\" sizes=\"(max-width: 960px) 100vw, 960px\" \/><\/a><\/p>\n<p>Over the past couple of months I&#8217;ve been tinkering, now and then, with\u00a0an automatic greenhouse waterer for my dad. Hopefully in time for summer. I &#8220;think&#8221; I have a working prototype. <span style=\"color: #999999;\"><em>(I&#8217;ll add to this post and clean up the code when the first prototype is set-up in the greenhouse and working).<\/em><\/span><\/p>\n<h2>Features:<\/h2>\n<p>Mini Menu for Customization.<br \/>\nSettings saved between boots (EEPROM).<br \/>\nDisplays Temperature\/Humidity.<br \/>\nTurns off the display to save a little power.<br \/>\nGravity fed (or hosepipe water source I think)<br \/>\nShort Parts List and cheapish.<!--more--><\/p>\n<h2>Parts List:<\/h2>\n<p>Arduino Uno Dev board.<br \/>\nSoil Moisture Detector.<br \/>\n5v\/12v Relay.<br \/>\n12v and 5v supplies.<br \/>\n12v Solenoid water valve.<br \/>\nDHT11 Sensor.<br \/>\n16&#215;2 LCD.<br \/>\nRotary Encoder(With button) + Knob.<br \/>\n5 x Resistors.<\/p>\n<h2>Pin Set-up:<\/h2>\n<p>{Image of set-up to come and finish desc}<\/p>\n<p>Analogue moisture sensor To Analogue Pin 0<br \/>\nLCD Backlight (resistor) To Pin 6<br \/>\nDHT11 To Pin 7<br \/>\nRelay To Pin 9<br \/>\nRotary encoder button To Pin 13<br \/>\nRotary encoder pulse (A) To Pin 8<br \/>\nRotary encoder pulse (B) To Pin 10<br \/>\nLCD RS\u00a0To Pin 12<br \/>\nLCD Enable To Pin 11<br \/>\nLCD D4 To Pin 5<br \/>\nLCD D5 To Pin 4<br \/>\nLCD D6 To Pin 3<br \/>\nLCD D7 To Pin 2<\/p>\n<h2>Settings Explanation:<\/h2>\n<p>The settings menu lets you tune the system, hopefully making it more useful.<\/p>\n<h3>Set Watering Duration:<\/h3>\n<p>When the system decides that it&#8217;s time to water, this setting determines how long the relay\/valve will be held open.<\/p>\n<h3>Set Delay Between Watering:<\/h3>\n<p>When the previous water has completed this setting determines how long the system will wait before watering again. This will cause the system to ignore the soil moisture reading until this time has elapsed. This setting is to safeguard against constant watering.<\/p>\n<h3>Set Watering Point:<\/h3>\n<p>The watering point is a value between 0 and 1024. This is compared against the reading we receive from the analogue sensor. When altering the value of this setting the current reading is displayed. When setting up the system stick the moisture sensor in some soil with the desired moist-ness and setting the value to the reading shown.<\/p>\n<h3>Set Display Timeout:<\/h3>\n<p>How long the LCD will continue to display after the last piece of user interaction. Save some power if running off batteries.<\/p>\n<h2>The Code<\/h2>\n<pre class=\"prettyprint\"><code class=\"language-java\" style=\"max-height: 600px!important; overflow: auto; line-height: 15px;\">\/*\r\n** Includes\r\n*\/\r\n#include <LiquidCrystal.h>\r\n#include <dht11.h>\r\n#include <EEPROM.h>\r\n\r\n\/*\r\n** Definitions\r\n*\/\r\n#define MOISTUREONEPIN 0\r\n#define MENUITEMCOUNT 5\r\n#define LCDBACKLIGHT 6\r\n#define DHT11PIN 7\r\n#define RELAYPIN 9\r\n#define PUSHBUTTON 13\r\n\r\n\/*\r\n** EEPROM Stuff\r\n** long values require 4 address' of 4 bytes\r\n*\/\r\n#define EEPROM_WATERINGDURATIONSTART 0\r\n#define EEPROM_WATERINGDELAYSTART 4\r\n#define EEPROM_DISPLAYTIMEOUT 8\r\n#define EEPROM_WATERINGPOINT 12\r\n\r\n\/*\r\n** Objects\r\n*\/\r\ndht11 DHT11;\r\nLiquidCrystal lcd(12, 11, 5, 4, 3, 2);\r\n\r\n\/*\r\n**Rotary Encoder stuff\r\n*\/\r\nunsigned long currentTime;\r\nunsigned long loopTime;\r\nunsigned long lastButtonPress;\r\nconst int pin_A = 8;  \r\nconst int pin_B = 10;  \r\nunsigned char encoder_A;\r\nunsigned char encoder_B;\r\nunsigned char encoder_A_prev=0;\r\n\r\n\/*\r\n** Settings Menu Stuff\r\n*\/\r\nboolean menuDirty = 0;\r\nint menuPosition = 0;\r\nboolean menu = 0;\r\nint option = -1;\r\nString optionArray[MENUITEMCOUNT] = {\r\n  \"Set Watering\",\"Set Delay\",\"Set Watering\",\"Set Display\",\"Exit\",};\r\nString optionArrayLineTwo[MENUITEMCOUNT] = {\r\n  \"Duration\",\"Between Watering\",\"Point\",\"Timeout\",\"\",};\r\n\r\n\/*\r\n**Watering Stuff\r\n*\/\r\nboolean wateringState = 0;\/\/wether or not watering is currently enabled\r\nunsigned long wateringInitiated;\/\/When Watering was started\r\nunsigned long wateringFinished = 0;\/\/When Watering was started\r\nunsigned long wateringDurationRateOfChange = 10000;\/\/For the settings menu\r\nunsigned long wateringDuration = wateringDurationRateOfChange; \/\/How long we water for\r\nunsigned long minDurationBetweenWaterings = wateringDurationRateOfChange*10; \/\/After watering, how long to wait before bothing to water again\r\nlong wateringPoint = 800;\/\/0-1023\r\nint wateringPointRateOfChange = 10;\r\n\r\n\/*\r\n**Display Stuff\r\n*\/\r\nboolean displayEnabled = 0;\r\nunsigned long displayInitiated; \/\/When the display was started for timeout\r\nunsigned long displayDuration = 20000;\/\/ How long the display is active for\r\n\r\n\/*\r\n**Dynamic stepping velocity\r\n*\/\r\nunsigned long lastStepTime;\r\nboolean lastStepDirection; \/\/true = right\r\nint timeout = 500; \/\/after 100ms reset to default \r\nint stepCount = 0; \/\/5 steps each within timeout, double the velocity\r\nint stepMax = 3; \/\/5 steps each within timeout, double the velocity\r\nint velocityMultiplier = 1; \/\/ Current velocty multiplier\r\n\r\nvoid setup() {\r\n  \/\/Load all settings from EEPROM\r\n  initialLoadFromEEPROM();\r\n  \/\/ set up the LCD's number of columns and rows: \r\n  lcd.begin(16, 2);\r\n  \/\/ Print a message to the LCD.\r\n  lcd.setCursor(0, 0);\r\n  lcd.print(\"Mr Green's Green\");\r\n  lcd.setCursor(0, 1);\r\n  lcd.print(\"House Waterer\");\r\n\r\n  pinMode(LCDBACKLIGHT, OUTPUT);\r\n  pinMode(RELAYPIN, OUTPUT);\r\n  pinMode(PUSHBUTTON, INPUT);   \r\n  digitalWrite(LCDBACKLIGHT, HIGH);\r\n\r\n  int chk = DHT11.read(DHT11PIN);\r\n  switch (chk)\r\n  {\r\n  case DHTLIB_OK: \r\n    break;\r\n  case DHTLIB_ERROR_CHECKSUM: \r\n    lcd.clear();\r\n    lcd.setCursor(0, 0);\r\n    lcd.print(\"Checksum error\"); \r\n    delay(5000);\r\n    break;\r\n  case DHTLIB_ERROR_TIMEOUT: \r\n    lcd.clear();\r\n    lcd.setCursor(0, 0);\r\n    lcd.print(\"Time out error\"); \r\n    delay(5000);\r\n    break;\r\n  default: \r\n    lcd.clear();\r\n    lcd.setCursor(0, 0);\r\n    lcd.print(\"Unknown error\"); \r\n    delay(5000);\r\n    break;\r\n  }\r\n  delay(3000);\r\n\r\n  displayEnabled = 1;\r\n  displayInitiated = millis();\r\n}\r\n\r\nvoid loop() {\r\n  checkDisplayTimeout();\r\n  checkStepTimeout();\r\n\r\n  int buttonState = LOW;\r\n  if(wateringState == 1){ \/\/ if watering\r\n    rotaryEncoderStuff();\r\n    buttonState = digitalRead(PUSHBUTTON);\r\n    if((millis()-wateringInitiated) > wateringDuration){\r\n      lcd.clear();\r\n      lcd.setCursor(0, 0);\r\n      lcd.print(\"Watering\");\r\n      lcd.setCursor(0, 1);\r\n      lcd.print(\"Complete!\");\r\n      wateringState = 0;\r\n      wateringFinished = millis();\r\n      delay(500);\r\n    } \r\n    else {\r\n      lcd.setCursor(0, 0);\r\n      lcd.print(\"Watering ...\");\r\n      lcd.setCursor(0, 1);\r\n      lcd.print(millisToDHM(wateringDuration-(millis()-wateringInitiated))+\"     \"); \/\/update duration\r\n    }\r\n\r\n    if (buttonState == HIGH ) { \r\n      if(displayEnabled == 1){\r\n        wateringState = 0;\r\n        menu = 1;  \r\n        menuDirty = 1; \r\n        keepDisplayAlive();\r\n      } \r\n      else {\r\n        keepDisplayAlive();\r\n      }\r\n\r\n    }\r\n    delay(75);\r\n  } \r\n  else {\r\n    rotaryEncoderStuff();\r\n    if(millis() > (wateringFinished+minDurationBetweenWaterings) && wateringState == 0 || millis() <= wateringDuration &#038;&#038; wateringState == 0 ){\/\/enough time passed since last watering?\r\n\r\n      \/\/get soil moisture\r\n      int moistOne = analogRead(MOISTUREONEPIN);\r\n      \/\/int moistTwo = analogRead(MOISTURETWOPIN);\r\n\r\n      if(moistOne >= wateringPoint){ \/\/&& moistTwo >= wateringPoint)\r\n        if(menu == 0){ \/\/ Dont try and water while the user is attempting to use the menu\r\n          wateringState = 1;\/\/wether or not watering is enabled\r\n          wateringInitiated = millis();\r\n          if(displayEnabled == 1){\r\n            lcd.clear();\r\n            lcd.setCursor(0, 0);\r\n            lcd.print(\"Watering ...\");\r\n          }\r\n        } \r\n        else {\r\n          display(); \/\/ just display the menu, wait for the user to finish\r\n        }\r\n      } \r\n      else {\r\n        display();\r\n      }\r\n    } \r\n    else {\r\n      if(wateringState == 0){\r\n        display();\r\n      }\r\n    }\r\n\r\n    if(wateringState == 1){\r\n      digitalWrite(RELAYPIN, HIGH);\r\n    } \r\n    else {\r\n      digitalWrite(RELAYPIN, LOW);\r\n    }\r\n\r\n    buttonState = digitalRead(PUSHBUTTON);\r\n\r\n    \/\/ check if the pushbutton is pressed.\r\n    if (buttonState == HIGH && abs((millis()-lastButtonPress))>500) { \r\n      if(displayEnabled == 0){\r\n        keepDisplayAlive();\r\n      } \r\n      else {\r\n        keepDisplayAlive();\r\n        if(menu == 0){\r\n          menu = 1;\r\n          menuDirty = 1;\r\n        } \r\n        else {\r\n          switch(menuPosition){\r\n          case 0:\r\n            if(option == -1){ \/\/ nothing selected, select the submenu\r\n              option = menuPosition;\r\n              menuDirty = 1;\r\n            } \r\n            else { \/\/ returning from submenu to main menu, save value of item0(watering duration)\r\n              \/\/Save to eeprom and return\r\n              if(option == 0){\r\n                \/\/save to eeprom\r\n                EEPROMWritelong(EEPROM_WATERINGDELAYSTART, wateringDuration);\r\n              }\r\n              option = -1;\r\n              menuDirty = 1;\r\n            }\r\n            break;\r\n          case 1:\r\n            if(option == -1){\r\n              option = menuPosition;\r\n              menuDirty = 1;\r\n            } \r\n            else {\r\n              \/\/Save to eeprom and return\r\n              if(option == 1){\r\n                \/\/save to eeprom\r\n                EEPROMWritelong(EEPROM_WATERINGDURATIONSTART, minDurationBetweenWaterings);\r\n              }\r\n              \/\/Save to eeprom and return\r\n              option = -1;\r\n              menuDirty = 1;\r\n            }\r\n            break;\r\n          case 2:\r\n            if(option == -1){\r\n              option = menuPosition;\r\n              menuDirty = 1;\r\n            } \r\n            else {\r\n              \/\/Save to eeprom and return\r\n              if(option == 2){\r\n                \/\/save to eeprom\r\n                EEPROMWritelong(EEPROM_WATERINGPOINT, wateringPoint);\r\n              }\r\n              \/\/Save to eeprom and return\r\n              option = -1;\r\n              menuDirty = 1;\r\n            }\r\n            break;\r\n          case 3:\r\n            if(option == -1){\r\n              option = menuPosition;\r\n              menuDirty = 1;\r\n            } \r\n            else {\r\n              \/\/Save to eeprom and return\r\n              if(option == 3){\r\n                \/\/save to eeprm\r\n                EEPROMWritelong(EEPROM_DISPLAYTIMEOUT, displayDuration);\r\n              }\r\n              option = -1;\r\n              menuDirty = 1;\r\n            }\r\n            break;\r\n          case 4:\r\n            menu = 0;\r\n            menuPosition = 0;\r\n            break;\r\n          case 5:\r\n            break;\r\n          }\r\n        }\r\n        lastButtonPress = millis();\r\n        delay(150);\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\nvoid display(){\r\n  if(displayEnabled == 1){\/\/Display is enabled\r\n    if(menu){\r\n      if(option == -1){\r\n        if(menuDirty == 1){\r\n          menuDirty = 0;\r\n          if(displayEnabled == 1){\r\n            lcd.clear();\r\n            lcd.setCursor(0, 0);\r\n            lcd.print(optionArray[menuPosition]);\r\n            lcd.setCursor(0, 1);\r\n            lcd.print(optionArrayLineTwo[menuPosition]);\r\n          }\r\n          delay(5);\r\n        }  \r\n      } \r\n      else {\r\n        if(menuDirty == 1){\r\n          if(displayEnabled == 1){\r\n            lcd.clear();\r\n          }\r\n          menuDirty = 0;\r\n        }\r\n        switch (option) {\r\n        case 0:\r\n          {\r\n            if(displayEnabled == 1){\r\n              lcd.setCursor(0, 0);\r\n              lcd.print(\"Water Duration\");\r\n              lcd.setCursor(0, 1);\r\n              lcd.print(millisToDHM(wateringDuration));\r\n            }\r\n          }\r\n          break;\r\n        case 1:\r\n          {\/\/do something when var equals 2\r\n            if(displayEnabled == 1){\r\n              int wminutes = ((minDurationBetweenWaterings\/1000)\/60);\r\n              int wseconds = ((minDurationBetweenWaterings\/1000)%60);\r\n              lcd.setCursor(0, 0);\r\n              lcd.print(\"Water Min Delay\");\r\n              lcd.setCursor(0, 1);\r\n              lcd.print(millisToDHM(minDurationBetweenWaterings));\r\n            }\r\n          }\r\n          break;\r\n        case 2:\r\n          if(displayEnabled == 1){\r\n            int an = analogRead(MOISTUREONEPIN);\r\n            lcd.setCursor(0, 0);\r\n            lcd.print(\"Threshold: \");\r\n            lcd.print(wateringPoint);\r\n            lcd.setCursor(0, 1);\r\n            lcd.print(\"Current:\");\r\n            lcd.print(an);\r\n          }\r\n          break;\r\n        case 3:\r\n          if(displayEnabled == 1){\r\n            lcd.setCursor(0, 0);\r\n            lcd.print(\"Display Timeout\");\r\n            lcd.setCursor(0, 1);\r\n            lcd.print(millisToDHM(displayDuration));\r\n          }\r\n          break;\r\n        default: \r\n          \/\/ if nothing else matches, do the default\r\n          \/\/ default is optional\r\n          break;\r\n        }\r\n      }\r\n    } \r\n    else {\r\n      DHT11.read(DHT11PIN);\r\n      float humid = (float)DHT11.humidity;\r\n      float temp = (float)DHT11.temperature;\r\n      if(displayEnabled == 1){\r\n        lcd.clear();\r\n        lcd.setCursor(0, 0);\r\n        lcd.print(\"H\");\r\n        lcd.print(humid);\r\n        lcd.print(\"% T\");\r\n        lcd.print(temp);\r\n        lcd.print(\"C\");\r\n        lcd.setCursor(0, 1);\r\n        lcd.print(\"Lst Wtr:\");\r\n        lcd.print(millisToDHM(millis()-wateringFinished));\r\n      }\r\n      delay(1000);\r\n    }\r\n  }\r\n}\r\n\r\nvoid rotaryEncoderStuff(){\r\n  \/\/ get the current elapsed time\r\n  currentTime = millis();\r\n  if(currentTime >= (loopTime + 5)){\r\n    \/\/ 5ms since last check of encoder = 200Hz  \r\n    encoder_A = digitalRead(pin_A);    \/\/ Read encoder pins\r\n    encoder_B = digitalRead(pin_B);   \r\n    if((!encoder_A) && (encoder_A_prev)){\r\n      \/\/ A has gone from high to low \r\n      if(encoder_B) {\r\n        \/\/ B is high so clockwise\r\n        \/\/ increase\r\n        keepDisplayAlive();\r\n        next();\r\n      }   \r\n      else {\r\n        \/\/ B is low so counter-clockwise      \r\n        \/\/ decrease\r\n        keepDisplayAlive();\r\n        previous();\r\n      }   \r\n    }   \r\n    encoder_A_prev = encoder_A;     \/\/ Store value of A for next time    \r\n    loopTime = currentTime;  \/\/ Updates loopTime\r\n  }\r\n}\r\n\r\nvoid next(){\r\n  stepVelocity(true);\r\n  if(menu ==1){\r\n    if(option == -1){\/\/on main menu\r\n      if(menuPosition < MENUITEMCOUNT){\r\n        menuPosition++;\r\n      } \r\n      if(menuPosition == MENUITEMCOUNT){\r\n        menuPosition = 0; \r\n      }\r\n      menuDirty = 1;\r\n    } \r\n    else {\r\n      if(option == 0){\/\/watering duration\r\n        if((wateringDuration+(wateringDurationRateOfChange*velocityMultiplier))>=2147483647){\r\n          wateringDuration = 2147483646;\/\/max long\r\n        } \r\n        else {\r\n          wateringDuration += (wateringDurationRateOfChange*velocityMultiplier);\r\n        }\r\n        menuDirty = 1;\r\n      }\r\n      if(option == 1){\/\/watering delay\r\n        if(minDurationBetweenWaterings+(wateringDurationRateOfChange*velocityMultiplier)>=2147483647){\r\n          minDurationBetweenWaterings = 2147483646;\/\/max long\r\n        } \r\n        else {\r\n          minDurationBetweenWaterings += (wateringDurationRateOfChange*velocityMultiplier);\r\n        }\r\n        menuDirty = 1;\r\n      }\r\n      if(option == 2){\/\/Watering Point\r\n        if((wateringPoint+wateringPointRateOfChange) >=1023){\r\n          wateringPoint = 1023;\r\n        } \r\n        else {\r\n          wateringPoint += wateringPointRateOfChange;\r\n        }\r\n        menuDirty = 1;\r\n      }\r\n\r\n      if(option == 3){\/\/Disable watering when freezing\r\n        if(displayDuration+wateringDurationRateOfChange>=2147483647){\r\n          displayDuration = 2147483646;\/\/max long\r\n        } \r\n        else {\r\n          displayDuration += (wateringDurationRateOfChange*velocityMultiplier);\r\n        }\r\n        menuDirty = 1;\r\n      }\r\n    }\r\n  }\r\n\r\n}\r\n\r\nvoid previous(){\r\n  stepVelocity(false);\r\n  if(menu ==1){\r\n    if(option == -1){\/\/on main menu\r\n      if(menuPosition == 0){\r\n        menuPosition = MENUITEMCOUNT-1; \r\n      } \r\n      else {\r\n        if(menuPosition > 0){\r\n          menuPosition--;\r\n        }\r\n      }\r\n      menuDirty = 1;\r\n    } \r\n    else {\r\n      if(option == 0){\/\/watering duration\r\n\r\n        if(wateringDuration<=(wateringDurationRateOfChange*velocityMultiplier)){\r\n          wateringDuration = wateringDurationRateOfChange;\/\/max long\r\n        } \r\n        else {\r\n          wateringDuration -= (wateringDurationRateOfChange*velocityMultiplier);\r\n        }\r\n        menuDirty = 1;\r\n\r\n      }\r\n      if(option == 1){\/\/watering delay\r\n        if(minDurationBetweenWaterings<=(wateringDurationRateOfChange*velocityMultiplier)){\r\n          minDurationBetweenWaterings = wateringDurationRateOfChange;\/\/max long\r\n        } \r\n        else {\r\n          minDurationBetweenWaterings -= (wateringDurationRateOfChange*velocityMultiplier);\r\n        }\r\n        menuDirty = 1;\r\n\r\n\r\n      }\r\n      if(option == 2){\/\/watering point\r\n\r\n        if((wateringPoint-wateringPointRateOfChange) <0){\r\n          wateringPoint = 0;\r\n        } \r\n        else {\r\n          wateringPoint-=wateringPointRateOfChange;\r\n        }\r\nmenuDirty = 1;\r\n      }\r\n\r\n      if(option == 3){\/\/Disable watering when freezing\r\n        if(displayDuration-(wateringDurationRateOfChange*velocityMultiplier)<=10000){\r\n          displayDuration = 10000;\/\/max long\r\n        } \r\n        else {\r\n          displayDuration -= (wateringDurationRateOfChange*velocityMultiplier);\r\n        }\r\n        menuDirty = 1;\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\nvoid stepVelocity(boolean right){\/\/true right\r\n\r\n\r\n    if(lastStepDirection == right){\/\/we are going the same direction\r\n       stepCount++;  \r\n       lastStepTime = millis();\r\n    } else {\r\n      velocityMultiplier = 1;\r\n      lastStepDirection = right;\r\n      stepCount = 0;\r\n      lastStepTime = millis();\r\n    }\r\n\r\n}\r\n\r\nvoid checkStepTimeout(){\r\n  \r\n  if(stepCount > 0){\r\n    if((millis()-lastStepTime)>timeout ){\r\n      stepCount = 0;\r\n      velocityMultiplier = 1;\r\n    } else {\r\n    \r\n      if(stepCount >= stepMax){\r\n        if(velocityMultiplier>100){\r\n        stepCount = 0;\r\n        }\r\n        else {\r\n        velocityMultiplier = (velocityMultiplier*2);\r\n        stepCount = 0;\r\n        }\r\n      }\r\n      \r\n    }\r\n    \r\n  }\r\n\r\n}\r\n\r\nString millisToDHM(long mils){\r\n  {\r\n    String result = \"\";\r\n    int hours = (((mils\/1000)\/60)\/60)%24;\r\n    int minutes = ((mils\/1000)\/60)%60;\r\n    int days = ((((mils \/ 1000)\/60)\/60)\/24);\r\n    int seconds = (mils\/1000)%60;\r\n\r\n    if(days>0){\r\n      result +=days;\r\n      result +=\"d\";\r\n    }\r\n    if(hours>0){\r\n      result +=hours;\r\n      result +=\"h\";\r\n    }\r\n    if(minutes>0){\r\n      result +=minutes;\r\n      result +=\"m\";\r\n    }\r\n    if(seconds>0){\r\n      result +=seconds;\r\n      result +=\"s\";\r\n    }\r\n\r\n    if(result == \"\"){\r\n      return \"0s\";\r\n    }\r\n    else{\r\n      return result;\r\n    }\r\n  } \r\n\r\n}\r\n\r\nvoid keepDisplayAlive(){\r\n  displayInitiated = millis();\r\n}\r\n\r\nvoid checkDisplayTimeout(){\r\n\r\n  if(displayEnabled == 0){\/\/display is off\r\n    if((millis()-displayInitiated) < displayDuration){\r\n      digitalWrite(LCDBACKLIGHT, HIGH);\r\n\r\n      lcd.display();\r\n      lcd.clear();\r\n      displayEnabled = 1;\r\n    }\r\n  } \r\n  else {\r\n    if((millis()-displayInitiated) > displayDuration){\r\n      lcd.noDisplay();\r\n      digitalWrite(LCDBACKLIGHT, LOW);\r\n      menuDirty = 0;\r\n      menuPosition = 0;\r\n      menu = 0;\r\n      option = -1;\r\n      displayEnabled = 0;  \r\n    } \r\n  }\r\n}\r\n\r\n\/*\r\n** EEPROM Section\r\n*\/\r\n\r\nvoid initialLoadFromEEPROM(){\r\n  long _displayDuration = EEPROMReadlong(EEPROM_DISPLAYTIMEOUT);\r\n  long _wateringPoint = EEPROMReadlong(EEPROM_WATERINGPOINT);\r\n  long _minDurationBetweenWaterings =EEPROMReadlong(EEPROM_WATERINGDURATIONSTART);\r\n  long _wateringDuration = EEPROMReadlong(EEPROM_WATERINGDELAYSTART);\r\n  \r\n  if(_displayDuration < 2000000000 &#038;&#038; _displayDuration >= 10){\r\n    displayDuration = _displayDuration;\r\n  } else {\r\n    EEPROMWritelong(EEPROM_DISPLAYTIMEOUT, displayDuration);\r\n  }\r\n  if(_wateringPoint< 2000000000 &#038;&#038; _wateringPoint >= 10){\r\n    wateringPoint = _wateringPoint;\r\n  } else {\r\n    EEPROMWritelong(EEPROM_WATERINGPOINT, wateringPoint);\r\n  }\r\n  if(_minDurationBetweenWaterings< 2000000000 &#038;&#038; _minDurationBetweenWaterings >= 10){\r\n    minDurationBetweenWaterings = _minDurationBetweenWaterings;\r\n  } else {\r\n    EEPROMWritelong(EEPROM_WATERINGDELAYSTART, minDurationBetweenWaterings);\r\n  }\r\n  if(_wateringDuration< 2000000000 &#038;&#038; _wateringDuration >= 10){\r\n    wateringDuration = _wateringDuration;\r\n  } else {\r\n    EEPROMWritelong(EEPROM_WATERINGDURATIONSTART, wateringDuration);\r\n  }\r\n}\r\n\r\n\/\/This function will write a 4 byte (32bit) long to the eeprom at\r\n\/\/the specified address to adress + 3.\r\nvoid EEPROMWritelong(int address, long value)\r\n{\r\n  \/\/Decomposition from a long to 4 bytes by using bitshift.\r\n  \/\/One = Most significant -> Four = Least significant byte\r\n  byte four = (value & 0xFF);\r\n  byte three = ((value >> 8) & 0xFF);\r\n  byte two = ((value >> 16) & 0xFF);\r\n  byte one = ((value >> 24) & 0xFF);\r\n\r\n  \/\/Write the 4 bytes into the eeprom memory.\r\n  EEPROM.write(address, four);\r\n  EEPROM.write(address + 1, three);\r\n  EEPROM.write(address + 2, two);\r\n  EEPROM.write(address + 3, one);\r\n}\r\n\r\nlong EEPROMReadlong(long address)\r\n{\r\n  \/\/Read the 4 bytes from the eeprom memory.\r\n  long four = EEPROM.read(address);\r\n  long three = EEPROM.read(address + 1);\r\n  long two = EEPROM.read(address + 2);\r\n  long one = EEPROM.read(address + 3);\r\n\r\n  \/\/Return the recomposed long by using bitshift.\r\n  return ((four << 0) &#038; 0xFF) + ((three << 8) &#038; 0xFFFF) + ((two << 16) &#038; 0xFFFFFF) + ((one << 24) &#038; 0xFFFFFFFF);\r\n}\r\n<\/code><\/pre>\n<p>Bug Fixes:<br \/>\n09\/01\/2015 - Save LCD Duration, Watering Point Increment\/Decrement, Flickering Home Screen.<\/p>\n<p>On a side note, box files make great temporary project cases.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Over the past couple of months I&#8217;ve been tinkering, now and then, with\u00a0an automatic greenhouse waterer for my dad. Hopefully in time for summer. I &#8220;think&#8221; I have a working prototype. (I&#8217;ll add to this post and clean up the &hellip; <a href=\"http:\/\/coffeefueledcreations.com\/blog\/?p=209\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[3,8,11],"tags":[23,24,43,81],"_links":{"self":[{"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/209"}],"collection":[{"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=209"}],"version-history":[{"count":1,"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/209\/revisions"}],"predecessor-version":[{"id":324,"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/209\/revisions\/324"}],"wp:attachment":[{"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=209"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=209"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/coffeefueledcreations.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=209"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}