Java & Bluetooth
Developing
applications for
mobile devices
PAUL TREMBLETT
The
Java specification Request 82 (http://jcp.org/) define a standard API for
developing Bluetooth applications using Lhe 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 Q2ME), 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), 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.12cap.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 getLocal Device() 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;
http://www.ddj.com)
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 L2CAP-Connection'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 L2CAP-Connection
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 getlnputStream() and
getOutputStream() methods of the Stream-Connection.
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 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 pottability.
Bluetooth
? Do you want to accept "Marcella-Adamsson.vcf" into
Contacts?
Category: ▼ Unfiled
Yes No
Figure 1: PDA dialog box when running the client on a
laptop.
Contact
Unfiled
Marcella Adamsson
World-class aunt
E-mail: [email protected]
Done Edit New
Figure 2: Accepting a vCard on a PDA.
Listing One
package ea. tremblett. bluetooth;
import javax. blue tooth .Local Device:
/** @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.12cap.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 = Ø: i < properties.length; ++i) {
System.out.printIn(properties[i] + " = " +
LocalDevice.getProperty(properties[i]));
}
}
}
Listing Two
finding devices
2 devices found
Device # 1:
Address: ØØØ272ØØDBF9
BELKIN_ØØdbf9 [192.168.2.2Ø2]
class = (Major:Lan/Network Access Point] [utilization:Fully
available]
Service record # 1:
<DATSEQ>
<DATSEQ>
<UUID>ØØØØØ1ØØØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB</UUID>
</DATSEQ>
<DATSEQ>
<UUID>ØØØØØØØ3ØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB<IUUID>
<U_INT_1>3</U_INT_1>
</DATSEQ>
</DATSEQ>
<DATSEQ>
<UUID>ØØØØ11Ø1ØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB</UUID>
</DATSEQ>
<U_INT_4>65537</U_INT_4>
URL: [btspp: //ØØØ272ØØDBF9:3;authenticate=false;
encrypt=false;
master=false]
Service record # 2:
<DATSEQ>
<DATSBQ>
<UUID>ØØØØØ1ØØØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB</UUID>
</DATSEQ>
<DATSEQ>
<UUID>ØØØØØØØ3ØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB</UUID>
<U_INT_1>1</U_INT_1>
</DATSEQ>
</DATSEQ>
<DATSEQ>
<UUID>ØØØØ11Ø2ØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB<IUUID>
</DATSEQ>
<U_INT_4>65536</U_INT_4>
URL: [btspp://ØØØ272ØØDBF9:1; authenticate=false;
encrypt=false;
master=false)
Device # 2:
Address: ØØØ7EØ43E7A5
Paul's Zire 72
class = (Major:Computer] [Minor:Palm sized PC/PDA]
Service record # 1:
<DATSEQ>
<DATSEQ>
<UUID>ØØØØØ1ØØØØØØ1ØØØBØØØØØBØ5F9B34FB</UUID>
</DATSEQ>
<DATSEQ>
<UUID>ØØØØØØØ3ØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB</UUID>
<U_INT_1>1</U_INT_1>
</DATSEQ>
<DATSEQ>
<UUID>ØØØØØØØ8ØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB</UUID>
</DATSEQ>
</DATSEQ>
<DATSEQ>
<UUID>ØØØØ11Ø5ØØØØ1ØØØ8ØØØØØ8Ø5F9B34FB</UUID>
</DATSEQ>
<U_INT_4>65537</U_INT_4>
URL: [btgoep://ØØØ7EØ43E7A5:1; authenticate=false;
encrypt=false;
master=false]
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.'
Paul is a technical lead at Vonage. He can be reached at [email protected].
Dr Dobb's Journal, #374 July 2005, Pg 41-44