Service Records

Each Bluetooth device supports one or more services, such as basic printing or generic telephony. Each service is identified by a UUIDsometimes a custom one, sometimes a standardized one. For example, the UUID for the basic printing service is 0x1122. (Normally, these are written using their short forms rather than the full 128-bit forms.)

Devices publish service records to tell other devices how to communicate with them. The Bluetooth specification lays out the exact structure and meaning of a service record in excruciating detail, but as usual Java encapsulates this in a much easier-to-use interface, javax.bluetooth.ServiceRecord.

A service record is essentially an indexed list of attributes. Attributes do not have names, only values. Given a ServiceRecord object, the getAttributeIDs( ) method returns an array of all the IDs of the attributes in the service record:

public int[] getAttributeIDs( )

You can then iterate through this list, passing each ID in turn to the getAttributeValue( ) method to retrieve each attribute:

public DataElement getAttributeValue(int attrID)

.6.1. The DataElement Class

Bluetooth attributes can have a variety of types, such as string, UUID, boolean, URL, sequence, null, and several signed and unsigned integer types. Java represents each of these as a DataElement object. These types, and the Java types they map to, are summarized in Table 25-4.

Table 25-4. Bluetooth attribute types

Bluetooth type

Java type

Java constant

NULL

null

DataElement.NULL

U_INT_1

1-byte unsigned long from 0 to 255

DataElement.U_INT_1

U_INT_2

2-byte unsigned long from 0 to 65,535

DataElement.U_INT_2

U_INT_4

4-byte unsigned long from 0 to 4,294,967,296

DataElement.U_INT_4

U_INT_8

8-byte byte[] array

DataElement.U_INT_8

U_INT_16

16-byte byte[] array

DataElement.U_INT_16

INT_1

1-byte signed long from -128 to 127

DataElement.INT_1

INT_2

2-byte signed long from -32,768 to 32,767

DataElement.INT_2

INT_4

4-byte signed long from -2,147,483,647 to 2,147,483,647

DataElement.INT_4

INT_8

8-byte unsigned long from -263 to 263-1

DataElement.INT_8

INT_16

16-byte byte[] array

DataElement.INT_16

URL

java.lang.String

DataElement.URL

UUID

javax.bluetooth.UUID

DataElement.UUID

BOOL

boolean

DataElement.BOOL

STRING

java.lang.String

DataElement.STRING

DATSEQ

java.util.Enumeration

DataElement.DATSEQ

DATALT

java.util.Enumeration

DataElement.DATALT

Most of these types do not map precisely onto Java primitive types, so the Java Bluetooth API encapsulates them all in the javax.bluetooth.DataElement class. This class has three methods to read the value out of a DataElement as a long, boolean, or Object:

public long getLong( ) public boolean getBoolean( ) public Object getValue( )

The getdataType( ) method tells you the Bluetooth type of the DataElement object:

public int getDataType( )

The return value is one of the named constants found in the third column of Table 25-4. Once you know the type of value to expect, you can use one of the three getter methods to return the value as the corresponding Java object or primitive type. If you try to get a mismatched typefor example, an INT_4 as a boolean or a URL as a longthese methods throw a ClassCastException.

Data elements can also wrap two list types. DATSEQ is an ordered sequence of values. DATALT is a list of values from which any one should be chosen. For either of these two types, getValue( ) returns a java.util.Enumeration. In this case, the getSize( ) method returns the number of items in that enumeration:

public int getSize( )

The addElement( ) method appends a new item of one of the 18 Bluetooth types to a DATSEQ or DATALT:

public void addElement(DataElement element)

The insertElementAt( ) method inserts a new data element into the specified position in a DATSEQ or DATALT:

public void insertElement(DataElement element, int position)

The removeElement( ) removes the first occurrence of the specified new data element from the DATSEQ or DATALT:

public void removeElement(DataElement element)

The same element may appear in the list more than once. These methods all throw a ClassCastException if you attempt to use them on a DataElement that does not represent a DATALT or DATSEQ.

