Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Bluetooth boogies, Part 2: Creating the Bluetooth Music Store

Create a versatile OBEX file-transfer client application

Bruce Hopkins (bhopkins@gestalt-llc.com), Technical Architect, Gestalt LLC
Bruce Hopkins is the author of the book, Bluetooth for Java (Apress) and is the creator of the JB-22 developer kit. He graduated from Wayne State University in Detroit with a B.S. degree in Electrical and Computer Engineering. He currently works as a Technical Architect for Gestalt LLC and focuses on distributed computing, Web services, and wireless technologies. He can be contacted at bhopkins@gestalt-llc.com.

Summary:  Object Exchange (OBEX) is a preferred method to send and receive files between two Bluetooth devices. Part 1 of this series introduced the semantics of OBEX and explained how to create a simple OBEX server application, FileServer.java. In this article, you'll learn how to create a simple OBEX client, FileClient.java, that can transfer a file to the server application. You'll also learn how to modify your OBEX client application to make it into a Bluetooth Music Store.

Date:  01 Nov 2005
Level:  Introductory
Also available in:   Chinese

Activity:  7692 views
Comments:  

In this article, I'm going to demonstrate how to create a simple OBEX client application that can transfer a file to the server application. You'll also learn how to modify your OBEX client application into a Bluetooth Music Store. OBEX is a preferred method of sending and receiving files between two Bluetooth devices. (In the first article in this two-part series, I introduced the semantics of OBEX and explained how to create an OBEX server application.)

I'll start by creating the OBEX client application. Before you examine the code necessary to build the OBEX client application, take a brief look at the OBEX server application. Figure 1 shows the FileServer.java application at startup.


Figure 1. FileServer.java at startup
FileServer.java at startup

As you can see from Figure 1, the server application is ready and waiting for clients to connect, so let's find out how to build the OBEX client application.

Creating an OBEX client application

As you can see in Figure 2, FileClient.java looks a lot like FileServer.java, so I'll skip the details of FileClient.java for now.


Figure 2. FileClient.java at startup
FileClient.java at startup

Now, as I stated in Part 1 of this series, Bluetooth clients have a lot more work to do in comparison with Bluetooth servers -- and if you think about it for a second, you'll understand why. First of all, how does the client know where to find the server? (Don't worry, this problem exists for every Bluetooth client/server application; it's not unique to your situation). In order for any Bluetooth client to find a Bluetooth server, the client must first discover it.

Bluetooth device discovery

DeviceDiscoverer.java is a helper application used by FileClient.java to find any remote Bluetooth devices in the vicinity. Listing 1 provides the import statements, class declarations, and the constructor of DeviceDiscoverer.java.


Listing 1. Import statement and constructor for DeviceDiscoverer.java
                
import javax.bluetooth.*;
import java.util.*;

public class DeviceDiscoverer implements DiscoveryListener {
    
  FileClient client;
  Vector remoteDevices = new Vector();
	
  DiscoveryAgent discoveryAgent;
    
