Bluetooth y J2ME

Bluetooth

j2ME



1.-¿Qué es Bluetooth?


1.1.-Introducción


             Es un protocolo de comunicación inálambrica  de bajo costo, robusto y con un bajo nivel de consumo de energía. Bluetooth es capaz de comunicarse de forma omnidireccional  hasta 10 metros a 1 Mb/s (
para la clase más común) esto representa una ventaja con respecto a la comunicación infrarroja. La comunicación infrarroja requiere de unos pocos centimetros de distancia y de linea directa entre los dispositivos para transmitir. En el caso de WiFi,  puede tipicamente transmitir hasta una distancia de 100 m y con una tasa de transmisión de hasta 54Mbps.

             ¿WiFi o Bluetooth?. El hecho es que las comparaciones no corresponden, dado que Bluetooth fue desarrollado para transferencias de datos pequeñas y/o comunicación de voz. Lo que lo hace un excelente candidato para dispositivos perifericos tales como microfonos inalambricos, audifonos, mouse, teclados y manos libres para celulares. En cambio, WiFi fue desarrollado para transmitir gran cantidad de datos y para servir como una extensión de redes existentes como LAN.

              Bluetooth no sólo  permite dejar de lado conexiones con cables como las seriales, paralelas, USB y Fire; tambien especifica un estandar único que permite que una diversidad de dispositivos puedan comunicarse entre si. Existen cientos sino miles de maneras en que Bluetooth puede mejorar nuestras vidas. Tanto para soluciones en el area de entretenimiento, por ejemplo, juegos multiplayer cara a cara, como para soluciones de negocios. Aqui hay un par de ideas:

  • Permite facilmente intercambiar informacion con otros, por ejemplo entre celulares.

  • Intercambio de datos concurrente, esto es de utilidad cuando existe un grupo de personas que se encuentran en una reunion o conferencia

  • Acceder a dispositivos tales como impresoras  y maquinas de fax.

  • Monitoreo de sistemas, por ejemplo para un personal de mantenimiento haciendo una rutina de chequeo en una fabrica, te permite facilmente verificar en cada punto de chequeo

  • Manejador de Perfiles- expliquemoslo con un ejemplo, digamos que estas usando una consola de un amigo que permite Bluetooth, tu podrias subir tus juegos guardados y descargar los avances de los juegos. Otro ejemplo, cuando vas a una farmacia y entregas tu receta una vez estuviese lista, podrías ser notificado en tu telefono permitiendote seguir con compras sin gastar tiempo esperando en la tienda.

  • Proveer entretenimiento durante periodos de espera, por ejemplo mientras esperas para comprar el ticket para la pelicula podrías jugar un juego de trivia de peliculas, revisar comentarios de peliculas que te ayuden a decidir que pelicula verás, y la posibilidad de ver o optar por descuentos y oportunidad de ganar premios.
  •  

1.2-Datos Técnicos sobre Bluetooth
 

  • Manejo de la interferencia,  bluetooth tiene un sistema adaptivo que permite sondear los canales de frecuencia que esten siendo ocupados en el entorno, para trasladar su operación a uno que se encuentre libre, a fin de entregar una comunicación entre dispositivos más eficiente.
  • 3 clases diferentes (Clase 1 100 metros, Clase 2 10 metros y Clase 3 1 metro)
  • La Clase 2 que es la más común usa 2.5mW de energía, además el sistema se apaga cuando se encuentra inactivo
  • 1 MBps para la version 1.2 , 3 MBps para la version 2.0
  • Capaz de transferir voz
  • Señal de radio omni-direccional
  • 2.4 Ghz-2.482 Ghz
  • La pila del protocolo Bluetooth (bluetooth stack) permite controlar el dispositivo Bluetooh por software, a través del paquete opcional JSR 82 de J2ME.
  • Perfiles Bluetooth - funcionalidades definidas como por ejemplo los perfiles de fax que permiten a un dispositivo bluetooth envia fax a traves de una maquina fax bluetooth. Estos perfiles podrian parecer similares a los perfiles J2ME pero no lo son. Los perfiles bluetooth pueden ser implementados en otros lenguajes como  C/C++.
  • La red entre dispositivos bluetooth habilitados se llama PAN, Personal Area Networks. Una PAN puede ser una piconet o scaternet, se tiene una piconet cuando existe un maestro y uno o mas esclavos. Una escaternet consiste de 2 o más maestros y varios esclavos, en otras palabras uno de los dispositivos bluetooth es tanto un maestro como un esclavo:



1.3-Datos Estadisticos

    Mercado Potencial:

En Noviembre 14 del 2006, se compro el dispositivo Bluetooth número 1 billon.

1 billon es más grande que el número de usuarios de PC en el munod
1 billon es igual al numero de celulares vendidos el 2006
1 billon de segundos atras, era 1974
1 billon de horas atras, nuestros ancestros vivian en la Edad de Piedra