.6.2. Finding Service Records

Like the remote devices themselves, the service records for a device are obtained via the DiscoveryAgent class. The simplest way to find a known service is to ask for it by UUID using the selectService( ) method:

public String selectService(UUID uuid, int security, boolean master) throws BluetoothStateException

This returns a connection string with the URL used to connect to the service, such as:

btspp://00904B2A88D6:1;authenticate=false;encrypt=false;master=false

The security argument is usually one of these three named constants, depending on what combination of authentication and encryption you desire:

Finally, the master argument is TRue if the client insists on being the master of the connection and false if it can act as the master or the slave.

For example, suppose you want to find a basic printing service. The UUID for this is 0x1122, so this code fragment locates one if theres one to be found:

UUID printingID = new UUID(0x1122); String url = agent.selectService( printingID, ServiceRecord.AUTHENTICATE_NOENCRYPT, false);

If it can locate the requested service, it returns null.

What if theres more than one available device that supports the relevant service? In this case, the results are implementation dependent, but usually one or another is returned. (The Avetana stack actually throws a custom checked exception here, which is not conformant with the specification.)

The searchServices( ) method asks a specific device what services it supports:

public int searchServices(int[] attrSet, UUID[] uuidSet, RemoteDevice device, DiscoveryListener listener) throws BluetoothStateException

This approach is somewhat more reliable if you might have more than one device that offers a given service. The uuidSet argument contains the UUIDs for all the protocols you e looking for. Table 25-5 lists the UUIDs (in 2-byte form) of the services you can request. The attrSet argument contains the list of attributes (in addition to the default attributes) whose information should be provided. device is the specific device to query for services, and listener is the listener to tell about any services that are found. The method returns an ID you can use if you later need to cancel the search, which you can do with the following method:

public boolean cancelServiceSearch(int transID)

This method does not work in the Avetana stack. That product cannot cancel an ongoing search.

Table 25-5. Bluetooth service UUIDs

Name

UUID

Protocol

SDP

0x0001

Service Discovery Protocol

UDP

0x0002

UDP/IP

RFCOMM

0x0003

Serial port emulation

TCP

0x0004

Telephony Control Protocol

TCS-BIN

0x0005

Telephony Control Service

TCS-AT

0x0006

Modems (i.e., AT command sequences)

OBEX

0x0008

Object Exchange protocol

IP

0x0009

Internet Protocol

FTP

0x000A

Bluetooth File Transfer Protocol; based on OBEX; not the same as the usual Internet FTP protocol

HTTP

0x000C

Web

WSP

0x000E

Wireless Session Protocol

BNEP

0x000F

Bluetooth Network Encapsulation Protocol

UPNP

0x0010

Universal Plug and Play

HIDP

0x0011

Human Interface Device Profile (same as the USB HID, but over Bluetooth instead of USB)

HardcopyControlChannel

0x0012

Wireless printing control channel (device to printer)

HardcopyDataChannel

0x0014

Wireless printing data channel (data being printed)

HardcopyNotification

0x0016

Wireless printing notification channel (printer to device)

AVCTP

0x0017

Audio/Video Control Transport Protocol, Bluetooth SIG

AVDTP

0x0019

Audio/Video Distribution Transport Protocol, Bluetooth SIG

CMTP

0x001B

Common ISDN API (CAPI) Message Transport Protocol

UDI_C-Plane

0x001D

Unrestricted Digital Information Profile

L2CAP

0x0100

Logical Link Control and Adaptation Protocol

Table 25-6 lists some of the attributes you can request. The first fiveServiceRecordHandle, ServiceClassIDList, ServiceRecordState, ServiceID, and ProtocolDescriptorListare always returned. The others need to be specifically requested.

Table 25-6. Bluetooth service attribute IDs

Name

ID

Type

ServiceRecordHandle

0x0000

4-byte unsigned integer

ServiceClassIDList

0x0001

DATSEQ of UUIDs

