6615f8313e2208816f11a4a26c58d078818aeb06
[iot2.git] / benchmarks / Java / HomeSecurityController / HomeSecurityController.java
1 package HomeSecurityController;
2
3 // Standard Java Packages
4 import java.util.Date;
5 import java.util.Iterator;
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.List;
9 import java.util.Map;
10
11 import java.util.Set;
12 import java.util.HashSet;
13 import java.util.concurrent.atomic.AtomicBoolean;
14 import java.util.concurrent.ConcurrentHashMap;
15
16 // RMI packages
17 import java.rmi.RemoteException;
18 import java.rmi.server.UnicastRemoteObject;
19
20 // IoT Runtime Packages
21 import iotruntime.slave.IoTSet;
22 import iotruntime.slave.IoTRelation;
23 import iotruntime.slave.IoTAddress;
24 import iotcode.annotation.*;
25
26 // IoT Driver Packages
27 import iotcode.interfaces.*;
28
29 // IoT Cloud
30 import iotcloud.*;
31
32 /** Class Home Security Controller for the home security application benchmark
33  *
34  * @author      Rahmadi Trimananda <rtrimana @ uci.edu>
35  * @version     1.0
36  * @since       2016-12-14
37  */
38 public class HomeSecurityController implements SmartthingsSensorCallback, SmartthingsActuatorCallback {
39
40         /*
41          *  Constants
42          */
43         private static final int MOTION_TIME_THRESHOLD = 60;    // in seconds
44         private static final int CAMERA_FPS = 15;
45         private static final int CHECK_TIME_WAIT = 1;                   // in seconds
46         private static final int SECOND_TO_TURN_ON = 60;                // in seconds
47         private static final int SECOND_TO_TURN_OFF = 1;                // in seconds
48         private static final int LOCK_DOOR = 0;
49         private static final int UNLOCK_DOOR = 1;
50         // For IoTCloud
51         private static final String BASE_URL = "http://dc-6.calit2.uci.edu/test.iotcloud/";
52         private static final String PASSWORD = "reallysecret";
53         private static final int LOCAL_MACHINE_ID = 399;
54         private static final int LISTENING_PORT = -1;   // We don't use any listening port for this application
55         
56         /**
57          *  IoT Sets and Relations
58          *  <p>
59          *  Devices involved:
60          *  1) Multipurpose sensor (detect windows open/close) - Smartthings sensor
61          *  2) Motion sensor (detect motion in certain radius) - Smartthings sensor
62          *  3) Water leak sensor (detect leakage) - Smartthings sensor
63          *  4) Camera (detect motion)
64          *  5) Alarm (using ESP board) - assuming 1 house alarm
65          *  6) Room (object as place of device)
66          *  7) Doorlock (detect open/locked)
67          */
68         @config private IoTSet<SmartthingsSensorSmart> smartSensorsSet;
69         @config private IoTSet<CameraSmart> camSet;
70         @config private IoTSet<AlarmSmart> alarmSet;
71         @config private IoTSet<RoomSmart> roomSet;
72         @config private IoTSet<SmartthingsActuatorSmart> doorlockSet;
73         // Set of addresses for Fidelius connection (dc-6.calit2.uci.edu) to give access
74         @config private IoTSet<IoTAddress> iotcloudServer;
75
76         @config private IoTRelation<RoomSmart, SmartthingsSensorSmart> roomSensorRelation;
77         @config private IoTRelation<RoomSmart, CameraSmart> roomCameraRelation;
78         //@config private IoTRelation<RoomSmart, DoorLock> roomDoorLockRelation;
79
80         /*******************************************************************************************************************************************
81         **
82         **  Variables
83         **
84         *******************************************************************************************************************************************/
85         long lastTimeChecked = 0;
86
87         private static int sensorId = 0;
88         private static int doorlockId = 0;
89         // Initialize values 1 and 0 (for active and not active)
90         private final IoTString ACTIVE = new IoTString("1");            // ACTIVE can mean detecting or being locked for doorlock
91         private final IoTString NOT_ACTIVE = new IoTString("0");        // NOT_ACTIVE can mean not detecting or being unlocked for doorlock
92         private Table t1 = null;
93
94         /*******************************************************************************************************************************************
95         **
96         **  States data structures
97         **
98         *******************************************************************************************************************************************/
99         // Camera and motion detection
100         private Map<CameraSmart, MotionDetection> camMotionDetect =
101                 new HashMap<CameraSmart, MotionDetection>();
102         // Smartthings sensors (true = motion, leakage, etc.)
103         private Map<Integer, Boolean> senDetectStatus =
104                 new ConcurrentHashMap<Integer, Boolean>();
105         // Camera (true = motion)
106         private Map<CameraSmart, Boolean> camDetectStatus =
107                 new ConcurrentHashMap<CameraSmart, Boolean>();
108         // Doorlock (true = locked)
109         private Map<Integer, Boolean> doorlockStatus =
110                 new ConcurrentHashMap<Integer, Boolean>();
111
112         // Alarm status
113         private Map<Integer, Boolean> alarmStatus =
114                 new HashMap<Integer, Boolean>();
115
116         public HomeSecurityController() {
117
118         }
119
120
121         /*******************************************************************************************************************************************
122         **
123         **  Helper Methods
124         **
125         *******************************************************************************************************************************************/
126
127         /** Method to initialize IoTCloud server (dc-6.calit2.uci.edu)
128          *
129          *   @return [void] None.
130          */
131         private void initIoTCloudServer() {
132
133                 try {
134                         System.out.println("DEBUG: Initialize IoTCloud table!");
135                         // Get and init the IoTCloud server address
136                         // Setup table
137                         t1 = new Table(BASE_URL, PASSWORD, LOCAL_MACHINE_ID, LISTENING_PORT);
138                     t1.initTable();
139                         // Setup is done somewhere else, we just do rebuild()
140                         //t1.rebuild();
141                         System.out.println("DEBUG: Table initialized!");
142                         // Initialize sensors!
143                         // TODO: Still deciding whether to initialize all devices here or inside each init method
144                         int id = 0;
145                         // Initialize alarms! One alarm for now
146                         for(AlarmSmart alarm : alarmSet.values()) {
147                                 createKeyIoTCloud("alarm", NOT_ACTIVE);
148                                 System.out.println("DEBUG: Setting alarm to NOT-ACTIVE!");
149                         }
150                         // TODO: We can extend the usage of other keys to have a complete monitoring system for every device
151                         /*for(SmartthingsSensorSmart smartSensor : smartSensorsSet.values()) {
152                                 
153                                 createKeyIoTCloud("sensor" + Integer.toString(id++), NOT_ACTIVE);
154                                 System.out.println("DEBUG: Setting sensor" + id + " to NOT-ACTIVE!");                           
155                         }
156                         // Initialize cameras! One camera for now...
157                         for(CameraSmart cam : camSet.values()) {
158                                 createKeyIoTCloud("camera", NOT_ACTIVE);
159                                 System.out.println("DEBUG: Setting camera to NOT-ACTIVE!");
160                         }
161                         int doorId = 0;
162                         for(SmartthingsActuatorSmart doorlock : doorlockSet.values()) {
163                                 createKeyIoTCloud("doorlock" + Integer.toString(doorId), NOT_ACTIVE);
164                                 System.out.println("DEBUG: Setting doorlock" + id + " to NOT-ACTIVE!");
165                         }*/
166
167                 } catch(Exception e) {
168                         e.printStackTrace();
169                 }
170                 System.out.println("DEBUG: Cloud server transactions committed successfully!");
171         }
172
173         /** Method to create key IoTCloud
174          *
175          *   @param key                 [String] , encrypted key.
176          *   @param val                         [IoTString] , encrypted value.
177          *
178          *   @return [void] None.
179          */
180         private void createKeyIoTCloud(String key, IoTString val) {
181
182                 try {
183                         IoTString iotKey = new IoTString(key);
184                         t1.update();
185                         t1.createNewKey(iotKey, LOCAL_MACHINE_ID);
186                         t1.startTransaction();
187                         t1.addKV(iotKey, val);
188                         t1.commitTransaction();
189                         t1.update();
190                         System.out.println("DEBUG: Successfully committed transaction for: " + key);
191                 } catch(Exception e) {
192                         e.printStackTrace();
193                 }
194         }
195
196         /** Method to update IoTCloud
197          *
198          *   @param key                 [String] , encrypted key.
199          *   @param val                         [IoTString] , encrypted value.
200          *
201          *   @return [void] None.
202          */
203         private void updateIoTCloud(String key, IoTString val) {
204                 // No key creation here!
205                 try {
206                         IoTString iotKey = new IoTString(key);
207                         t1.update();
208                         t1.startTransaction();
209                         t1.addKV(iotKey, val);
210                         t1.commitTransaction();
211                         t1.update();
212                         System.out.println("DEBUG: Successfully committed transaction for: " + key);
213                 } catch(Exception e) {
214                         e.printStackTrace();
215                 }
216         }
217
218         /** Method to read IoTCloud
219          *
220          *   @param key                 [String] , encrypted key.
221          *   @param val                         [IoTString] , encrypted value.
222          *
223          *   @return [boolean] Check if it is ACTIVE or NOT_ACTIVE (true or false).
224          */
225         private boolean readIoTCloud(String key, IoTString iotTestVal) {
226
227                 t1.update();
228                 IoTString iotKey = new IoTString(key);
229                 IoTString iotVal = t1.getCommitted(iotKey);
230
231                 // Matching value and test value
232                 if ((iotVal != null) && (iotVal.equals(iotTestVal) == true))
233             return true;
234         // Mismatch between value and test value
235         return false;
236         }
237
238         /** Method to initialize Smartthings sensors
239          *
240          *   @return [void] None.
241          */
242         private void initSmartthingsSensors(RoomSmart rm) {
243
244                 // Get and init the IAS sensors for this specific room
245                 HashSet<SmartthingsSensorSmart> sensors = roomSensorRelation.get(rm);
246                 System.out.println("DEBUG: We have " + sensors.size() + " sensors!");
247                 for (SmartthingsSensorSmart sen : sensors) {
248         
249                         try {
250                                 // Initialize sensors
251                                 sen.init();
252                                 System.out.println("DEBUG: Initialized smartthings sensor! ID: " + sensorId + " Room ID: " + rm.getRoomID());
253                                 senDetectStatus.put(sensorId, false);
254                                 System.out.println("DEBUG: Initialized sensor detection to false!");
255                                 // Initialize IoTCloud
256                                 sen.setId(sensorId++);
257                                 sen.registerCallback(this);
258                                 System.out.println("DEBUG: Registered sensor callback!");
259                         } catch (Exception e) {
260                                 e.printStackTrace();
261                         }
262                 }
263         }
264
265
266         /** Method to initialize cameras
267          *
268          *   @return [void] None.
269          */
270         private void initCameras() {
271
272                 // Setup the cameras, start them all and assign each one a motion detector
273                 for (CameraSmart cam : camSet.values()) {
274
275                         // Each camera will have a motion detector unique to it since the motion detection has state
276                         MotionDetection mo = new MotionDetection(12, 0.5f, 10, 10);
277
278                         // initialize the camera, might need to setup some stuff internally
279                         cam.init();
280
281                         // set the camera parameters.
282                         cam.setFPS(CAMERA_FPS);
283                         cam.setResolution(Resolution.RES_VGA);
284
285                         // camera will call the motion detector directly with data not this controller
286                         cam.registerCallback(mo);
287
288                         // Start the camera (example is start the HTTP stream if it is a network camera)
289                         cam.start();
290                         System.out.println("DEBUG: Initialized camera!");
291
292                         // Remember which motion detector is for what camera
293                         camMotionDetect.put(cam, mo);
294
295                         // Initialize detection to false
296                         camDetectStatus.put(cam, false);
297                 }
298         }
299
300
301         /** Method to initialize alarms
302          *
303          *   @return [void] None.
304          */
305         private void initAlarms() {
306
307                 // Get and init the alarm (this single alarm set can serve multiple zones / rooms)
308                 Iterator alarmIt = alarmSet.iterator();
309                 AlarmSmart alm = (AlarmSmart) alarmIt.next();
310                 // Initialize the alarm controller, do it here since it only needs to be done once per controller
311                 try {
312                         alm.init();
313                         System.out.println("DEBUG: Initialized alarm!");
314                 } catch (Exception e) {
315                         e.printStackTrace();
316                 }
317         }
318
319
320         /** Method to initialize doorlocks
321          *
322          *   @return [void] None.
323          */
324         private void initDoorLocks() {
325
326                 // Get and init the doorlocks (we only assume 1 main doorlock)
327                 Set<SmartthingsActuatorSmart> doorlocks = doorlockSet.values();
328                 for (SmartthingsActuatorSmart doorlock : doorlocks) {
329         
330                         try {
331                                 // Initialize doorlocks
332                                 doorlock.init();
333                                 System.out.println("DEBUG: Initialized doorlock! ID: " + doorlockId);
334                                 doorlockStatus.put(doorlockId, false);
335                                 System.out.println("DEBUG: Initialized doorlock status to false!");
336                                 doorlock.setId(doorlockId++);
337                                 doorlock.registerCallback(this);
338                                 System.out.println("DEBUG: Registered doorlock callback!");
339                         } catch (Exception e) {
340                                 e.printStackTrace();
341                         }
342                 }
343         }
344
345
346         /** Method to detect if a room has seen motion within the last few seconds (time specified as parameter).
347          *   Checks all the motion detectors for the given room
348          *
349          *   @param _room            [RoomSmart] , Room of interest.
350          *   @param _numberOfSeconds [int]  , Number of seconds in the past that we consider recent.
351          *   @param _upperThreshold  [int]  , Number of seconds as an upper bound before we turn off.
352          *
353          *   @return [boolean] None.
354          */
355         private boolean roomDidHaveMotionRecently(RoomSmart _room, int _numberOfSeconds) {
356                 long currentTimeSeconds = (new Date()).getTime() / 1000;
357
358                 // Loop through all the cameras in the room
359                 for (CameraSmart cam : roomCameraRelation.get(_room)) {
360                         long lastDetectedMotionSeconds = currentTimeSeconds;
361
362                         Date motionTime = ((MotionDetection)camMotionDetect.get(cam)).getTimestampOfLastMotion();
363
364                         // Motion was detected at least once
365                         if (motionTime != null) {
366                                 lastDetectedMotionSeconds = motionTime.getTime() / 1000;
367                         } else {
368                                 // motionTime == null means this is the initialization phase
369                                 // so we put false
370                                 return false;
371                         }
372
373                         // Did detect motion recently
374                         if (Math.abs(currentTimeSeconds - lastDetectedMotionSeconds) < _numberOfSeconds) {
375                                 return true;
376                         }
377                 }
378
379                 return false;
380         }
381
382
383         /** Method to update state data structures for Smartthings sensors
384          *
385          *   @return [void] None.
386          */
387         public void newReadingAvailable(int _sensorId, int _value, boolean _activeValue) {
388
389                 System.out.println("DEBUG: Sensor reading value: " + _value);
390
391                 String sensor = "sensor" + Integer.toString(_sensorId);
392                 if(_activeValue) {
393                         System.out.println("DEBUG: Sensor " + sensorId + " is detecting something: " + _activeValue);
394                         senDetectStatus.put(_sensorId, true);
395                         //updateIoTCloud(sensor, ACTIVE);
396                 } else {
397                         //System.out.println("DEBUG: Sensor " + sensorId + " is not detecting something: " + _activeValue);
398                         senDetectStatus.put(_sensorId, false);
399                         //updateIoTCloud(sensor, NOT_ACTIVE);
400                 }
401         }
402
403         /** Method to update state data structures for Smartthings actuators
404          *
405          *   @return [void] None.
406          */
407         public void newActuatorReadingAvailable(int _sensorId, int _value, boolean _activeValue) {
408
409                 System.out.println("DEBUG: Actuator " + _sensorId + " reading value: " + _value);
410
411                 // Update IoTCloud              
412                 String actuator = "doorlock" + Integer.toString(_sensorId);
413                 if(_activeValue) {
414                         System.out.println("DEBUG: Actuator " + _sensorId + " is detecting something: " + _activeValue);
415                         doorlockStatus.put(_sensorId, true);
416                         //updateIoTCloud(actuator, ACTIVE);
417                 } else {
418                         //System.out.println("DEBUG: Actuator " + _sensorId + " is not detecting something: " + _activeValue);
419                         doorlockStatus.put(_sensorId, false);
420                         //updateIoTCloud(actuator, NOT_ACTIVE);
421                 }
422         }
423
424
425         /** Update the status of all devices
426          *
427          *   @return [void] None.
428          */
429         private void updateUniversalStatus() {
430
431                 // Check for motion in rooms and if there is motion then report
432                 for (RoomSmart room : roomSet.values()) {
433
434                         // Update status of camera
435                         updateCameraStatus(room);
436
437                         // Update status of other devices if any
438                         // ...
439                 }
440         }
441
442
443         /** Update the status of all devices
444          *
445          *   @return [void] None.
446          */
447         private void updateCameraStatus(RoomSmart room) {
448
449                 HashSet<CameraSmart> cameras = roomCameraRelation.get(room);
450                 if (roomDidHaveMotionRecently(room, MOTION_TIME_THRESHOLD)) {
451
452                         // Motion was detected
453                         System.out.println("DEBUG: Camera detected something!");
454                         for(CameraSmart cam : cameras) {
455                                 camDetectStatus.put(cam, true);
456                                 //updateIoTCloud("camera", ACTIVE);
457                         }
458                 } else {
459
460                         // No motion was detected
461                         //System.out.println("DEBUG: Camera didn't detect anything!");
462                         for(CameraSmart cam : cameras) {
463                                 camDetectStatus.put(cam, false);
464                                 //updateIoTCloud("camera", NOT_ACTIVE);
465                         }
466                 }
467         }
468
469         /** Method to turn on alarms
470          *
471          *   @return [void] None.
472          */
473         private void turnOnAlarms(int zoneId) {
474
475                 // Get and init the alarm (this single alarm set can serve multiple zones / rooms)
476                 Iterator alarmIt = alarmSet.iterator();
477                 AlarmSmart alm = (AlarmSmart) alarmIt.next();
478                 alm.setZone(zoneId, true, SECOND_TO_TURN_OFF);
479                 updateIoTCloud("alarm", ACTIVE);
480         }
481
482
483         /** Method to turn off alarms
484          *
485          *   @return [void] None.
486          */
487         private void turnOffAlarms(int zoneId) {
488
489                 // Get and init the alarm (this single alarm set can serve multiple zones / rooms)
490                 Iterator alarmIt = alarmSet.iterator();
491                 AlarmSmart alm = (AlarmSmart) alarmIt.next();
492                 // Turn this alarm off indefinitely
493                 alm.setZone(zoneId, false, SECOND_TO_TURN_ON);
494                 updateIoTCloud("alarm", NOT_ACTIVE);
495         }
496
497
498         /** Method to lock doors
499          *
500          *   @return [void] None.
501          */
502         private void lockDoors() {
503
504                 // Get and lock the doorlocks (we only assume 1 main doorlock)
505                 Set<SmartthingsActuatorSmart> doorlocks = doorlockSet.values();
506                 for (SmartthingsActuatorSmart doorlock : doorlocks) {
507         
508                         doorlock.actuate(LOCK_DOOR);
509                         int doorId = doorlock.getId();
510                         System.out.println("DEBUG: Lock doorlock! ID: " + doorId);
511                         doorlockStatus.put(doorId, true);
512                         //updateIoTCloud("doorlock" + doorId, ACTIVE);
513                 }
514         }
515
516
517         /** Check status of devices and turn on alarm accordingly
518          *  <p>
519          *  Simple rule is whenever any sensor or camera detect something unusual
520          *      (sensor/camera becomes active) then we sound the corresponding alarm.
521          *  This means we send the signal to the right zone in the EspAlarm
522          *
523          *   @return [void] None.
524          */
525         private void decideToTurnOnAlarm() {
526
527                 int zoneId = 0;
528
529                 // Check for motion in rooms and if there is motion then report
530                 for (RoomSmart room : roomSet.values()) {
531
532                         // Loop through all the cameras in the room
533                         for (CameraSmart cam : roomCameraRelation.get(room)) {
534
535                                 // Get the right camera and the right detection status (true or false)
536                                 if (camDetectStatus.get(cam)) {
537                                         zoneId = room.getRoomID();
538                                         turnOnAlarms(zoneId);
539                                         System.out.println("DETECTION: Camera active in room: " + zoneId);
540                                         lockDoors();
541                                         while(readIoTCloud("alarm", ACTIVE)) { 
542                                                 System.out.println("DETECTION: Now alarm is on so wait here until turned off!");
543                                                 try {
544                                                         Thread.sleep(5000); // sleep for a tenth of the time
545                                                 } catch (Exception e) {
546                                                         e.printStackTrace();
547                                                 }
548                                         } // Wait until turned off!
549                                         System.out.println("DETECTION: Now alarm is turned off!");
550                                         cleanUp();
551                                         return;
552                                 }
553                         }
554
555                         // Loop through all the cameras in the room
556                         for (SmartthingsSensorSmart sensor : roomSensorRelation.get(room)) {
557
558                                 // Get the right sensor and the right detection status (true or false)
559                                 //System.out.println("ABOUT TO DETECT: Reading sensor: " + sensor.getId());
560                                 if (senDetectStatus.get(sensor.getId())) {
561                                         zoneId = room.getRoomID();
562                                         turnOnAlarms(zoneId);
563                                         System.out.println("DETECTION: Sensor active in room: " + zoneId);
564                                         System.out.println("DETECTION: Detection by sensor: " + sensor.getId());
565                                         lockDoors();
566                                         while(readIoTCloud("alarm", ACTIVE)) { 
567                                                 System.out.println("DETECTION: Now alarm is on so wait here until turned off!");
568                                                 try {
569                                                         Thread.sleep(5000); // sleep for a tenth of the time
570                                                 } catch (Exception e) {
571                                                         e.printStackTrace();
572                                                 }
573                                         } // Wait until turned off!
574                                         System.out.println("DETECTION: Now alarm is turned off!");
575                                         cleanUp();
576                                         return;
577                                 }
578                         }
579                 }
580         }
581
582
583         /** Clean up all status booleans
584          *  <p>
585          *
586          *   @return [void] None.
587          */
588         private void cleanUp() {
589
590                 // Clear sensor status
591                 for (Boolean senStatus : senDetectStatus.values()) {
592                         senStatus = false;
593                 }
594                 // Clear camera status
595                 for (Boolean camStatus : camDetectStatus.values()) {
596                         camStatus = false;
597                 }
598                 // Clear doorlock status
599                 for (Boolean doorStatus : doorlockStatus.values()) {
600                         doorStatus = false;
601                 }
602                 // Clear alarm status
603                 updateIoTCloud("alarm", NOT_ACTIVE);
604                 // Turn off alaarms
605                 for (RoomSmart room : roomSet.values()) {
606                         int zoneId = room.getRoomID();
607                         turnOffAlarms(zoneId);
608                 }
609         }
610
611
612         /*******************************************************************************************************************************************
613         **
614         **  Public Methods
615         **
616         *******************************************************************************************************************************************/
617
618         /** Initialization method, called by the runtime (effectively the main of the controller)
619          *   This method runs a continuous loop and is blocking
620          *
621          *   @return [void] None;
622          */
623         public void init() {
624
625                 // Initialize IoTCloud server
626                 initIoTCloudServer();
627
628                 // Iterate over the set of rooms
629                 for (RoomSmart rm : roomSet.values()) {
630
631                         // Init all Smartthings sensors
632                         initSmartthingsSensors(rm);
633                 }
634                 // Init all doorlocks
635                 initDoorLocks();
636
637                 // Init all alarms
638                 initAlarms();
639
640                 // Init all cameras
641                 initCameras();
642
643                 System.out.println("DEBUG: Initialized all devices! Now starting detection loop!");
644
645                 // Run the main loop that will keep checking the sensors and cameras periodically
646                 while (true) {
647
648                         // Run this code every <specified time>
649                         long currentTimeSeconds = (new Date()).getTime() / 1000;
650                         if ((currentTimeSeconds - lastTimeChecked) > CHECK_TIME_WAIT) {
651                                 lastTimeChecked = currentTimeSeconds;
652
653                                 // Update the status of all devices
654                                 updateUniversalStatus();
655
656                                 // Decide to turn on alarm if any of the sensor/camera detects something unusual
657                                 decideToTurnOnAlarm();
658
659                         } else {
660
661                                 try {
662
663                                         Thread.sleep(CHECK_TIME_WAIT * 100); // sleep for a tenth of the time
664
665                                 } catch (Exception e) {
666
667                                         e.printStackTrace();
668                                 }
669                         }
670
671                 }
672         }
673 }
674
675