Adding config file for sharing.
[iot2.git] / benchmarks / Java / IrrigationController / test_version / 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                 // TODO: Remove this to uncommented!!! This is only for testing!!!
208 /*              long timeElapsed = (_currentDate.getTime() - lastTimeWatered.getTime()) / 1000;
209
210                 // needs watering now urgently
211                 if (timeElapsed >= MAX_TIME_BETWEEN_WATERING_SESSIONS) {
212                         return true;
213                 }
214
215                 // calculate the average moisture readings of all the
216                 // sensors in this lawn
217                 double averageMoistureValue = getAverageMoistureReading();
218
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) {
223                                 return true;
224                         } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
225                                 // moisture is high so no need to water
226                                 return false;
227                         }
228                 }
229
230                 return false;
231 */
232                 double averageMoistureValue = getAverageMoistureReading();
233 //              System.out.println("DEBUG: Average moisture value: " + averageMoistureValue);
234
235                 return true;
236         }
237
238
239         /** Method to test if this lawn should be watered or not
240          *
241          *   @param _currentDate  [Date], the current date and time.
242          *
243          *   @return [Boolean] lawn does need watering.
244          */
245         public boolean needsWatering(Date _currentDate) {
246
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;
253
254                         // Calculate what we should mask out days to water byte to see if it is a 1
255                         int mask = (1 << dayOfWeek);
256
257                         // mask the bye
258                         int shouldWaterToday = daysToWaterOn & mask;
259
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) {
263                                 return false;
264                         }
265
266                 }
267
268                 // it is a scheduled day so we need to water soon;
269                 didWaterSinceLastSchedualedDate = false;
270
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)) {
273                         return false;
274                 }
275
276                 // The grass was never watered before so water now
277                 if (lastTimeWatered == null) {
278                         return true;
279                 }
280
281                 // calculate the average moisture readings of all the
282                 // sensors in this lawn
283                 double averageMoistureValue = getAverageMoistureReading();
284
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) {
289                                 return true;
290                         } else if (averageMoistureValue >= MOISTURE_LEVEL_FOR_NO_WATERING) {
291                                 // moisture is high so no need to water
292                                 return false;
293                         }
294                 }
295
296                 // if got here then no condition says we should not water today so we should
297                 // water the grass today
298                 return true;
299         }
300
301
302         /** Method to get the date of the last time the lawn was watered
303          *
304          *   @return [Date] date of last watering.
305          */
306         public Date getLastTimeWatered() {
307                 return lastTimeWatered;
308         }
309
310         /** Method to keep track of the last few times it rained on this lawn
311          *
312          *   @param _dateOfRain  [Date], the date of the rain.
313          *
314          *   @return [void] None.
315          */
316         public void rainedOnDate(Date _dateOfRain) {
317
318                 // the grass was technically watered on this day
319                 lastTimeWatered = _dateOfRain;
320
321                 didWaterSinceLastSchedualedDate = true;
322
323                 // it rained on this date
324                 daysRained.add(_dateOfRain);
325
326                 // only keep the last 20 days that it rained
327                 if (daysRained.size() > 20) {
328                         daysRained.remove(0);
329                 }
330
331         }
332
333
334         /** Method to water lawn, calculates how much to water and sends water signal to controller
335          *
336          *   @param _currentDate  [Date], the current date and time.
337          *
338          *   @return [void] None.
339          */
340         public void waterLawn(Date _currentDate) {
341                 lastTimeWatered = _currentDate;
342                 didWaterSinceLastSchedualedDate = true;
343
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;
348
349
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);
354
355                         int shouldWaterToday = daysToWaterOn & mask;
356
357                         if (shouldWaterToday != 0) {
358                                 isLastDay = false;
359                                 break;
360                         }
361                 }
362
363
364                 int secondsToWater = 0;
365                 if (isLastDay) {
366
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));
371
372                 } else {
373
374                         // if it is not the last day then just water a normal amount
375                         timeWateredSoFar += timePerWatering;
376                         secondsToWater = (int)((double)(timePerWatering * 60));
377                 }
378
379                 try {
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) {
383                         e.printStackTrace();
384                 }
385
386         }
387
388         /** Method callback from the motion detection callback interface, processes the motion
389          *  to see how long the motion was and saves that motion.
390          *
391          *   @param _md  [MotionDetection], motion detector with the motion
392          *
393          *   @return [void] None.
394          */
395         public void motionDetected(long timeStampOfLastMotion) {
396
397                 Date currMotTime = new Date(timeStampOfLastMotion);
398
399                 if (lastMotionDetectedTime == null) {
400                         lastMotionDetectedTime = currMotTime;
401                 }
402
403                 if (startOfThisMotion == null) {
404                         startOfThisMotion = currMotTime;
405                 }
406
407                 long timeElapsed = (currMotTime.getTime() - lastMotionDetectedTime.getTime()) / 1000;
408
409                 if (timeElapsed >= NEW_MOTION_THRESHOLD) {
410                         try {
411                                 mutex.lock();
412                                 long motiontime = (lastMotionDetectedTime.getTime() - startOfThisMotion.getTime()) / 1000;
413                                 totalMotionOnLawn += motiontime;
414                                 numberOfMotionsOnLawnToday++;
415
416
417                         } catch (Exception e) {
418                                 e.printStackTrace();
419                         }
420                         finally {
421                                 mutex.unlock();
422                         }
423
424                         startOfThisMotion = currMotTime;
425                 }
426
427                 lastMotionDetectedTime = currMotTime;
428         }
429
430
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.
434          *
435          *   @param _sensor [MoistureSensor] .
436          *
437          *   @return [void] None.
438          */
439         public void newReadingAvailable(int sensorId, float moisture, long timeStampOfLastReading) {
440
441                 moistureSensorReadings.put(sensorId, (double) moisture);
442                 moistureSensorUpdateTimes.put(sensorId, new Date(timeStampOfLastReading));
443         }
444
445
446         /*******************************************************************************************************************************************
447         **
448         **  Helper Methods
449         **
450         *******************************************************************************************************************************************/
451
452         /** Method to check if it rained recently in the near past.
453          *
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.
456          *
457          *   @return [boolean] weather it rained recently or not.
458          */
459         private boolean didRainRecently(Date _currentDate, long _numberOfDaysInPast) {
460
461                 // it never rained before
462                 if (daysRained.size() == 0) {
463                         return false;
464                 }
465
466                 // convert the days to seconds for calculation
467                 long numberOfSecondsInPast = _numberOfDaysInPast * 24 * 60 * 60;
468
469                 // go through all the stored days that it rained on
470                 for (Date d : daysRained) {
471
472                         // check the difference time and convert to seconds.
473                         long numberOfSecondsDifference = (_currentDate.getTime() - d.getTime()) / 1000;
474
475                         // if it rained in the last specified time then return true
476                         if (numberOfSecondsDifference < numberOfSecondsInPast) {
477                                 return true;
478                         }
479                 }
480
481                 return false;
482         }
483
484
485         /** Method calculate the average moisture readings of the most recent moisture reading of each sensor
486          *   if that reading is not stale
487          *
488          *   @return [double] average value of moisture readings.
489          */
490         private double getAverageMoistureReading() {
491
492                 Date currentTime = new Date();
493                 double total = 0;
494                 int numberOfReadings = 0;
495
496                 for (Integer sen : moistureSensorReadings.keySet()) {
497
498                         // check the timestamp of the watering of the lawn
499                         Date readingTimestamp = moistureSensorUpdateTimes.get(sen);
500
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);                     
504
505                         //long timeElapsedSinceLastWatering = (currentTime.getTime() - readingTimestamp.getTime()) / 1000;
506
507                         // if reading is old then dont use it since it is noise
508                         //if (timeElapsedSinceLastWatering > TWO_HOURS) {
509                         //      continue;
510                         //}
511
512                         // Do averaging
513                         numberOfReadings++;
514                         total += moistureSensorReadings.get(sen);
515
516                         System.out.println("DEBUG: Sensor reading value: " + moistureSensorReadings.get(sen) + " with total: " + total);
517                 }
518
519
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) {
522                         return -1;
523                 }
524
525                 // return the calculated average of all the recent moisture readings
526                 return total / (double)numberOfReadings;
527         }
528 }
529
530
531
532
533
534
535
536
537
538
539