I wanted to write an applet for my cell phone (a Motorola RAZR V3i) to talk to a Bluetooth device (a BlueSMiRF from SparkFun). This turned out to be more complicated than I expected, and a few people have asked me how it works, so I've written this webpage.
This isn't really a tutorial, it's just a snapshot of what I did to get the connection working, which will hopefully help other people start out in the right direction.
I've put some hacked-together source code here: bt-midp-example.tar.gz. You can read the source code along with this page to get a better idea of what's going on.
Some other useful pages:
The hardest part for me was just getting to the point where even a simple
Hello World applet would run on my phone. Most computers have
a Java compiler these days, but in addition to that, you'll need a
set of .class files corresponding
to the classes available on your phone, and you'll need a preverifier.
Both of these are available in SDKs from e.g. Sun. They're free,
but can be a pain to find and install. You'll also need to package your application into a .jar file with a manifest that the phone will accept. My phone is kind of picky and doesn't give useful error messages.
The class files you need depend on what your phone supports, which are different from the classes available to a desktop or web-browser Java program. You'll need to read up on the relationships between CLDC, MIDP, and JSR-082. Also, be sure that your phone actually supports JSR-082 (the java-bluetooth API) — unfortunately there are a lot of phones that support Java and support Bluetooth, but don't let the Java applets use the Bluetooth radio. (Such as earlier models of the RAZR.)It's convenient to refer to Sun's JavaDoc pages for the MIDP and JSR-082 APIs.
Before your applet can talk to a bluetooth device it needs to know what device to talk to and how to talk to it; this is called device discovery and service discovery by the Bluetooth people.
The discovery doesn't have to be done by the same applet that
does the communication. You can discover the service you want to
talk to and then hard-code the result into another applet, or store
it in a MIDP
RMS record store.
Anyway, discovery takes several steps:
discoverable, which basically means it'll respond to inquiry packets broadcast by the device doing the discovering. The result of device discovery is a list of the addresses of Bluetooth devices in your neighborhood (e.g. 00-a0-96-11-fe-32 for my BlueSMiRF). Usually you'd present this list to the user so they can pick a device to communicate with. You can retrieve each device's name and so forth to make the display friendlier.
port numberthat service is on. (They aren't called port numbers in Bluetooth-speak, they're called channel numbers, but it's the same concept.)
To discover devices, call the startInquiry() method of the DiscoveryAgent instance:
The method takes two arguments.
access code is broadcast with the inquiry and tells devices whether to respond (GIAC is the
general inquiry access code, which asks about all devices; in theory you can use other access codes to discover only specific types of devices).
The second argument is an object implementing the DiscoveryListener interface, specifically the deviceDiscovered() and inquiryCompleted() methods. Discovery takes a few seconds, so the startInquiry() call returns immediately and calls back your other methods later.
In my example code I only have one object playing all rôles, so I pass this as the callback listener.
You'll get a series of calls to deviceDiscovered as each device is discovered, and inquiryCompleted when the inquiry is done. My example code just adds the discovered devices to a Vector, and then displays that Vector in inquiryCompleted(). The devices are represented by RemoteDevice instances, which support a handful of useful methods, such as getFriendlyName().
Searching for services on a device follows the same pattern. Start the service inquiry like this:
javax.bluetooth.LocalDevice.getLocalDevice().getDiscoveryAgent().searchServices(attributes, uuids, device, this);
This one's a little more complex. What are the parameters for? Well, the easy ones first: device is the RemoteDevice instance representing the device, and this (in my example) is the object to call back with the results of the inquiry.
uuids is a list of the UUIDs of the services you're interested in. Each kind of service gets assigned a UUID. For example, the UUID of the serial port service class is UUID(0x1101), which is actually shorthand for a full 128-bit UUID. You can get a list of service class UUIDs from the Bluetooth Assigned Numbers for SDP document. The only service I'm interested in is the serial port service, so I pass in a 1-element array containing that UUID.
The attributes list is a list of 16-bit numeric service attribute IDs, indicating which attributes of each service to fetch while inquiring. You can pass null or an empty list and that just means that your applet will have to wait while the attribute you want is retrieved later.
The listener object will get a sequence of calls to servicesDiscovered() followed by a single call to serviceSearchCompleted(). My example code does the same thing as it did for device discovery: it stuffs the ServiceRecords in a list and displays them. Once the search is complete, it connects to a service. A real application would need to deal with possibly getting no services or many. Mine just connects to the first-discovered service (the only one, for a BlueSMiRF).
The most useful method of ServiceRecord is getConnectionURL(), which returns a URL string like "btspp://00a09611fe32:1;master=false". That URL is what you actually need in order to connect. At this point you can forget all about the DiscoveryAgents and ServiceRecords and so on.
This part is easy! The discovery process will provide you with a connection URL. Pass that string to javax.microedition.io.Connector.open() to make a connection, which will be represented by a StreamConnection object; use the stream connection's openOutputStream() and openInputStream() methods to get a read-stream and write-stream. Bytes written to the write stream will be sent across the Bluetooth connection and eventually show up on the BlueSMiRF's serial output pin, and vice versa. You can use an OutputStreamWriter for the convenience of sending character Strings across what is actually a byte-oriented connection.
StreamConnection c = (StreamConnection)Connector.open(someBtsppURL); OutputStream os = c.openOutputStream(); OutputStreamWriter ow = new OutputStreamWriter(os, "US-ASCII"); InputStream is = c.openInputStream(); InputStreamReader ir = new InputStreamReader(is, "US-ASCII"); ow.write("Hello, serial world!");That's all!
discoverable(normally, once you know a device's address, you can take it out of Discoverable mode and prolong its battery life).
Also, you may wonder if you can just get the connection URL once and then hard-code it into your applet, if you only ever want to connect to one specific device. Yes, you can.
In my example code I get attribute 0x0100 of the service and display it. What's up with that? Where did that number come from?
Every service has a handful of attributes, each one with a numeric ID. Some of the numbers are well-known and defined in advance, such as ServiceAvailability (attribute number 8) which indicates whether the service is available right now or already in use. The attribute IDs are defined in the same Assigned Numbers doc that contains the service class UUIDs.
I want to display the name of the service. However, Bluetooth allows services to provide information in multiple languages, so textual attributes don't have a single attribute ID. Instead, all string attributes are specified as offsets from some base number which depends on the language:
To get the base number for a given language, you need to retrieve attribute 6, LanguageBaseAttributeIDList, which contains a list of languages and the base attribute ID for each one. Then, you add the attribute ID offset to the attribute ID base for your favorite language, to get the attribute ID. But wait! There's a simpler way! The primary language for the service (whatever that means) is required to have a base of 0x100. Which means you can retrieve attribute IDs 0x100 and 0x101, and be sure you're getting the service's name and description, even though you won't be sure what language they're in unless you retrieve the language list as well. So, once again, you can take a shortcut and use 0x100, but a finished application really ought to try to retrieve the strings for its user's preferred language.
My example code does essentially no error handling.
If anything goes wrong, an exception is thrown, and my applet displays the exception and quits. Sometimes that's an overreaction: for example, some devices don't have a
friendly name, and getFriendlyName() will throw an exception. A well-behaved app would continue past such an exception.