import java.io.*; import java.net.*; import java.util.*; /** * This class provides location awareness to the granularity of a room * inside a building based on the reception of 802.11 broadcasts and the * corresponding detection of ultrasound. This class is intended to be used * by an application running on a mobile device. If the mobile device moves * close to a desktop running the ServerBeacon class/application, it will * know that it is in the same room as the desktop based on the information * provided by this MobileClient class. *
* An application using this class needs to provide a reference to a * LocationTracker object in the MobileClient class's constructor so that * the MobileClient class can signal the LocationTracker object when a room * change has occurred. *
* The MobileClient class implements both an UltrasoundUpdateListener * and a DatagramUpdateListener so that it can be informed of ultrasound * detections and datagram receptions. * * @author Tony Offer * @author Chris Palistrant * @version 1.0 */ public class MobileClient implements UltrasoundUpdateListener, DatagramUpdateListener, Constants { // Reference to the object/application that instantiated this MobileClient protected LocationTracker notifyApp = null; // This string identifies the current room in which this // MobileClient object perceives itself to exist. protected String room = null; // History of the latest room messages received. This is an array // containing Room objects that store both the time at which the // message was received and the message itself. The index marks the // space after the most recently received message. protected Room [] messageHistory; protected int msgHistInd = 0; // History of the most recent ultrasound updates. This array // contains the room messages for the most recent ultrasound // detections in the order in which they were received. This // history is necessary to determine relative probabilities of being // in a given room. protected String [] detections = null; // The running index for the history of recent ultrasound updates. // This index constantly increments with wraparound so that the // oldest ultrasound updates are overwritten first. protected int latestUpdate = 0; protected boolean debug = false; /** * This is the only constructor for this class. It takes a reference * to a LocationTracker object so that the MobileClient class * can notify the object when a room change has occurred. No other * constructor is provided because the MobileClient class must have * access to a roomChanged() method. * * @param extApp Reference to the LocationTracker object that * will receive room change notifications. * @param inDebug Whether or not to turn debugging information * on. */ public MobileClient(LocationTracker extApp, boolean inDebug) { notifyApp = extApp; debug = inDebug; messageHistory = new Room[ROOM_MSG_BUF]; for (int i = 0; i < messageHistory.length; i++) messageHistory[i] = new Room("", 0); // Initialize the array of recent ultrasound updates. Also // initialize the strings in this array since the history is // used almost immediately for analysis. detections = new String[HISTORY_SIZE]; for (int i = 0; i < detections.length; i++) detections[i] = ""; // Start a datagram listener thread and an ultrasound listener // thread that will both notify this class of updates. new UltrasoundListener(this, debug).start(); new DatagramListener(this).start(); } /** * Callback method for the UltrasoundListener thread that receives * notifications of newly received ultrasound detections. This * method attempts to correlate the time of the received ultrasound * detection with the time of one of the previously received * datagram transmissions in order to make a determination of room * location. * * @param timeDetected The time (in milliseconds since January 1, * 1970) at which the ultrasound was * received. */ public void ultrasoundDetected(long timeDetected) { /*** TESTING ***/ //System.out.println("US Detected: " + timeDetected); // Find all 802.11 times less than the max detection time. If // there are more than one of these, find the one with the // detection time closest to the average detection time. The // datagram listener thread might access the message history at // the same time as this thread, so synchronized on the message // history. HashSet viableMsgs = new HashSet(); synchronized (messageHistory) { // Find all message received times whose difference from // this ultrasound detection time is less than the // experimentally determined maximum detection time. for (int i = 0; i < messageHistory.length; i++) { long timeDelta = timeDetected - messageHistory[i].timeReceived; if (timeDelta < MAX_DETECT_TIME) viableMsgs.add(messageHistory[i]); } } /*** TESTING ***/ System.out.println("US detected: " + viableMsgs.size() + " messages within range:"); Iterator go = viableMsgs.iterator(); while (go.hasNext()) { Room i = (Room) go.next(); System.out.println("\t" + i.roomMsg + ": " + (timeDetected - i.timeReceived)); } // If there are multiple potential messages to correlate with // the ultrasound detection, find the one that was closest to // being received the average detection time from the ultrasound // detection. Room closestRoom = null; long closestTime = 0; if (viableMsgs.size() > 0) { Iterator I = viableMsgs.iterator(); Room i = (Room) I.next(); closestRoom = i; long iTime = java.lang.Math.abs(AVG_DETECT_TIME - (timeDetected - i.timeReceived)); closestTime = iTime; while (I.hasNext()) { i = (Room) I.next(); iTime = java.lang.Math.abs(AVG_DETECT_TIME - (timeDetected - i.timeReceived)); if (iTime < closestTime) { closestTime = iTime; closestRoom = i; } } } if (closestRoom != null) { // Place the room message in the most recent position of the // update history and increment the index. detections[latestUpdate] = closestRoom.roomMsg; latestUpdate = (latestUpdate+1) % HISTORY_SIZE; // To each of the room messages in the history, assign a relative // magnitude representing its presence in the history. More // recent room messages are given more weight. HashMap roomDistribution = new HashMap(); for (int weight = 1, i = latestUpdate; weight <= HISTORY_SIZE; weight++, i = (i+1) % HISTORY_SIZE) { // If room message already exists in the room distribution, // just add the magnitude to the existing magnitude. // Otherwise, just use the weight in the room distribution. int newMag = weight; if (roomDistribution.containsKey(detections[i])) { newMag = newMag + ((Integer) (roomDistribution.get (detections[i]))).intValue(); } // Put the magnitude at the key identified by the room // string unless the room string is blank, which happens at // the beginning of program execution when a history of // ultrasound detections has not yet been established. if (!detections[i].equals("")) roomDistribution.put(detections[i], new Integer(newMag)); } // Notify the LocationTracker of a newly calculated room // distribution. notifyApp.roomChanged(roomDistribution); } } /** * Callback method for the DatagramListener thread that receives * notifications of newly received datagram messages. This method * stores the received datagram message and the time at which it was * received for later correlation with ultrasound detections. If an * ultrasound detection is later correlated with the datagram * message received by this callback method, then the received * datagram message describes the current room location. * * @param timeDetected The time (in milliseconds since January 1, * 1970) at which the datagram message was * received. * @param message The string that was transmitted in the datagram. */ public void datagramDetected(String message, long timeDetected) { /*** TESTING ***/ System.out.println("\"" + message + "\" Received: " + timeDetected); // Put datagram message and time on the message history. // Synchronize this because the ultrasound listener thread will // also be accessing the message history. synchronized(messageHistory) { messageHistory[msgHistInd] = new Room(message, timeDetected); msgHistInd = (msgHistInd+1) % ROOM_MSG_BUF; } } }