  public DeviceDiscoverer(FileClient client) {
    this.client = client;
    try {
     LocalDevice localDevice = LocalDevice.getLocalDevice();
     discoveryAgent = localDevice.getDiscoveryAgent();

     client.updateStatus("[client:] LocalDevice properties: " + 
         localDevice.getFriendlyName() + 
         " (" + localDevice.getBluetoothAddress() + ")");
     client.updateStatus("[client:] 
          Searching for Bluetooth devices in the vicinity...");
     discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);

    } catch(Exception e) {
      e.printStackTrace();
    }
  }

As you can see in Listing 1, DeviceDiscoverer.java implements javax.bluetooth.DiscoveryListener. This way, the helper class gets notified when a remote Bluetooth device is discovered. To start the device discovery process, you need to first get an instance of javax.bluetooth.LocalDevice, which allows you to get an instance of javax.bluetooth.DiscoveryAgent. After you have instantiated a DiscoveryAgent, you're free to call discoveryAgent.startInquiry(), which starts the device discovery process.

You may also notice in Listing 1 that LocalDevice has some pertinent information about your own Bluetooth device, such as its friendly name (like "Bruce's laptop" or "Joe's PDA"). LocalDevice also knows your 6-byte Bluetooth address; for instance, 00:0A:3E:56:57:B5. For informational purposes, DeviceDiscoverer.java displays that information before it enters the discovery process.

If you refer back to Figure 2, you'll see that FileClient.java has three buttons, of which one of them is aptly named Discover Devices. When you click on the Discover Devices button, you'll instantiate the helper class, DeviceDiscoverer.java. If you're new to Bluetooth, you may assume that the device discovery process is instantaneous. Unfortunately, it isn't.

However, the good news is that because the helper class, DeviceDiscoverer.java, is a DiscoveryListener, it is notified asynchronously when Bluetooth devices are found. For each remote Bluetooth device found in the vicinity, the Java Virtual Machine (JVM) calls the deviceDiscovered() method. When the device discovery process has ended, the JVM also calls the inquiryCompleted() method. Listing 2 and Listing 3 demonstrate the code for deviceDiscovered() and inquiryCompleted().


Listing 2. DeviceDiscoverer.deviceDiscovered()
                
  public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) {

    try{
      remoteDevices.addElement(remoteDevice);
      client.updateStatus("[client:] New device discovered : "  + 
         remoteDevice.getFriendlyName(true)+ " (" + 
         remoteDevice.getBluetoothAddress() + ")" );

    } catch(Exception e){
      e.printStackTrace();
    }  

  }


Listing 3. DeviceDiscoverer.inquiryCompleted()
                
  public void inquiryCompleted(int discType) {
    String inqStatus = null;
        
    if (discType == DiscoveryListener.INQUIRY_COMPLETED) {
      inqStatus = "[client:] Inquiry completed";            
    } else if (discType == DiscoveryListener.INQUIRY_TERMINATED) {
      inqStatus = "[client:] Inquiry terminated";
    } else if (discType == DiscoveryListener.INQUIRY_ERROR) {
      inqStatus = "[client:] Inquiry error";
    }
        
    client.updateStatus(inqStatus);
    client.serviceButton.setEnabled(true);
    client.deviceButton.setEnabled(false);
  }

As you can see in Listing 2, whenever a new Bluetooth device is discovered, I just add it to a Vector and display the friendly name and Bluetooth address of the remote device. When the device discovery process has ended, I update the client with status of the discovery process, whether it succeeded or failed. Figure 3 shows FileClient.java after it has instantiated DeviceDiscoverer.java and has discovered all the Bluetooth devices in the vicinity.

Look at FileClient.java after the discovery process.


Figure 3. FileClient.java after the discovery process
FileClient.java after the discovery process

OK, it looks like there's a slight problem. According to Figure 3, there are four remote Bluetooth devices in the area, but how do we know which one of them is running FileServer.java? Good question. That's where service discovery comes in very handy. And don't worry, I have another helper class that I'll introduce to you that will aid in the search for the specific service that I want.

Bluetooth service discovery

Listing 4 contains the import statements, class declaration, and constructor for ServiceDiscoverer.java.


Listing 4. Import statement and constructor for ServiceDiscoverer.java
                
import javax.bluetooth.*;
import java.io.*;
import java.util.Vector;

public class ServiceDiscoverer extends Thread implements DiscoveryListener {
    
  UUID[] uuidSet = {new UUID("8841", true)};
  int[] attrSet = {0x0100, 0x0003, 0x0004};
    
