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:
1.2-Datos
Técnicos sobre Bluetooth
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 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.
2.2 .-Creación de MIDlet
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.
2.2.1.- Paso 1: DiseñoLos 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
|
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.
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.
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.
El proyecto esta listo para ser creado. Verifique que el Toolkit ha creado la carpeta DateTimeApp bajo el directorio apps.
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.
Esto creara el archivo JAR, corrigendo la información necesaria.
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.
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.
3.- Ejemplo
Bluetooth
y J2ME
Este ejemplo consta de 3 archivos fuentes:
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.
Un cliente Bluetooth
deberá realizar las siguientes operaciones para comunicarse con
un servidor Bluetooth:
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:
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:
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:
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();
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 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