Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java & Bluetooth


July, 2005: Java & Bluetooth

Paul is a technical lead at Vonage. He can be reached at [email protected].


The Java Specification Request 82 (http://jcp.org/) defines a standard API for developing Bluetooth applications using the Java programming language. In this article, I examine how you can use the JSR 82 API to determine properties of local Bluetooth devices; discover nearby Bluetooth devices and determine the services they offer; and connect to devices and communicate with them using well-defined protocols. Typically, Bluetooth applications run under Java 2 Micro Edition (J2ME), but one of the requirements defined by the specification is that it should be able to run on any Java 2 platform that supplies the Generic Connection Framework (GCF). Consequently, I was able to run all of the sample code presented here under Java 2 Standard (J2SE) using Rococosoft's Impronto Developer Kit (http://www.rococosoft.com/java.html), which implements GCF.

Device Properties

All Bluetooth devices do not have identical characteristics. The JSR 82 API defines properties that either provide additional information about a Bluetooth system or define restrictions that are placed on an application by an implementation. Each property can be retrieved by a call to the static getProperty() method of the LocalDevice class. The single argument expected by the method is a String that contains the name of the property. The method returns a String that contains the value of the specified property. DisplayDeviceProperties.java (Listing One) displays the results returned when each of the properties defined by the API is retrieved. When running the program on a Linux machine using a Belkin USB Bluetooth device, the BlueZ stack, and Rococosoft's Impronto, the output looks like this:

bluetooth.api.version 1.0a
bluetooth.l2cap.receiveMTU.max 65535
bluetooth.connected.devices.max 7
bluetooth.connected.inquiry true
bluetooth.connected.page true
bluetooth.connected.inquiry.scan true
bluetooth.connected.page.scan true
bluetooth.master.switch true
bluetooth.sd.trans.max 1
bluetooth.sd.attr.retrievable.max 128

If you run the example, you can show how the values that are returned reflect the current Bluetooth configuration. Just modify the inquiry and page scan values in /etc/bluetooth/hcid.conf, bring the Bluetooth device down and back up, rerun the program, and observe the results.

Discovery

An important characteristic of Bluetooth devices is the ability to find other Bluetooth devices to which they can connect, and once such devices are found, to determine the services they offer. The process by which this is accomplished is called "Discovery." The API provides classes that enable discovery. You start the discovery process by invoking the static getLocalDevice() method of the LocalDevice class. The LocalDevice object this method returns, which is constant across multiple invocations, provides the lowest level of interface possible into the Bluetooth stack and provides access to, and control of, the local Bluetooth device.

The local device must have exactly one DiscoveryAgent object, which you retrieve by calling the getDiscoveryAgent() method on the LocalDevice object. The DiscoveryAgent class provides methods to perform both device and service discovery.

The DiscoveryAgent object provides two ways to discover devices. The first is the nonblocking startInquiry() method, which finds devices in proximity to the local device and returns each discovered device using a callback to the deviceDiscovered() method of the DiscoveryListener interface. You provide implementations of each of the methods the interface defines. When the inquiry is complete, the inquiryCompleted() method is called. The DiscoveryAgent class also provides a method for canceling an inquiry. An example of when you might invoke the cancel() method is if you are searching for any Bluetooth printer and are willing to accept the first one that is returned.

The second device discovery method is the blocking retrieveDevices() method, which returns a list of devices that have been discovered by previous inquiries and are classified as "preknown."

The DiscoveryAgent object provides two methods you can use to discover services. The first is selectService(), which attempts to locate a service that contains a specified universally unique identifier (UUID) in the ServiceClassIDList of its service record. If it finds the service, it returns a string that can be used to establish a connection to the service.

The second method, searchServices(), starts a search for devices that have all the UUIDs in a specified set. As services are found, they are reported via a callback to the servicesDiscovered() method of the DiscoveryListener interface, which passes an array of ServiceRecord objects. The ServiceRecord interface contains methods you can use to obtain information about a service. When a service search is completed or terminated because of an error, the serviceSearchCompleted() is called.

DiscoveryDemo.java (available electronically; see "Resource Center," page 3) shows how to discover all nearby Bluetooth devices and the services they offer. It implements the DiscoveryListener interface. When I run the program using the configuration previously described and turn on my Belkin F8T030 Bluetooth Access Point and my PalmOne Zire 72, I see the results in DiscoveryDemo.out (Listing Two). I used ca.tremblett.bluetooth.BluetoothUtils (available electronically) to convert the numeric device codes to English descriptions and to help format the service records.

Communicating With Devices

The API defined by JSR 82 was designed to operate on top of the Connected Limited Device Configuration (CLDC), an optional package designed to extend the capability of a J2ME profile (such as the MIDP Mobile Information Device Profile). Chapter 2 of the JSR 82 specification states that the API should only require CLDC libraries. It further states that it should be able to run on any Java 2 platform that supports the Generic Connection Framework (GCF). The GCF is already implemented in Connected Device Configuration (CDC)-based profiles, such as the Foundation Profile, the Personal Basis Profile, and the Personal Profile. When JSR 197 (Generic Connection Framework Optional Package for J2SE) is complete, GCF will be available as the optional package javax.microedition. Until then, you can use toolkits that implement the GCF. Again, I use Rococosoft's Impronto.

When you use the GCF, you connect to a remote device or service by calling one of the three Connector.open() methods. All three take a String containing a URL as the first argument. Look again at DiscoveryDemo.out (Listing Two) and you see the URL extracted from each service record. Having the ability to extract this URL means that once you discover the service you need, you know how to access it. The URL is the only argument accepted by the first form of Connector.open(). The second form accepts an argument that specifies a mode, which can be READ, WRITE, or READ_WRITE. In addition to the mode argument, the third form takes an additional argument that indicates whether the caller wants timeout exceptions.

Once a connection has been established between two Bluetooth devices, communication can take place. The classes and methods you use to perform the transfer of data between the devices depend on the protocol you are using.

The Logical Link Control and Adaption Profile

The L2CAP protocol lies closest to the stack. The server side of an L2CAP connection uses an L2CAPConnectionNotifier object, which it obtains by passing a suitable URL to Connector.open() and casting the Connection object that is returned. The protocol used to form the URL is btl2cap. The target contains "localhost" and a Universally Unique Identifier (UUID) for the service. The parameters can include keyword/value pairs for the service name, ReceiveMTU and TransmitMTU and boolean values for master, encrypt, and authorize and authenticate. Once the server program gets the L2CAPConnectionNotifier object, it obtains an L2CAPConnection object by calling the notifier's acceptAndOpen() method, which blocks until a connection is received from a client. The server typically passes the connection it receives from the acceptAndOpen() method to a thread that conducts the conversation with the client and then closes the connection. While the thread is executing, the server makes another call to acceptAndOpen(). L2CAPServer.java (available electronically) is a simple server that accepts and displays a message received from a client.

When an L2CAP server uses an L2CAPConnection's receive() method to receive a packet, if the size of the buffer passed to the method is smaller than ReceiveMTU, all data that will not fit into the buffer is discarded. The server must be aware of this to avoid losing data. In the example, I chose a value of 64 for ReceiveMTU. This is much smaller than you would normally use, but it demonstrates how the server should receive data in chunks and how the client should be aware of the server's limitations.

The L2CAP client obtains the L2CAPConnection object it needs to communicate with the server by passing a URL to Connector.open() and casting the Connection that is returned. The protocol used to form the URL is btl2cap. The target is comprised of the Bluetooth address of the server and the port. The parameters can include keyword/value pairs for ReceiveMTU and TransmitMTU and boolean values for master, encrypt, and authorize and authenticate. Packets of data are exchanged with the server using the send() and receive() methods. The client is responsible for making sure it does not send a packet larger than the server is capable of receiving, so it determines that value by invoking the L2CAPConnection object's getTransmitMTU() method and sends the message as a series of packets of the appropriate size. L2CAPClient.java (available electronically) is a client that can communicate with L2CAPServer.java. When you run the client, the server displays the output in L2CAPServer.out (Listing Three). Notice how the message is received as three packets of 64 bytes each followed by a packet whose length is 7 bytes.

The Serial Port Profile

The Serial Port Profile (SPP), based on the Bluetooth Radio Frequency Communications Protocol (RFCOMM), provides RS-232 serial port emulation for Bluetooth links. An SPP server passes an appropriate URL to Connector.open() and casts the resulting object to the StreamConnectionNotifier interface. The protocol used to build the URL string is btspp. The target contains "localhost" and a UUID that identifies the service. The parameters can include boolean values for master, encrypt, and authorize and authenticate. The implementation of Connector.open() creates a service record and adds it to the Service Discovery Database (SDDB). It also assigns a channel, which it adds to the ProtocolDescriptorList in the service record along with the UUID. The server invokes the acceptAndOpen() method on the StreamConnectionNotifier object; this method returns a StreamConnection. Data is transmitted using the input and output streams that are returned by the getInputStream() and getOutputStream() methods of the StreamConnection. SerialPortServer.java (available electronically) is a simple server that returns the current date/time to a client.

An SPP client makes a connection to the server by passing an appropriate URL to Connector.open() and casting the resulting object to StreamConnection. You can obtain the URL by passing the UUID that describes the service to the DiscoveryAgent object's selectService() method. If you print the URL, you see that the protocol is btspp. The data transmission mechanism is identical to that used by the server. SerialPortClient.java (available electronically) is a client that requests the time from SerialPortServer.java. When you run the program, you see this output:

locating service for UUID:
3F9FA89220578C313344AAB294118F01
URL = btspp://000A3A52BC04:1;
authenticate=false;encrypt=false;
master=false
25 bytes received
received date/time 06 February 2005 15:59:12

The Object Exchange Protocol

The Object Exchange Protocol (OBEX) was designed by the Infrared Association (IrDA) for "pushing" or "pulling" objects such as files, vCards, or even byte arrays to/from clients and servers. As with the other protocols, OBEX clients and servers communicate over a connection that is obtained by passing the appropriate URL to the Connector.open() method. The protocol used to build the URL is formed by the concatenation of the name of the underlying protocol and "obex." So, for TCP/IP and IrDA's Tiny TP, you would use tcpobex and irdaobex, respectively. RFCOMM deviates from the pattern and uses btgoep, indicating that the protocol is the implementation of the Generic Object Exchange Protocol (GOEP) defined by the Bluetooth Special Interest Group (SIG). I limit this discussion to RFCOMM.

OBEXDemo.java (available electronically) is an OBEX client that executes on a PC and pushes a vCard that it reads from the file Marcella_Adamsson.vcf (available electronically) to a PDA. The target component of the URL the client uses is the Bluetooth address of the remote device on which the server is running, plus the channel separated by a colon. Allowable parameters are boolean values for master, encrypt, and authenticate.

Once the client establishes a connection to the client, it conducts an OBEX session that is similar in many ways to an HTTP session. The client starts the session by sending a CONNECT command and ends it by sending a DISCONNECT command. In between, the client can use PUT/GET commands to send a receive object or specify a target location using the SETPATH command. The client and server can exchange additional information using headers similar to those used by HTTP.

When I run the client on my laptop specifying the Bluetooth address of my PalmOne Zire 72 as a target, the PDA displays the dialog box in Figure 1. When I accept the vCard on the PDA by tapping "OK," the contents of the card in Figure 2 are displayed.

Conclusion

The classes and interfaces supplied with an implementation of JSR 82 provide everything you need to develop Bluetooth applications for a variety of devices. The approach taken by the designers of the JSR shields you from unnecessary lower level details and Java guarantees portability.

DDJ



Listing One

package ca.tremblett.bluetooth;
import javax.bluetooth.LocalDevice;

/** @author paul tremblett
 * A simple program that displays Bluetooth properties.
 */
public class DisplayBluetoothProperties {
  
  /* Array of String containing the properties defined by JSR 82. */
  private static final String[] properties = {
    "bluetooth.api.version",
    "bluetooth.l2cap.receiveMTU.max",
    "bluetooth.connected.devices.max",
    "bluetooth.connected.inquiry",
    "bluetooth.connected.page",
    "bluetooth.connected.inquiry.scan",
    "bluetooth.connected.page.scan",
    "bluetooth.master.switch",
    "bluetooth.sd.trans.max",
    "bluetooth.sd.attr.retrievable.max"
  };
  /* The main method.
   * @param args Command line arguments. None are required and none accepted.
   */
  public static void main(String[] args) {  
    for (int i = 0; i < properties.length; ++i) {
      System.out.println(properties[i] + " = " + 
        LocalDevice.getProperty(properties[i]));
    }
  }
}
Back to article


Listing Two
finding devices
2 devices found
Device # 1:
Address: 00027200DBF9
BELKIN_00dbf9[192.168.2.202]
class = [Major:Lan/Network Access Point][utilization:Fully available]
Service record # 1:
<DATSEQ>
  <DATSEQ>
    <UUID>0000010000001000800000805F9B34FB</UUID>
  </DATSEQ>
  <DATSEQ>
    <UUID>0000000300001000800000805F9B34FB</UUID>
    <U_INT_1>3</U_INT_1>
  </DATSEQ>
</DATSEQ>
<DATSEQ>
  <UUID>0000110100001000800000805F9B34FB</UUID>
</DATSEQ>
<U_INT_4>65537</U_INT_4>
URL: [btspp://00027200DBF9:3;authenticate=false;encrypt=false;
    master=false]
Service record # 2:
<DATSEQ>
  <DATSEQ>
    <UUID>0000010000001000800000805F9B34FB</UUID>
  </DATSEQ>
  <DATSEQ>
    <UUID>0000000300001000800000805F9B34FB</UUID>
    <U_INT_1>1</U_INT_1>
  </DATSEQ>
</DATSEQ>
<DATSEQ>
  <UUID>0000110200001000800000805F9B34FB</UUID>
</DATSEQ>
<U_INT_4>65536</U_INT_4>
URL: [btspp://00027200DBF9:1;authenticate=false;encrypt=false;
    master=false]
Device # 2:
Address: 0007E043E7A5
Paul's Zire 72
class = [Major:Computer][Minor:Palm sized PC/PDA]
Service record # 1:
<DATSEQ>
  <DATSEQ>
    <UUID>0000010000001000800000805F9B34FB</UUID>
  </DATSEQ>
  <DATSEQ>
    <UUID>0000000300001000800000805F9B34FB</UUID>
    <U_INT_1>1</U_INT_1>
  </DATSEQ>
  <DATSEQ>
    <UUID>0000000800001000800000805F9B34FB</UUID>
  </DATSEQ>
</DATSEQ>
<DATSEQ>
  <UUID>0000110500001000800000805F9B34FB</UUID>
</DATSEQ>
<U_INT_4>65537</U_INT_4>
URL: [btgoep://0007E043E7A5:1;authenticate=false;encrypt=false;
    master=false]
Back to article


Listing Three
accepting connections
waiting for client connection
got client connection
waiting for client connection
ClientThread running
received 64 bytes
received 64 bytes
received 64 bytes
received 7 bytes
done
'The time has come,' the Walrus said,
'To talk of many things:
Of shoes -- and ships -- and sealing wax --
Of cabbages -- and kings --
And why the sea is boiling hot --
And whether pigs have wings.'
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.