  FileClient client;
  ServiceRecord serviceRecord;
  String connectionURL;
  Vector deviceList;

    
  public ServiceDiscoverer(FileClient client, Vector deviceList) {

    this.client = client;
    this.deviceList = deviceList;

  }

As you can see in Listing 4, ServiceDiscoverer.java is quite similar to the first helper class, DeviceDiscoverer.java, especially because both of them implement the same interface. However, ServiceDiscoverer.java differs due to the fact that it needs to run in a separate thread; if it doesn't, the user interface for FileClient.java will hang while it is searching for services on the Bluetooth devices in the vicinity.

Do you remember from Part 1 that every Bluetooth service (whether or not it uses OBEX) must have a unique identifier? You may recall that I set the UUID for FileServer.java to be 8841, the same value that I set in ServiceDiscoverer.java. This way, ServiceDiscoverer.java will find only the services on the remote Bluetooth devices that carry the UUID of 8841. Please note that UUIDs can either be 4 or 16 digits long; I chose a short UUID to provide an easier example. I also want you to notice that in the constructor, I passed in a Vector that contains all the remote Bluetooth devices that were found by the first helper class. Listing 5 provides the run() method of ServiceDiscoverer.java.


Listing 5. ServiceDiscoverer.run()
                
  public void run(){

   try {
    LocalDevice localDevice = LocalDevice.getLocalDevice();
    DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
    RemoteDevice remoteDevice = null;

    for(int i=0; i < deviceList.size(); i++){
      
      remoteDevice = (RemoteDevice)deviceList.get(i);

      client.updateStatus("[client:] Searching for Services on: "  + 
          remoteDevice.getFriendlyName(true)+ " (" + 
          remoteDevice.getBluetoothAddress() + ")" );
      discoveryAgent.searchServices(attrSet, uuidSet, 
           remoteDevice, this);
      try{
        Thread.sleep(2000);
      } catch (Exception e){
      }

    }

  }
  catch(Exception e) {
    e.printStackTrace();
  }

  }

Now, because ServiceDiscoverer.java implements the same interface that the other helper class does, it obtains a DiscoveryAgent in the same manner, just as I did in Listing 1. You should notice that I iterated over the Vector of remote Bluetooth devices and searched for the services that exist on each device.

These examples were tested on real Bluetooth hardware, so you should see that as I iterated through the Vector, I "backed off" for two seconds before I continued through the loop. The "backing off" period depends on your hardware; you may not need it at all. Without it though, your Bluetooth hardware may only search for services on the first remote Bluetooth devices that you pass in, and it may ignore the others. For each service that matches the UUID, the JVM calls the servicesDiscovered() method, which is shown in Listing 6.


Listing 6. ServiceDiscoverer.servicesDiscovered()
                
  public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
        
    for(int i = 0; i < servRecord.length; i++) {

      DataElement serviceNameElement = servRecord[i].getAttributeValue(0x0100);
      String serviceName = (String)serviceNameElement.getValue();  

      if(serviceName.equals("FTP")){

        client.updateStatus("[client:] A matching service has been found");
        
        try {
          connectionURL = servRecord[i].getConnectionURL(1,false);
        } catch (Exception e){
          client.updateStatus("[client:] oops");
        }
               
        client.updateStatus("[client:] The connection URL is: " + connectionURL );
        client.serviceButton.setEnabled(false);
        client.connButton.setEnabled(true);
      }
    }
  }

You may recall from Part 1 that the UUID is mandatory and the service name is optional for all Bluetooth servers; however, we specified that the service name for our service would be "FTP." As you can see in Listing 6, I'm checking to see if the service name is FTP, but just remember that every Bluetooth service may not specify a service name. After I've determined that a matching service has been found, I store the connection URL in a String and display some information on the client. A screenshot of FileClient.java after the service searching process is shown in Figure 4.


Figure 4. FileClient.java after the service searching process
FileClient.java after the service searching process

Aha! So according to Figure 4, I've found a matching service and it exists on the ibook. You may notice that although I found a matching service on the ibook, I continued to iterate through the Vector and search for services. As you can see, the two helper classes came in very handy in order to get the connection URL to the server. Now that I have the URL to the server, let's connect and send a file to the server!

Connecting to the server and sending a file

