**
*******************************************************************************************************************************************/
// private static final int NUMBER_OF_TIMES_PER_WEEK_TO_WATER = 2;
- //TODO: Change these back to normal - this is just for testing to make it awake all the time
-// private static final int TIME_HOURS_TO_WATER_GRASS = 7; // 7 am
- private static final int TIME_HOURS_TO_WATER_GRASS = 3;
-// private static final int TIME_MINUTES_TO_WATER_GRASS = 30; // 30 minutes
- private static final int TIME_MINUTES_TO_WATER_GRASS = 30;
-// private static final int TIME_TO_RECOVER_GRASS_FOR = 8 * 24 * 60 * 60; // 8 days
- private static final int TIME_TO_RECOVER_GRASS_FOR = 10;
-// private static final int TIME_TO_HIBERNATE_GRASS_FOR = 30 * 24 * 60 * 60; // 30 days
- private static final int TIME_TO_HIBERNATE_GRASS_FOR = 10;
+ private static final int TIME_HOURS_TO_WATER_GRASS = 7; // 7 am
+ private static final int TIME_MINUTES_TO_WATER_GRASS = 30; // 30 minutes
+ private static final int TIME_TO_RECOVER_GRASS_FOR = 8 * 24 * 60 * 60; // 8 days
+ private static final int TIME_TO_HIBERNATE_GRASS_FOR = 30 * 24 * 60 * 60; // 30 days
public static final int CAMERA_FPS = 15; // In frames per second
// Seconds since the start of the day to start the watering
long secondsForWateringStart = (TIME_HOURS_TO_WATER_GRASS * 3600) + (TIME_MINUTES_TO_WATER_GRASS * 60);
-// System.out.println("beginingOfToday " + beginingOfToday);
-// System.out.println("secondsSinceStartOfDay " + secondsSinceStartOfDay);
-// System.out.println("secondsForWateringStart " + secondsForWateringStart);
+ System.out.println("beginingOfToday " + beginingOfToday);
+ System.out.println("secondsSinceStartOfDay " + secondsSinceStartOfDay);
+ System.out.println("secondsForWateringStart " + secondsForWateringStart);
// check if the current time is within the start watering interval
- /*if ((secondsSinceStartOfDay < secondsForWateringStart) || (secondsSinceStartOfDay > (secondsForWateringStart + (60 * 60)))) {
+ if ((secondsSinceStartOfDay < secondsForWateringStart) || (secondsSinceStartOfDay > (secondsForWateringStart + (60 * 60)))) {
System.out.println("Sleep for 10 minutes.. ");
try {
//Thread.sleep(10 * 60 * 1000); // sleep for 10 minutes
}
continue;
- }*/
+ }
// check if we already checked if we should water today
// we only need to do this once per day
- /*if ((dayOfLastCheck == currentDate.getDate()) && (monthOfLastCheck == currentDate.getMonth())) {
+ if ((dayOfLastCheck == currentDate.getDate()) && (monthOfLastCheck == currentDate.getMonth())) {
System.out.println("Sleep for 1 hour...");
try {
Thread.sleep(60 * 60 * 1000); // sleep for an hour
}
continue;
- }*/
+ }
// we decided to check if we should water today so save the fact that we chose to water on this day
dayOfLastCheck = currentDate.getDate();
}
// check if we are in hibernation mode and do the correct loop action
if (isHibernationMode) {
-// System.out.println("Hibernation mode!");
+ System.out.println("Hibernation mode!");
// If we are in hibernation mode then use the hibernation loop code
wateringHibernationLoop(currentDate);
} else {
-// System.out.println("Normal mode!");
+ System.out.println("Normal mode!");
// Using the normal watering loop code
wateringNormalLoop(currentDate);
}
public void informationRetrieved(double _inchesPerWeek, int _weatherZipCode, int _daysToWaterOn, double _inchesPerMinute) {
System.out.println("DEBUG: Information is retrieved from phone!!!");
- /*try {
- // get the parameters that the interface (phone app) reads from the user
- inchesPerWeek = _wgw.getInchesPerWeek();
- weatherZipCode = _wgw.getWeatherZipCode();
- daysToWaterOn = _wgw.getDaysToWaterOn();
- inchesPerMinute.add(_wgw.getInchesPerMinute());
- } catch(RemoteException ex) {
- ex.printStackTrace();
- }*/
inchesPerWeek = _inchesPerWeek;
weatherZipCode = _weatherZipCode;
e.printStackTrace();
}
}
- // TODO: Use a phone input interface later
- /*inchesPerWeek = 20.00;
- weatherZipCode = 92612;
- daysToWaterOn = 255;
- inchesPerMinute.add(1.50);*/
System.out.println("DEBUG: inchesPerWeek: " + inchesPerWeek);
System.out.println("DEBUG: weatherZipCode: " + weatherZipCode);
// Setup the cameras, start them all and assign each one a motion detector
for (CameraSmart cam : cameraSet.values()) {
- //try {
- // initialize the camera, might need to setup some stuff internally
- cam.init();
- // set the camera parameters.
- cam.setFPS(CAMERA_FPS);
- cam.setResolution(Resolution.RES_VGA);
+ // initialize the camera, might need to setup some stuff internally
+ cam.init();
- // Start the camera (example is start the HTTP stream if it is a network camera)
- cam.start();
- System.out.println("DEBUG: Init camera! " + cam.toString());
- //} catch (RemoteException e) {
- // e.printStackTrace();
- //}
+ // set the camera parameters.
+ cam.setFPS(CAMERA_FPS);
+ cam.setResolution(Resolution.RES_VGA);
+ // Start the camera (example is start the HTTP stream if it is a network camera)
+ cam.start();
+ System.out.println("DEBUG: Init camera! " + cam.toString());
}
// counter so that we can match the lawn inches per min data with the specific lawn
Iterator camIt = cameras.iterator();
CameraSmart cam = (CameraSmart)camIt.next();
System.out.println("DEBUG: Registering callback to camera: " + cam.toString());
- //try {
- // setup the callback
- cam.registerCallback(mo);
- //} catch (RemoteException e) {
- // e.printStackTrace();
- //}
+ cam.registerCallback(mo);
}
// we also only need 1 sprinkler controller per lawn so grab the first one
// get the weather data for the next little bit
List<DayWeather> weatherData = weatherGrabber.getWeatherData();
- // TODO: Replace this with the WeatherGrabber.getWeatherData() above
-// List<DayWeather> weatherData = new ArrayList<DayWeather>();
// Go through each lawn and check if we should water it and if we should, water it
for (LawnState ls : lawns) {
// if we are in recovery mode then run the recovery action
// we are still in hibernation mode but we need to recover the grass
if (isInHibernationRecoveryMode) {
-// System.out.println("DEBUG: Recovery mode!");
+ System.out.println("DEBUG: Recovery mode!");
hibernationRecoveryLoop(_currentDate);
return;
}
// start recovery mode
isInHibernationRecoveryMode = true;
hibernationRecoveryModeStartDate = null;
-// System.out.println("DEBUG: We enter recovery mode for the first time!");
+ System.out.println("DEBUG: We enter recovery mode for the first time!");
// first cycle of recovery
hibernationRecoveryLoop(_currentDate);
return;
if (!lawnHasMotion) {
continue;
}
-// System.out.println("DEBUG: We water the lawn! (wateringHibernationLoop)");
+ System.out.println("DEBUG: We water the lawn! (wateringHibernationLoop)");
// water specific lawn since it has motion
waterLawn(ls, _currentDate, weatherData);
}
// we have been in recovery mode long enough
if (elapsedTime >= TIME_TO_RECOVER_GRASS_FOR) {
-// System.out.println("DEBUG: We have been in recovery mode long enough!");
+ System.out.println("DEBUG: We have been in recovery mode long enough!");
// reset the recovery mode
isInHibernationRecoveryMode = false;
hibernationRecoveryModeStartDate = null;
// Go through each lawn and check if we should water it and if we should, water it
for (LawnState ls : lawns) {
-// System.out.println("DEBUG: We water the lawn! (hibernationRecoveryLoop)");
+ System.out.println("DEBUG: We water the lawn! (hibernationRecoveryLoop)");
// water specific lawn since it has motion
waterLawn(ls, _currentDate, weatherData);
}
// check if today or tomorrow is a wet day
boolean todayIsWetDay = _weatherData.get(0).getIsWetDay();
boolean tomorrowIsWetDay = _weatherData.get(1).getIsWetDay();
- // TODO: Remove this later - hack the values for now!!!
-// boolean todayIsWetDay = false;
-// boolean tomorrowIsWetDay = false;
-
// lawn cannot wait anymore for water so water not
boolean lawnNeedsWaterNow = _ls.needsWateringUrgently(_currentDate);
if (lawnNeedsWaterNow) {
System.out.println("DEBUG: Is wet day? " + todayIsWetDay);
System.out.println("DEBUG: Tomorrow is wet day? " + tomorrowIsWetDay);
// if it is not going to rain today then water the lawn
- // TODO: Put this back to uncommented!!! Only for testing!!!
-// if (!todayIsWetDay) {
+ if (!todayIsWetDay) {
_ls.waterLawn(_currentDate);
-// }
+ }
return;
}
public boolean needsWateringUrgently(Date _currentDate) {
// get difference between now and last time watered
- // TODO: Remove this to uncommented!!! This is only for testing!!!
-/* long timeElapsed = (_currentDate.getTime() - lastTimeWatered.getTime()) / 1000;
+ long timeElapsed = (_currentDate.getTime() - lastTimeWatered.getTime()) / 1000;
// needs watering now urgently
if (timeElapsed >= MAX_TIME_BETWEEN_WATERING_SESSIONS) {
// calculate the average moisture readings of all the
// sensors in this lawn
double averageMoistureValue = getAverageMoistureReading();
-
+ System.out.println("DEBUG: Average moisture value: " + averageMoistureValue);
// is a valid average
if (averageMoistureValue != -1) {
// moisture is very low so we need to water now!
}
return false;
-*/
- double averageMoistureValue = getAverageMoistureReading();
-// System.out.println("DEBUG: Average moisture value: " + averageMoistureValue);
-
- return true;
}
System.out.println("DEBUG: Current time stamp: " + currentTime.getTime());
System.out.println("Time elapsed: " + (currentTime.getTime() - readingTimestamp.getTime()) / 1000);
- //long timeElapsedSinceLastWatering = (currentTime.getTime() - readingTimestamp.getTime()) / 1000;
+ long timeElapsedSinceLastWatering = (currentTime.getTime() - readingTimestamp.getTime()) / 1000;
// if reading is old then dont use it since it is noise
- //if (timeElapsedSinceLastWatering > TWO_HOURS) {
- // continue;
- //}
+ if (timeElapsedSinceLastWatering > TWO_HOURS) {
+ continue;
+ }
// Do averaging
numberOfReadings++;
--- /dev/null
+package IrrigationController;
+// Standard Java Packages
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+// RMI packages
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+// IoT Runtime Packages
+import iotruntime.slave.IoTSet;
+import iotruntime.slave.IoTRelation;
+import iotruntime.slave.IoTAddress;
+import iotcode.annotation.*;
+
+// IoT Driver Packages
+import iotcode.interfaces.*;
+import iotcode.WeatherPhoneGateway.*;
+
+public class IrrigationController extends UnicastRemoteObject implements WeatherGatewayCallback {
+
+
+ /*******************************************************************************************************************************************
+ **
+ ** Constants
+ **
+ *******************************************************************************************************************************************/
+ // private static final int NUMBER_OF_TIMES_PER_WEEK_TO_WATER = 2;
+ //TODO: Change these back to normal - this is just for testing to make it awake all the time
+// private static final int TIME_HOURS_TO_WATER_GRASS = 7; // 7 am
+ private static final int TIME_HOURS_TO_WATER_GRASS = 3;
+// private static final int TIME_MINUTES_TO_WATER_GRASS = 30; // 30 minutes
+ private static final int TIME_MINUTES_TO_WATER_GRASS = 30;
+// private static final int TIME_TO_RECOVER_GRASS_FOR = 8 * 24 * 60 * 60; // 8 days
+ private static final int TIME_TO_RECOVER_GRASS_FOR = 10;
+// private static final int TIME_TO_HIBERNATE_GRASS_FOR = 30 * 24 * 60 * 60; // 30 days
+ private static final int TIME_TO_HIBERNATE_GRASS_FOR = 10;
+ public static final int CAMERA_FPS = 15; // In frames per second
+
+
+ /*******************************************************************************************************************************************
+ **
+ ** Variables
+ **
+ *******************************************************************************************************************************************/
+ private int dayOfLastCheck = -1;
+ private int monthOfLastCheck = -1;
+ private boolean isInHibernationRecoveryMode = false;
+ private Date hibernationRecoveryModeStartDate = null;
+ private boolean isHibernationMode = false;
+ private Date hibernationModeStartDate = null;
+ private List<LawnState> lawns = new ArrayList<LawnState>();
+ private WeatherGrabber weatherGrabber = null;
+
+ // used to block until gui is done and the settings are ready to be polled
+ private AtomicBoolean waitingForInterface = new AtomicBoolean(true);
+
+ // the settings from the interface, used to setup the system
+ private double inchesPerWeek = 0;
+ private int weatherZipCode = 0;
+ private int daysToWaterOn = 0;
+ private List<Double> inchesPerMinute;
+
+ private static int sensorId = 0;
+
+ /*******************************************************************************************************************************************
+ **
+ ** IoT Sets and Relations
+ **
+ *******************************************************************************************************************************************/
+ @config private IoTSet<IoTAddress> weatherDataAddresses;
+ @config private IoTSet<IoTAddress> weatherDataAddressMain;
+ @config private IoTSet<WeatherGatewaySmart> gwSet;
+ @config private IoTSet<LawnSmart> lawnSet;
+ @config private IoTSet<MoistureSensorSmart> moistureSensorsSet;
+ @config private IoTSet<CameraSmart> cameraSet;
+ @config private IoTRelation<LawnSmart, CameraSmart> lawnCameraRelation;
+ @config private IoTRelation<LawnSmart, SprinklerSmart> lawnSprinklerRelation;
+ @config private IoTRelation<LawnSmart, MoistureSensorSmart> lawnMoistureSensorRelation;
+
+
+ public IrrigationController() throws RemoteException {
+
+ }
+
+ /*******************************************************************************************************************************************
+ **
+ ** Public Methods
+ **
+ *******************************************************************************************************************************************/
+
+
+ /** Method to set whether the controller should maintain the lawns in hibernation mode
+ * or in normal mode. Lawns should be put in hibernation mode in drought conditions
+ *
+ * @param _hibMode [boolean] set the hibernation mode for this lawn controllers (true = hibernation)
+ *
+ * @return [void] None.
+ */
+ public void setHibernationMode(boolean _hibMode) {
+
+ // change hibernation mode status
+ isHibernationMode = _hibMode;
+
+ // set the start date for when we started this hibernation mode
+ if (_hibMode) {
+
+ // make sure we dont reset this cycle
+ if (!isHibernationMode) {
+ hibernationModeStartDate = new Date();
+ }
+ } else {
+ // reset all hibernation stuff
+ hibernationModeStartDate = null;
+ isInHibernationRecoveryMode = false;
+ hibernationRecoveryModeStartDate = null;
+ }
+ }
+
+ /** Method to start the controller and run the main control loop
+ *
+ * @return [void] None.
+ */
+ public void init() throws RemoteException {
+
+ // initialize the controller
+ initController();
+ System.out.println("Initialized controller!");
+
+ // Main Loop
+ while (true) {
+
+ // get the current time of day (date and time)
+ Date currentDate = new Date();
+
+ // get the epoch time till the beginning of the day
+ Date beginingOfToday = new Date(currentDate.getYear(), currentDate.getMonth(), currentDate.getDate());
+
+ // calculate the seconds since the start of the day.
+ long secondsSinceStartOfDay = (currentDate.getTime() - beginingOfToday.getTime()) / 1000;
+
+ // Seconds since the start of the day to start the watering
+ long secondsForWateringStart = (TIME_HOURS_TO_WATER_GRASS * 3600) + (TIME_MINUTES_TO_WATER_GRASS * 60);
+
+// System.out.println("beginingOfToday " + beginingOfToday);
+// System.out.println("secondsSinceStartOfDay " + secondsSinceStartOfDay);
+// System.out.println("secondsForWateringStart " + secondsForWateringStart);
+
+ // check if the current time is within the start watering interval
+ /*if ((secondsSinceStartOfDay < secondsForWateringStart) || (secondsSinceStartOfDay > (secondsForWateringStart + (60 * 60)))) {
+ System.out.println("Sleep for 10 minutes.. ");
+ try {
+ //Thread.sleep(10 * 60 * 1000); // sleep for 10 minutes
+ Thread.sleep(10); // sleep for 10 seconds
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ continue;
+ }*/
+
+ // check if we already checked if we should water today
+ // we only need to do this once per day
+ /*if ((dayOfLastCheck == currentDate.getDate()) && (monthOfLastCheck == currentDate.getMonth())) {
+ System.out.println("Sleep for 1 hour...");
+ try {
+ Thread.sleep(60 * 60 * 1000); // sleep for an hour
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ continue;
+ }*/
+
+ // we decided to check if we should water today so save the fact that we chose to water on this day
+ dayOfLastCheck = currentDate.getDate();
+ monthOfLastCheck = currentDate.getMonth();
+
+ // update the lawn states everyday
+ for (LawnState ls : lawns) {
+ ls.updateLawn(currentDate);
+ }
+ // check if we are in hibernation mode and do the correct loop action
+ if (isHibernationMode) {
+// System.out.println("Hibernation mode!");
+ // If we are in hibernation mode then use the hibernation loop code
+ wateringHibernationLoop(currentDate);
+ } else {
+// System.out.println("Normal mode!");
+ // Using the normal watering loop code
+ wateringNormalLoop(currentDate);
+ }
+ }
+ }
+
+
+ /** Callback method for when the information is retrieved.
+ *
+ * @param _inchesPerWeek [double].
+ * @param _weatherZipCode [int].
+ * @param _daysToWaterOn [int].
+ * @param _inchesPerMinute [double].
+ * @return [void] None.
+ */
+ public void informationRetrieved(double _inchesPerWeek, int _weatherZipCode, int _daysToWaterOn, double _inchesPerMinute) {
+
+ System.out.println("DEBUG: Information is retrieved from phone!!!");
+ /*try {
+ // get the parameters that the interface (phone app) reads from the user
+ inchesPerWeek = _wgw.getInchesPerWeek();
+ weatherZipCode = _wgw.getWeatherZipCode();
+ daysToWaterOn = _wgw.getDaysToWaterOn();
+ inchesPerMinute.add(_wgw.getInchesPerMinute());
+ } catch(RemoteException ex) {
+ ex.printStackTrace();
+ }*/
+
+ inchesPerWeek = _inchesPerWeek;
+ weatherZipCode = _weatherZipCode;
+ daysToWaterOn = _daysToWaterOn;
+ inchesPerMinute.add(_inchesPerMinute);
+
+ // the gui is done so release the spin wait that was waiting for the gui
+ waitingForInterface.set(false);
+ }
+
+ /*******************************************************************************************************************************************
+ **
+ ** Helper Methods
+ **
+ *******************************************************************************************************************************************/
+
+
+ /** Method to initialize the controller variables and all the drivers and such
+ *
+ * @return [void] None.
+ */
+ private void initController() throws RemoteException {
+
+ // Setup the weather grabber object with the correct address of the weather api
+ Iterator it = weatherDataAddresses.iterator();
+ weatherGrabber = new WeatherGrabber((IoTAddress)it.next());
+
+ // Initialize inchesPerMinute
+ inchesPerMinute = new ArrayList<Double>();
+
+ // We setup a Gateway object to get information from the phone app
+ for (WeatherGatewaySmart gw : gwSet.values()) {
+ gw.init();
+ gw.registerCallback(this);
+ gw.start();
+ }
+
+ System.out.println("DEBUG: Waiting for phone to send weather information");
+ while (waitingForInterface.get()) {
+ try {
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ // TODO: Use a phone input interface later
+ /*inchesPerWeek = 20.00;
+ weatherZipCode = 92612;
+ daysToWaterOn = 255;
+ inchesPerMinute.add(1.50);*/
+
+ System.out.println("DEBUG: inchesPerWeek: " + inchesPerWeek);
+ System.out.println("DEBUG: weatherZipCode: " + weatherZipCode);
+ System.out.println("DEBUG: daysToWaterOn: " + daysToWaterOn);
+ System.out.println("DEBUG: inchesPerMinute: " + inchesPerMinute.get(0));
+
+ // set the zip code and the the number of days of the weather grabber
+ // here the number of days is set to the max that the grabber supports
+ weatherGrabber.setZipcode(weatherZipCode);
+ weatherGrabber.setNumberOfDays(16);
+
+ // Setup the cameras, start them all and assign each one a motion detector
+ for (CameraSmart cam : cameraSet.values()) {
+
+ // initialize the camera, might need to setup some stuff internally
+ cam.init();
+
+ // set the camera parameters.
+ cam.setFPS(CAMERA_FPS);
+ cam.setResolution(Resolution.RES_VGA);
+
+ // Start the camera (example is start the HTTP stream if it is a network camera)
+ cam.start();
+ System.out.println("DEBUG: Init camera! " + cam.toString());
+ }
+
+ // counter so that we can match the lawn inches per min data with the specific lawn
+ int counter = 0;
+ for (LawnSmart l : lawnSet.values()) {
+ // create a motionDetector for each lawn object
+ MotionDetection mo = new MotionDetection(12, 0.5f, 10, 10);
+
+ // for 1 camera, if there are any then register the camera for that lawn
+ HashSet<CameraSmart> cameras = lawnCameraRelation.get(l);
+ System.out.println("DEBUG: Camera.size(): " + cameras.size());
+ if (cameras.size() >= 1) {
+
+ // we only need 1 camera per lawn so get the first one in the list
+ Iterator camIt = cameras.iterator();
+ CameraSmart cam = (CameraSmart)camIt.next();
+ System.out.println("DEBUG: Registering callback to camera: " + cam.toString());
+ //try {
+ // setup the callback
+ cam.registerCallback(mo);
+ //} catch (RemoteException e) {
+ // e.printStackTrace();
+ //}
+ }
+
+ // we also only need 1 sprinkler controller per lawn so grab the first one
+ HashSet<SprinklerSmart> sprinklers = lawnSprinklerRelation.get(l);
+ Iterator sprinklersIt = sprinklers.iterator();
+ SprinklerSmart spr = (SprinklerSmart)sprinklersIt.next();
+
+ // init the sprinkler controller, do it here since it only needs to be done once per controller
+ try {
+ spr.init();
+ // Wait until sprinkler is active
+ Thread.sleep(30000);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ System.out.println("DEBUG: Init sprinkler: " + spr.toString());
+
+ // get and init the moisture sensors for this specific lawn
+ HashSet<MoistureSensorSmart> sensors = lawnMoistureSensorRelation.get(l);
+ for (MoistureSensorSmart sen : sensors) {
+ System.out.println("DEBUG: Init sensors: " + sen.toString());
+ try {
+ sen.init();
+ sen.setId(sensorId++);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // create the lawn objects
+ System.out.println("DEBUG: Creating a LawnState object");
+ LawnState ls =
+ new LawnState(l, daysToWaterOn, mo, inchesPerMinute.get(counter), inchesPerWeek, spr, counter, sensors);
+ lawns.add(ls);
+
+ // dont forget to increment the counter
+ counter++;
+ }
+ }
+
+ /** Main loop for when the controller is watering the lawns under normal conditions, not in hibernation mode
+ *
+ * @param _currentDate [Date] current date
+ *
+ * @return [void] None.
+ */
+ private void wateringNormalLoop(Date _currentDate) {
+
+ // get the weather data for the next little bit
+ List<DayWeather> weatherData = weatherGrabber.getWeatherData();
+ // TODO: Replace this with the WeatherGrabber.getWeatherData() above
+// List<DayWeather> weatherData = new ArrayList<DayWeather>();
+
+ // Go through each lawn and check if we should water it and if we should, water it
+ for (LawnState ls : lawns) {
+
+ // water for specific lawn
+ waterLawn(ls, _currentDate, weatherData);
+ }
+ }
+
+ /** Main loop for when the controller is watering the lawns in hibernation mode
+ *
+ * @param _currentDate [Date] current date
+ *
+ * @return [void] None.
+ */
+ private void wateringHibernationLoop(Date _currentDate) {
+
+ // if we are in recovery mode then run the recovery action
+ // we are still in hibernation mode but we need to recover the grass
+ if (isInHibernationRecoveryMode) {
+// System.out.println("DEBUG: Recovery mode!");
+ hibernationRecoveryLoop(_currentDate);
+ return;
+ }
+
+ // check if we should enter recovery mode
+ long elapsedTime = (_currentDate.getTime() - hibernationModeStartDate.getTime()) / 1000;
+ if (elapsedTime >= TIME_TO_HIBERNATE_GRASS_FOR) {
+
+ // start recovery mode
+ isInHibernationRecoveryMode = true;
+ hibernationRecoveryModeStartDate = null;
+// System.out.println("DEBUG: We enter recovery mode for the first time!");
+ // first cycle of recovery
+ hibernationRecoveryLoop(_currentDate);
+ return;
+ }
+
+ // get the weather data for the next little bit
+ List<DayWeather> weatherData = weatherGrabber.getWeatherData();
+
+ // Go through each lawn and check if we should water it and if we should, water it
+ for (LawnState ls : lawns) {
+
+ boolean lawnHasMotion = ls.lawnHasSufficientMotion();
+
+ // there is no motion on the lawn so no need to water it
+ if (!lawnHasMotion) {
+ continue;
+ }
+// System.out.println("DEBUG: We water the lawn! (wateringHibernationLoop)");
+ // water specific lawn since it has motion
+ waterLawn(ls, _currentDate, weatherData);
+ }
+ }
+
+
+ /** Main loop for when the controller is watering the lawns in hibernation mode
+ *
+ * @param _currentDate [Date] current date
+ *
+ * @return [void] None.
+ */
+ private void hibernationRecoveryLoop(Date _currentDate) {
+
+ // start recovery mode if it wasnt started yet
+ if (hibernationRecoveryModeStartDate == null) {
+ hibernationRecoveryModeStartDate = _currentDate;
+ }
+
+ // time since this mode was started
+ long elapsedTime = (_currentDate.getTime() - hibernationRecoveryModeStartDate.getTime()) / 1000;
+
+ // we have been in recovery mode long enough
+ if (elapsedTime >= TIME_TO_RECOVER_GRASS_FOR) {
+
+// System.out.println("DEBUG: We have been in recovery mode long enough!");
+ // reset the recovery mode
+ isInHibernationRecoveryMode = false;
+ hibernationRecoveryModeStartDate = null;
+
+ // revived grass so restart the grass hibernation cycle
+ hibernationModeStartDate = _currentDate;
+
+ // do the hibernation loop since we are no longer in recovery mode
+ wateringHibernationLoop(_currentDate);
+ return;
+ }
+
+
+ // if we got here then we are trying to recover the grass
+
+ // get the weather data for the next little bit
+ List<DayWeather> weatherData = weatherGrabber.getWeatherData();
+
+ // Go through each lawn and check if we should water it and if we should, water it
+ for (LawnState ls : lawns) {
+
+// System.out.println("DEBUG: We water the lawn! (hibernationRecoveryLoop)");
+ // water specific lawn since it has motion
+ waterLawn(ls, _currentDate, weatherData);
+ }
+
+ }
+
+
+ /** Method for watering a specific lawn if it needs to be watered
+ *
+ * @param _ls [LawnState] lawn to water
+ * @param _currentDate [Date] current date
+ * @param _weatherData [List<DayWeather>] latest weather data
+ *
+ * @return [void] None.
+ */
+ private void waterLawn(LawnState _ls, Date _currentDate, List<DayWeather> _weatherData) {
+
+ // check if today or tomorrow is a wet day
+ boolean todayIsWetDay = _weatherData.get(0).getIsWetDay();
+ boolean tomorrowIsWetDay = _weatherData.get(1).getIsWetDay();
+ // TODO: Remove this later - hack the values for now!!!
+// boolean todayIsWetDay = false;
+// boolean tomorrowIsWetDay = false;
+
+ // lawn cannot wait anymore for water so water not
+ boolean lawnNeedsWaterNow = _ls.needsWateringUrgently(_currentDate);
+ if (lawnNeedsWaterNow) {
+ System.out.println("DEBUG: Need water now!!!");
+ System.out.println("DEBUG: Is wet day? " + todayIsWetDay);
+ System.out.println("DEBUG: Tomorrow is wet day? " + tomorrowIsWetDay);
+ // if it is not going to rain today then water the lawn
+ // TODO: Put this back to uncommented!!! Only for testing!!!
+// if (!todayIsWetDay) {
+ _ls.waterLawn(_currentDate);
+// }
+ return;
+ }
+
+ // check if this lawn needs watering based on watering algoritm/sensors/ext
+ boolean shouldWaterLawn = _ls.needsWatering(_currentDate);
+
+ // should not water this lawn then just skip to the next lawn
+ if (!shouldWaterLawn) {
+ return;
+ }
+
+ // it is going to rain soon so wait it out.
+ // Grass is not in critical condition so it can wait a bit.
+ if (todayIsWetDay || tomorrowIsWetDay) {
+ return;
+ }
+
+ // if we got here then we need to water the lawn
+ _ls.waterLawn(_currentDate);
+ }
+}
+
--- /dev/null
+package IrrigationController;
+
+// Standard Java Packages
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+
+// RMI packages
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+// IoT Driver Packages
+import iotcode.interfaces.*;
+
+// Checker annotations
+//import iotchecker.qual.*;
+
+/** Class LawnState that represents the state of the lawn, also help calculate if the lawn needs to be watered.
+ *
+ * @author Ali Younis <ayounis @ uci.edu>
+ * @version 1.0
+ * @since 2016-04-04
+ */
+
+public class LawnState extends UnicastRemoteObject implements MotionDetectionCallback, MoistureSensorCallback {
+
+
+ /*******************************************************************************************************************************************
+ **
+ ** Constants
+ **
+ *******************************************************************************************************************************************/
+ private static final long MAX_TIME_BETWEEN_WATERING_SESSIONS = 4 * 24 * 60 * 60; // 5 days
+ private static final int MAX_DAYS_RAINED_RECORD = 20;
+ private static final int RAINED_RECENTLY_DAYS_INTERVAL = 1;
+ private static final long TWENTY_FIVE_HOURS = 25 * 60 * 60;
+ private static final long TWO_HOURS = 2 * 60 * 60;
+
+ private static final long NEW_MOTION_THRESHOLD = 2 * 60; // 2 minutes
+ private static final long AMOUNT_OF_MOTION_FOR_ACTIVE = 60 * 60; // 1 hour
+ private static final long AMOUNT_OF_TIME_FOR_ACTIVE_TO_HOLD = 7 * 24 * 60 * 60; // 1 week
+
+ private static final double MOISTURE_LEVEL_FOR_NORMAL_WATERING = 25; // Percentage
+ private static final double MOISTURE_LEVEL_FOR_EMERGENCY_WATERING = 5; // Percentage
+ private static final double MOISTURE_LEVEL_FOR_NO_WATERING = 80; // Percentage
+
+ /*******************************************************************************************************************************************
+ **
+ ** Variables
+ **
+ *******************************************************************************************************************************************/
+ private boolean isInHibernationMode = false;
+ private Date lastTimeWatered = null;
+ private boolean didWaterSinceLastSchedualedDate = false;
+ private List<Date> daysRained = new ArrayList<Date>();
+ private int daysToWaterOn = 0;
+ private LawnSmart iotLawnObject;
+ private MotionDetection motionDetector;
+ private double inchesPerMinute = 0;
+ private double inchesPerWeek = 0;
+ private double timePerWatering = 0;
+ private double timePerWeek = 0;
+ private double timeWateredSoFar = 0;
+ private SprinklerSmart sprinkler;
+ private int zone = 0;
+ private Date lastMotionDetectedTime = null;
+ private Date startOfThisMotion = null;
+ private Date lastUpdateDate = null;
+ private Lock mutex = new ReentrantLock();
+ private long totalMotionOnLawn = 0;
+ private long numberOfMotionsOnLawnToday = 0;
+ private boolean lawnIsActive = false;
+ private Date lawnBecameActiceDate = null;
+ private Map<Integer, Double> moistureSensorReadings =
+ new ConcurrentHashMap<Integer, Double>();
+ private Map<Integer, Date> moistureSensorUpdateTimes =
+ new ConcurrentHashMap<Integer, Date>();
+
+
+ // 0th bit = Monday, 1th bit = Tuesday ext
+ public LawnState(LawnSmart _l, int _daysToWaterOn, MotionDetection _mo,
+ double _inchesPerMinute, double _inchesPerWeek, SprinklerSmart _sprinkler,
+ int _zone, Set<MoistureSensorSmart> _moistureSensors) throws RemoteException {
+ iotLawnObject = _l;
+ daysToWaterOn = _daysToWaterOn;
+ inchesPerMinute = _inchesPerMinute;
+ inchesPerWeek = _inchesPerWeek;
+ sprinkler = _sprinkler;
+ zone = _zone;
+
+ // register the callback with self
+ motionDetector = _mo;
+ _mo.registerCallback(this);
+
+ // register callback to self
+ for (MoistureSensorSmart sen : _moistureSensors) {
+
+ try {
+ sen.registerCallback(this);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // parse the days that we are going to water on
+ int numberOfDaysForWatering = 0;
+ for (int i = 0; i < 7; i++) {
+ if ((daysToWaterOn & (1 << i)) > 0) {
+ numberOfDaysForWatering++;
+ }
+ }
+
+ // calculate lawn watering water amounts
+ timePerWeek = _inchesPerWeek / _inchesPerMinute;
+ timePerWatering = timePerWeek / (double)numberOfDaysForWatering;
+ }
+
+ /*******************************************************************************************************************************************
+ **
+ ** Public Methods
+ **
+ *******************************************************************************************************************************************/
+
+
+ /** Method to update the lawn state, updates lawn activity state based on activity timeout
+ *
+ * @param _currentDate [Date], the current date and time.
+ *
+ * @return [void] None.
+ */
+ public void updateLawn(Date _currentDate) {
+ if (lastUpdateDate != null) {
+
+ // check if we already did an update today
+ if ((lastUpdateDate.getDate() == _currentDate.getDate())
+ && (lastUpdateDate.getMonth() == _currentDate.getMonth())
+ && (lastUpdateDate.getYear() == _currentDate.getYear())) {
+ return;
+ }
+ }
+
+ lastUpdateDate = _currentDate;
+
+ // lawn was active at some time so check if it can be deemed inactive because
+ // time has passed and it has not been active in that time
+ if (lawnBecameActiceDate != null) {
+ long timeElapsed = (_currentDate.getTime() - lawnBecameActiceDate.getTime()) / 1000;
+
+ if (timeElapsed >= AMOUNT_OF_TIME_FOR_ACTIVE_TO_HOLD) {
+ lawnBecameActiceDate = null;
+ lawnIsActive = false;
+ }
+ }
+
+
+ // check activity of lawn
+ boolean isActiveLawn = false;
+ try {
+ mutex.lock();
+ if (totalMotionOnLawn >= AMOUNT_OF_MOTION_FOR_ACTIVE) {
+ isActiveLawn = true;
+ }
+
+ // reset motion counters
+ totalMotionOnLawn = 0;
+ numberOfMotionsOnLawnToday = 0;
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ mutex.unlock();
+ }
+
+ // update lawn state
+ if (isActiveLawn) {
+ lawnIsActive = true;
+ lawnBecameActiceDate = _currentDate;
+ }
+ }
+
+
+ /** Method to test if this lawn is active or not.
+ *
+ * @return [Boolean] lawn is active.
+ */
+ public boolean lawnHasSufficientMotion() {
+ return lawnIsActive;
+ }
+
+
+ /** Method to test if this lawn should be watered or not right now.
+ * Lawn urgently needs to be watered right now.
+ *
+ * @param _currentDate [Date], the current date and time.
+ *
+ * @return [Boolean] lawn does need watering.
+ */
+ public boolean needsWateringUrgently(Date _currentDate) {
+
+ // get difference between now and last time watered
+ // TODO: Remove this to uncommented!!! This is only for testing!!!
+/* long timeElapsed = (_currentDate.getTime() - lastTimeWatered.getTime()) / 1000;
+
+ // needs watering now urgently
+ if (timeElapsed >= MAX_TIME_BETWEEN_WATERING_SESSIONS) {
+ return true;
+ }
+
+ // calculate the average moisture readings of all the
+ // sensors in this lawn
+ double averageMoistureValue = getAverageMoistureReading();
+
+ // is a valid average
+ if (averageMoistureValue != -1) {
+ // moisture is very low so we need to water now!
+ if (averageMoistureValue <= MOISTURE_LEVEL_FOR_EMERGENCY_WATERING) {
+ return true;
+ } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
+ // moisture is high so no need to water
+ return false;
+ }
+ }
+
+ return false;
+*/
+ double averageMoistureValue = getAverageMoistureReading();
+// System.out.println("DEBUG: Average moisture value: " + averageMoistureValue);
+
+ return true;
+ }
+
+
+ /** Method to test if this lawn should be watered or not
+ *
+ * @param _currentDate [Date], the current date and time.
+ *
+ * @return [Boolean] lawn does need watering.
+ */
+ public boolean needsWatering(Date _currentDate) {
+
+ // only check if we have watered since the last date
+ if (didWaterSinceLastSchedualedDate) {
+ // get the day of the week from the date and convert it to be
+ // 0=Monday, 1=Sunday, ....
+ int dayOfWeek = _currentDate.getDay();
+ dayOfWeek = (dayOfWeek - 1) % 7;
+
+ // Calculate what we should mask out days to water byte to see if it is a 1
+ int mask = (1 << dayOfWeek);
+
+ // mask the bye
+ int shouldWaterToday = daysToWaterOn & mask;
+
+ // if the post masked data is 0 then we should not water today since that bit was not set to 1
+ // do not water today
+ if (shouldWaterToday == 0) {
+ return false;
+ }
+
+ }
+
+ // it is a scheduled day so we need to water soon;
+ didWaterSinceLastSchedualedDate = false;
+
+ // check if it rained in the last little bit so there is no need to water this grass right now.
+ if (didRainRecently(_currentDate, RAINED_RECENTLY_DAYS_INTERVAL)) {
+ return false;
+ }
+
+ // The grass was never watered before so water now
+ if (lastTimeWatered == null) {
+ return true;
+ }
+
+ // calculate the average moisture readings of all the
+ // sensors in this lawn
+ double averageMoistureValue = getAverageMoistureReading();
+
+ // is a valid average
+ if (averageMoistureValue != -1) {
+ // moisture is low enough to need to water now
+ if (averageMoistureValue <= MOISTURE_LEVEL_FOR_NORMAL_WATERING) {
+ return true;
+ } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
+ // moisture is high so no need to water
+ return false;
+ }
+ }
+
+ // if got here then no condition says we should not water today so we should
+ // water the grass today
+ return true;
+ }
+
+
+ /** Method to get the date of the last time the lawn was watered
+ *
+ * @return [Date] date of last watering.
+ */
+ public Date getLastTimeWatered() {
+ return lastTimeWatered;
+ }
+
+ /** Method to keep track of the last few times it rained on this lawn
+ *
+ * @param _dateOfRain [Date], the date of the rain.
+ *
+ * @return [void] None.
+ */
+ public void rainedOnDate(Date _dateOfRain) {
+
+ // the grass was technically watered on this day
+ lastTimeWatered = _dateOfRain;
+
+ didWaterSinceLastSchedualedDate = true;
+
+ // it rained on this date
+ daysRained.add(_dateOfRain);
+
+ // only keep the last 20 days that it rained
+ if (daysRained.size() > 20) {
+ daysRained.remove(0);
+ }
+
+ }
+
+
+ /** Method to water lawn, calculates how much to water and sends water signal to controller
+ *
+ * @param _currentDate [Date], the current date and time.
+ *
+ * @return [void] None.
+ */
+ public void waterLawn(Date _currentDate) {
+ lastTimeWatered = _currentDate;
+ didWaterSinceLastSchedualedDate = true;
+
+ // get the day of the week from the date and convert it to be
+ // 0=Monday, 1=Sunday, ....
+ int dayOfWeek = _currentDate.getDay();
+ dayOfWeek = (dayOfWeek - 1) % 7;
+
+
+ // check if it is the last day to water for this week
+ boolean isLastDay = true;
+ for (int i = 6; i > dayOfWeek; i--) {
+ int mask = (1 << dayOfWeek);
+
+ int shouldWaterToday = daysToWaterOn & mask;
+
+ if (shouldWaterToday != 0) {
+ isLastDay = false;
+ break;
+ }
+ }
+
+
+ int secondsToWater = 0;
+ if (isLastDay) {
+
+ // last day of week to water so water the remaining amount
+ double minutesToWater = timePerWeek - timeWateredSoFar;
+ timeWateredSoFar = 0;
+ secondsToWater = (int)((double)(minutesToWater * 60));
+
+ } else {
+
+ // if it is not the last day then just water a normal amount
+ timeWateredSoFar += timePerWatering;
+ secondsToWater = (int)((double)(timePerWatering * 60));
+ }
+
+ try {
+ System.out.println("DEBUG: We water the lawn!!! Zone: " + zone + " Seconds to water: " + secondsToWater);
+ sprinkler.setZone(zone, true, secondsToWater);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ /** Method callback from the motion detection callback interface, processes the motion
+ * to see how long the motion was and saves that motion.
+ *
+ * @param _md [MotionDetection], motion detector with the motion
+ *
+ * @return [void] None.
+ */
+ public void motionDetected(long timeStampOfLastMotion) {
+
+ Date currMotTime = new Date(timeStampOfLastMotion);
+
+ if (lastMotionDetectedTime == null) {
+ lastMotionDetectedTime = currMotTime;
+ }
+
+ if (startOfThisMotion == null) {
+ startOfThisMotion = currMotTime;
+ }
+
+ long timeElapsed = (currMotTime.getTime() - lastMotionDetectedTime.getTime()) / 1000;
+
+ if (timeElapsed >= NEW_MOTION_THRESHOLD) {
+ try {
+ mutex.lock();
+ long motiontime = (lastMotionDetectedTime.getTime() - startOfThisMotion.getTime()) / 1000;
+ totalMotionOnLawn += motiontime;
+ numberOfMotionsOnLawnToday++;
+
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ mutex.unlock();
+ }
+
+ startOfThisMotion = currMotTime;
+ }
+
+ lastMotionDetectedTime = currMotTime;
+ }
+
+
+ /** Callback method for when a new moisture reading is available.
+ * Called when a new reading is ready by the sensor and the sensor
+ * can be checked for the frame data.
+ *
+ * @param _sensor [MoistureSensor] .
+ *
+ * @return [void] None.
+ */
+ public void newReadingAvailable(int sensorId, float moisture, long timeStampOfLastReading) {
+
+ moistureSensorReadings.put(sensorId, (double) moisture);
+ moistureSensorUpdateTimes.put(sensorId, new Date(timeStampOfLastReading));
+ }
+
+
+ /*******************************************************************************************************************************************
+ **
+ ** Helper Methods
+ **
+ *******************************************************************************************************************************************/
+
+ /** Method to check if it rained recently in the near past.
+ *
+ * @param _numberOfDaysInPast [long], number of days in the past to check if it rained recently.
+ * @param _currentDate [Date], the current date and time.
+ *
+ * @return [boolean] weather it rained recently or not.
+ */
+ private boolean didRainRecently(Date _currentDate, long _numberOfDaysInPast) {
+
+ // it never rained before
+ if (daysRained.size() == 0) {
+ return false;
+ }
+
+ // convert the days to seconds for calculation
+ long numberOfSecondsInPast = _numberOfDaysInPast * 24 * 60 * 60;
+
+ // go through all the stored days that it rained on
+ for (Date d : daysRained) {
+
+ // check the difference time and convert to seconds.
+ long numberOfSecondsDifference = (_currentDate.getTime() - d.getTime()) / 1000;
+
+ // if it rained in the last specified time then return true
+ if (numberOfSecondsDifference < numberOfSecondsInPast) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /** Method calculate the average moisture readings of the most recent moisture reading of each sensor
+ * if that reading is not stale
+ *
+ * @return [double] average value of moisture readings.
+ */
+ private double getAverageMoistureReading() {
+
+ Date currentTime = new Date();
+ double total = 0;
+ int numberOfReadings = 0;
+
+ for (Integer sen : moistureSensorReadings.keySet()) {
+
+ // check the timestamp of the watering of the lawn
+ Date readingTimestamp = moistureSensorUpdateTimes.get(sen);
+
+ System.out.println("DEBUG: Sensor reading time stamp: " + readingTimestamp.getTime());
+ System.out.println("DEBUG: Current time stamp: " + currentTime.getTime());
+ System.out.println("Time elapsed: " + (currentTime.getTime() - readingTimestamp.getTime()) / 1000);
+
+ //long timeElapsedSinceLastWatering = (currentTime.getTime() - readingTimestamp.getTime()) / 1000;
+
+ // if reading is old then dont use it since it is noise
+ //if (timeElapsedSinceLastWatering > TWO_HOURS) {
+ // continue;
+ //}
+
+ // Do averaging
+ numberOfReadings++;
+ total += moistureSensorReadings.get(sen);
+
+ System.out.println("DEBUG: Sensor reading value: " + moistureSensorReadings.get(sen) + " with total: " + total);
+ }
+
+
+ // if no readings were valid then return -1 so that we can signal that moisture cannot be used for now
+ if (numberOfReadings == 0) {
+ return -1;
+ }
+
+ // return the calculated average of all the recent moisture readings
+ return total / (double)numberOfReadings;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
// create the correct number of zones for this controller
for (int i = 0; i < NUMBER_OF_ZONES; i++) {
- //zoneStates.add(new ZoneState(i, false, 0));
ZoneState zTmp = new ZoneState();
zTmp.zoneNumber = i;
zTmp.onOffState = false;
{
// We replaced methods with fields
//z.zoneNumber, z.onOffState z.duration
- //if (z.getZoneNumber() == _zone) {
if (z.zoneNumber == _zone) {
// turn on or off the valve
try {
zoneStateMutex.acquire();
for (ZoneState z : zoneStates) {
- //System.out.println("Iterating on zone: " + z.zoneNumber);
if (z.onOffState) {
- //System.out.println("Turning on zone: " + z.zoneNumber);
- //System.out.println("Duration: " + z.duration);
// if on and time has expired then turn off
if (z.duration == 0) {