ServiceRecordState

0x0002

4-byte unsigned integer

ServiceID

0x0003

UUID

ProtocolDescriptorList

0x0004

DATSEQ of DATSEQ of UUID and optional parameters

BrowseGroupList

0x0005

DATSEQ of UUIDs

LanguageBasedAttributeIDList

0x0006

DATSEQ of DATSEQ triples

ServiceInfoTimeToLive

0x0007

4-byte unsigned integer

ServiceAvailability

0x0008

1-byte unsigned integer

BluetoothProfileDescriptorList

0x0009

DATSEQ of DATSEQ pairs

DocumentationURL

0x000A

URL

ClientExecutableURL

0x000B

URL

IconURL

0x000C

URL

VersionNumberList

0x0200

DATSEQ of 2-byte unsigned integers

ServiceDatabaseState

0x0201

4-byte unsigned integer

Some of these IDs may vary depending on the profile. For instance, 0x0301 means "external network" in the Cordless Telephony Profile but "supported data stores" in the Synchronization Profile.

.6.3. The ServiceRecord Interface

The ServiceRecord interface provides a number of setter and getter methods for inspecting and updating service records. By far the most important thing youll need from a ServiceRecord object is the connection string. This is the URL youll use to open a connection to the device:

public String getConnectionURL(int requiredSecurity, boolean mustBeMaster)

The security argument is one of these three named constants, depending on what combination of authentication and encryption you desire:

The master argument is true if the client insists on being the master of the connection or false if it can act as the master or the slave.

The value you get back is a complete GCF Bluetooth URL, such as:

btspp://00904B2A88D6:1;authenticate=false;encrypt=false;master=false

Once you have the URL, you can talk to the device using the methods of the last chapter.

You can add or remove parameters from the URL using substring operations. For instance, you could change the above URL to:

btspp://00904B2A88D6:1;authenticate=false;encrypt=false;master=true

However, getConnectionURL() is the only way to get the necessary protocol, address, and channel for the device.

The getAttributeIDs( ) method returns an array of the IDs of all the attributes this service possesses:

public int[] getAttributeIDs( )

You can retrieve one of these attributes with the getAttributeValue( ) method:

public DataElement getAttributeValue(int attrID)

This returns a DataElement object that wraps the Bluetooth object in a Java class, as described in Table 25-4.

Example 25-4 is a program that searches for all L2CAP (UUID 0x0100) services. When it finds one, it lists its URL. Notice that you have to explicitly start a search for each devices services. That is, Example 25-4 first starts a search for devices as previously seen in Example 25-3. When a device is found, it searches that device for services using searchServices( ). For each service, it requests all attributes that might be present.

Example 25-4. Finding all L2CAP services