2.-¿Que es J2ME (Java MicroEdition)?

          Java 2 Micro Edition (J2ME) combina una Maquina Virtual de Java (JVM) restringida y un conjunto de APIs de Java que permiten desarrollar aplicaciones para tecnología mobíl. En este documento se pretende explicar como crear aplicaciones J2ME, también conocidas como MIDlets, esto mediante un ejemplo sencillo. Además se cubrirá como probar y desplegar este MIDlets. Finalmente, se revisará el ciclo de vida de un MIDlet.

2.1- Introducción

          "Cortando y quitando el exceso de grasa con lo que quedas es con otro conjunto de APIs de JAVA". Dado que estas APIs no pueden correr en una JVM tradicional, debido al tamaño limitado de los dspositivos mobiles en lo que respecta a la memoria y la disponibilidad de recursos, para este proposito J2ME define una version limitada de la Máquina virtual (JVM).

          ¿Se debe instalar la JVM y las APIs en los dispositivos Mobiles?No. Los fabricadores de los dispositivos instalan y pre empaquetan en sus dispositivos a la JVM (y las APIs).

           J2ME puede ser dividido en 3 partes (como se muestra en la figura): configuración, perfil y paquetes opcionales. La configuración contiene a la JVM (no la JVM tradicional, sino la version reducida) y algunas librerias de clases; el perfil construido sobre las librerias base otorgando un conjunto de APIs; por último, los paquetes opcionales que son APIs  que se pueden usar o no para crear aplicaciones. Los paquetes opcionales tradicionalmente no son empaquetadas por el fabricador de los dispositivos, por lo tanto se tiene que que empaquetar y distribuirlos con la aplicación . La configuración y el pefil son suplidos por el manufacturador y son embebidos en el dispositivo.

Figure 1


            El perfil  y configuración más popular que Sun provee es el perfil de dispositivos de información mobil (MIDP, Mobile Information Device Profiles) y la configuración de dispositivos limitados en conexion (CLDC, Connected Limited Device Configuration) , respectivamente. CLDC es para dispositivos  que tengan sólo 128 a 1MB de memoria disponible para aplicaciones Java. En ese sentido, la JVM que provee es muy limitada y soporta un número pequeño de las clases de Java tradicionales(lang, io y util). (Esta JVM se llama KVM, k-kilobytes). Su contropartida, la configuracion de dispositivos conectados (CDC) es para dispositivos con al menos 2MB de memoria disponible y soporta un JVM con más clases(pero todavia no es la JVM estandar).


            El perfil MIDP complementa al CLDC muy bien porque minimiza tanto el uso de la memoria como el de la energía requerida por los dispositivos. Provee la API básica que es usada para crear aplicaciones en estos dispositivos. Por ejejmplo, provee javax.microedition.lcdui que permite crear elementos de la GUI que pueden ser mostrados en un dispositvo (limitado) corriendo el perfil MIDP sobre una configuración CLDC. Notar que la MIDP no puede ser usada con dispositivos CDC. Los dispositivos CDC obtienen su propio conjunto de perfiles, como el perfil personal y fundación.


            Las últimas versiones de MIDP y CLDC son 2.0 y 1.1, respectivamente.

2.2 .-Creación de MIDlet


             Son 7 los pasos involucrados en la creación de MIDlet. Estos pasos son: diseño, codigo, compilar, preverificar, empaquetar, probar y desplegar. Alguno de estos pasos no son obligatorios (pero cada aplicacion necesita ser diseñada, codificada y compilada). WTK permite abstraerse de alguno de estos pasos para que sea más sencillo su manejo. 

             Por ejemplo, se creara un MIDlet que, cuando se ejecute, imprima la fecha y hora actual en un dispositivo móbil por un periodo de tiempo.

Figure 2



2.2.1.- Paso 1: Diseño

             Los MIDlets son diferentes de otras aplicaciones que se podrían haber creado hasta ahora, dado que los MIDlet corren en un ambiente muy diferente a los usuales. Por ejemplo, la interactividad de su MIDlet con el usuario.

             Para el ejemplo, el MIDlet no requiere de interactividad con el usuario. Necesita mostrar la hora y fecha actual por unos pocos segundos desde cuando el usuario ejecuta el MIDlet. Para casos simples como éste, es suficiente para esta etapa especificar en un papel el diseño del MIDlet. Para diseños más complejos con multiples pantallas, sería mejor diseñar las pantallas profesionalmente antes de comenzar a crear el código para el proceso.

2.2.2.- Paso 2: Código

              Cada MIDlet debe extender la clase abstracta MIDlet encontrada en javax.microedition.midlet, parecido a como se crea un applet extendiendo la clase java.applet.Applet. Como minimo, el MIDlet debe sobreescribir 3 metodos de la clase abstracta, starApp(), pauseApp() y destroyApp(boolean unconditional).

