1 package IrrigationController;
3 // Standard Java Packages
6 import java.util.ArrayList;
7 import java.util.concurrent.locks.Lock;
8 import java.util.concurrent.locks.ReentrantLock;
10 import java.util.concurrent.ConcurrentHashMap;
14 import java.rmi.RemoteException;
15 import java.rmi.server.UnicastRemoteObject;
17 // IoT Driver Packages
18 import iotcode.interfaces.*;
20 // Checker annotations
21 //import iotchecker.qual.*;
23 /** Class LawnState that represents the state of the lawn, also help calculate if the lawn needs to be watered.
25 * @author Ali Younis <ayounis @ uci.edu>
30 public class LawnState extends UnicastRemoteObject implements MotionDetectionCallback, MoistureSensorCallback {
33 /*******************************************************************************************************************************************
37 *******************************************************************************************************************************************/
38 private static final long MAX_TIME_BETWEEN_WATERING_SESSIONS = 4 * 24 * 60 * 60; // 5 days
39 private static final int MAX_DAYS_RAINED_RECORD = 20;
40 private static final int RAINED_RECENTLY_DAYS_INTERVAL = 1;
41 private static final long TWENTY_FIVE_HOURS = 25 * 60 * 60;
42 private static final long TWO_HOURS = 2 * 60 * 60;
44 private static final long NEW_MOTION_THRESHOLD = 2 * 60; // 2 minutes
45 private static final long AMOUNT_OF_MOTION_FOR_ACTIVE = 60 * 60; // 1 hour
46 private static final long AMOUNT_OF_TIME_FOR_ACTIVE_TO_HOLD = 7 * 24 * 60 * 60; // 1 week
48 private static final double MOISTURE_LEVEL_FOR_NORMAL_WATERING = 25; // Percentage
49 private static final double MOISTURE_LEVEL_FOR_EMERGENCY_WATERING = 5; // Percentage
50 private static final double MOISTURE_LEVEL_FOR_NO_WATERING = 80; // Percentage
52 /*******************************************************************************************************************************************
56 *******************************************************************************************************************************************/
57 private boolean isInHibernationMode = false;
58 private Date lastTimeWatered = null;
59 private boolean didWaterSinceLastSchedualedDate = false;
60 private List<Date> daysRained = new ArrayList<Date>();
61 private int daysToWaterOn = 0;
62 private LawnSmart iotLawnObject;
63 private MotionDetection motionDetector;
64 private double inchesPerMinute = 0;
65 private double inchesPerWeek = 0;
66 private double timePerWatering = 0;
67 private double timePerWeek = 0;
68 private double timeWateredSoFar = 0;
69 private SprinklerSmart sprinkler;
71 private Date lastMotionDetectedTime = null;
72 private Date startOfThisMotion = null;
73 private Date lastUpdateDate = null;
74 private Lock mutex = new ReentrantLock();
75 private long totalMotionOnLawn = 0;
76 private long numberOfMotionsOnLawnToday = 0;
77 private boolean lawnIsActive = false;
78 private Date lawnBecameActiceDate = null;
79 private Map<Integer, Double> moistureSensorReadings =
80 new ConcurrentHashMap<Integer, Double>();
81 private Map<Integer, Date> moistureSensorUpdateTimes =
82 new ConcurrentHashMap<Integer, Date>();
85 // 0th bit = Monday, 1th bit = Tuesday ext
86 public LawnState(LawnSmart _l, int _daysToWaterOn, MotionDetection _mo,
87 double _inchesPerMinute, double _inchesPerWeek, SprinklerSmart _sprinkler,
88 int _zone, Set<MoistureSensorSmart> _moistureSensors) throws RemoteException {
90 daysToWaterOn = _daysToWaterOn;
91 inchesPerMinute = _inchesPerMinute;
92 inchesPerWeek = _inchesPerWeek;
93 sprinkler = _sprinkler;
96 // register the callback with self
98 _mo.registerCallback(this);
100 // register callback to self
101 for (MoistureSensorSmart sen : _moistureSensors) {
104 sen.registerCallback(this);
105 } catch (Exception e) {
110 // parse the days that we are going to water on
111 int numberOfDaysForWatering = 0;
112 for (int i = 0; i < 7; i++) {
113 if ((daysToWaterOn & (1 << i)) > 0) {
114 numberOfDaysForWatering++;
118 // calculate lawn watering water amounts
119 timePerWeek = _inchesPerWeek / _inchesPerMinute;
120 timePerWatering = timePerWeek / (double)numberOfDaysForWatering;
123 /*******************************************************************************************************************************************
127 *******************************************************************************************************************************************/
130 /** Method to update the lawn state, updates lawn activity state based on activity timeout
132 * @param _currentDate [Date], the current date and time.
134 * @return [void] None.
136 public void updateLawn(Date _currentDate) {
137 if (lastUpdateDate != null) {
139 // check if we already did an update today
140 if ((lastUpdateDate.getDate() == _currentDate.getDate())
141 && (lastUpdateDate.getMonth() == _currentDate.getMonth())
142 && (lastUpdateDate.getYear() == _currentDate.getYear())) {
147 lastUpdateDate = _currentDate;
149 // lawn was active at some time so check if it can be deemed inactive because
150 // time has passed and it has not been active in that time
151 if (lawnBecameActiceDate != null) {
152 long timeElapsed = (_currentDate.getTime() - lawnBecameActiceDate.getTime()) / 1000;
154 if (timeElapsed >= AMOUNT_OF_TIME_FOR_ACTIVE_TO_HOLD) {
155 lawnBecameActiceDate = null;
156 lawnIsActive = false;
161 // check activity of lawn
162 boolean isActiveLawn = false;
165 if (totalMotionOnLawn >= AMOUNT_OF_MOTION_FOR_ACTIVE) {
169 // reset motion counters
170 totalMotionOnLawn = 0;
171 numberOfMotionsOnLawnToday = 0;
173 } catch (Exception e) {
183 lawnBecameActiceDate = _currentDate;
188 /** Method to test if this lawn is active or not.
190 * @return [Boolean] lawn is active.
192 public boolean lawnHasSufficientMotion() {
197 /** Method to test if this lawn should be watered or not right now.
198 * Lawn urgently needs to be watered right now.
200 * @param _currentDate [Date], the current date and time.
202 * @return [Boolean] lawn does need watering.
204 public boolean needsWateringUrgently(Date _currentDate) {
206 // get difference between now and last time watered
207 // TODO: Remove this to uncommented!!! This is only for testing!!!
208 /* long timeElapsed = (_currentDate.getTime() - lastTimeWatered.getTime()) / 1000;
210 // needs watering now urgently
211 if (timeElapsed >= MAX_TIME_BETWEEN_WATERING_SESSIONS) {
215 // calculate the average moisture readings of all the
216 // sensors in this lawn
217 double averageMoistureValue = getAverageMoistureReading();
219 // is a valid average
220 if (averageMoistureValue != -1) {
221 // moisture is very low so we need to water now!
222 if (averageMoistureValue <= MOISTURE_LEVEL_FOR_EMERGENCY_WATERING) {
224 } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
225 // moisture is high so no need to water
232 double averageMoistureValue = getAverageMoistureReading();
233 // System.out.println("DEBUG: Average moisture value: " + averageMoistureValue);
239 /** Method to test if this lawn should be watered or not
241 * @param _currentDate [Date], the current date and time.
243 * @return [Boolean] lawn does need watering.
245 public boolean needsWatering(Date _currentDate) {
247 // only check if we have watered since the last date
248 if (didWaterSinceLastSchedualedDate) {
249 // get the day of the week from the date and convert it to be
250 // 0=Monday, 1=Sunday, ....
251 int dayOfWeek = _currentDate.getDay();
252 dayOfWeek = (dayOfWeek - 1) % 7;
254 // Calculate what we should mask out days to water byte to see if it is a 1
255 int mask = (1 << dayOfWeek);
258 int shouldWaterToday = daysToWaterOn & mask;
260 // if the post masked data is 0 then we should not water today since that bit was not set to 1
261 // do not water today
262 if (shouldWaterToday == 0) {
268 // it is a scheduled day so we need to water soon;
269 didWaterSinceLastSchedualedDate = false;
271 // check if it rained in the last little bit so there is no need to water this grass right now.
272 if (didRainRecently(_currentDate, RAINED_RECENTLY_DAYS_INTERVAL)) {
276 // The grass was never watered before so water now
277 if (lastTimeWatered == null) {
281 // calculate the average moisture readings of all the
282 // sensors in this lawn
283 double averageMoistureValue = getAverageMoistureReading();
285 // is a valid average
286 if (averageMoistureValue != -1) {
287 // moisture is low enough to need to water now
288 if (averageMoistureValue <= MOISTURE_LEVEL_FOR_NORMAL_WATERING) {
290 } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
291 // moisture is high so no need to water
296 // if got here then no condition says we should not water today so we should
297 // water the grass today
302 /** Method to get the date of the last time the lawn was watered
304 * @return [Date] date of last watering.
306 public Date getLastTimeWatered() {
307 return lastTimeWatered;
310 /** Method to keep track of the last few times it rained on this lawn
312 * @param _dateOfRain [Date], the date of the rain.
314 * @return [void] None.
316 public void rainedOnDate(Date _dateOfRain) {
318 // the grass was technically watered on this day
319 lastTimeWatered = _dateOfRain;
321 didWaterSinceLastSchedualedDate = true;
323 // it rained on this date
324 daysRained.add(_dateOfRain);
326 // only keep the last 20 days that it rained
327 if (daysRained.size() > 20) {
328 daysRained.remove(0);
334 /** Method to water lawn, calculates how much to water and sends water signal to controller
336 * @param _currentDate [Date], the current date and time.
338 * @return [void] None.
340 public void waterLawn(Date _currentDate) {
341 lastTimeWatered = _currentDate;
342 didWaterSinceLastSchedualedDate = true;
344 // get the day of the week from the date and convert it to be
345 // 0=Monday, 1=Sunday, ....
346 int dayOfWeek = _currentDate.getDay();
347 dayOfWeek = (dayOfWeek - 1) % 7;
350 // check if it is the last day to water for this week
351 boolean isLastDay = true;
352 for (int i = 6; i > dayOfWeek; i--) {
353 int mask = (1 << dayOfWeek);
355 int shouldWaterToday = daysToWaterOn & mask;
357 if (shouldWaterToday != 0) {
364 int secondsToWater = 0;
367 // last day of week to water so water the remaining amount
368 double minutesToWater = timePerWeek - timeWateredSoFar;
369 timeWateredSoFar = 0;
370 secondsToWater = (int)((double)(minutesToWater * 60));
374 // if it is not the last day then just water a normal amount
375 timeWateredSoFar += timePerWatering;
376 secondsToWater = (int)((double)(timePerWatering * 60));
380 System.out.println("DEBUG: We water the lawn!!! Zone: " + zone + " Seconds to water: " + secondsToWater);
381 sprinkler.setZone(zone, true, secondsToWater);
382 } catch (Exception e) {
388 /** Method callback from the motion detection callback interface, processes the motion
389 * to see how long the motion was and saves that motion.
391 * @param _md [MotionDetection], motion detector with the motion
393 * @return [void] None.
395 public void motionDetected(long timeStampOfLastMotion) {
397 Date currMotTime = new Date(timeStampOfLastMotion);
399 if (lastMotionDetectedTime == null) {
400 lastMotionDetectedTime = currMotTime;
403 if (startOfThisMotion == null) {
404 startOfThisMotion = currMotTime;
407 long timeElapsed = (currMotTime.getTime() - lastMotionDetectedTime.getTime()) / 1000;
409 if (timeElapsed >= NEW_MOTION_THRESHOLD) {
412 long motiontime = (lastMotionDetectedTime.getTime() - startOfThisMotion.getTime()) / 1000;
413 totalMotionOnLawn += motiontime;
414 numberOfMotionsOnLawnToday++;
417 } catch (Exception e) {
424 startOfThisMotion = currMotTime;
427 lastMotionDetectedTime = currMotTime;
431 /** Callback method for when a new moisture reading is available.
432 * Called when a new reading is ready by the sensor and the sensor
433 * can be checked for the frame data.
435 * @param _sensor [MoistureSensor] .
437 * @return [void] None.
439 public void newReadingAvailable(int sensorId, float moisture, long timeStampOfLastReading) {
441 moistureSensorReadings.put(sensorId, (double) moisture);
442 moistureSensorUpdateTimes.put(sensorId, new Date(timeStampOfLastReading));
446 /*******************************************************************************************************************************************
450 *******************************************************************************************************************************************/
452 /** Method to check if it rained recently in the near past.
454 * @param _numberOfDaysInPast [long], number of days in the past to check if it rained recently.
455 * @param _currentDate [Date], the current date and time.
457 * @return [boolean] weather it rained recently or not.
459 private boolean didRainRecently(Date _currentDate, long _numberOfDaysInPast) {
461 // it never rained before
462 if (daysRained.size() == 0) {
466 // convert the days to seconds for calculation
467 long numberOfSecondsInPast = _numberOfDaysInPast * 24 * 60 * 60;
469 // go through all the stored days that it rained on
470 for (Date d : daysRained) {
472 // check the difference time and convert to seconds.
473 long numberOfSecondsDifference = (_currentDate.getTime() - d.getTime()) / 1000;
475 // if it rained in the last specified time then return true
476 if (numberOfSecondsDifference < numberOfSecondsInPast) {
485 /** Method calculate the average moisture readings of the most recent moisture reading of each sensor
486 * if that reading is not stale
488 * @return [double] average value of moisture readings.
490 private double getAverageMoistureReading() {
492 Date currentTime = new Date();
494 int numberOfReadings = 0;
496 for (Integer sen : moistureSensorReadings.keySet()) {
498 // check the timestamp of the watering of the lawn
499 Date readingTimestamp = moistureSensorUpdateTimes.get(sen);
501 System.out.println("DEBUG: Sensor reading time stamp: " + readingTimestamp.getTime());
502 System.out.println("DEBUG: Current time stamp: " + currentTime.getTime());
503 System.out.println("Time elapsed: " + (currentTime.getTime() - readingTimestamp.getTime()) / 1000);
505 //long timeElapsedSinceLastWatering = (currentTime.getTime() - readingTimestamp.getTime()) / 1000;
507 // if reading is old then dont use it since it is noise
508 //if (timeElapsedSinceLastWatering > TWO_HOURS) {
514 total += moistureSensorReadings.get(sen);
516 System.out.println("DEBUG: Sensor reading value: " + moistureSensorReadings.get(sen) + " with total: " + total);
520 // if no readings were valid then return -1 so that we can signal that moisture cannot be used for now
521 if (numberOfReadings == 0) {
525 // return the calculated average of all the recent moisture readings
526 return total / (double)numberOfReadings;