35583546a5ab5d3ecf1c8a9ac08c8c6bc382e9dc
[iot2.git] / benchmarks / Java / IrrigationController / LawnState.java
1 package IrrigationController;
2
3 // Standard Java Packages
4 import java.util.Date;
5 import java.util.List;
6 import java.util.ArrayList;
7 import java.util.concurrent.locks.Lock;
8 import java.util.concurrent.locks.ReentrantLock;
9 import java.util.Set;
10 import java.util.concurrent.ConcurrentHashMap;
11 import java.util.Map;
12
13 // RMI packages
14 import java.rmi.RemoteException;
15 import java.rmi.server.UnicastRemoteObject;
16
17 // IoT Driver Packages
18 import iotcode.interfaces.*;
19
20 // Checker annotations
21 //import iotchecker.qual.*;
22
23 /** Class LawnState that represents the state of the lawn, also help calculate if the lawn needs to be watered.
24  *
25  * @author      Ali Younis <ayounis @ uci.edu>
26  * @version     1.0
27  * @since       2016-04-04
28  */
29
30 public class LawnState extends UnicastRemoteObject implements MotionDetectionCallback, MoistureSensorCallback {
31
32
33         /*******************************************************************************************************************************************
34         **
35         **  Constants
36         **
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;
43
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
47
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
51
52         /*******************************************************************************************************************************************
53         **
54         **  Variables
55         **
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;
70         private int zone = 0;
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>();
83
84
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 {
89                 iotLawnObject = _l;
90                 daysToWaterOn = _daysToWaterOn;
91                 inchesPerMinute = _inchesPerMinute;
92                 inchesPerWeek = _inchesPerWeek;
93                 sprinkler = _sprinkler;
94                 zone = _zone;
95
96                 // register the callback with self
97                 motionDetector = _mo;
98                 _mo.registerCallback(this);
99
100                 // register callback to self
101                 for (MoistureSensorSmart sen : _moistureSensors) {
102
103                         try {
104                                 sen.registerCallback(this);
105                         } catch (Exception e) {
106                                 e.printStackTrace();
107                         }
108                 }
109
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++;
115                         }
116                 }
117
118                 // calculate lawn watering water amounts
119                 timePerWeek = _inchesPerWeek / _inchesPerMinute;
120                 timePerWatering = timePerWeek / (double)numberOfDaysForWatering;
121         }
122
123         /*******************************************************************************************************************************************
124         **
125         **  Public Methods
126         **
127         *******************************************************************************************************************************************/
128
129
130         /** Method to update the lawn state, updates lawn activity state based on activity timeout
131          *
132          *   @param _currentDate  [Date], the current date and time.
133          *
134          *   @return [void] None.
135          */
136         public void updateLawn(Date _currentDate) {
137                 if (lastUpdateDate != null) {
138
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())) {
143                                 return;
144                         }
145                 }
146
147                 lastUpdateDate = _currentDate;
148
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;
153
154                         if (timeElapsed >= AMOUNT_OF_TIME_FOR_ACTIVE_TO_HOLD) {
155                                 lawnBecameActiceDate = null;
156                                 lawnIsActive = false;
157                         }
158                 }
159
160
161                 // check activity of lawn
162                 boolean isActiveLawn = false;
163                 try {
164                         mutex.lock();
165                         if (totalMotionOnLawn >= AMOUNT_OF_MOTION_FOR_ACTIVE) {
166                                 isActiveLawn = true;
167                         }
168
169                         // reset motion counters
170                         totalMotionOnLawn = 0;
171                         numberOfMotionsOnLawnToday = 0;
172
173                 } catch (Exception e) {
174                         e.printStackTrace();
175                 }
176                 finally {
177                         mutex.unlock();
178                 }
179
180                 // update lawn state
181                 if (isActiveLawn) {
182                         lawnIsActive = true;
183                         lawnBecameActiceDate = _currentDate;
184                 }
185         }
186
187
188         /** Method to test if this lawn is active or not.
189          *
190          *   @return [Boolean] lawn is active.
191          */
192         public boolean lawnHasSufficientMotion() {
193                 return lawnIsActive;
194         }
195
196
197         /** Method to test if this lawn should be watered or not right now.
198          *   Lawn urgently needs to be watered right now.
199          *
200          *   @param _currentDate  [Date], the current date and time.
201          *
202          *   @return [Boolean] lawn does need watering.
203          */
204         public boolean needsWateringUrgently(Date _currentDate) {
205
206                 // get difference between now and last time watered
207                 long timeElapsed = (_currentDate.getTime() - lastTimeWatered.getTime()) / 1000;
208
209                 // needs watering now urgently
210                 if (timeElapsed >= MAX_TIME_BETWEEN_WATERING_SESSIONS) {
211                         return true;
212                 }
213
214                 // calculate the average moisture readings of all the
215                 // sensors in this lawn
216                 double averageMoistureValue = getAverageMoistureReading();
217                 System.out.println("DEBUG: Average moisture value: " + averageMoistureValue);
218                 // is a valid average
219                 if (averageMoistureValue != -1) {
220                         // moisture is very low so we need to water now!
221                         if (averageMoistureValue <= MOISTURE_LEVEL_FOR_EMERGENCY_WATERING) {
222                                 return true;
223                         } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
224                                 // moisture is high so no need to water
225                                 return false;
226                         }
227                 }
228
229                 return false;
230         }
231
232
233         /** Method to test if this lawn should be watered or not
234          *
235          *   @param _currentDate  [Date], the current date and time.
236          *
237          *   @return [Boolean] lawn does need watering.
238          */
239         public boolean needsWatering(Date _currentDate) {
240
241                 // only check if we have watered since the last date
242                 if (didWaterSinceLastSchedualedDate) {
243                         // get the day of the week from the date and convert it to be
244                         // 0=Monday, 1=Sunday, ....
245                         int dayOfWeek = _currentDate.getDay();
246                         dayOfWeek = (dayOfWeek - 1) % 7;
247
248                         // Calculate what we should mask out days to water byte to see if it is a 1
249                         int mask = (1 << dayOfWeek);
250
251                         // mask the bye
252                         int shouldWaterToday = daysToWaterOn & mask;
253
254                         // if the post masked data is 0 then we should not water today since that bit was not set to 1
255                         // do not water today
256                         if (shouldWaterToday == 0) {
257                                 return false;
258                         }
259
260                 }
261
262                 // it is a scheduled day so we need to water soon;
263                 didWaterSinceLastSchedualedDate = false;
264
265                 // check if it rained in the last little bit so there is no need to water this grass right now.
266                 if (didRainRecently(_currentDate, RAINED_RECENTLY_DAYS_INTERVAL)) {
267                         return false;
268                 }
269
270                 // The grass was never watered before so water now
271                 if (lastTimeWatered == null) {
272                         return true;
273                 }
274
275                 // calculate the average moisture readings of all the
276                 // sensors in this lawn
277                 double averageMoistureValue = getAverageMoistureReading();
278
279                 // is a valid average
280                 if (averageMoistureValue != -1) {
281                         // moisture is low enough to need to water now
282                         if (averageMoistureValue <= MOISTURE_LEVEL_FOR_NORMAL_WATERING) {
283                                 return true;
284                         } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
285                                 // moisture is high so no need to water
286                                 return false;
287                         }
288                 }
289
290                 // if got here then no condition says we should not water today so we should
291                 // water the grass today
292                 return true;
293         }
294
295
296         /** Method to get the date of the last time the lawn was watered
297          *
298          *   @return [Date] date of last watering.
299          */
300         public Date getLastTimeWatered() {
301                 return lastTimeWatered;
302         }
303
304         /** Method to keep track of the last few times it rained on this lawn
305          *
306          *   @param _dateOfRain  [Date], the date of the rain.
307          *
308          *   @return [void] None.
309          */
310         public void rainedOnDate(Date _dateOfRain) {
311
312                 // the grass was technically watered on this day
313                 lastTimeWatered = _dateOfRain;
314
315                 didWaterSinceLastSchedualedDate = true;
316
317                 // it rained on this date
318                 daysRained.add(_dateOfRain);
319
320                 // only keep the last 20 days that it rained
321                 if (daysRained.size() > 20) {
322                         daysRained.remove(0);
323                 }
324
325         }
326
327
328         /** Method to water lawn, calculates how much to water and sends water signal to controller
329          *
330          *   @param _currentDate  [Date], the current date and time.
331          *
332          *   @return [void] None.
333          */
334         public void waterLawn(Date _currentDate) {
335                 lastTimeWatered = _currentDate;
336                 didWaterSinceLastSchedualedDate = true;
337
338                 // get the day of the week from the date and convert it to be
339                 // 0=Monday, 1=Sunday, ....
340                 int dayOfWeek = _currentDate.getDay();
341                 dayOfWeek = (dayOfWeek - 1) % 7;
342
343
344                 // check if it is the last day to water for this week
345                 boolean isLastDay = true;
346                 for (int i = 6; i > dayOfWeek; i--) {
347                         int mask = (1 << dayOfWeek);
348
349                         int shouldWaterToday = daysToWaterOn & mask;
350
351                         if (shouldWaterToday != 0) {
352                                 isLastDay = false;
353                                 break;
354                         }
355                 }
356
357
358                 int secondsToWater = 0;
359                 if (isLastDay) {
360
361                         // last day of week to water so water the remaining amount
362                         double minutesToWater = timePerWeek - timeWateredSoFar;
363                         timeWateredSoFar = 0;
364                         secondsToWater = (int)((double)(minutesToWater * 60));
365
366                 } else {
367
368                         // if it is not the last day then just water a normal amount
369                         timeWateredSoFar += timePerWatering;
370                         secondsToWater = (int)((double)(timePerWatering * 60));
371                 }
372
373                 try {
374                         System.out.println("DEBUG: We water the lawn!!! Zone: " + zone + " Seconds to water: " + secondsToWater);
375                         sprinkler.setZone(zone, true, secondsToWater);
376                 } catch (Exception e) {
377                         e.printStackTrace();
378                 }
379
380         }
381
382         /** Method callback from the motion detection callback interface, processes the motion
383          *  to see how long the motion was and saves that motion.
384          *
385          *   @param _md  [MotionDetection], motion detector with the motion
386          *
387          *   @return [void] None.
388          */
389         public void motionDetected(long timeStampOfLastMotion) {
390
391                 Date currMotTime = new Date(timeStampOfLastMotion);
392
393                 if (lastMotionDetectedTime == null) {
394                         lastMotionDetectedTime = currMotTime;
395                 }
396
397                 if (startOfThisMotion == null) {
398                         startOfThisMotion = currMotTime;
399                 }
400
401                 long timeElapsed = (currMotTime.getTime() - lastMotionDetectedTime.getTime()) / 1000;
402
403                 if (timeElapsed >= NEW_MOTION_THRESHOLD) {
404                         try {
405                                 mutex.lock();
406                                 long motiontime = (lastMotionDetectedTime.getTime() - startOfThisMotion.getTime()) / 1000;
407                                 totalMotionOnLawn += motiontime;
408                                 numberOfMotionsOnLawnToday++;
409
410
411                         } catch (Exception e) {
412                                 e.printStackTrace();
413                         }
414                         finally {
415                                 mutex.unlock();
416                         }
417
418                         startOfThisMotion = currMotTime;
419                 }
420
421                 lastMotionDetectedTime = currMotTime;
422         }
423
424
425         /** Callback method for when a new moisture reading is available.
426          *   Called when a new reading is ready by the sensor and the sensor
427          *   can be checked for the frame data.
428          *
429          *   @param _sensor [MoistureSensor] .
430          *
431          *   @return [void] None.
432          */
433         public void newReadingAvailable(int sensorId, float moisture, long timeStampOfLastReading) {
434
435                 moistureSensorReadings.put(sensorId, (double) moisture);
436                 moistureSensorUpdateTimes.put(sensorId, new Date(timeStampOfLastReading));
437         }
438
439
440         /*******************************************************************************************************************************************
441         **
442         **  Helper Methods
443         **
444         *******************************************************************************************************************************************/
445
446         /** Method to check if it rained recently in the near past.
447          *
448          *   @param _numberOfDaysInPast  [long], number of days in the past to check if it rained recently.
449          *   @param _currentDate  [Date], the current date and time.
450          *
451          *   @return [boolean] weather it rained recently or not.
452          */
453         private boolean didRainRecently(Date _currentDate, long _numberOfDaysInPast) {
454
455                 // it never rained before
456                 if (daysRained.size() == 0) {
457                         return false;
458                 }
459
460                 // convert the days to seconds for calculation
461                 long numberOfSecondsInPast = _numberOfDaysInPast * 24 * 60 * 60;
462
463                 // go through all the stored days that it rained on
464                 for (Date d : daysRained) {
465
466                         // check the difference time and convert to seconds.
467                         long numberOfSecondsDifference = (_currentDate.getTime() - d.getTime()) / 1000;
468
469                         // if it rained in the last specified time then return true
470                         if (numberOfSecondsDifference < numberOfSecondsInPast) {
471                                 return true;
472                         }
473                 }
474
475                 return false;
476         }
477
478
479         /** Method calculate the average moisture readings of the most recent moisture reading of each sensor
480          *   if that reading is not stale
481          *
482          *   @return [double] average value of moisture readings.
483          */
484         private double getAverageMoistureReading() {
485
486                 Date currentTime = new Date();
487                 double total = 0;
488                 int numberOfReadings = 0;
489
490                 for (Integer sen : moistureSensorReadings.keySet()) {
491
492                         // check the timestamp of the watering of the lawn
493                         Date readingTimestamp = moistureSensorUpdateTimes.get(sen);
494
495                         System.out.println("DEBUG: Sensor reading time stamp: " + readingTimestamp.getTime());
496                         System.out.println("DEBUG: Current time stamp: " + currentTime.getTime());
497                         System.out.println("Time elapsed: " + (currentTime.getTime() - readingTimestamp.getTime()) / 1000);                     
498
499                         long timeElapsedSinceLastWatering = (currentTime.getTime() - readingTimestamp.getTime()) / 1000;
500
501                         // if reading is old then dont use it since it is noise
502                         if (timeElapsedSinceLastWatering > TWO_HOURS) {
503                                 continue;
504                         }
505
506                         // Do averaging
507                         numberOfReadings++;
508                         total += moistureSensorReadings.get(sen);
509
510                         System.out.println("DEBUG: Sensor reading value: " + moistureSensorReadings.get(sen) + " with total: " + total);
511                 }
512
513
514                 // if no readings were valid then return -1 so that we can signal that moisture cannot be used for now
515                 if (numberOfReadings == 0) {
516                         return -1;
517                 }
518
519                 // return the calculated average of all the recent moisture readings
520                 return total / (double)numberOfReadings;
521         }
522 }
523
524
525
526
527
528
529
530
531
532
533