Aqui esta la clase DateTimeApp:


package com.j2me.part1;

import java.util.Date;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class DateTimeApp extends MIDlet {

Alert timeAlert;

public DateTimeApp() {
timeAlert = new Alert("Alert!");
timeAlert.setString(new Date().toString());
}

public void startApp() {
Display.getDisplay(this).setCurrent(timeAlert);
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

             En este ejemplo,  el constructor de DateTimeApp crea los elementos necesarios para mostrar el tiempo en la pantalla del dispositivo y el método starApp efectua la tarea de mostrar el elementos.

2.2.3.- Paso 3: Compilar

             Ahora se necesita compilar el código para que este listo para incluirlo en el dispositivo mobil. Compilar el MIDlet no es muy diferente de como se compilan comunmente las aplicaciones Java. Todavia se usa javac como compilador, pero se necesita cambiar el CLASSPATH mientras se compila el MIDlet. Esto tiene como efecto cambiar la base de las clases de Java que el compilador usa en el MIDlet,  asegurandose que la compilacion se efectue con las APIs de la plataforma J2ME. En vez de compilar en java.lang.Date en el Java "normal", se quiere que la compilación sea con java.lang.Date de J2ME. Esto es hecho apuntando al CLDC y las clases MIDP con la opcion de javac -bootclasspath mientras se compila. Por ejemplo:

C:\WTK22\article>javac -bootclasspath ..\lib\cldcapi11.jar;..\lib\midpapi20.jar com\j2me\part1\DateTimeApp.java

2.2.4.- Paso 4: Preverificado

             Antes de desplegar la clase MIDlet, se necesita preverificar. La verificación del byte code se realiza por la JVM antes de correr cualquier archivo clase con el fin de asegurarse que el archivo esta estructural y conceptualmente correcto tal como corresponde con la especificación de la JVM. Si este proceso falla, se rechaza y la JVM se apaga, indicando ya sea violacion de seguridad o integridad del archivo clase. Esta verificacion es hecha por todas las JVM, incluso por la pequeña JVM contenida en la configuración CLDC de un dispositivo J2ME. Esto no es un problema para las aplicaciones normales, la verificacion en dispositivos J2ME por el grado de memoria y recursos utilizados resulta prohibitivo. Por esto, se necesita la preverificacion.

             La preverificacion es una proceso de dos pasos, especialmente diseñado para dispositivos restringidos, tales como los que corren JVM basados en CLDC. La idea es dejar  al desarrollador preverificar sus clases,  lo que limita la cantidad de trabajo necesitada a ser realizada cuando las clases son verificadas en el dispositivo. Esta preverificacion agrega informacion especial a las clases que las identifica como preverificadas y hace el proceso en el dispositivo mucho más eficiente.

Para el ejemplo:

C:\WTK22\article>..\bin\preverify.exe -classpath ..\lib\cldcapi11.jar;..\lib\midpapi20.jar com.j2me.part1.DateTimeApp

2.2.5.- Paso 5: Paquete

               Empaquetar el MIDlet tal que este listo para ser probado  y desplegado es un proceso que involucra unos cuantos pasos. Los cuales se deben seguir en una secuencia apropiada.

              El primer paso es crear un archivo Manifest.Este archivo Manifest describe el contenido del Archivo Java (JAR) que será creado en el siguiente paso. Existen varios atributos que pueden ir en este archivo, para el ejemplo propuesto es uno sencillo como el que se muestra a continuación:

MIDlet-Name: DateTimeApp
MIDlet-Version: 1.0.0
MIDlet-Vendor: Vikram Goyal

Guarda este archivo como Manifest.mf .

Para crear el JAR:

C:\WTK22\article\output>jar cvfm DateTimeApp.jar Manifest.mf .\com

           El ultimo paso es crear un archivo que tiene extension .jad. Un descriptor de aplicación Java (JAD) apunta a la ubicación del MIDlet con el proposito de que un dispositivo J2ME pueda instalarlo. Este archivo puede contener varios atributos para un sólo MIDlet (o para varios MIDlets), para el ejemplo:
MIDlet-1: DateTimeApp, , com.j2me.part1.DateTimeApp
MIDlet-Name: DateTimeApp
MIDlet-Version: 1.0.0
MIDlet-Vendor: Vikram Goyal
MIDlet-Jar-URL: DateTimeApp.jar
MIDlet-Jar-Size:
MicroEdition-Profile: MIDP-2.0
MicroEdition-Configuration: CLDC-1.1

Por último se debe especificar el tamaño del DateTimeApp.jar:

MIDlet-Jar-Size: 1469

2.2.6.- Paso 6: Prueba

          Antes de desplegar el MIDlet, debe ser probado usando un emulador de dispositivo. Este emulador es parte del WTK y provee funcionalidades que seguro estarán presentes en la mayor parte de los dispositivos a los cuales los MIDlet seran cargados.

Se invoca el emulador junto al proyecto como:

C:\WTK22\article\output>..\..\bin\emulator.exe -Xdescriptor DateTimeApp.jad


Figure 3

Nota que el MIDlet esta todavia corriendo aún cuando la fecha y el tiempo desaparecieron, porque en el codigo, en ninguna parte este fue destruido.

2.2.7.- Paso 7: Desplegar
Por ejemplo a través de USB.

2.3.- Usando el Toolkit

Una vez instalado WTK 2.5 con soporte CLDC 1.1 y MIDP 2.0 descargado de la página de Sun. Se observa el contenido del directorio donde fue instalado en la  siguiente figura.

Figure 4

Las carpetas más importantes son apps y bin.

Nombre del directorio
Descripción del directorio
appdb Directorio que actúa como una simulación del sistema de archivo del dispositivo móbil
apps Los MIDlets creados usando el Toolkit residen en este directorio
bin Contiene ejecutables para varias herramientas, incluyendo el toolkit, y algunos emuladores.
docs Documentacion del WTK y de las APIs incluidas
lib Contiene los archivos jar para  MIDP (both 2.0 and 1.1), CLDC (1.1 y 1.0) y algunas otras librerias opcionales
sessions Directorio donde la sesion de perfiles y redes son matenidos
wtklib Contiene las librerias del Toolkit, incluyendo las propiedades de varios emuladores de dispositivos

           El directorio apps es donde todos los MIDlet creados por el Toolkit son instalados. Navegando este directorio se observan varios ejemplos. Los proyectos tienen sus propia estructura que permite una separación limpia del codigo fuente, librerias y el resto de los archivos asociados con un projecto MIDlet.

           El directorio bin contiene los ejecutables del Toolkit. El más importante es ktoolbar.exe(en windows) el cual abre la interfaz principal para el Toolkit. Este directorio contiene tambien otros ejecutables, como preverify.exe y emulator.exe.

Figure 5


          Se puede crear un nuevo proyecto o abrir uno existente. En open project se listan los proyectos. Estos proyectos corresponden a lo que estan en la carpeta apps. Seleccionando un proyecto de esta lista lo abrira y permitira cambiar la configuracion de éste, compilar (proceso que incluye compilacion, preverificacion y empaquetado) y correr.  Los pasos de diseño y codificación no son soportador por el Toolkit.

Para crear el MIDlet Date-Time del ejemplo propuesto. Presionando en el boton New Project se ingresan datos como se muestra en la figura.

Figure 6

            La proxima ventana permite cambiar las configuraciones que controlan la plataforma objetivo del MIDlet. En el caso del ejemplo, MIDP 2.0 y CLDC 1.1, para lo cual se debe escoger JTWI.

Figure 7

           El proyecto esta listo para ser creado. Verifique que el Toolkit ha creado la carpeta DateTimeApp bajo el directorio apps.

Figure 8

            Guardar el archivo DateTimeApp.java al directorio src de la aplicación. Luego se deben efectuar los pasos ya descrito: build y run, del resto se encarga el Toolkit.

             Una vez efectuas build, si observas en la carpeta bin contendra el archivo JAD y el archivo manifest, mientras las clases estaran en la carpeta con ese nombre. Para crear el archivo jar, necesitarás seleccionar el menú project->Package->Create Package, luego de esto se creará el archivo JAR.

Figure 9


            Esto creara el archivo JAR, corrigendo la información necesaria. 


2.4.- Ciclo de Vida del MIDlet

             Los dispositivos mobiles, sea emulador o real, interactuan con un MIDlet usando su propio software, el cual es llamado software de administración de aplicación (AMS). La AMS es responzable por inicializar, comenzar, pausar, resumir y destruir un MIDlet. (Ademas de estos servicios, el AMS puede ser responsable de instalar y remover un MIDlet). Para facilitar esta administración, un MIDlet puede verse como un arbol de estados que es controlado a traves de los metodos de la clase MIDlet, y todo MIDlet que la extiende y sobreescribe. Estos estados, son activo, pausa y destruye.

Figure 10


            Un MIDlet instalado es puesto en pausa por el AMS creando una instancia de él, llamando a un constructor sin argumentos. Este  no es el unico medio en que puede ser pausado. Puede entrar en este estado cuando el AMS invoca el metodo pauseApp() sobre un MIDlet activo. El MIDlet puede provocar el ingreso en este estado invocando el método notifyPaused(), en contrario a pauseApp() que es llamado por el AMS.

             En un estado de pausa, el MIDlet esta esperando por  entrar en estado activo. Teoricamente, en este estado, no se deberia estar usando ninguno de los recursos del dispositivo. Una vez el MIDlet es creado, este es el estado antes de pasar a activo. Tambien se entra al estado pausa  cuando el dispositivo requiere consumir pocos recursos, porque estos recursos podrian ser requeridos para mantener otras funciones del dispositivo, como manejar una llamada entrante. En estos casos es cuando los dispositivos invocan al metodo pauseApp() a través del AMS.

             Otra forma en que el MIDlet puede entrar en estado pausado es con el método startApp() del MIDlet, el cual es llamado cuando el AMS invoca a iniciar el MIDlet (ya sea para la primera vez  o desde el estado de pausa), tira un MIDletStateChangeException. Esencialmente, en caso de un error, el MIDlet toma el camino seguro de establecer un estado de pausa.

               El estado activo es donde cada MIDlet quiere estar. Esto es cuando el MIDlet puede hacer sus funciones, mantener los recursos del dispositivo y generalmente, hacer lo que supuestamente hace. Como se dijo previamente, un MIDlet esta en un estado activo cuando el AMS llama al metodo starApp() en un MIDlet pausado. Un MIDlet pausado puede pedir ir al estado activo llamando al metodo resumeRequest(), el cual informa al AMS que el MIDlet desea estar activo. El AMS podría por supuesto, elegir ignorar esta petición o, alternativamente, encolarla si hay otros MIDlet solicitando los mismo.


            El estado destruido se entra cuando el método destroyApp(boolean unconditional) del MIDlet es llamado y retorna exitosamente, ya sea desde un estado activo o pausado. Este método es llamado por el AMS cuando siente que no hay necesidad de seguir corriendo el MIDlet y es tiempo de que el MIDlet pueda realizar limpieza y otras actividades de último minuto. El MIDlet puede entrar en este estado, llamando al metodo notifyDestroy(), el cual informa al AMS que el MIDlet ha limpiado sus recursos y esta listo para su destrucción. Por supuesto, en este caso, el método destroyApp(boolean unconditional) no es llamado por el AMS, cualquier actividad de último minuto debe ser hecha antes de que este método sea invocado.


           ¿Que sucede si el AMS llama al metodo destroyApp(boolean unconditional) en medio de un importante paso que el MIDlet este haciendo? El flag unconditional si es seteado a true, el MIDlet será destruido, sin importar de que este haciendo el MIDlet. Sin embargo, si es falso, efectivamente, el AMS generara una excpecion  MIDletStateChangeException, y el AMS no lo destruira todavia. Sin embargo, no hay garantias de que el MIDlet no será destruido, y cada dispositivo decide como manejarlo.

Figure 11


2.5.- Bluetooth y J2ME / JSR 82


           Como otras APIS que extienden a  J2ME tenemos la JSR 82 para Bluetooth . El JSR-82[1] especifica un API de alto nivel para la programación de dispositivos Bluetooth. Depende de la configuración CLDC de J2ME, y se divide en dos paquetes: javax.bluetooth y javax.obex. El primer paquete provee la funcionalidad para la realización de búsquedas de dispositivos, búsquedas de servicios y comunicación mediante flujos de datos (streams) o arrays de bytes. Por otro lado el paquete javax.obex permite la comunicación mediante el protocolo OBEX (OBject Exchange); se trata de un protocolo de alto nivel muy similar a HTTP.

          Para empezar el desarrollo de aplicaciones J2ME se puede descargar el WTK 2.5, que incluye la  JSR 82 y una aplicación que ejemplica el uso de Bluetooth. El demo es un programa que permite compartir imagenes, el programa es muy simple de usar, basta escoger el proyecto y ejecutar run dos veces para abrir dos emuladores de celular.





3.- Ejemplo Bluetooth y J2ME

         Este ejemplo consta de 3 archivos fuentes:

  • BluetoothEchoDemo.java – El archivo principal que permite escoger entre cliente y servidor
  • EchoClient – La clase cliente
  • EchoServer – La clase servidor

            Este ejemplo usa RFCOMM para la transferencia de datos, existen otras opciones como  L2CAP y OBEX. RFCOMM es usado para datos del tipo streaming, L2CAP es para paquetes de datos y OBEX es para tipo de datos objetos como imagenes.

3.1.- Cliente

      Un cliente Bluetooth deberá realizar las siguientes operaciones para comunicarse con un servidor Bluetooth:

  • Búsqueda de dispositivos
  • Búsqueda de servicios
  • Establecimiento de la conexión
  • Comunicación

        El punto de partida es la clase LocalDevice que representa el dispositivo en el que se está ejecutando la aplicación. Este objeto es un singleton y se obtiene
mediante:
                    LocalDevice localDevice = LocalDevice.getLocalDevice();.

        Este objeto permite obtener información sobre el dispositivo: modo de conectividad, dirección bluetooth y nombre del dispositivo.

        El primer paso que debe realizar un cliente es realizar una búsqueda de dispositivos. Para ello deberemos obtener un objeto DiscoveryAgent. Este objeto es único y se obtiene a través del objeto LocalDevice.
          
          discoveryAgent = localDevice.getDiscoveryAgent();

        El objeto DiscoveryAgent nos va a permitir realizar y cancelar búsquedas de dispositivos y de servicios. Y también nos servirá para obtener listas de dispositivos ya conocidos. Esto se lleva a cabo llamando al método retrieveDevices(). A este método se le debe pasar un argumento de tipo entero que puede ser:          

  • DiscoveryAgent.PREKNOWN. Para obtener una lista de dispositivos encontrados en búsquedas anteriores.
  • DiscoveryAgent.CACHED. Para obtener una lista de dispositivos “favoritos”.

        El método retrieveDevices() devuelve un array de objetos RemoteDevice. La clase RemoteDevice representa un dispositivo remoto y tiene métodos similares a
LocalDevice que, recordemos, representa al dispositivo en el que se ejecuta la aplicación. Así pues, podemos obtener el nombre del dispositivo mediante
getFriendlyName() y su dirección bluetooth mediante getBluetoothAddress().

        Podríamos omitir la búsqueda de dispositivos y pasar directamente a la búsqueda de servicios en caso de que deseásemos conectar con alguno de los dispositivos pertenecientes a alguna de estas listas. Sin embargo lo más común será intentar conectar con un dispositivo encontrado en una búsqueda de dispositivos, debido a que obviamente lo tendremos a nuestro alcance.

        Una búsqueda de dispositivos se inicia llamando al método startInquiry().

discoveryAgent.startInquiry(DiscoveryAgent.GIAC,this);

        Este método requiere un argumento de tipo DiscoveryListener. DiscoveryListener es una interfaz que implementaremos a nuestra conveniencia y que será usada para que el dispositivo notifique eventos a la aplicación cada vez que se descubre un dispositivo, un servicio, o se finaliza una búsqueda. DiscorveryAgent.GIAC se refiera al  “The inquiry access code for General/Unlimited Inquiry Access Code”. Estos son los cuatro métodos de la interfaz DiscoveryListener:

Nombre del método
Descripción
void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) Llamado cuando un dispositivo es encontrado durante un inquiry.
void inquiryCompleted(int discType) Llamado cuando un inquiry es completado.
void servicesDiscovered(int transID, ServiceRecord[] servRecord) Llamado cuando los servicios son encontrados durante una busqueda de servicios.
void serviceSearchCompleted(int transID, int respCode) Llamado cuando una busqueda de servicios es completada o fue terminada debido a un error.

            Los dos primeros métodos son llamados en el proceso de búsqueda de dispositivos. Los otros dos son llamados en procesos de búsqueda de servicios. Cada vez que un dispositivo es encontrado se llama al método deviceDiscovered() pasando un argumento de tipo RemoteDevice.

            Una vez que la búsqueda de dispositivos ha concluido se llama al método inquiryCompleted() pasando como argumento un entero que indica el motivo de la finalización. Este entero puede valer:

  • DiscoveryListener.INQUIRY_COMPLETED si la búsqueda concluyó con normalidad,
  • DiscoveryListener.INQUIRY_TERMINATED si la búsqueda ha sido cancelada manualmente o
  • DiscoveryListener.INQUIRY_ERROR si se produjo un error en el proceso de búsqueda. 

              En el método deviceDiscovered obtenermos  informacion del dispositivo la cual en general es autoexplicativa. Excepto por la clasificación de Major y Minor, estos dos atributos te dejan saber que tipo de dispositivo es. A continuacion se muestra algunas definiciones de Major y Minor. Cuando se corra el programa obtendremos como salida Major 512 y Minor 4, lo que de acuerdo a la tabla corresponde a Phone / Cellular.

Major Class

Minor Class

Major Class Description

Minor Class Description

256

4

Computer

Desktop

256

8

Computer

Server

256

12

Computer

Laptop

256

20

Computer

PDA

512

4

Phone

Cellular

512

8

Phone

Household cordless

512

12

Phone

Smart phone

1536

32

Imaging device

Camera

1536

64

Imaging device

Scanner

1536

128

Imaging device

Printer

        Ya hemos conseguido dar el primer paso para realizar una conexión cliente. El siguiente paso es realizar una búsqueda de servicios. Antes de seguir deberemos comprender ciertos conceptos.

        Una aplicación cliente es una aplicación que requiere un servidor para que le ofrezca un servicio. Este servicio puede ser: un servicio de impresión, un servicio de videoconferencia, un servicio de transferencia de archivos, etc. En una comunicación TCP-IP un cliente se conecta directamente a un servidor del que conoce el
servicio que ofrece, es decir, conocemos a priori la localización del servidor y el servicio que nos ofrecerá; sin embargo un cliente Bluetooth no conoce de
antemano qué dispositivos tiene a su alcance ni cuáles de ellos pueden ofrecerle el servicio que necesita. De modo que un cliente Bluetooth necesita primero buscar
los dispositivos que tiene a su alcance y posteriormente les preguntará si ofrecen el servicio en el que está interesado. Este último proceso se denomina búsqueda
de servicios y es el siguiente paso que un cliente debe realizar.

        Cada servicio es identificado numéricamente. Es decir, a cada servicio le asignamos un número y para referirnos a dicho servicio usaremos su número asociado. Este identificador se denomina UUID (Universal Unique IDentifier). Adicionalmente, cada servicio tiene ciertos atributos que lo describen. Por ejemplo un servicio de impresión podría describirse por diversos atributos como: tipo de papel (dinA4, US-letter,…), tipo de tinta (color, blanco y negro), etc. Los atributos también están identificados numéricamente, es decir, para referirnos a un atributo usaremos su número asociado.

        Las búsquedas de dispositivos también se realizan mediante el objeto DiscoveryAgent. Concretamente usaremos el método searchServices() al que le tendremos que pasar un objeto DiscoveryListener que recibirá los eventos de la búsqueda, el dispositivo en el que realizar la búsqueda (un objeto RemoteDevice que normalmente obtendremos en la búsqueda de dispositivos), los servicios en los que estamos interesados, y los atributos que queremos conocer sobre
dichos servicios (tipo de papel, tipo de tinta, etc). Por ejemplo un cliente que esté interesado en un servicio de impresión, para imprimir un texto probablemente sólo le interese conocer el tipo de papel, sin embargo si queremos imprimir una imagen estaremos también interesados en si soporta o no tinta de color.

        Si se encuentra algún servicio se nos notificará a través del objeto DiscoveryListener mediante el método servicesDiscovered(). Se nos pasará un array de objetos ServiceRecord que encapsulan los atributos de servicio que solicitamos al invocar la búsqueda. Los valores de estos atributos de servicio son objetos DataElement.

        Un objeto DataElement encapsula los tipos de datos en los que puede ser representado un atributo de servicio. Estos pueden ser: números enteros de diferente longitud con o sin signo, cadenas de texto, URLs, booleanos, o colecciones de DataElements.

        Un ServiceRecord es, pues, como una tabla que relaciona los identificadores de los atributos con sus valores (objetos DataElement). Cuando finalice la búsqueda de servicios se nos notificará mediante una llamada al método serviceSearchCompleted() de la interfaz DiscoveryListener. Se nos pasará un argumento de tipo entero indicando el motivo de la finalización. Este entero puede valer:

  • SERVICE_SEARCH_COMPLETED: la búsqueda ha finalizado con normalidad.
  • SERVICE_SEARCH_TERMINATED: la búsqueda ha sido cancelada manualmente.
  • SERVICE_SEARCH_NO_RECORDS: no existe la información solicitada.
  • SERVICE_SEARCH_ERROR: finalizó por un error.
  • SERVICE_SEARCH_DEVICE_NOT_REACHABLE: el dispositivo no está a nuestro alcance.

        Estas constantes son miembros de la interfaz DiscoveryListener.

        Si hemos encontrado algún servicio que nos interesa pasaremos al siguiente paso: abrir la conexión.

        Abrir una conexión Bluetooth se lleva a cabo de la misma forma que se abre cualquier otro tipo de conexión en CLDC: a través de la clase javax.microedition.Connector. Usaremos su método open() y le pasaremos una URL que contendrá los datos necesarios para realizar la conexión.

        No necesitaremos construir la URL a mano ya que el objeto ServiceRecord posee un método que nos ahorra esta tarea: getConnectionURL().

        Llegados a este punto debemos saber que tenemos dos formas diferentes de comunicación: a través de flujos de datos utilizando el protocolo SPP (Serial Port Profile) , o bien a través de L2CAP enviando y recibiendo arrays de bytes. La forma más sencilla es mediante SPP.

        Si el servidor utiliza SPP el método Connector.open() nos devolverá un objeto de tipo javax.microedition.io.StreamConnection. A través de este objeto podemos obtener un (Data)InputStream y un (Data)OutputStream. Por lo tanto ya tenemos un flujo de lectura y un flujo de escritura por lo que estamso en
condiciones de leer y escribir datos.

        En caso de que el servidor utilice L2CAP el método Connector.open() nos devolverá un objeto del tipo javax.bluetooth.L2CAPConnection. Con este objeto leeremos bytes con receive() y escribiremos bytes con send().


En el método  servicesDiscovered se puede obtener la respectiva URL que se requiere para abrir una conexión a los servicios disponibles.

for(int i=0;i<servRecord.length;i++) {
  serviceUrl = servRecord[i].getConnectionURL(0,false);
}

Ahora que se tiene la url, se puede enviar datos a através del conéctor genérico estandar.

String msg = "Say Hello World";
conn = (StreamConnection)Connector.open(serviceUrl);
OutputStream output = conn.openOutputStream();
output.write(msg.length());
output.write(msg.getBytes());
output.close();

3.2.- Servidor

        La creación de un servidor Bluetooth es más sencilla que la programación de un cliente ya que no necesitamos realizar ningún tipo de búsqueda. Concretamente los pasos que debe realizar un servidor Bluetooth son los siguientes:

  • Crear una conexión servidora
  • Especificar los atributos de servicio
  • Abrir las conexiones cliente

        Crear la conexión servidora es relativamente simple.Sencillamente debemos llamar al método Connector.open() pasándole una URL con una sintaxis determinada. En caso de querer comunicarnos mediante SPP la URL comenzará por “btspp://” y en caso de querer comunicarnos mediante L2CAP la URL comenzará por “btl2cap://”. A continuación deberemos indicar “localhost/” como host. Esto determina que no queremos conectarnos a nadie, sino que queremos ser servidores. Seguidamente sólo nos queda concatenar a la URL el identificador del servicio (UUID) que vamos a ofrecer.

        A continuación llamaremos al método Connector.open() pasando la URL como argumento. Si la URL comienza por “btspp://” nos devolverá un objeto del tipo  javax.microedition.StreamConnectionNotifier y en caso de que la URL comience por “btl2cap://” nos devolverá un objeto javax.bluetooth.L2CAPConnectionNotifier.

        El siguiente paso es especificar los atributos de servicio. Por ejemplo si vamos a ofrecer un hipotético servicio de impresión podríamos indicar qué tipo de papel y de tinta ofrecemos. Los atributos de servicio se almacenan en un objeto ServiceRecord. Cada conexión servidora tiene un ServiceRecord asociado que se obtiene a través del LocalDevice.

        Establecer los atributos de servicio es sencillo, simplemente tenemos que crear objetos DataElement y añadirlos al ServiceRecord.

        Una vez establecidos los atributos de servicio ya estamos
en condiciones de escuchar y procesar las conexiones cliente. Para ello usaremos el método acceptAndOpen().

        En una conexión servidora SPP este método devuelve un javax.microedition.StreamConnection, y en una conexión servidora L2CAP devuelve un objeto del tipo javax.bluetooth.L2CAPConnection. En este punto ya podemos leer y escribir datos del mismo modo que lo hace un cliente.


        En la clase servidor necesitamos inicializar una vez mas pero esta vez en vez de buscar dispositivos se necesita configurar un dispositivo.

private static String serverUrl = "btspp://localhost:" +
BluetoothEchoDemo.RFCOMM_UUID + ";name=rfcommtest;authorize=true";
.
.
.
conn = null;
localDevice = LocalDevice.getLocalDevice();
localDevice.setDiscoverable( DiscoveryAgent.GIAC );
notifier = (StreamConnectionNotifier)Connector.open(serverUrl);
.
.
.

Ahora establecemos conexión con el mismo conector genérico que se usa cuando se hace un llamada http.la definición de url es:

scheme://host:port;parameters

Nombre

Descripción

scheme

Conexion como http, en el caso de bluetooth se usa btspp para RFCOMM o btl2cap para L2CAP

host

La dirección para conectar o si tu estas configurando un servidor entonces ponla  en localhost

port

El puerto de las conexiones del cliente y para el servidor describe el  UUID.

parameters

Especifica parametros opcionales, por ejemplo como el nombre del servicio name=rfcommtest

            Ahora se espera por la respuesta del cliente, esto se logra deteniendo la hebra hasta que se reciba algo.

conn = notifier.acceptAndOpen();

Una vez un cliente responde, se leen los datos entrantes y se envia un mensaje de respuesta

// Read Data Transmission
String msg = BluetoothEchoDemo.readData(conn);
System.out.println("Received Message from Client: " + msg);
// Send Back a Message
msg = "Hello Back from Server";
output = conn.openOutputStream();
output.write(msg.length()); // length is 1 byte
output.write(msg.getBytes());
output.close();

3.3.- Corriendo el Ejemplo

            Se requiere invocar el WTK dos veces porque las salidas son mostradas en la consola. Corre una instancia y selecciona servidor, entonces comienza una segunda instancia y selecciona cliente. Como en conexiones http se tendra que contestar si para las conexiones que se estan realizando.

             Al final verás en la consola del servidor:

Starting Echo Server
Server Running...
Received Message from Client: Say Hello World


     
En la consola del cliente:


Device Discovered
Major Device Class: 512 Minor Device Class: 4
Bluetooth Address: 000060854FBF
Bluetooth Friendly Name: WirelessToolkit
InquiryCompleted
ServicesDiscovered
SERVICE_SEARCH_COMPLETED
Service URL: btspp://000060854FBF:1;master=false;encrypt=false;authenticate=false
Hello Back from Server


4.- Nokia 5300

Descripción