17fcd67f2fe0ad08d3ceb4097dfcd49107a6c98d
[iot2.git] / benchmarks / SpeakerController / SpeakerController.java
1 package SpeakerController;
2
3 // IoT Runtime packages
4 import iotruntime.slave.IoTSet;
5 import iotruntime.slave.IoTRelation;
6 //import iotcode.annotation.*;
7
8 // IoT driver packages
9 import iotcode.interfaces.*;
10 //import iotcode.annotation.*;
11
12
13 // Standard Java packages
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.List;
17 import java.util.ArrayList;
18 import java.io.File;
19 import java.util.Date;  // TODO: Get rid of all depreciated stuff for date, switch to Calender
20 import java.util.concurrent.Semaphore;
21 import java.util.concurrent.atomic.AtomicBoolean;
22 import java.util.Random;
23
24 // RMI packages
25 import java.rmi.RemoteException;
26 import java.rmi.server.UnicastRemoteObject;
27
28 // Checker annotations
29 import iotchecker.qual.*;
30
31 /** Class SpeakerController for the smart home application benchmark
32  *  <p>
33  *  This controller controls speakers and streams music based on
34  *  GPS-like input from a phone app that notifies the controller
35  *  about the position of the person
36  *
37  * @author      Rahmadi Trimananda <rahmadi.trimananda @ uci.edu>
38  * @version     1.0
39  * @since       2016-04-28
40  */
41 public class SpeakerController extends UnicastRemoteObject implements GPSGatewayCallback, SpeakerCallback {
42
43         /*
44          *  Constants
45          */
46         public static final int CHECK_TIME_WAIT_MSEC = 100;     // 1 means 1 x 100 = 0.1 second
47         public static final String MUSIC_FILE_DIRECTORY = "./music/";   // file that the music files are in
48         public static final int CURRENT_POSITION_SAMPLE_THRESHOLD = (int)((long)((long)3 * (long)CHECK_TIME_WAIT_MSEC * (long)44100) / (long)1000);
49
50         /**
51          * IoT Sets of Devices
52          */
53         @config private IoTSet < @NonLocalRemote GPSGateway > gpsSet;
54         @config private IoTSet < @NonLocalRemote Speaker > speakerSet;
55
56         /**
57          * IoT Sets of Things that are not devices such as rooms
58          */
59         @config private IoTSet < @NonLocalRemote Room > audioRooms;
60
61         /**
62          * IoT Relations
63          */
64         @config private IoTRelation < @NonLocalRemote Room, @NonLocalRemote Speaker > roomSpeakerRel;
65
66         /**
67          * The state that the room main lights are supposed to be in
68          */
69         Map < @NonLocalRemote Room, Boolean > roomSpeakersOnOffStatus = new HashMap < @NonLocalRemote Room, Boolean > ();
70
71         // used to notify if new data is available
72         private AtomicBoolean newDataAvailable = new AtomicBoolean(false);
73
74         // the settings from the interface, used to setup the system
75         private int roomIdentifier = 0;
76         private boolean ringStatus = false;
77
78         // playback state variables
79         private int currentPosition = 0;
80         private AtomicBoolean playbackDone = new AtomicBoolean(false);
81
82
83         public SpeakerController() throws RemoteException {
84
85         }
86
87
88         /** Callback method for when room ID is retrieved.
89          *
90          * @param _ggw [GPSGateway].
91          * @return [void] None.
92          */
93         public void newRoomIDRetrieved(@NonLocalRemote GPSGateway _ggw) {
94
95                 try {
96                         // get the parameters that the interface (phone app) reads from the user
97                         roomIdentifier = _ggw.getRoomID();
98
99                         System.out.println("DEBUG: New room ID is retrieved from phone!!! Room: " + roomIdentifier);
100
101                         // Data is read, so we set this back to false
102                         _ggw.setNewRoomIDAvailable(false);
103                 } catch (RemoteException ex) {
104                         ex.printStackTrace();
105                 }
106
107                 // new data available so set it to true
108                 newDataAvailable.set(true);
109         }
110
111
112         /** Callback method for when ring status is retrieved.
113          *
114          * @param _ggw [GPSGateway].
115          * @return [void] None.
116          */
117         public void newRingStatusRetrieved(@NonLocalRemote GPSGateway _ggw) {
118
119                 try {
120                         // get the parameters that the interface (phone app) reads from the user
121                         ringStatus = _ggw.getRingStatus();
122
123                         System.out.println("DEBUG: New ring status is retrieved from phone!!! Status: " + ringStatus);
124
125                         // Data is read, so we set this back to false
126                         _ggw.setNewRingStatusAvailable(false);
127                 } catch (RemoteException ex) {
128                         ex.printStackTrace();
129                 }
130
131                 // new data available so set it to true
132                 newDataAvailable.set(true);
133         }
134
135
136         /** Callback method when a speaker has finished playing what is in its audio buffer.
137          *
138          * @param _speaker [Speaker].
139          * @return [void] None.
140          */
141         public void speakerDone(@NonLocalRemote Speaker _speaker) {
142                 for (@NonLocalRemote Speaker speakers : speakerSet.values()) {
143                         playbackDone.set(true);
144                 }
145         }
146
147
148         /*******************************************************************************************************************************************
149         **
150         ** Private Helper Methods
151         **
152         *******************************************************************************************************************************************/
153
154         /**
155          * Update speakers action based on the updated speakers status
156          */
157         private void updateSpeakersAction() {
158
159                 // Stream music on speakers based on their status
160                 for (@NonLocalRemote Room room : audioRooms.values()) {
161
162                         // Check status of the room
163                         if (roomSpeakersOnOffStatus.get(room)) {
164
165                                 // used to get the average of the speakers position
166                                 long currPosTotal = 0;
167                                 long currPosCount = 0;
168
169                                 // Get the speaker objects one by one assuming that we could have
170                                 // more than one speaker per room
171                                 for (@NonLocalRemote Speaker speakers : roomSpeakerRel.get(room)) {
172                                         // System.out.println("DEBUG: Turn on speaker!");
173
174                                         try {
175
176                                                 // start the speaker playback if the speaker is not playing yet
177                                                 if (!speakers.getPlaybackState()) {
178
179                                                         System.out.println("Turning a speaker On");
180                                                         speakers.startPlayback();
181                                                         speakers.setPosition(currentPosition);
182
183                                                 } else {
184                                                         // get average of the positions
185                                                         currPosTotal += speakers.getPosition();
186                                                         currPosCount++;
187                                                 }
188
189
190                                         } catch (RemoteException e) {
191                                                 e.printStackTrace();
192                                         }
193                                 }
194
195                                 if (currPosCount != 0) {
196                                         
197                                         // get average position of the speakers
198                                         int currentPosOfSpeakers = (int)(currPosTotal / currPosCount);
199
200                                         // check how close we are to the correct position
201                                         if (Math.abs(currentPosOfSpeakers - currentPosition) > CURRENT_POSITION_SAMPLE_THRESHOLD) {
202                                                 // we were kind of far so update all the positions
203
204                                                 for (@NonLocalRemote Speaker speakers : roomSpeakerRel.get(room)) {
205                                                         try {
206                                                                 speakers.setPosition(currentPosOfSpeakers);
207                                                         } catch (RemoteException e) {
208                                                                 e.printStackTrace();
209                                                         }
210                                                 }
211                                         }
212
213                                         // update the current position
214                                         currentPosition = currentPosOfSpeakers;
215                                 }
216
217
218
219                         } else {
220                                 // Room status is "off"
221
222                                 // used to get the average of the speakers position
223                                 long currPosTotal = 0;
224                                 long currPosCount = 0;
225
226                                 // Get the speaker objects one by one assuming that we could have
227                                 // more than one speaker per room
228                                 for (@NonLocalRemote Speaker speakers : roomSpeakerRel.get(room)) {
229                                         // System.out.println("DEBUG: Turn off speaker!");
230                                         try {
231                                                 // Turning off speaker if they are still on
232                                                 if (speakers.getPlaybackState()) {
233                                                         System.out.println("Turning a speaker off");
234
235                                                         currPosTotal += (long)speakers.getPosition();
236                                                         currPosCount++;
237                                                         boolean tmp = speakers.stopPlayback();
238
239                                                         if (!tmp) {
240                                                                 System.out.println("Error Turning off");
241                                                         }
242                                                 }
243
244                                         } catch (RemoteException e) {
245                                                 e.printStackTrace();
246                                         }
247                                 }
248
249                                 // get the average current position of the speakers
250                                 // so we can resume other speakers from same position
251                                 if (currPosCount != 0) {
252                                         currentPosition = (int)(currPosTotal / currPosCount);
253                                 }
254                         }
255                 }
256
257                 // a speaker has finished playing and so we should change all the audio buffers
258                 if (playbackDone.get()) {
259
260                         // song done so update the audio buffers
261                         prepareNextSong();
262                         playbackDone.set(false);
263                 }
264         }
265
266
267         /**
268          * Update speakers status based on room ID and ring status information from phone
269          */
270         private void updateSpeakersStatus() {
271
272                 // If we have new data, we update the speaker streaming
273                 if (newDataAvailable.get()) {
274
275                         // System.out.println("DEBUG: New data is available!!!");
276                         // System.out.println("DEBUG: Ring status: " + ringStatus);
277                         // Check for ring status first
278                         if (ringStatus) {
279
280                                 // Turn off all speakers if ring status is true (phone is ringing)
281                                 for (@NonLocalRemote Room room : audioRooms.values()) {
282
283                                         // System.out.println("DEBUG: Update status off for speakers! Phone is ringing!!!");
284                                         // Turn off speaker
285                                         roomSpeakersOnOffStatus.put(room, false);
286                                 }
287
288                         } else {
289                                 // Phone is not ringing... just play music on the right speaker
290
291                                 // Check for every room
292                                 for (@NonLocalRemote Room room : audioRooms.values()) {
293
294                                         try {
295                                                 // Turn on the right speaker based on room ID sent from phone app
296                                                 // Stream audio to a speaker based on room ID
297                                                 if (room.getRoomID() == roomIdentifier) {
298
299                                                         // System.out.println("DEBUG: This room ID: " + room.getRoomID());
300                                                         // System.out.println("DEBUG: Turn on the speaker(s) in this room!!!");
301                                                         // Set speaker status to on
302                                                         roomSpeakersOnOffStatus.put(room, true);
303
304                                                 } else {
305                                                         // for the rooms whose IDs aren't equal to roomIdentifier
306
307                                                         // System.out.println("DEBUG: Turn on speaker!");
308                                                         // Set speaker status to off
309                                                         roomSpeakersOnOffStatus.put(room, false);
310                                                 }
311
312                                         } catch (RemoteException ex) {
313                                                 ex.printStackTrace();
314                                         }
315                                 }
316                         }
317                         // Finish processing data - put this back to false
318                         newDataAvailable.set(false);
319                 }
320         }
321
322         /**
323          * Prepare the speakers for a new song to start playing
324          */
325         private void prepareNextSong() {
326                 System.out.println("Starting Music Prep");
327
328                 System.out.println("Stopping all device playback");
329                 // stop all devices that are still playing and clear their buffers
330                 // they are about to end playback anyways
331                 for (@NonLocalRemote Speaker speakers : speakerSet.values()) {
332                         try {
333
334                                 if (speakers.getPlaybackState()) {
335                                         speakers.stopPlayback();
336                                 }
337                                 speakers.clearData();
338                         } catch (Exception e) {
339                                 e.printStackTrace();
340                         }
341                 }
342
343                 // get the music file names that are in the music files directory
344                 File musicFolder = new File(MUSIC_FILE_DIRECTORY);
345                 File[] audioFiles = musicFolder.listFiles();
346                 List<String> audioFileNames = new ArrayList<String>();
347
348                 // put all names in a list
349                 for (int i = 0; i < audioFiles.length; i++) {
350                         if (audioFiles[i].isFile()) {
351                                 try {
352                                         audioFileNames.add(audioFiles[i].getCanonicalPath());
353                                 } catch (Exception ex) {
354                                         ex.printStackTrace();
355                                 }
356                         }
357                 }
358
359                 // pick a random file to play
360                 Random rand = new Random(System.nanoTime());
361                 String audioFilename = audioFileNames.get(rand.nextInt(audioFileNames.size()));
362
363                 System.out.println("Going to load audio file");
364                 System.out.println(audioFilename);
365
366                 // decode the mp3 file
367                 System.out.println("Starting Decode");
368                 MP3Decoder dec = new MP3Decoder(audioFilename);
369                 List<short[]> dat = dec.getDecodedFrames();
370                 System.out.println("Ending Decode");
371
372                 currentPosition = 0;
373
374                 // count the number of samples
375                 int count = 0;
376                 for (short[] d : dat) {
377                         count += d.length;
378                 }
379
380                 // make into a single large buffer for 1 large RMI call
381                 short[] compressedArray = new short[count];
382                 count = 0;
383                 for (short[] d : dat) {
384                         for (short s : d) {
385                                 compressedArray[count] = s;
386                                 count++;
387                         }
388                 }
389
390
391                 System.out.println("Loading Speakers");
392                 // send the new data to all the speakers
393                 for (@NonLocalRemote Speaker speakers : speakerSet.values()) {
394                         System.out.println("Loading a single speaker with data");
395                         try {
396                                 speakers.loadData(compressedArray, 0, compressedArray.length);
397                         } catch (Exception e) {
398                                 e.printStackTrace();
399                         }
400                         System.out.println("Done loading a single speaker with data");
401                 }
402
403                 System.out.println("All Speakers done loading");
404
405         }
406
407
408
409         /********************************************************************************************************
410         ** Public methods, called by the runtime
411         *********************************************************************************************************/
412
413         /** Initialization method, called by the runtime (effectively the main of the controller)
414          *   This method runs a continuous loop and is blocking
415          *
416          *   @return [void] None;
417          */
418         public void init() throws RemoteException, InterruptedException {
419
420                 // Initialize the rooms
421                 for (@NonLocalRemote Room room : audioRooms.values()) {
422                         // All rooms start with the speakers turned off
423                         roomSpeakersOnOffStatus.put(room, false);
424                 }
425
426                 // Setup the cameras, start them all and assign each one a motion detector
427                 for (@NonLocalRemote GPSGateway gw : gpsSet.values()) {
428
429                         try {
430                                 // initialize, register callback, and start the gateway
431                                 gw.init();
432                                 gw.registerCallback(this);
433                                 gw.start();
434                         } catch (RemoteException ex) {
435                                 ex.printStackTrace();
436                         }
437                 }
438
439
440                 //Initialize the speakers
441                 for (@NonLocalRemote Speaker speakers : speakerSet.values()) {
442                         speakers.init();
443                 }
444
445                 prepareNextSong();
446
447                 // Run the main loop that will keep checking stuff
448                 while (true) {
449
450                         // Update speakers status (on/off) based on info from phone
451                         updateSpeakersStatus();
452
453                         // Check and turn on/off (stream music) through speakers based on its status
454                         updateSpeakersAction();
455
456                         try {
457                                 Thread.sleep(CHECK_TIME_WAIT_MSEC); // sleep for a tenth of the time
458                         } catch (Exception e) {
459                         }
460                 }
461         }
462 }
463
464
465
466