import java.io.IOException; import javax.bluetooth.*; public class BluetoothServicesSearch implements DiscoveryListener { private DiscoveryAgent agent; private final static UUID L2CAP = new UUID(0x0100); public static void main(String[] args) throws Exception { BluetoothServicesSearch search = new BluetoothServicesSearch( ); search.agent = LocalDevice.getLocalDevice().getDiscoveryAgent( ); search.agent.startInquiry(DiscoveryAgent.GIAC, search); } public void deviceDiscovered(RemoteDevice device, DeviceClass type) { try { System.out.println("Found " + device.getFriendlyName(false) + " at " + device.getBluetoothAddress( )); } catch (IOException ex) { System.out.println("Found unnamed device " + " at " + device.getBluetoothAddress( )); } searchServices(device); } public final static int SERVICE_RECORD_HANDLE = 0X0000; public final static int SERVICE_CLASSID_LIST = 0X0001; public final static int SERVICE_RECORD_STATE = 0X0002; public final static int SERVICE_ID = 0X0003; public final static int PROTOCOL_DESCRIPTOR_LIST = 0X0004; public final static int BROWSE_GROUP_LIST = 0X0005; public final static int LANGUAGE_BASED_ATTRIBUTE_ID_LIST = 0X0006; public final static int SERVICE_INFO_TIME_TO_LIVE = 0X0007; public final static int SERVICE_AVAILABILITY = 0X0008; public final static int BLUETOOTH_PROFILE_DESCRIPTOR_LIST = 0X0009; public final static int DOCUMENTATION_URL = 0X000A; public final static int CLIENT_EXECUTABLE_URL = 0X000B; public final static int ICON_URL = 0X000C; public final static int VERSION_NUMBER_LIST = 0X0200; public final static int SERVICE_DATABASE_STATE = 0X0201; private void searchServices(RemoteDevice device) { UUID[] searchList = {L2CAP}; int[] attributes = {SERVICE_RECORD_HANDLE, SERVICE_CLASSID_LIST, SERVICE_RECORD_STATE, SERVICE_ID, PROTOCOL_DESCRIPTOR_LIST, BROWSE_GROUP_LIST, LANGUAGE_BASED_ATTRIBUTE_ID_LIST, SERVICE_INFO_TIME_TO_LIVE, SERVICE_AVAILABILITY, BLUETOOTH_PROFILE_DESCRIPTOR_LIST, DOCUMENTATION_URL, CLIENT_EXECUTABLE_URL, ICON_URL, VERSION_NUMBER_LIST, SERVICE_DATABASE_STATE}; try { System.out.println("Searching " + device.getBluetoothAddress( ) + " for services"); int trans = this.agent.searchServices(attributes, searchList, device, this); System.out.println("Service Search " + trans + " started"); } catch (BluetoothStateException ex) { System.out.println( "BluetoothStateException: " + ex.getMessage( ) ); } } public void servicesDiscovered(int transactionID, ServiceRecord[] record) { for (int i = 0; i < record.length; i++) { System.out.println("Found service " + record[i].getConnectionURL( ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false)); } } public void serviceSearchCompleted(int transactionID, int responseCode) { switch (responseCode) { case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE: System.out.println("Could not find device on search " + transactionID); break; case DiscoveryListener.SERVICE_SEARCH_ERROR: System.out.println("Error searching device on search " + transactionID); break; case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS: System.out.println("No service records on device on search " + transactionID); break; case DiscoveryListener.SERVICE_SEARCH_TERMINATED: System.out.println("User cancelled search " + transactionID); break; case DiscoveryListener.SERVICE_SEARCH_COMPLETED: System.out.println("Service search " + transactionID + " complete"); break; default: System.out.println("Unexpected response code " + responseCode + " from search " + transactionID); } } public void inquiryCompleted(int transactionID) { System.out.println("Device search " + transactionID + " complete"); } }

Most other Bluetooth protocols are built on top of L2CAP, so this program will probably find all the accessible devices. Heres what I got when I ran it on my system after making sure all devices were discoverable:

Found Earthmate Blue Logger GPS at 00904B2A88D6 Searching 00904B2A88D6 for services Service Search 1 started Found service btspp://00904B2A88D6:1;authenticate=false;encrypt=false;master=false Service search 1 complete Found elharos mouse at 000A95095A59 Searching 000A95095A59 for services Service Search 2 started Found service btl2cap://000A95095A59:11;authenticate=false;encrypt=false;master=false Found service btl2cap://000A95095A59:1;authenticate=false;encrypt=false;master=false Service search 2 complete Found WACOM Pen Tablet at 0013C2000D23 Searching 0013C2000D23 for services Service Search 3 started Found service btl2cap://0013C2000D23:11;authenticate=false;encrypt=false;master=false Found service btl2cap://0013C2000D23:1;authenticate=false;encrypt=false;master=false Service search 3 complete Device search 0 complete

You can see there are three devices on this system: a GPS unit, a pen tablet, and a mouse. The GPS unit has a single serial port (RFCOMM) connection, which well make use of in the next section. The mouse and the graphics tablet each have two L2CAP URLs, one for the control channel and one for the interrupt channel. This is the common pattern for HID devices.

