Minor adjustments/cleanups for SpeakerController application
[iot2.git] / benchmarks / Java / 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 < GPSGatewaySmart > gpsSet;
54         @config private IoTSet < SpeakerSmart > speakerSet;
55
56         /**
57          * IoT Sets of Things that are not devices such as rooms
58          */
59         @config private IoTSet < RoomSmart > audioRooms;
60
61         /**
62          * IoT Relations
63          */
64         @config private IoTRelation < RoomSmart, SpeakerSmart > roomSpeakerRel;
65
66         /**
67          * The state that the room main lights are supposed to be in
68          */
69         Map < RoomSmart, Boolean > roomSpeakersOnOffStatus = new HashMap < RoomSmart, 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 _roomIdentifier [int].
91          * @return [void] None.
92          */
93         public void newRoomIDRetrieved(int _roomIdentifier) {
94
95                 roomIdentifier = _roomIdentifier;
96                 System.out.println("DEBUG: New room ID is retrieved from phone!!! Room: " + roomIdentifier);
97
98                 // new data available so set it to true
99                 newDataAvailable.set(true);
100         }
101
102
103         /** Callback method for when ring status is retrieved.
104          *
105          * @param _ringStatus [boolean].
106          * @return [void] None.
107          */
108         public void newRingStatusRetrieved(boolean _ringStatus) {
109
110                 ringStatus = _ringStatus;
111                 System.out.println("DEBUG: New ring status is retrieved from phone!!! Status: " + ringStatus);
112
113                 // new data available so set it to true
114                 newDataAvailable.set(true);
115         }
116
117
118         /** Callback method when a speaker has finished playing what is in its audio buffer.
119          *
120          * @return [void] None.
121          */
122         public void speakerDone() {
123                 for (SpeakerSmart speakers : speakerSet.values()) {
124                         playbackDone.set(true);
125                 }
126         }
127
128
129         /*******************************************************************************************************************************************
130         **
131         ** Private Helper Methods
132         **
133         *******************************************************************************************************************************************/
134
135         /**
136          * Update speakers action based on the updated speakers status
137          */
138         private void updateSpeakersAction() {
139
140                 // Stream music on speakers based on their status
141                 for (RoomSmart room : audioRooms.values()) {
142
143                         // Check status of the room
144                         if (roomSpeakersOnOffStatus.get(room)) {
145
146                                 // used to get the average of the speakers position
147                                 long currPosTotal = 0;
148                                 long currPosCount = 0;
149
150                                 // Get the speaker objects one by one assuming that we could have
151                                 // more than one speaker per room
152                                 for (SpeakerSmart speakers : roomSpeakerRel.get(room)) {
153                                         System.out.println("DEBUG: Turn on speaker!");
154                                         // start the speaker playback if the speaker is not playing yet
155                                         if (!speakers.getPlaybackState()) {
156
157                                                 System.out.println("Turning a speaker On in room: " + room.getRoomID());
158                                                 speakers.startPlayback();
159                                                 speakers.setPosition(currentPosition);
160
161                                         } else {
162                                                 // get average of the positions
163                                                 currPosTotal += speakers.getPosition();
164                                                 currPosCount++;
165                                         }
166                                 }
167
168                                 if (currPosCount != 0) {
169                                         
170                                         // get average position of the speakers
171                                         int currentPosOfSpeakers = (int)(currPosTotal / currPosCount);
172
173                                         // check how close we are to the correct position
174                                         if (Math.abs(currentPosOfSpeakers - currentPosition) > CURRENT_POSITION_SAMPLE_THRESHOLD) {
175                                                 // we were kind of far so update all the positions
176
177                                                 for (SpeakerSmart speakers : roomSpeakerRel.get(room)) {
178                                                         speakers.setPosition(currentPosOfSpeakers);
179                                                 }
180                                         }
181
182                                         // update the current position
183                                         currentPosition = currentPosOfSpeakers;
184                                 }
185
186
187
188                         } /*else {
189                                 // Room status is "off"
190
191                                 // used to get the average of the speakers position
192                                 long currPosTotal = 0;
193                                 long currPosCount = 0;
194
195                                 // Get the speaker objects one by one assuming that we could have
196                                 // more than one speaker per room
197                                 for (Speaker speakers : roomSpeakerRel.get(room)) {
198                                         // System.out.println("DEBUG: Turn off speaker!");
199                                         try {
200                                                 // Turning off speaker if they are still on
201                                                 if (speakers.getPlaybackState()) {
202                                                         System.out.println("Turning a speaker off");
203
204                                                         currPosTotal += (long)speakers.getPosition();
205                                                         currPosCount++;
206                                                         boolean tmp = speakers.stopPlayback();
207
208                                                         if (!tmp) {
209                                                                 System.out.println("Error Turning off");
210                                                         }
211                                                 }
212
213                                         } catch (RemoteException e) {
214                                                 e.printStackTrace();
215                                         }
216                                 }
217
218                                 // get the average current position of the speakers
219                                 // so we can resume other speakers from same position
220                                 if (currPosCount != 0) {
221                                         currentPosition = (int)(currPosTotal / currPosCount);
222                                 }
223                         }*/
224                 }
225
226                 // a speaker has finished playing and so we should change all the audio buffers
227                 if (playbackDone.get()) {
228
229                         // song done so update the audio buffers
230                         prepareNextSong();
231                         playbackDone.set(false);
232                 }
233         }
234
235
236         /**
237          * Update speakers status based on room ID and ring status information from phone
238          */
239         private void updateSpeakersStatus() {
240
241                 // If we have new data, we update the speaker streaming
242                 if (newDataAvailable.get()) {
243
244                         System.out.println("DEBUG: New data is available!!!");
245                         System.out.println("DEBUG: Ring status: " + ringStatus);
246                         // Check for ring status first
247                         if (ringStatus) {
248
249                                 // Turn off all speakers if ring status is true (phone is ringing)
250                                 for (RoomSmart room : audioRooms.values()) {
251
252                                         System.out.println("DEBUG: Update status off for speakers! Phone is ringing!!!");
253                                         // Turn off speaker
254                                         roomSpeakersOnOffStatus.put(room, false);
255                                 }
256
257                         } else {
258                                 // Phone is not ringing... just play music on the right speaker
259
260                                 // Check for every room
261                                 for (RoomSmart room : audioRooms.values()) {
262
263                                         // Turn on the right speaker based on room ID sent from phone app
264                                         // Stream audio to a speaker based on room ID
265                                         if (room.getRoomID() == roomIdentifier) {
266
267                                                 System.out.println("DEBUG: This room ID: " + room.getRoomID());
268                                                 System.out.println("DEBUG: Turn on the speaker(s) in this room!!!");
269                                                 // Set speaker status to on
270                                                 roomSpeakersOnOffStatus.put(room, true);
271
272                                         } else {
273                                                 // for the rooms whose IDs aren't equal to roomIdentifier
274
275                                                 System.out.println("DEBUG: Turn on speaker!");
276                                                 // Set speaker status to off
277                                                 roomSpeakersOnOffStatus.put(room, false);
278                                         }
279                                 }
280                         }
281                         // Finish processing data - put this back to false
282                         newDataAvailable.set(false);
283                 }
284         }
285
286         /**
287          * Prepare the speakers for a new song to start playing
288          */
289         private void prepareNextSong() {
290                 System.out.println("Starting Music Prep");
291
292                 System.out.println("Stopping all device playback");
293                 // stop all devices that are still playing and clear their buffers
294                 // they are about to end playback anyways
295                 for (SpeakerSmart speakers : speakerSet.values()) {
296                         try {
297
298                                 if (speakers.getPlaybackState()) {
299                                         speakers.stopPlayback();
300                                 }
301                                 speakers.clearData();
302                         } catch (Exception e) {
303                                 e.printStackTrace();
304                         }
305                 }
306
307                 // get the music file names that are in the music files directory
308                 File musicFolder = new File(MUSIC_FILE_DIRECTORY);
309                 File[] audioFiles = musicFolder.listFiles();
310                 List<String> audioFileNames = new ArrayList<String>();
311
312                 // put all names in a list
313                 for (int i = 0; i < audioFiles.length; i++) {
314                         if (audioFiles[i].isFile()) {
315                                 try {
316                                         audioFileNames.add(audioFiles[i].getCanonicalPath());
317                                 } catch (Exception ex) {
318                                         ex.printStackTrace();
319                                 }
320                         }
321                 }
322
323                 // pick a random file to play
324                 Random rand = new Random(System.nanoTime());
325                 String audioFilename = audioFileNames.get(rand.nextInt(audioFileNames.size()));
326
327                 System.out.println("Going to load audio file");
328                 System.out.println(audioFilename);
329
330                 // decode the mp3 file
331                 System.out.println("Starting Decode");
332                 MP3Decoder dec = new MP3Decoder(audioFilename);
333                 List<short[]> dat = dec.getDecodedFrames();
334                 System.out.println("Ending Decode");
335
336                 currentPosition = 0;
337
338                 // count the number of samples
339                 int count = 0;
340                 for (short[] d : dat) {
341                         count += d.length;
342                 }
343
344                 // make into a single large buffer for 1 large RMI call
345                 short[] compressedArray = new short[count];
346                 count = 0;
347                 for (short[] d : dat) {
348                         for (short s : d) {
349                                 compressedArray[count] = s;
350                                 count++;
351                         }
352                 }
353
354
355                 System.out.println("Loading Speakers");
356                 // send the new data to all the speakers
357                 for (SpeakerSmart speakers : speakerSet.values()) {
358                         System.out.println("Loading a single speaker with data");
359                         try {
360                                 speakers.loadData(compressedArray, 0, compressedArray.length);
361                         } catch (Exception e) {
362                                 e.printStackTrace();
363                         }
364                         System.out.println("Done loading a single speaker with data");
365                 }
366
367                 System.out.println("All Speakers done loading");
368
369         }
370
371
372
373         /********************************************************************************************************
374         ** Public methods, called by the runtime
375         *********************************************************************************************************/
376
377         /** Initialization method, called by the runtime (effectively the main of the controller)
378          *   This method runs a continuous loop and is blocking
379          *
380          *   @return [void] None;
381          */
382         public void init() throws RemoteException, InterruptedException {
383
384                 // Initialize the rooms
385                 for (RoomSmart room : audioRooms.values()) {
386                         // All rooms start with the speakers turned off
387                         roomSpeakersOnOffStatus.put(room, false);
388                 }
389
390                 // Setup the cameras, start them all and assign each one a motion detector
391                 for (GPSGatewaySmart gw : gpsSet.values()) {
392
393                         gw.init();
394                         gw.registerCallback(this);
395                         gw.start();
396                 }
397
398
399                 //Initialize the speakers
400                 for (SpeakerSmart speakers : speakerSet.values()) {
401                         speakers.init();
402                 }
403
404                 prepareNextSong();
405
406                 // Run the main loop that will keep checking stuff
407                 while (true) {
408
409                         // Update speakers status (on/off) based on info from phone
410                         updateSpeakersStatus();
411
412                         // Check and turn on/off (stream music) through speakers based on its status
413                         updateSpeakersAction();
414
415                         try {
416                                 Thread.sleep(CHECK_TIME_WAIT_MSEC); // sleep for a tenth of the time
417                         } catch (Exception e) {
418                         }
419                 }
420         }
421 }
422
423
424
425