Java Bluetooth
Java Bluetooth
El presente tutorial trata sobre la programación de aplicaciones Java™ que hagan uso de
Bluetooth. Más concretamente trata sobre las APIs definidas en el JSR-82.
This work is licensed under the Creative Commons Attribution-ShareAlike License. To view a copy of this license,
visit http://creativecommons.org/licenses/by-sa/2.0/ or send a letter to Creative Commons, 559 Nathan Abbott
Way, Stanford, California 94305, USA..
Tabla de contenidos
1. Introducción .................................................................................................... 1
Sobre la tecnología Bluetooth ...................................................................... 1
Sobre el API JSR-82 ................................................................................... 1
Sobre este tutorial ....................................................................................... 1
2. El paquete javax.bluetooth ............................................................................... 2
Algunas clases básicas ............................................................................... 2
Clase LocalDevice .............................................................................. 2
Excepción BluetoothStateException ..................................................... 4
Clase DeviceClass .............................................................................. 4
Clase UUID ........................................................................................ 4
Búsqueda de dispositivos y servicios ........................................................... 5
Clase DiscoveryAgent ......................................................................... 5
La interfaz DiscoveryListener ............................................................... 6
La clase DataElement ....................................................................... 11
Comunicación .......................................................................................... 15
Comunicación cliente ........................................................................ 15
Comunicación del lado del servidor .................................................... 19
3. El paquete javax.obex ................................................................................... 25
Clases básicas ......................................................................................... 25
La clase HeaderSet .......................................................................... 25
La clase Operation ............................................................................ 26
Conexión cliente ....................................................................................... 27
Conexión servidor ..................................................................................... 28
4. Implementaciones de APIs Bluetooth para Java .............................................. 29
Implementaciones del JSR-82 ................................................................... 29
APIs no-JSR-82 ........................................................................................ 29
5. Documentación de interés ............................................................................. 31
iv
Lista de tablas
2.1. Tipos de datos DataElemnet ....................................................................... 11
3.1. Cabeceras y tipos de datos relacionados ..................................................... 25
3.2. Rangos y tipos de datos asociados para identificadores creados por el usuario
......................................................................................................................... 26
v
Capítulo 1. Introducción
Sobre la tecnología Bluetooth
Bluetooth es una tecnología de comunicación inalámbrica omnidireccional. Se ideó pen-
sando en dispositivos de bajo consumo y comunicaciones a corta distancia (10 metros).
Se trata de una tecnología barata con un ancho de banda reducido: hasta 11 Mbit/s. Es
ideal para periféricos de ordenador (ratón, teclado, manos libres,...) y dispositivos móviles
(teléfonos móviles, PDAs, Pocket PCs,...).
Por el contrario el paquete javax.obex permite manejar el protocolo de alto nivel OBEX
(OBject EXchange). Este protocolo es muy similar a HTTP y es utilizado sobre todo para el
intercambio de archivos. El protocolo OBEX es un estándar desarrollado por IrDA y es uti-
lizado también sobre otras tecnologías inalámbricas distintas de Bluetooth.
La plataforma principal de desarrollo del API JSR-82 es J2ME. El API ha sido diseñada
para depender de la configuración CLDC. Sin embargo existen implementaciones para po-
der hacer uso de este API en J2SE. Al final del tutorial se listan la mayoría de las imple-
mentaciones del JSR-82 existentes.
Es muy recomendable tener la documentación javadoc del API a mano. Se puede descar-
gar de la página del JSR-82 en jcp.org [http://jcp.org/en/jsr/detail?id=82]
1
Capítulo 2. El paquete javax.bluetooth
En una comunicación Bluetooth existe un dispositivo que ofrece un servicio (servidor) y
otros dispositivos acceden a él (clientes). Dependiendo de qué parte de la comunicación
debamos programar deberemos realizar una serie de acciones diferentes.
• Búsqueda de servicios. La aplicación realizará una búsqueda de servicios por cada dis-
positivo.
Por otro lado, un servidor Bluetooth deberá hacer las siguientes operaciones:
Alguna información de interés que podemos obtener a través de este objeto es, por ejem-
plo, la dirección Bluetooth de nuestro dispositivo, el apodo o "friendly-name" (también lla-
mado "Bluetooth device name" o "user-friendly name"). A través de este objeto también
podemos obtener y establecer el modo de conectividad: la forma en que nuestro dispositi-
vo está o no visible para otros dispositivos. Esta clase es un "singleton"; para obtener la
única instancia existente de esta clase llamaremos al método getLocalDevice() de la
clase LocalDevice. Veamos un ejemplo:
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.bluetooth.*;
public class Ejemplo1 extends MIDlet {
public void startApp() {
LocalDevice localDevice = null;
try {
localDevice = LocalDevice.getLocalDevice();
} catch(BluetoothStateException e) {
System.out.println("Error al iniciar"+
" el sistema Bluetooth");
2
El paquete javax.bluetooth
return;
}
String address = localDevice.getBluetoothAddress();
System.out.println("Dirección bluetooth del"+
" dispositivo local: "+address);
String friendlyName = localDevice.getFriendlyName();
if(friendlyName == null)
System.err.println("El dispositivo "+
"local no soporta un 'friendly-name'"+
" o no ha sido establecido");
else
System.out.println("El 'friendly-name'"+
" del dispositivo local es:"+
friendlyName);
try {
if(!localDevice.setDiscoverable(DiscoveryAgent.GIAC))
System.out.println("El dispositivo no"+
" soporta el modo de "+
"conectividad 'GIAC'");
} catch(BluetoothStateException e) {
System.err.println("No se pudo establecer"+
" la propiedad 'discoverable'"+
" a 'GIAC'");
}
DeviceClass deviceClass = localDevice.getDeviceClass();
int discoverable = localDevice.getDiscoverable();
String discoverableString = null;
switch(discoverable) {
case DiscoveryAgent.GIAC:
discoverableString =
"General / Unlimited Inquiry Access";
break;
case DiscoveryAgent.LIAC:
discoverableString =
"Limited Dedicated Inquiry Access";
break;
case DiscoveryAgent.NOT_DISCOVERABLE:
discoverableString = "Not discoverable";
break;
default:
discoverableString = "Desconocido";
}
StringBuffer device = new StringBuffer("0x");
device.append(Integer.toHexString(
deviceClass.getMajorDeviceClass()));
device.append(", 0x");
device.append(Integer.toHexString(
deviceClass.getMinorDeviceClass()));
device.append(", 0x");
device.append(Integer.toHexString(
deviceClass.getServiceClasses()));
//interfaz de usuario
Form main = new Form("Ejemplo LocalDevice");
main.append(new TextField("Dirección bluetooth",
address, 12, TextField.UNEDITABLE));
main.append(new TextField("Nombre del dispositivo",
friendlyName,
friendlyName.length(), TextField.UNEDITABLE));
main.append(new TextField("Modo de conectividad",
discoverableString,
discoverableString.length(),
TextField.UNEDITABLE));
main.append(new TextField("Tipo de dispositivo",
device.toString(),
device.length(),
TextField.UNEDITABLE));
3
El paquete javax.bluetooth
Display.getDisplay(this).setCurrent(main);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Los posibles valores que admite setDiscoverable() están definidos en la clase Dis-
coveryAgent como campos estáticos. Estos son:
Excepción BluetoothStateException
Al llamar al método getLocalDevice() se puede producir una excepción del tipo Blue-
toothStateException. Esto significa que no se pudo inicializar el sistema Bluetooth.
La excepción BluetoothStateException extiende de java.io.IOException y no
añade ningún método adicional.
En el paquete javax.bluetooth se definen dos tipos más de excepciones que veremos más
adelante.
Clase DeviceClass
En el ejemplo hemos usado el método getDeviceClass() que devuelve un objeto de ti-
po DeviceClass. Este tipo de objeto describe el tipo de dispositivo. A través de sus mé-
todos podremos saber, por ejemplo, si se trata de un teléfono, de un ordenador,... En la
documentación del API hay una pequeña tabla con los posibles valores que devuelven los
métodos de esta clase y su significado.
Clase UUID
La clase UUID (universally unique identifier) representa indentificadores únicos universa-
les. Se trata de enteros de 128 bits que identifican protocolos y servicios. Como veremos
más adelante un dispositivo puede ofrecer varios servicios. Los UUID servirán para identi-
ficarlos. En la documentación de la clase UUID se puede encontrar una tabla con los pro-
tocolos y servicios más comunes y sus UUIDs asignadas.
Del mismo modo que cuando hacemos aplicaciones de ejemplo con sockets usamos nú-
meros de puertos "inventados", que no estén asociados a ningún protocolo existente, en
los ejemplos de este tutorial usaremos una UUID cualquiera.
Podremos crear un objeto UUID a partir de un String o de un entero largo. En los ejemplos
del tutorial crearemos UUIDs mediante enteros que representaremos en hexadecimal, co-
mo se acostumbra a hacer en los documentos relacionados con Bluetooth.
4
El paquete javax.bluetooth
Clase DiscoveryAgent
Las búsquedas de dispositivos y servicios Bluetooth las realizaremos a través del objeto
DiscoveryAgent. Este objeto es único y lo obtendremos a través del método getDis-
coveryAgent() del objeto LocalDevice:
DiscoveryAgent discoveryAgent =
LocalDevice.getLocalDevice().getDiscoveryAgent();
El array devuelto por este método es de objetos RemoteDevice, los cuales, representan
dispositivos remotos. La clase RemoteDevice permite obtener la dirección Bluetooth del
dispositivo que representa a través del método getBluetoothAddress() en forma de
String. También podremos obtener el "friendly-name" del dispositivo a través del método
getFriendlyName(). Este último método requiere un argumento de tipo booleano. Este
argumento permite especificar si se debe forzar a que se contacte con el dispositivo para
preguntar su "friendly-name" (true) o bien se puede obtener un "friendly-name" que tuvo
en una ocasión previa (false). El método getFriendlyName() puede lanzar una ja-
va.io.IOException en caso de que no se pueda contactar con el dispositivo o este no
provea un "friendly-name".
import javax.microedition.midlet.MIDlet;
import javax.bluetooth.*;
import java.io.IOException;
public class Ejemplo2 extends MIDlet {
public void startApp() {
LocalDevice localDevice = null;
try {
localDevice = LocalDevice.getLocalDevice();
} catch(BluetoothStateException e) {
System.out.println("Error al iniciar"+
" el sistema Bluetooth");
return;
}
DiscoveryAgent discoveryAgent =
localDevice.getDiscoveryAgent();
RemoteDevice[] preknown =
discoveryAgent.retrieveDevices(
DiscoveryAgent.PREKNOWN);
if(preknown == null) {
System.out.println("No hay"+
" dispositivos conocidos");
} else {
System.out.println("Dispositivos conocidos:");
5
El paquete javax.bluetooth
Antes hemos visto que el método retrieveDevices() no realiza una búsqueda, sino
que nos devuelve un array de dispositivos remotos ya conocidos o encontrados en bús-
quedas anteriores. Para comenzar una nueva búsqueda de dispositivos llamaremos al mé-
todo startInquiry(). Este método requiere dos argumentos. El primer argumento es
un entero que especifica el modo de conectividad que deben tener los dispositivos a bus-
car. Este valor deberá ser DiscoveryAgent.GIAC o bien DiscoveryAgent.LIAC. El segundo
argumento es un objeto que implemente DiscoveryListener. A través de este último
objeto serán notificados los dispositivos que se vayan descubriendo. Para cancelar la bús-
queda usaremos el método cancelInquiry().
La interfaz DiscoveryListener
La interfaz DiscoveryListener tiene los siguientes métodos:
6
El paquete javax.bluetooth
Los dos primeros métodos serán llamados durante el proceso de búsqueda de dispositi-
vos. Los otros dos métodos serán llamados durante un proceso de búsqueda de servicios.
El proceso de búsqueda de servicios se verá más adelante. Pasemos a investigar más
profundamente los dos primeros métodos que son los usados durante una búsqueda de
dispositivos.
Cada vez que se descubre un dispositivo se llama a este método. Nos pasa dos argumen-
tos. El primero es un objeto de la clase RemoteDevice que representa el dispositivo en-
contrado. El segundo argumento nos permitirá determinar el tipo de dispositivo encontra-
do.
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.bluetooth.*;
import java.io.IOException;
public class Ejemplo3 extends MIDlet implements
CommandListener, DiscoveryListener {
private Command comenzar, cancelar;
private List dispositivos;
private Form busqueda;
public void startApp() {
dispositivos = new List("Ejemplo descubrimiento"+
" de dispositivos", List.IMPLICIT);
comenzar = new Command("Buscar", Command.ITEM, 1);
cancelar = new Command("Cancelar", Command.ITEM, 1);
busqueda = new Form("Busqueda de dispositivos");
busqueda.append(new Gauge("Buscando dispositivos...",
false, Gauge.INDEFINITE,
Gauge.CONTINUOUS_RUNNING));
busqueda.addCommand(cancelar);
busqueda.setCommandListener(this);
dispositivos.addCommand(comenzar);
dispositivos.setCommandListener(this);
LocalDevice localDevice = null;
try {
localDevice = LocalDevice.getLocalDevice();
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
Display.getDisplay(this).setCurrent(dispositivos);
} catch(Exception e) {
e.printStackTrace();
Alert alert = new Alert("Error",
"No se puede hacer uso de Bluetooth",
7
El paquete javax.bluetooth
null, AlertType.ERROR);
Display.getDisplay(this).setCurrent(alert);
}
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
if (c == comenzar) {
comenzar();
} else if( c == cancelar) {
cancelar();
}
}
private void comenzar() {
dispositivos.deleteAll();
try {
DiscoveryAgent discoveryAgent =
LocalDevice.getLocalDevice().getDiscoveryAgent();
discoveryAgent.startInquiry(DiscoveryAgent.GIAC,
this);
Display.getDisplay(this).setCurrent(busqueda);
} catch(BluetoothStateException e) {
e.printStackTrace();
Alert alert = new Alert("Error",
"No se pudo comenzar la busqueda",
null, AlertType.ERROR);
Display.getDisplay(this).setCurrent(alert);
}
}
private void cancelar() {
try {
DiscoveryAgent discoveryAgent =
LocalDevice.getLocalDevice().getDiscoveryAgent();
discoveryAgent.cancelInquiry(this);
} catch(BluetoothStateException e) {
e.printStackTrace();
Alert alert = new Alert("Error",
"No se pudo cancelar la busqueda",
null, AlertType.ERROR);
Display.getDisplay(this).setCurrent(alert);
}
}
//metodos de la interfaz DiscoveryListener
public void deviceDiscovered(RemoteDevice remoteDevice,
DeviceClass deviceClass) {
String address = remoteDevice.getBluetoothAddress();
String friendlyName = null;
try {
friendlyName = remoteDevice.getFriendlyName(true);
} catch(IOException e) { }
String device = null;
if(friendlyName == null) {
device = address;
} else {
device = friendlyName + " ("+address+")";
}
dispositivos.append(device, null);
}
8
El paquete javax.bluetooth
Es posible que queramos hacer diversas búsquedas de servicios al mismo tiempo. El mé-
todo searchServices() nos devolverá un entero que identificará la búsqueda. Este va-
lor entero nos servirá para saber a qué búsqueda pertenecen los eventos servicesDis-
covered() y serviceSearchCompleted(). Ahora mismo comenzamos a estudiar es-
tos eventos.
9
El paquete javax.bluetooth
guientes valores:
Este método nos notifica que se han encontrado servicios. El primer argumento es un en-
tero que es el que identifica el proceso de búsqueda. Este entero es el mismo que devol-
vió searchDevices() cuando se comenzó la búsqueda.
Los identificadores son números enteros y los valores son objetos de tipo DataElement.
Los objetos DataElement encapsulan los posibles tipos de datos mediante los cuales se
pueden describir los servicios bluetooth. Estos tipos de datos son: valor nulo, enteros de
diferente longitud, arrays de bytes, URLs, UUIDs, booleanos, Strings o enumeraciones
(java.util.Enumeration) de los tipos anteriores.
Para entender mejor para qué sirven los atributos de servicio nada mejor que un ejemplo.
Supongamos que queremos crear un servicio de impresión, es decir, supongamos que de-
seamos hacer una aplicación cliente-servidor en la cual el cliente envía información al ser-
vidor para imprimirla. En este caso podríamos indicar en los atributos de servicio qué tipos
de impresora hay disponibles en el servicio de impresión (impresora láser blanco y negro
tamaño papel din A-4, impresora de tinta color tamaño del papel din A-4,...). El cliente rea-
lizará una búsqueda de los dispositivos que ofrezcan un servicio de impresión y los atribu-
tos le permitirán elegir el mejor servicio de impresión encontrado para imprimir la informa-
ción seleccionada por el usuario. Por ejemplo para imprimir una imagen buscaremos un
servicio de impresión que disponga de impresora a color, sin embargo para imprimir un
texto nos será indiferente si las impresoras disponibles son a color o en blanco y negro.
Ahora que hemos entendido la utilidad de los atributos de servicio veamos cómo podemos
acceder a ellos a través de un objeto ServiceRecord. A través del método getAttri-
buteValue() de un objeto ServiceRecord podemos obtener el valor de un atributo de
servicio pasándole su identificador como argumento. También podemos obtener un array
de todos los identificadores de atributo de servicios disponibles mediante el método ge-
tAttributeIDs().
...
10
El paquete javax.bluetooth
La clase DataElement
Como hemos visto la clase DataElement se encarga de encapsular los tipos de datos
disponibles para describir un atributo de servicio. Estos tipos de datos son: valor nulo, en-
teros de diferente longitud, arrays de bytes, URLs, UUIDs, booleanos, Strings o enume-
raciones (java.util.Enumeration) de los tipos anteriores.
Para saber qué tipo de dato está almacenando un DataElement usaremos el método
getDataType(). Según el tipo de dato que esté almacenando usaremos un método dife-
rente para obtenerlo. Si está almacenano un valor booleano usaremos el método get-
Boolean(), si se trata de un entero usaremos el método getLong() y en otro caso usa-
remos el método getValue() que devolverá un java.lang.String si está almacenan-
do una URL o un String, o bien devolverá un javax.bluetooth.UUID si se trata de
un UUID, o bien un java.util.Enumeration. Para aclarar esto veamos la siguiente ta-
bla que relaciona el valor devuelto por getDataType(), el valor almacenado, y el método
que debemos usar para almacenar dicho valor:
Es sencillo: exceptuando los valores enteros y los tipos booleanos que necesitan un méto-
do propio (getLong() y getBoolean() respectivamente) porque son tipos primitivos y
no objetos, el resto de tipos de datos se obtienen mediante getValue() que devuelve un
java.lang.Object, de modo que deberemos hacer el "cast" apropiado según se ha in-
dicado en la tabla. Por ejemplo:
11
El paquete javax.bluetooth
La diferencia entre un dato de tipo DATSEQ y uno de tipo DATALT es unicamente su sig-
nificado. El primero representa una "secuencia", es decir, representa una colección de da-
tos secuenciales. Sin embargo el tipo DATALT representa una colección de objetos "alter-
nativos", es decir, una colección de alternativas entre las que el usuario debe elegir.
Veamos un ejemplo completo de una aplicación que accede a los atributos de un servicio.
Antes de probar este ejemplo se debe ejecutar el servidor Ejemplo5.java
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.bluetooth.*;
import java.io.IOException;
import java.util.*;
public class Ejemplo4 extends MIDlet implements
CommandListener, DiscoveryListener {
public static final UUID SERVICIO_MUSEO = new UUID(0x1234);
public static final int ATRIBUTO_LISTADO_OBRAS = 0x4321;
public static final UUID[] SERVICIOS =
new UUID[]{ SERVICIO_MUSEO };
public static final int[] ATRIBUTOS =
new int[]{ ATRIBUTO_LISTADO_OBRAS };
private Command comenzar, cancelar;
private List obras;
private Form busqueda;
private Vector busquedas;
private DiscoveryAgent discoveryAgent;
public void startApp() {
busquedas = new Vector();
obras = new List("Ejemplo descubrimiento de servicios",
List.IMPLICIT);
comenzar = new Command("Buscar", Command.ITEM, 1);
cancelar = new Command("Cancelar", Command.ITEM, 1);
busqueda = new Form("Busqueda de servicio");
busqueda.append(new Gauge("Buscando servicio...",
false, Gauge.INDEFINITE,
Gauge.CONTINUOUS_RUNNING));
busqueda.addCommand(cancelar);
busqueda.setCommandListener(this);
obras.addCommand(comenzar);
obras.setCommandListener(this);
LocalDevice localDevice = null;
try {
localDevice = LocalDevice.getLocalDevice();
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
discoveryAgent = localDevice.getDiscoveryAgent();
Display.getDisplay(this).setCurrent(obras);
} catch(Exception e) {
e.printStackTrace();
Alert alert = new Alert("Error",
"No se puede hacer uso de Bluetooth",
null, AlertType.ERROR);
Display.getDisplay(this).setCurrent(alert);
}
12
El paquete javax.bluetooth
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
if (c == comenzar) {
comenzar();
} else if( c == cancelar) {
cancelar();
}
}
private void comenzar() {
obras.deleteAll();
try {
discoveryAgent.startInquiry(
DiscoveryAgent.GIAC, this);
Display.getDisplay(this).setCurrent(busqueda);
} catch(BluetoothStateException e) {
e.printStackTrace();
Alert alert = new Alert("Error",
"No se pudo comenzar la busqueda",
null, AlertType.ERROR);
Display.getDisplay(this).setCurrent(alert);
}
}
private void cancelar() {
discoveryAgent.cancelInquiry(this);
Enumeration en = busquedas.elements();
Integer i;
while(en.hasMoreElements()) {
i = (Integer) en.nextElement();
discoveryAgent.cancelServiceSearch(i.intValue());
}
}
//metodos de la interfaz DiscoveryListener
public void deviceDiscovered(RemoteDevice remoteDevice,
DeviceClass deviceClass) {
String address = remoteDevice.getBluetoothAddress();
String friendlyName = null;
try {
friendlyName = remoteDevice.getFriendlyName(true);
} catch(IOException e) { }
13
El paquete javax.bluetooth
14
El paquete javax.bluetooth
Comunicación
El API javax.bluetooth permite usar dos mecanismos de conexión: SPP y L2CAP. Median-
te SPP obtendremos un InputStream y un OutputStream. Mediante L2CAP enviaremos y
recibiremos arrays de bytes.
Comunicación cliente
Obtendremos la URL necesaria para realizar la conexión a través del método getCon-
nectionURL() de un objeto ServiceRecord. Recordemos que un objeto ServiceRe-
cord representa un servicio, es decir una vez hayamos encontrado el servicio deseado
(un objeto ServiceRecord), él mismo nos proveerá la URL necesaria para conectarnos a
él.
Este método requiere dos argumentos, el primero de los cuales indica si se debe autenti-
car y/o cifrar la conexión. Los posibles valores de este primer argumento son:
ServiceRecord sr = ...;
String url = sr.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
o bien
15
El paquete javax.bluetooth
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.bluetooth.*;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
public class Ejemplo6 extends MIDlet implements
CommandListener, DiscoveryListener {
public static final UUID SERVICIO_CHAT = new UUID(0x2345);
public static final UUID[] SERVICIOS =
new UUID[]{ SERVICIO_CHAT };
public static final int[] ATRIBUTOS = null;
//no necesitamos ningun atributo de servicio
private Command comenzar, cancelar;
private TextBox texto;
private Form principal;
private Form busqueda;
private Vector busquedas;
private DiscoveryAgent discoveryAgent;
public void startApp() {
busquedas = new Vector();
principal = new Form("Ejemplo cliente SPP");
texto = new TextBox("El servidor te dice...",
"", 50, TextField.UNEDITABLE);
comenzar = new Command("Buscar", Command.ITEM, 1);
cancelar = new Command("Cancelar", Command.ITEM, 1);
busqueda = new Form("Busqueda de servicio");
busqueda.append(new Gauge("Buscando servicio...",
false, Gauge.INDEFINITE,
Gauge.CONTINUOUS_RUNNING));
busqueda.addCommand(cancelar);
busqueda.setCommandListener(this);
principal.addCommand(comenzar);
principal.setCommandListener(this);
LocalDevice localDevice = null;
try {
localDevice = LocalDevice.getLocalDevice();
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
discoveryAgent = localDevice.getDiscoveryAgent();
Display.getDisplay(this).setCurrent(principal);
} catch(Exception e) {
e.printStackTrace();
Alert alert = new Alert("Error",
"No se puede hacer uso de Bluetooth",
null, AlertType.ERROR);
Display.getDisplay(this).setCurrent(alert);
}
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
16
El paquete javax.bluetooth
17
El paquete javax.bluetooth
System.out.println("Busqueda "+
"de dispositivos concluida "+
"con normalidad");
break;
case DiscoveryListener.INQUIRY_TERMINATED:
System.out.println("Busqueda de"+
"dispositivos cancelada");
break;
case DiscoveryListener.INQUIRY_ERROR:
System.out.println("Busqueda de"+
"dispositivos finalizada debido a "+
"un error");
break;
}
}
public void servicesDiscovered(int transID,
ServiceRecord[] servRecord) {
ServiceRecord service = null;
for(int i=0; i<servRecord.length; i++){
service = servRecord[i];
String url =
service.getConnectionURL(
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
StreamConnection connection = null;
DataInputStream in = null;
DataOutputStream out = null;
try {
connection =
(StreamConnection) Connector.open(url);
in = connection.openDataInputStream();
out = connection.openDataOutputStream();
out.writeUTF("saludos desde el cliente!");
out.flush();
String s = in.readUTF();
texto.setString(s);
Display.getDisplay(this).setCurrent(texto);
cancelar(); //cancelamos las busquedas
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(in != null)
in.close();
if(out != null)
out.close();
if(connection != null)
connection.close();
} catch(IOException e) {}
}
}
}
public void serviceSearchCompleted(int transID, int respCode) {
System.out.println("Busqueda de servicios "+
transID+ " completada");
switch(respCode) {
case DiscoveryListener.SERVICE_SEARCH_COMPLETED:
System.out.println("Busqueda completada "+
"con normalidad");
break;
case DiscoveryListener.SERVICE_SEARCH_TERMINATED:
18
El paquete javax.bluetooth
System.out.println("Busqueda cancelada");
break;
case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHAB
System.out.println("Dispositivo no alcanzable");
break;
case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS:
System.out.println("No se encontraron registros"+
" de servicio");
break;
case DiscoveryListener.SERVICE_SEARCH_ERROR:
System.out.println("Error en la busqueda");
break;
}
}
}
En una conexión L2CAP el tamaño de los arrays de bytes leídos y los recibidos están limi-
tados a un tamaño máximo. Los tamaños límite se establecen mediante parámetros en la
URL. Para saber el tamaño máximo de los arrays recibidos usaremos getRecieveMTU()
y para saber el tamaño máximo de los arrays que se envían usaremos getTransmitM-
TU()
Para evitar pérdida de información el array de bytes pasado al método receive() deberá
ser igual o mayor que getRecieveMTU().
Para crear una conexión servidora necesitaremos pasarle una URL al método Connec-
tor.open() del mismo modo como hemos hecho para realizar conexiones clientes. La
diferencia está en que deberemos crearla nosotros y para indicar que queremos ser servi-
dores indicaremos "localhost" como host en la URL. De este modo la URL deberá comen-
zar por "btspp://localhost:" o por "btl2cap://localhost:".
Además del host de la URL deberemos indicar el UUID que identifica el servicio. Posterior-
mente indicaremos el nombre del servicio y otros parámetros, como por ejemplo el reque-
rimiento o no de autenticación.
Al pasar una URL de este tipo al método Connector.open(), éste nos devolverá un "no-
19
El paquete javax.bluetooth
Como se puede observar en los parámetros de la URL hemos especificado los tamaños
máximos de los arrays de envío y recepción.
Una vez hemos creado el "notifier" que escuchará las conexiones clientes debemos espe-
cificar los atributos de nuestro servicio. Los atributos de nuestro servicio están almacena-
dos en un ServiceRecord. El ServiceRecord se obtiene a través de la clase Local-
Device con el método getRecord(). Una vez tenemos el ServiceRecord podremos
establecer sus atributos mediante el método setAttributeValue() al que le pasare-
mos un identificador numérico y un objeto DataElement que representará el valor del
atributo de servicio.
Anteriormente hemos visto cómo utilizar objetos DataElement, pero no hemos visto có-
mo crearlos. Los constructores de la clase DataElmenet son cuatro.
Para crear un DataElement que almacene un valor entero le pasaremos el tipo de valor
entero como primer argumento, y su valor como segundo argumento:
Para crear un DataElement que almacene una URL o un String usaremos el siguiente
constructor:
DataElement element1 =
new DataElement(DataElement.STRING, "Hola, esto es un String");
DataElement element2 =
new DataElement(DataElement.URL, "http://esto.es.una.url");
20
El paquete javax.bluetooth
Como vemos, para insertar elementos en una colección usaremos el método insertE-
lementAt(). Para eliminar un elemento usaremos el método removeElement() al que
le pasaremos el objeto que queremos eliminar y que devolverá true o false según la elimi-
nación se ha llevado a cabo con éxito o no respectivamente. Para obtener el tamaño de
una colección (DATSEQ o DATALT) usaremos el método getSize().
Para crear un objeto DataElement que represente un valor nulo haremos lo siguiente:
Veamos un ejemplo de un servidor que establece atributos de servicio. Este ejemplo tra-
baja con el cliente Ejemplo4.java visto anteriormente.
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.bluetooth.*;
import javax.microedition.io.*;
import java.io.IOException;
public class Ejemplo5 extends MIDlet implements
CommandListener, Runnable {
public static final UUID SERVICIO_MUSEO = new UUID(0x1234);
public static final int ATRIBUTO_LISTADO_OBRAS = 0x4321;
private Command comenzar;
private Form principal;
private Thread thread;
public void startApp() {
thread = new Thread(this);
comenzar = new Command("Comenzar", Command.ITEM, 1);
principal = new Form("Ejemplo servidor Bluetooth");
principal.addCommand(comenzar);
principal.setCommandListener(this);
Display.getDisplay(this).setCurrent(principal);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
if (c == comenzar) {
thread.start();
}
}
public void run() {
try {
LocalDevice localDevice =
21
El paquete javax.bluetooth
LocalDevice.getLocalDevice();
if(!localDevice.setDiscoverable(
DiscoveryAgent.GIAC)) {
Display.getDisplay(this).setCurrent(
new Alert("Imposible"+
" ofrecer un servicio"));
}
StringBuffer url =
new StringBuffer("btspp://localhost:");
url.append(SERVICIO_MUSEO.toString());
url.append(";name=Servicio museo;authorize=true");
StreamConnectionNotifier notifier =
(StreamConnectionNotifier)
Connector.open(url.toString());
ServiceRecord record =
localDevice.getRecord(notifier);
DataElement listado =
new DataElement(DataElement.DATSEQ);
listado.insertElementAt(
new DataElement(DataElement.STRING,
"Las hilanderas"), 0);
listado.insertElementAt(
new DataElement(DataElement.STRING,
"Las meninas"), 1);
listado.insertElementAt(
new DataElement(DataElement.STRING,
"La rendicion de Breda"), 2);
listado.insertElementAt(
new DataElement(DataElement.STRING,
"Gernica"), 3);
record.setAttributeValue(
ATRIBUTO_LISTADO_OBRAS, listado);
principal.append("Servidor en marcha...");
while(true) {
StreamConnection con =
notifier.acceptAndOpen();
}
} catch(IOException e) {
e.printStackTrace();
Display.getDisplay(this).setCurrent(
new Alert("Error: "+e));
}
}
}
22
El paquete javax.bluetooth
Para terminar: un ejemplo de comunicación por parte del servidor. Este ejemplo trabaja
con el cliente Ejemplo6.java
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.bluetooth.*;
import javax.microedition.io.*;
import java.io.*;
public class Ejemplo7 extends MIDlet implements
CommandListener, Runnable {
public static final UUID SERVICIO_CHAT = new UUID(0x2345);
private Command comenzar;
private Form principal;
private Thread thread;
public void startApp() {
thread = new Thread(this);
comenzar = new Command("Comenzar", Command.ITEM, 1);
principal = new Form("Ejemplo servidor Bluetooth SPP");
principal.addCommand(comenzar);
principal.setCommandListener(this);
Display.getDisplay(this).setCurrent(principal);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
if (c == comenzar) {
thread.start();
}
}
public void run() {
try {
LocalDevice localDevice =
LocalDevice.getLocalDevice();
if(!localDevice.setDiscoverable(
DiscoveryAgent.GIAC)) {
Display.getDisplay(this).setCurrent(
new Alert("Imposible ofrecer"+
" un servicio"));
}
StringBuffer url =
new StringBuffer("btspp://localhost:");
url.append(SERVICIO_CHAT.toString());
url.append(";name=Servicio chat;authorize=true");
StreamConnectionNotifier notifier =
(StreamConnectionNotifier)
Connector.open(url.toString());
principal.append("Servidor en marcha...");
StreamConnection connection = null;
DataInputStream in = null;
23
El paquete javax.bluetooth
24
Capítulo 3. El paquete javax.obex
Este paquete es totalmente independiente del paquete javax.bluetooth, es decir, cuando
hagamos una aplicación utilizando el protocolo OBEX no usaremos ninguna de las clases
del paquete javax.bluetooth.
El servidor por su parte recibirá los comandos del cliente y responderá con un código de
respuesta indicando el éxito o no de la petición. Enviará además una serie de cabeceras
de mensaje con información adicional y un cuerpo de mensaje en caso de tratarse de una
respuesta al comando GET.
Clases básicas
La clase HeaderSet
La clase HeaderSet representa las cabeceras de mensaje tanto de los mensajes envia-
dos por un cliente como los enviados por un servidor.
Las cabeceras de mensaje se guardan como pares clave-valor en los que la clave es un
número entero; es decir, las cabeceras de mensaje se identifican numéricamente. Estos
identificadores numéricos serán utilizados en los métodos setHeader() y getHeader()
para establecer y obtener respectivamente una cabecera de mensaje. También podemos
obtener un array con todos los identificadores de las cabeceras que guarda un objeto
HeaderSet mediante el método getHeaderList().
25
El paquete javax.obex
También se pueden usar identificadores propios para establecer cabeceras definidas por
el usuario. Sin embargo, habrá que tener en cuenta que no se podrá utilizar cualquier valor
para identificarlas. Se deberán utilizar valores dentro de un rango específico según el tipo
de dato que deberá almacenar la cabecera de mensaje. Estos rengos de valores están re-
flejados en la siguiente tabla:
Los mensajes enviados por el servidor tienen adicionalmente un código de respuesta que
indica si tuvo éxito la petición o en caso de no tenerlo cuál fue el motivo. Este código es al-
macenado también en el objeto HeaderSet. El código de respuesta es un número entero
que se obtiene a través del método getResponseCode(); los posibles valores que pue-
de tomar están reflejados como variables estáticas de la clase ResponseCodes
La clase Operation
Comom se verá más adelante en los comandos GET y PUT necesitaremos enviar o reci-
bir, además de las cabeceras, el cuerpo del mensaje. Pues bien, un objeto Operation
encapsula las cabeceras y el cuerpo del mensaje. Las cabeceras de mensaje se guardan
en un objeto HeaderSet que se obtiene mediante getRecivedHeaders(). Para enviar
datos usaremos OutputStream que obtendremos con openOutputStream() o bien
usaremos un DataOutputStream que obtendremos con openDataOutputStream. En
caso de que estemos recibiendo datos usaremos un InputStream que obtendremos con
openInputStream() o bien usaremos un DataInputStream que obtendremos con
openDataInputStream.
26
El paquete javax.obex
Conexión cliente
Una conexión cliente OBEX viene encapsulada en forma de un objeto ClientSession.
Para obtener un objeto ClientSession usaremos el método Connector.open(). A
este método le pasaremos una URL del tipo "irdao-
bex://discover.0210;ias=NombreServicio" Una vez obtenido el objeto ClientSession lla-
maremos al método connect(). Veamos un ejemplo:
A partir de ahora podemos efectuar las operaciones DELETE, PUT, GET, y SETPATH a
través de los métodos delete(), put(), get() y setPath() respectivamente. Todos
estos métodos requieren un parámetro de tipo HeaderSet.
Los métodos get() y put() devuelven un objeto de tipo Operation, el cual encapsula
un mensaje completo: código de respuesta, cabeceras de mensaje y cuerpo del mensaje.
Cuando ejecutemos una llamada al método get() nos interesará leer el cuerpo del men-
saje usando openInputStream() u openDataInputStream(); mientras que cuando
ejecutemos una llamada al método put() nos interesará escribir en el cuerpo del mensa-
je usando openOutpuStream() u openDataOutputStream().
Ahora veamos un ejemplo de lectura de datos a través del método get(). Supongamos
que queremos leer un archivo que se llama "imagen.jpg".
27
El paquete javax.obex
DataInputStream in = operation.openDataInputStream();
in.read(data);
in.close();
int code = operation.getResponseCode();
if( code != ResponseCodes.OBEX_HTTP_OK)
System.err.println("La operacion no concluyo con exito");
Como se ha hecho en el ejemplo, para especificar el recurso que se desea obtener o eli-
minar estableceremos la cabecera HeaderSet.NAME indicando el nombre del recurso.
Conexión servidor
Una conexión servidora viene encapsulada en un objeto SessionNotifier. Para obte-
nerlo utilizaremos también el método Connection.open() pasándole una URL del tipo
"irdaobex://localhost.0010;ias=NombreServicio". A través del objeto SessionNotifier
podremos escuchar las conexiones cliente mediante acceptAndOpen(). El método ac-
ceptAndOpen() requiere que se le pase un parámetro de tipo
ServerRequestHandler. Este objeto nos servirá como "listener". Cada vez que un
cliente nos envíe un comando CONNECT, GET, PUT, DELETE o DISCONECT se llamará
a los métodos onConnect(), onGet(), onPut(), onDelete() u onDisconnect()
respectivamente. En su semántica son métodos muy similares a los métodos doGet() y
doPost() de un servlet. A excepción de los métodos onGet() y onPut() el resto de es-
tos métodos tienen dos argumentos de tipo HeaderSet. El primero representa las cabece-
ras que envió el cliente y el segundo representa las cabeceras que serán enviadas al
cliente.
Los métodos onGet() y onPut() son especiales ya que requieren un cuerpo de mensa-
je. Por ello a estos métodos se les pasa un único argumento de tipo Operation. Como se
ha descrito anteriormente, este objeto encapsula las cabeceras de mensaje y el cuerpo del
mensaje.
28
Capítulo 4. Implementaciones de APIs
Bluetooth para Java
Implementaciones del JSR-82
• El Java™ Wireless Toolkit 2.2 Beta
[http://java.sun.com/products/j2mewtoolkit/download-2_2.html] se trata de un entorno
de desarrollo de aplicaciones J2ME. Este entorno implementa una gran cantidad de
JSRs, entre ellos el JSR-82. La implementación del JSR-82 del Java™ Wireless Toolkit
es virtual, es decir, no usa hardware bluetooth, sino que lo emula. Para simular un en-
torno de dispositivos Bluetooth simplemente tendremos que ejecutar nuevas instancias
del simulador de móviles, y cada instancia se podrá comunicar con las demás a través
del API Bluetooth como si de un entorno real se tratara.
El Java™ Wireless Toolkit 2.2 Beta es gratuito pero sólo está disponible por el momen-
to para plataformas Windows.
Está disponible para Windows y Linux. Para Linux es gratuito para uso no comercial y
universitario.
Está en desarrollo y pretende estar disponible para Windows, Mac OS X, y Linux. Por
el momento sólo funciona en Linux.
APIs no-JSR-82
Además del API javax.bluetooth existen otras APIs para la programación de aplicaciones
29
Implementaciones de APIs Bluetooth pa-
ra Java
Java™ que hagan uso de Bluetooth. A pesar de no tratarlas en este tutorial he creído inte-
resante al menos mencionarlas.
30
Capítulo 5. Documentación de interés
A continuación se lista una serie de enlaces relacionados con Java™ y Bluetooth.
31