1 package IrrigationController;
2 // Standard Java Packages
4 import java.util.Iterator;
6 import java.util.ArrayList;
7 import java.util.HashSet;
8 import java.util.concurrent.atomic.AtomicBoolean;
12 import java.rmi.RemoteException;
13 import java.rmi.server.UnicastRemoteObject;
15 // IoT Runtime Packages
16 import iotruntime.slave.IoTSet;
17 import iotruntime.slave.IoTRelation;
18 import iotruntime.slave.IoTAddress;
19 //import iotcode.annotation.*;
21 // IoT Driver Packages
22 import iotcode.interfaces.*;
23 import iotcode.WeatherPhoneGateway.*;
25 // Checker annotations
26 import iotchecker.qual.*;
28 public class IrrigationController extends UnicastRemoteObject implements WeatherGatewayCallback {
31 /*******************************************************************************************************************************************
35 *******************************************************************************************************************************************/
36 // private static final int NUMBER_OF_TIMES_PER_WEEK_TO_WATER = 2;
37 //TODO: Change these back to normal - this is just for testing to make it awake all the time
38 // private static final int TIME_HOURS_TO_WATER_GRASS = 7; // 7 am
39 private static final int TIME_HOURS_TO_WATER_GRASS = 3;
40 // private static final int TIME_MINUTES_TO_WATER_GRASS = 30; // 30 minutes
41 private static final int TIME_MINUTES_TO_WATER_GRASS = 30;
42 // private static final int TIME_TO_RECOVER_GRASS_FOR = 8 * 24 * 60 * 60; // 8 days
43 private static final int TIME_TO_RECOVER_GRASS_FOR = 10;
44 // private static final int TIME_TO_HIBERNATE_GRASS_FOR = 30 * 24 * 60 * 60; // 30 days
45 private static final int TIME_TO_HIBERNATE_GRASS_FOR = 10;
46 public static final int CAMERA_FPS = 15; // In frames per second
49 /*******************************************************************************************************************************************
53 *******************************************************************************************************************************************/
54 private int dayOfLastCheck = -1;
55 private int monthOfLastCheck = -1;
56 private boolean isInHibernationRecoveryMode = false;
57 private Date hibernationRecoveryModeStartDate = null;
58 private boolean isHibernationMode = false;
59 private Date hibernationModeStartDate = null;
60 private List<@LocalRemote LawnState> lawns = new ArrayList<@LocalRemote LawnState>();
61 private WeatherGrabber weatherGrabber = null;
63 // used to block until gui is done and the settings are ready to be polled
64 private AtomicBoolean waitingForInterface = new AtomicBoolean(true);
66 // the settings from the interface, used to setup the system
67 private double inchesPerWeek = 0;
68 private int weatherZipCode = 0;
69 private int daysToWaterOn = 0;
70 private List<Double> inchesPerMinute;
73 /*******************************************************************************************************************************************
75 ** IoT Sets and Relations
77 *******************************************************************************************************************************************/
78 @config private IoTSet<IoTAddress> weatherDataAddresses;
79 @config private IoTSet<IoTAddress> weatherDataAddressMain;
80 @config private IoTSet<@NonLocalRemote WeatherGateway> gwSet;
81 @config private IoTSet<@NonLocalRemote Lawn> lawnSet;
82 @config private IoTSet<@NonLocalRemote MoistureSensor> moistureSensorsSet;
83 @config private IoTSet<@NonLocalRemote Camera> cameraSet;
84 @config private IoTRelation<@NonLocalRemote Lawn, @NonLocalRemote Camera> lawnCameraRelation;
85 @config private IoTRelation<@NonLocalRemote Lawn, @NonLocalRemote Sprinkler> lawnSprinklerRelation;
86 @config private IoTRelation<@NonLocalRemote Lawn, @NonLocalRemote MoistureSensor> lawnMoistureSensorRelation;
89 public IrrigationController() throws RemoteException {
93 /*******************************************************************************************************************************************
97 *******************************************************************************************************************************************/
100 /** Method to set whether the controller should maintain the lawns in hibernation mode
101 * or in normal mode. Lawns should be put in hibernation mode in drought conditions
103 * @param _hibMode [boolean] set the hibernation mode for this lawn controllers (true = hibernation)
105 * @return [void] None.
107 public void setHibernationMode(boolean _hibMode) {
109 // change hibernation mode status
110 isHibernationMode = _hibMode;
112 // set the start date for when we started this hibernation mode
115 // make sure we dont reset this cycle
116 if (!isHibernationMode) {
117 hibernationModeStartDate = new Date();
120 // reset all hibernation stuff
121 hibernationModeStartDate = null;
122 isInHibernationRecoveryMode = false;
123 hibernationRecoveryModeStartDate = null;
127 /** Method to start the controller and run the main control loop
129 * @return [void] None.
131 public void init() throws RemoteException {
133 // initialize the controller
135 System.out.println("Initialized controller!");
140 // get the current time of day (date and time)
141 Date currentDate = new Date();
143 // get the epoch time till the beginning of the day
144 Date beginingOfToday = new Date(currentDate.getYear(), currentDate.getMonth(), currentDate.getDate());
146 // calculate the seconds since the start of the day.
147 long secondsSinceStartOfDay = (currentDate.getTime() - beginingOfToday.getTime()) / 1000;
149 // Seconds since the start of the day to start the watering
150 long secondsForWateringStart = (TIME_HOURS_TO_WATER_GRASS * 3600) + (TIME_MINUTES_TO_WATER_GRASS * 60);
152 // System.out.println("beginingOfToday " + beginingOfToday);
153 // System.out.println("secondsSinceStartOfDay " + secondsSinceStartOfDay);
154 // System.out.println("secondsForWateringStart " + secondsForWateringStart);
156 // check if the current time is within the start watering interval
157 /*if ((secondsSinceStartOfDay < secondsForWateringStart) || (secondsSinceStartOfDay > (secondsForWateringStart + (60 * 60)))) {
158 System.out.println("Sleep for 10 minutes.. ");
160 //Thread.sleep(10 * 60 * 1000); // sleep for 10 minutes
161 Thread.sleep(10); // sleep for 10 seconds
162 } catch (Exception e) {
169 // check if we already checked if we should water today
170 // we only need to do this once per day
171 /*if ((dayOfLastCheck == currentDate.getDate()) && (monthOfLastCheck == currentDate.getMonth())) {
172 System.out.println("Sleep for 1 hour...");
174 Thread.sleep(60 * 60 * 1000); // sleep for an hour
175 } catch (Exception e) {
182 // we decided to check if we should water today so save the fact that we chose to water on this day
183 dayOfLastCheck = currentDate.getDate();
184 monthOfLastCheck = currentDate.getMonth();
186 // update the lawn states everyday
187 for (@LocalRemote LawnState ls : lawns) {
188 ls.updateLawn(currentDate);
190 // check if we are in hibernation mode and do the correct loop action
191 if (isHibernationMode) {
192 // System.out.println("Hibernation mode!");
193 // If we are in hibernation mode then use the hibernation loop code
194 wateringHibernationLoop(currentDate);
196 // System.out.println("Normal mode!");
197 // Using the normal watering loop code
198 wateringNormalLoop(currentDate);
204 /** Callback method for when the information is retrieved.
206 * @param _wgw [WeatherGateway].
207 * @return [void] None.
209 public void informationRetrieved(@NonLocalRemote WeatherGateway _wgw) {
211 // System.out.println("DEBUG: Information is retrieved from phone!!!");
213 // get the parameters that the interface (phone app) reads from the user
214 inchesPerWeek = _wgw.getInchesPerWeek();
215 weatherZipCode = _wgw.getWeatherZipCode();
216 daysToWaterOn = _wgw.getDaysToWaterOn();
217 inchesPerMinute.add(_wgw.getInchesPerMinute());
218 } catch(RemoteException ex) {
219 ex.printStackTrace();
222 // the gui is done so release the spin wait that was waiting for the gui
223 waitingForInterface.set(false);
226 /*******************************************************************************************************************************************
230 *******************************************************************************************************************************************/
233 /** Method to initialize the controller variables and all the drivers and such
235 * @return [void] None.
237 private void initController() throws RemoteException {
239 // Setup the weather grabber object with the correct address of the weather api
240 Iterator it = weatherDataAddresses.iterator();
241 weatherGrabber = new WeatherGrabber((IoTAddress)it.next());
243 // Initialize inchesPerMinute
244 inchesPerMinute = new ArrayList<Double>();
246 // We setup a Gateway object to get information from the phone app
247 for (@NonLocalRemote WeatherGateway gw : gwSet.values()) {
249 gw.registerCallback(this);
253 System.out.println("DEBUG: Waiting for phone to send weather information");
254 while (waitingForInterface.get()) {
257 } catch (Exception e) {
261 // TODO: Use a phone input interface later
262 //inchesPerWeek = 20.00;
263 //weatherZipCode = 92612;
264 //daysToWaterOn = 255;
265 //inchesPerMinute.add(1.50);
267 System.out.println("DEBUG: inchesPerWeek: " + inchesPerWeek);
268 System.out.println("DEBUG: weatherZipCode: " + weatherZipCode);
269 System.out.println("DEBUG: daysToWaterOn: " + daysToWaterOn);
270 System.out.println("DEBUG: inchesPerMinute: " + inchesPerMinute.get(0));
272 // set the zip code and the the number of days of the weather grabber
273 // here the number of days is set to the max that the grabber supports
274 weatherGrabber.setZipcode(weatherZipCode);
275 weatherGrabber.setNumberOfDays(16);
277 // Setup the cameras, start them all and assign each one a motion detector
278 for (@NonLocalRemote Camera cam : cameraSet.values()) {
280 // initialize the camera, might need to setup some stuff internally
283 // set the camera parameters.
284 cam.setFPS(CAMERA_FPS);
285 cam.setResolution(Camera.Resolution.RES_VGA);
287 // Start the camera (example is start the HTTP stream if it is a network camera)
289 System.out.println("DEBUG: Init camera! " + cam.toString());
290 } catch (RemoteException e) {
296 // counter so that we can match the lawn inches per min data with the specific lawn
298 for (@NonLocalRemote Lawn l : lawnSet.values()) {
299 // create a motionDetector for each lawn object
300 @LocalRemote MotionDetection mo = new @LocalRemote MotionDetection(12, 0.5f, 10, 10);
302 // for 1 camera, if there are any then register the camera for that lawn
303 HashSet<@NonLocalRemote Camera> cameras = lawnCameraRelation.get(l);
304 System.out.println("DEBUG: Camera.size(): " + cameras.size());
305 if (cameras.size() >= 1) {
307 // we only need 1 camera per lawn so get the first one in the list
308 Iterator camIt = cameras.iterator();
309 @NonLocalRemote Camera cam = (@NonLocalRemote Camera)camIt.next();
310 System.out.println("DEBUG: Registering callback to camera: " + cam.toString());
312 // setup the callback
313 cam.registerCallback(mo);
314 } catch (RemoteException e) {
319 // we also only need 1 sprinkler controller per lawn so grab the first one
320 HashSet<@NonLocalRemote Sprinkler> sprinklers = lawnSprinklerRelation.get(l);
321 Iterator sprinklersIt = sprinklers.iterator();
322 @NonLocalRemote Sprinkler spr = (@NonLocalRemote Sprinkler)sprinklersIt.next();
324 // init the sprinkler controller, do it here since it only needs to be done once per controller
327 } catch (Exception e) {
330 System.out.println("DEBUG: Init sprinkler: " + spr.toString());
332 // get and init the moisture sensors for this specific lawn
333 HashSet<@NonLocalRemote MoistureSensor> sensors = lawnMoistureSensorRelation.get(l);
334 for (@NonLocalRemote MoistureSensor sen : sensors) {
335 System.out.println("DEBUG: Init sensors: " + sen.toString());
338 } catch (Exception e) {
343 // create the lawn objects
344 System.out.println("DEBUG: Creating a LawnState object");
345 @LocalRemote LawnState ls =
346 new @LocalRemote LawnState(l, daysToWaterOn, mo, inchesPerMinute.get(counter), inchesPerWeek, spr, counter, sensors);
349 // dont forget to increment the counter
354 /** Main loop for when the controller is watering the lawns under normal conditions, not in hibernation mode
356 * @param _currentDate [Date] current date
358 * @return [void] None.
360 private void wateringNormalLoop(Date _currentDate) {
362 // get the weather data for the next little bit
363 List<DayWeather> weatherData = weatherGrabber.getWeatherData();
364 // TODO: Replace this with the WeatherGrabber.getWeatherData() above
365 // List<DayWeather> weatherData = new ArrayList<DayWeather>();
367 // Go through each lawn and check if we should water it and if we should, water it
368 for (@LocalRemote LawnState ls : lawns) {
370 // water for specific lawn
371 waterLawn(ls, _currentDate, weatherData);
375 /** Main loop for when the controller is watering the lawns in hibernation mode
377 * @param _currentDate [Date] current date
379 * @return [void] None.
381 private void wateringHibernationLoop(Date _currentDate) {
383 // if we are in recovery mode then run the recovery action
384 // we are still in hibernation mode but we need to recover the grass
385 if (isInHibernationRecoveryMode) {
386 // System.out.println("DEBUG: Recovery mode!");
387 hibernationRecoveryLoop(_currentDate);
391 // check if we should enter recovery mode
392 long elapsedTime = (_currentDate.getTime() - hibernationModeStartDate.getTime()) / 1000;
393 if (elapsedTime >= TIME_TO_HIBERNATE_GRASS_FOR) {
395 // start recovery mode
396 isInHibernationRecoveryMode = true;
397 hibernationRecoveryModeStartDate = null;
398 // System.out.println("DEBUG: We enter recovery mode for the first time!");
399 // first cycle of recovery
400 hibernationRecoveryLoop(_currentDate);
404 // get the weather data for the next little bit
405 List<DayWeather> weatherData = weatherGrabber.getWeatherData();
407 // Go through each lawn and check if we should water it and if we should, water it
408 for (@LocalRemote LawnState ls : lawns) {
410 boolean lawnHasMotion = ls.lawnHasSufficientMotion();
412 // there is no motion on the lawn so no need to water it
413 if (!lawnHasMotion) {
416 // System.out.println("DEBUG: We water the lawn! (wateringHibernationLoop)");
417 // water specific lawn since it has motion
418 waterLawn(ls, _currentDate, weatherData);
423 /** Main loop for when the controller is watering the lawns in hibernation mode
425 * @param _currentDate [Date] current date
427 * @return [void] None.
429 private void hibernationRecoveryLoop(Date _currentDate) {
431 // start recovery mode if it wasnt started yet
432 if (hibernationRecoveryModeStartDate == null) {
433 hibernationRecoveryModeStartDate = _currentDate;
436 // time since this mode was started
437 long elapsedTime = (_currentDate.getTime() - hibernationRecoveryModeStartDate.getTime()) / 1000;
439 // we have been in recovery mode long enough
440 if (elapsedTime >= TIME_TO_RECOVER_GRASS_FOR) {
442 // System.out.println("DEBUG: We have been in recovery mode long enough!");
443 // reset the recovery mode
444 isInHibernationRecoveryMode = false;
445 hibernationRecoveryModeStartDate = null;
447 // revived grass so restart the grass hibernation cycle
448 hibernationModeStartDate = _currentDate;
450 // do the hibernation loop since we are no longer in recovery mode
451 wateringHibernationLoop(_currentDate);
456 // if we got here then we are trying to recover the grass
458 // get the weather data for the next little bit
459 List<DayWeather> weatherData = weatherGrabber.getWeatherData();
461 // Go through each lawn and check if we should water it and if we should, water it
462 for (@LocalRemote LawnState ls : lawns) {
464 // System.out.println("DEBUG: We water the lawn! (hibernationRecoveryLoop)");
465 // water specific lawn since it has motion
466 waterLawn(ls, _currentDate, weatherData);
472 /** Method for watering a specific lawn if it needs to be watered
474 * @param _ls [LawnState] lawn to water
475 * @param _currentDate [Date] current date
476 * @param _weatherData [List<DayWeather>] latest weather data
478 * @return [void] None.
480 private void waterLawn(@LocalRemote LawnState _ls, Date _currentDate, List<DayWeather> _weatherData) {
482 // check if today or tomorrow is a wet day
483 boolean todayIsWetDay = _weatherData.get(0).getIsWetDay();
484 boolean tomorrowIsWetDay = _weatherData.get(1).getIsWetDay();
485 // TODO: Remove this later - hack the values for now!!!
486 // boolean todayIsWetDay = false;
487 // boolean tomorrowIsWetDay = false;
489 // lawn cannot wait anymore for water so water not
490 boolean lawnNeedsWaterNow = _ls.needsWateringUrgently(_currentDate);
491 if (lawnNeedsWaterNow) {
492 System.out.println("DEBUG: Need water now!!!");
493 System.out.println("DEBUG: Is wet day? " + todayIsWetDay);
494 System.out.println("DEBUG: Tomorrow is wet day? " + tomorrowIsWetDay);
495 // if it is not going to rain today then water the lawn
496 // TODO: Put this back to uncommented!!! Only for testing!!!
497 // if (!todayIsWetDay) {
498 _ls.waterLawn(_currentDate);
503 // check if this lawn needs watering based on watering algoritm/sensors/ext
504 boolean shouldWaterLawn = _ls.needsWatering(_currentDate);
506 // should not water this lawn then just skip to the next lawn
507 if (!shouldWaterLawn) {
511 // it is going to rain soon so wait it out.
512 // Grass is not in critical condition so it can wait a bit.
513 if (todayIsWetDay || tomorrowIsWetDay) {
517 // if we got here then we need to water the lawn
518 _ls.waterLawn(_currentDate);