1 package SpeakerController;
3 // IoT Runtime packages
4 import iotruntime.slave.IoTSet;
5 import iotruntime.slave.IoTRelation;
6 //import iotcode.annotation.*;
9 import iotcode.interfaces.*;
10 //import iotcode.annotation.*;
13 // Standard Java packages
14 import java.util.HashMap;
16 import java.util.List;
17 import java.util.ArrayList;
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;
25 import java.rmi.RemoteException;
26 import java.rmi.server.UnicastRemoteObject;
28 // Checker annotations
29 import iotchecker.qual.*;
31 /** Class SpeakerController for the smart home application benchmark
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
37 * @author Rahmadi Trimananda <rahmadi.trimananda @ uci.edu>
41 public class SpeakerController extends UnicastRemoteObject implements GPSGatewayCallback, SpeakerCallback {
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);
53 @config private IoTSet < @NonLocalRemote GPSGateway > gpsSet;
54 @config private IoTSet < @NonLocalRemote Speaker > speakerSet;
57 * IoT Sets of Things that are not devices such as rooms
59 @config private IoTSet < @NonLocalRemote Room > audioRooms;
64 @config private IoTRelation < @NonLocalRemote Room, @NonLocalRemote Speaker > roomSpeakerRel;
67 * The state that the room main lights are supposed to be in
69 Map < @NonLocalRemote Room, Boolean > roomSpeakersOnOffStatus = new HashMap < @NonLocalRemote Room, Boolean > ();
71 // used to notify if new data is available
72 private AtomicBoolean newDataAvailable = new AtomicBoolean(false);
74 // the settings from the interface, used to setup the system
75 private int roomIdentifier = 0;
76 private boolean ringStatus = false;
78 // playback state variables
79 private int currentPosition = 0;
80 private AtomicBoolean playbackDone = new AtomicBoolean(false);
83 public SpeakerController() throws RemoteException {
88 /** Callback method for when room ID is retrieved.
90 * @param _ggw [GPSGateway].
91 * @return [void] None.
93 public void newRoomIDRetrieved(@NonLocalRemote GPSGateway _ggw) {
96 // get the parameters that the interface (phone app) reads from the user
97 roomIdentifier = _ggw.getRoomID();
99 System.out.println("DEBUG: New room ID is retrieved from phone!!! Room: " + roomIdentifier);
101 // Data is read, so we set this back to false
102 _ggw.setNewRoomIDAvailable(false);
103 } catch (RemoteException ex) {
104 ex.printStackTrace();
107 // new data available so set it to true
108 newDataAvailable.set(true);
112 /** Callback method for when ring status is retrieved.
114 * @param _ggw [GPSGateway].
115 * @return [void] None.
117 public void newRingStatusRetrieved(@NonLocalRemote GPSGateway _ggw) {
120 // get the parameters that the interface (phone app) reads from the user
121 ringStatus = _ggw.getRingStatus();
123 System.out.println("DEBUG: New ring status is retrieved from phone!!! Status: " + ringStatus);
125 // Data is read, so we set this back to false
126 _ggw.setNewRingStatusAvailable(false);
127 } catch (RemoteException ex) {
128 ex.printStackTrace();
131 // new data available so set it to true
132 newDataAvailable.set(true);
136 /** Callback method when a speaker has finished playing what is in its audio buffer.
138 * @param _speaker [Speaker].
139 * @return [void] None.
141 public void speakerDone(@NonLocalRemote Speaker _speaker) {
142 for (@NonLocalRemote Speaker speakers : speakerSet.values()) {
143 playbackDone.set(true);
148 /*******************************************************************************************************************************************
150 ** Private Helper Methods
152 *******************************************************************************************************************************************/
155 * Update speakers action based on the updated speakers status
157 private void updateSpeakersAction() {
159 // Stream music on speakers based on their status
160 for (@NonLocalRemote Room room : audioRooms.values()) {
162 // Check status of the room
163 if (roomSpeakersOnOffStatus.get(room)) {
165 // used to get the average of the speakers position
166 long currPosTotal = 0;
167 long currPosCount = 0;
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!");
176 // start the speaker playback if the speaker is not playing yet
177 if (!speakers.getPlaybackState()) {
179 System.out.println("Turning a speaker On");
180 speakers.startPlayback();
181 speakers.setPosition(currentPosition);
184 // get average of the positions
185 currPosTotal += speakers.getPosition();
190 } catch (RemoteException e) {
195 if (currPosCount != 0) {
197 // get average position of the speakers
198 int currentPosOfSpeakers = (int)(currPosTotal / currPosCount);
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
204 for (@NonLocalRemote Speaker speakers : roomSpeakerRel.get(room)) {
206 speakers.setPosition(currentPosOfSpeakers);
207 } catch (RemoteException e) {
213 // update the current position
214 currentPosition = currentPosOfSpeakers;
220 // Room status is "off"
222 // used to get the average of the speakers position
223 long currPosTotal = 0;
224 long currPosCount = 0;
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!");
231 // Turning off speaker if they are still on
232 if (speakers.getPlaybackState()) {
233 System.out.println("Turning a speaker off");
235 currPosTotal += (long)speakers.getPosition();
237 boolean tmp = speakers.stopPlayback();
240 System.out.println("Error Turning off");
244 } catch (RemoteException e) {
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);
257 // a speaker has finished playing and so we should change all the audio buffers
258 if (playbackDone.get()) {
260 // song done so update the audio buffers
262 playbackDone.set(false);
268 * Update speakers status based on room ID and ring status information from phone
270 private void updateSpeakersStatus() {
272 // If we have new data, we update the speaker streaming
273 if (newDataAvailable.get()) {
275 // System.out.println("DEBUG: New data is available!!!");
276 // System.out.println("DEBUG: Ring status: " + ringStatus);
277 // Check for ring status first
280 // Turn off all speakers if ring status is true (phone is ringing)
281 for (@NonLocalRemote Room room : audioRooms.values()) {
283 // System.out.println("DEBUG: Update status off for speakers! Phone is ringing!!!");
285 roomSpeakersOnOffStatus.put(room, false);
289 // Phone is not ringing... just play music on the right speaker
291 // Check for every room
292 for (@NonLocalRemote Room room : audioRooms.values()) {
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) {
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);
305 // for the rooms whose IDs aren't equal to roomIdentifier
307 // System.out.println("DEBUG: Turn on speaker!");
308 // Set speaker status to off
309 roomSpeakersOnOffStatus.put(room, false);
312 } catch (RemoteException ex) {
313 ex.printStackTrace();
317 // Finish processing data - put this back to false
318 newDataAvailable.set(false);
323 * Prepare the speakers for a new song to start playing
325 private void prepareNextSong() {
326 System.out.println("Starting Music Prep");
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()) {
334 if (speakers.getPlaybackState()) {
335 speakers.stopPlayback();
337 speakers.clearData();
338 } catch (Exception e) {
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>();
348 // put all names in a list
349 for (int i = 0; i < audioFiles.length; i++) {
350 if (audioFiles[i].isFile()) {
352 audioFileNames.add(audioFiles[i].getCanonicalPath());
353 } catch (Exception ex) {
354 ex.printStackTrace();
359 // pick a random file to play
360 Random rand = new Random(System.nanoTime());
361 String audioFilename = audioFileNames.get(rand.nextInt(audioFileNames.size()));
363 System.out.println("Going to load audio file");
364 System.out.println(audioFilename);
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");
374 // count the number of samples
376 for (short[] d : dat) {
380 // make into a single large buffer for 1 large RMI call
381 short[] compressedArray = new short[count];
383 for (short[] d : dat) {
385 compressedArray[count] = s;
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");
396 speakers.loadData(compressedArray, 0, compressedArray.length);
397 } catch (Exception e) {
400 System.out.println("Done loading a single speaker with data");
403 System.out.println("All Speakers done loading");
409 /********************************************************************************************************
410 ** Public methods, called by the runtime
411 *********************************************************************************************************/
413 /** Initialization method, called by the runtime (effectively the main of the controller)
414 * This method runs a continuous loop and is blocking
416 * @return [void] None;
418 public void init() throws RemoteException, InterruptedException {
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);
426 // Setup the cameras, start them all and assign each one a motion detector
427 for (@NonLocalRemote GPSGateway gw : gpsSet.values()) {
430 // initialize, register callback, and start the gateway
432 gw.registerCallback(this);
434 } catch (RemoteException ex) {
435 ex.printStackTrace();
440 //Initialize the speakers
441 for (@NonLocalRemote Speaker speakers : speakerSet.values()) {
447 // Run the main loop that will keep checking stuff
450 // Update speakers status (on/off) based on info from phone
451 updateSpeakersStatus();
453 // Check and turn on/off (stream music) through speakers based on its status
454 updateSpeakersAction();
457 Thread.sleep(CHECK_TIME_WAIT_MSEC); // sleep for a tenth of the time
458 } catch (Exception e) {