To keep the code FileClient.java as clean as possible, I isolated all the OBEX client code into a file named ObjectPusher.java. Of course, ObjectPusher.java is multithreaded so that it won't hang the GUI application during the file-transfer process. Listing 7 shows the code for ObjectPusher.run().


Listing 7. ObjectPusher.run()
                
  public void run(){

    try{
      connection = Connector.open(connectionURL);
      client.updateStatus("Connection obtained");

      ClientSession cs = (ClientSession)connection;
      HeaderSet hs = cs.createHeaderSet();

      cs.connect(hs);
      client.updateStatus("OBEX session created");      

      InputStream is = new FileInputStream(file);
      byte filebytes[] = new byte[is.available()];
      is.read(filebytes);
      is.close();

      hs = cs.createHeaderSet();
      hs.setHeader(HeaderSet.NAME, file.getName());
      hs.setHeader(HeaderSet.TYPE, "text/plain");
      hs.setHeader(HeaderSet.LENGTH, new Long(filebytes.length));

      Operation putOperation = cs.put(hs);
      client.updateStatus("Pushing file: " + file.getName());
      client.updateStatus("Total file size: " + filebytes.length + " bytes");

      OutputStream outputStream = putOperation.openOutputStream();
      outputStream.write(filebytes);
      client.updateStatus("File push complete");

      outputStream.close();
      putOperation.close();
      
      cs.disconnect(null);

      connection.close();
    } catch (Exception e){
    }

  }

Obviously, the main purpose of ObjectPusher.java is to push a file from a client to the server. I start off by taking the connection URL and creating a connection object. With the connection in place, I'm able to create the OBEX session.

The next step is to convert the file that I want to send into a byte array. Afterwards, the OBEX headers are set and the OBEX PUT operation is initiated by the call to cs.put(). This returns a javax.obex.Operation object, which I named putOperation. An OutputStream was then created in order to send the file data, and the PUT operation was completed when the byte array was written to the OutputStream. Figure 5 shows FileClient.java after the file transfer process.


Figure 5. FileClient.java after the file transfer process
FileClient.java after the file transfer process

In conclusion: Creating the Bluetooth Music Store

In Part 1, you learned how to create an OBEX server application. In this article, you learned how to create a very versatile OBEX client application. You've seen that OBEX clients are more difficult to create, but this article presented a few helper classes to aid you in the device discovery and service discovery processes. Figure 6 shows a simple application that I call the Bluetooth Music Store.


Figure 6. The Bluetooth Music Store
The Bluetooth Music Store

It's a modified version of FileClient.java that uses the helper classes DeviceDiscoverer.java, ServiceDiscoverer.java, and ObjectPusher.java. With the Bluetooth Music Store, you can select a song or ringtone in MP3 format and push it to any phone, PDA, or computer that supports OBEX. Cool, huh?

So why do I call it a "Music Store?" Well, of course, if you own the rights to any music file, ringtone, or podcast, then you can easily use this application as a foundation to make a kiosk application and sell your audio files. Enjoy!


Resources

Learn

Get products and technologies

  • Download the FileClient, FileServer, and Bluetooth Music Store Application, all three used in this article, in a ZIP file.

  • The examples in this article were created using the JB-22 Java Bluetooth Development kit. The JB-22 is a complete Java Bluetooth development kit featuring both Bluetooth hardware and software, starting at $199.

Discuss

About the author

Bruce Hopkins

Bruce Hopkins is the author of the book, Bluetooth for Java (Apress) and is the creator of the JB-22 developer kit. He graduated from Wayne State University in Detroit with a B.S. degree in Electrical and Computer Engineering. He currently works as a Technical Architect for Gestalt LLC and focuses on distributed computing, Web services, and wireless technologies. He can be contacted at bhopkins@gestalt-llc.com.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=97849
ArticleTitle=Bluetooth boogies, Part 2: Creating the Bluetooth Music Store
publish-date=11012005
author1-email=bhopkins@gestalt-llc.com
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers