package se.nicklasgavelin.bluetooth; import java.io.IOException; import javax.bluetooth.*; import se.nicklasgavelin.log.Logging; import se.nicklasgavelin.sphero.exception.RobotBluetoothException; /** * Manages a single bluetooth device * Masks the "RemoteDevice" class as extra methods are implemented * to make it easier to communicate with the given device * * @author Nicklas Gavelin, nicklas.gavelin@gmail.com, LuleƄ University of Technology * */ public class BluetoothDevice implements DiscoveryListener { // Internal storage private RemoteDevice device; private final Bluetooth bt; private BluetoothService service; private int activeSearch; private String name = null; private String address = null; private String connectionUrl = null; private static final String UNKNOWN_NAME = "(UNKNOWN)"; /** * Create a bluetooth device using the "Bluetooth bt" to connect to it * * @param bt The bluetooth connection * @param device The remote device */ protected BluetoothDevice( Bluetooth bt, RemoteDevice device ) { this.bt = bt; this.device = device; this.activeSearch = -1; this.address = device.getBluetoothAddress(); try { // Try to fetch the name for the device this.setName( this.device.getFriendlyName( false ) ); } catch( IOException e ) { this.setName( UNKNOWN_NAME ); } } /** * Create a bluetooth device from a bluetooth class instance and * a connection url to a specific device. * WARNING: When using this constructor some of the methods * that this class posess will return erroneous data or throw exceptions. * * @param bt The bluetooth instance * @param connectionUrl The device connection url */ public BluetoothDevice( Bluetooth bt, String connectionUrl ) { this.bt = bt; this.connectionUrl = connectionUrl; this.address = this.connectionUrl.split( "://" )[1].split( ":" )[0]; } /** * Returns the connection url for the specific bluetooth device * and service. * * @return The bluetooth connection url */ public String getConnectionURL() { if( this.connectionUrl != null ) return this.connectionUrl; try { return this.service.getConnectionURL(); } catch( NullPointerException e ) { return null; } } /** * Set the name of the device * * @param name The name of the device */ private void setName( String name ) { this.name = name; } /** * Returns the Bluetooth address of the device * * @return The Bluetooth address */ public String getAddress() { return this.address; } /** * Returns the name of the device or the value of UNKNOWN_NAME * if no name could be found. * * @return The name of the device or the value of * BluetoothDevice.UNKNOWN_NAME */ public String getName() { if( this.name == null && this.device != null ) { try { // Try and updat the name this.name = this.device.getFriendlyName( false ); } catch( IOException e ) { this.name = UNKNOWN_NAME; } } return this.name; } /** * Returns the remote device, * this remote device shouldn't be used except * when absolutely necessary as the BluetoothDevice * class should handle all communications with the RemoteDevice class * instance. * * @return The remote device (RemoteDevice) */ public RemoteDevice getRemoteDevice() { return this.device; } /** * Ask the device for the bluetooth name and update * it internally when a response is received */ public void updateName() { // Request name try { if( this.device != null ) this.setName( this.device.getFriendlyName( false ) ); } catch( IOException e ) { error( "Failed to update bluetooth device name: " + e.getMessage() ); } } /** * Connect to the Bluetooth device and return the created connection * or null if no connection could be made. * * @throws RobotBluetoothException If failure to connect * @return The created Bluetooth connection or null if no connection could be created */ public BluetoothConnection connect() throws RobotBluetoothException { // Check if we have any active services if( this.connectionUrl != null ) { // Fetch a new service this.service = new BluetoothService( this.connectionUrl ); if( this.service == null ) return null; } else if( this.service == null ) { // Force discovery this.discover(); // Check if we found anything if( this.service == null ) return null; } // Connect to the available service try { // Connect to the service return this.service.connect(); } catch( IOException e ) { // Failure to connect for some reason return null; } } /** * Start with discovering services available for this Bluetooth device. * * @throws RobotBluetoothException If failure to perform device discovert */ public void discover() throws RobotBluetoothException { // Check if we have tried with discovery earlier if( this.activeSearch < 0 ) { try { // See to it that we are the only one performing stuff on the bt // instance. synchronized( this.bt ) { // Serh for available services for this device this.activeSearch = this.bt.getDiscoveryAgent().searchServices( new int[] { BluetoothService.ATTR_SERVICENAME, BluetoothService.ATTR_SERVICEDESC, BluetoothService.ATTR_PROVIDERNAME }, new javax.bluetooth.UUID[] { this.bt.getUUID() }, this.device, this ); // Lock until we are done this.bt.wait(); } } catch( BluetoothStateException e ) { throw new RobotBluetoothException( e.getMessage() ); } catch( InterruptedException e ) { // Failure to discover throw new RobotBluetoothException( e.getMessage() ); } } } /** * Cancel an active service discovery */ public void cancelDiscovery() { // Cancel any active discovery searches if( this.activeSearch >= 0 ) { this.bt.getDiscoveryAgent().cancelServiceSearch( this.activeSearch ); activeSearch = -1; } } /** * Called when the service search is completed * * @param transId - * @param respCode - */ @Override public void serviceSearchCompleted( int transId, int respCode ) { // Notify observers synchronized( this.bt ) { if( this.activeSearch == transId ) this.bt.notifyAll(); } // Check the response code switch ( respCode ) { case DiscoveryListener.SERVICE_SEARCH_COMPLETED: log( "The service search completed normally" ); break; case DiscoveryListener.SERVICE_SEARCH_TERMINATED: log( "The service search request was cancelled by a call to DiscoveryAgent.cancelServiceSearch(int)" ); break; case DiscoveryListener.SERVICE_SEARCH_ERROR: log( "An error occurred while processing the request" ); break; case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS: log( "No records were found during the service search" ); break; case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE: log( "The device specified in the search request could not be reached or the local device could not establish a connection to the remote device" ); break; default: log( "Unknown Response Code - " + respCode ); break; } } /** * Called when a new service is discovered * NOTICE: Only takes the first available service * * TODO: add functionality for more services * * @param transId - * @param records Discovered service records */ @Override public void servicesDiscovered( int transId, ServiceRecord[] records ) { if( this.activeSearch == transId ) { if( records.length > 0 ) this.service = new BluetoothService( this, records[0] );//, this.bt );// TODO: // Will // there // ever // be // multiple // services??? } } @Override public void deviceDiscovered( RemoteDevice arg0, DeviceClass arg1 ) { } @Override public void inquiryCompleted( int arg0 ) { } /** * Log internal stuff * * @param msg The message to log (debug level) */ private void log( String msg ) { Logging.debug( msg ); } private void error( String msg ) { Logging.error( msg ); } }