More often, youll want to look for a particular service with a particular UUID. This normally happens asynchronously, but theres a maximum number of searches you can run at once. (The exact number varies from device to device but can be read from the bluetooth.sd.trans.max property.) Consequently, you need to keep track of the searches and cancel the ongoing searches when youve found what you e looking for. Example 25-5 demonstrates. The static BluetoothServiceFinder.getConnectionURL( ) method finds a service with a specified UUID. Well use this class again shortly.

Example 25-5. A utility class to find a specified service

import javax.bluetooth.*; import java.util.Vector; public class BluetoothServiceFinder implements DiscoveryListener { public static String getConnectionURL(String uuid) throws BluetoothStateException { BluetoothServiceFinder finder = new BluetoothServiceFinder(BluetoothReceiver.UUID); return finder.getFirstURL( ); } private DiscoveryAgent agent; private int serviceSearchCount; private ServiceRecord record; // Id rather use ArrayList, but Vector is more // commonly available in J2ME environments private Vector devices = new Vector( ); private String uuid; // Every search has an ID that allows it to be cancelled. // We need to store these so we can tell when all searches // are complete. private int[] transactions; private BluetoothServiceFinder(String serviceUUID) throws BluetoothStateException { this.uuid = serviceUUID; agent = LocalDevice.getLocalDevice().getDiscoveryAgent( ); int maxSimultaneousSearches = Integer.parseInt( LocalDevice.getProperty("bluetooth.sd.trans.max")); transactions = new int[maxSimultaneousSearches]; // We need to initialize the transactions list with illegal // values. According to spec, the transaction ID is supposed to be // positive, and thus nonzero. However, several implementations // get this wrong and use zero as a transaction ID. for (int i = 0; i < maxSimultaneousSearches; i++) { transactions[i] = -1; } } private void addTransaction(int transactionID) { for (int i = 0; i < transactions.length; i++) { if (transactions[i] == -1) { transactions[i] = transactionID; return; } } } private void removeTransaction(int transactionID) { for (int i = 0; i < transactions.length; i++) { if (transactions[i] == transactionID) { transactions[i] = -1; return; } } } private boolean searchServices(RemoteDevice[] devices) { UUID[] searchList = { new UUID(uuid, false) }; for (int i = 0; i < devices.length; i++) { if (record != null) { return true; } try { // don care about attributes int transactionID = agent.searchServices(null, searchList, devices[i], this); addTransaction(transactionID); } catch (BluetoothStateException ex) { } synchronized (this) { serviceSearchCount++; if (serviceSearchCount == transactions.length) { try { this.wait( ); } catch (InterruptedException ex) { // continue } } } } while (serviceSearchCount > 0) { // unfinished searches synchronized (this) { try { this.wait( ); } catch (InterruptedException ex) { // continue } } } if (record != null) return true; else return false; } private String getFirstURL( ) { try { agent.startInquiry(DiscoveryAgent.GIAC, this); synchronized (this) { try { this.wait( ); } catch (InterruptedException ex) { } } } catch (BluetoothStateException ex) { System.out.println("No devices in range"); } if (devices.size( ) > 0) { RemoteDevice[] remotes = new RemoteDevice[devices.size( )]; devices.copyInto(remotes); if (searchServices(remotes)) { return record.getConnectionURL( ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); } } return null; } // DiscoveryListener methods public void deviceDiscovered(RemoteDevice device, DeviceClass type) { devices.addElement(device); } public void serviceSearchCompleted(int transactionID, int responseCode) { removeTransaction(transactionID); serviceSearchCount--; synchronized (this) { this.notifyAll( ); } } public void servicesDiscovered(int transactionID, ServiceRecord[] records) { if (record == null) { record = records[0]; for (int i = 0; i < transactions.length; i++) { if (transactions[i] != -1) { agent.cancelServiceSearch(transactions[i]); } } } } public void inquiryCompleted(int discType) { synchronized (this) { this.notifyAll( ); } } }

Категории

© amp.flylib.com,