jueves, 1 de marzo de 2012

Aplicación Bluetooth para Android

Explicare lo que se necesita para la API Bluetooth de android que será útiles para mi aplicación ya que usare una conexion de estas en mi proyecto. Espero a alguien le sea de utilidad.

Utilizando la API Bluetooth, una aplicación Android puede realizar las siguientes operaciones:

  • Buscar otros dispositivos bluetooth.
  • Consultar si está emparejado con un dispositivo bluetooth concreto.
  • Establecer canales RFCOMM.
  • Conectar con otros dispositivos a través del servicio de descubrimiento de dispositivos.
  • Realizar transferencia de datos entre otros dispositivos de forma bidireccional.
  • Manejar múltiples conexiones.
El paquete android.bluetooth proporciona cinco clases para el acceso a todas las funcionalidades que ofrece la API. Éstas clases son las siguientes:

·         BluetoothAdapter
Representa el adaptador bluetooth local, es decir, el dispositivo físico. Ésta clase el es entry point para toda la infraestructura bluetooth de nuestra aplicación. Con ella podemos descubrir otros dispositivos bluetooth activos cerca de nosotros, acceder a la lista de dispositivos emparejados, instanciar un objeto de la clase BluetoothDevice usando una MAC conocida o crear sockets para comunicar con otros dispositivos.
·         BluetoothDevice
Representa a un dispositivo bluetooth remoto. Se utiliza para solicitar una conexión con dicho dispositivo a través de un BluetoothSocket o también solicitar información acerca del dispositivo, como puede ser el nombre, la MAC, clase, etcétera.
·         BluetoothSocket
Representa una interfaz con otro dispositivo bluetooth en forma de socket. Ésta es la clase que permite una comunicación punto a punto con otro dispositivo enviando y recibiendo información en forma de streams de datos.
·         BluetoothServerSocket
Para poder establecer una conexión, uno de los dispositivos debe asumir el rol de Servidor (conexión pasiva) y otro el rol de Cliente (conexión activa).BluetoothServerSocket representa a un socket escuchando en el lado de la aplicación servidora.
Cuando el cliente solicita una conexión, un objeto de la clase BluetoothServerSocket se encarga de retornar un objeto de la clase BluetoothSocketque representará al servidor, siempre y cuando la conexión haya sido aceptada.
·         BluetoothClass
Ésta clase contiene un conjunto de propiedades (de solo lectura) que definen las características del dispositivo bluetooth al que representan.

Permisos


Para poder acceder a la API Bluetooth es necesario declarar al menos uno de los permisos dentro del manifest de nuestra aplicación.

Los permisos disponibles son dos:
  • BLUETOOTH, que permite únicamente realizar conexiones bluetooth y transferir datos.
  • BLUETOOTH_ADMIN, que permite, además de realizar conexiones bluetooth y transferencias de datos, manipular las opciones del sistema en lo referente a bluetooth, buscar otros dispositivos y realizar vínculos.
Un ejemplo de declaración del permiso en un manifest podría ser el siguiente


Vamos a conocer lo necesario en código para poder establecer nuestra conexión del android con algún otro dispositivo por medio de bluetooth.
Configuración del bluetooth

Antes de que cualquier aplicación pueda utilizar la infraestructura bluetooth debe hacer dos cosas: asegurarse de que el dispositivo sobre el que está ejecutando tiene soporte para bluetooth y si así es comprobar que está activado, activándolo si fuese necesario.
Éstas tareas básicas de comprobación y configuración son llevadas a cabo por la clase BluetoothAdapter antes mencionada.
Los pasos a seguir se pueden resumir de la siguiente manera:
  • Obtener el BluetoothAdapter que representará a nuestro dispositivo.
  • Activar el sistema bluetooth.
El siguiente ejemplo muestra como se realizarían éstas operaciones


Como podemos ver, lo primero que hacemos el obtener el BluetoothAdapter llamando al método estático getDefaultAdapter(). Una vez que tenemos el adaptador comprobamos que su valor no sea null, situación que correspondería con un adaptador nulo debido a que no hay dispositivos bluetooth. getDefaultAdapter es una operación estática debido a que el dispositivo físico de nuestro sistema que se encarga de realizar las tareas Bluetooth es único. Todas las instancias que tengamos de la clase BluetoothAdapter deben referirse al mismo dispositivo hardware, por lo que ésta clase se ha implementado como un singleton.
Una vez que tenemos el representante del dispositivo bluetooth, ya solo queda comprobar si está activado. Si no lo está, lo activamos nosotros.


Si el valor devuelto por isEnabled() es falso, tendremos que solicitar la activación del bluetooth. Creamos un Intent para que llame a la actividad de encendido del bluetooth (ACTION_REQUEST_ENABLE). Cuando dicha actividad finalice retornará el identificadorREQUEST_ENABLE_BT y el valor de retorno de la operación, que podrá ser RESULT_OK en caso de que se haya activado el bluetooth oRESULT_CANCELED en caso de que o bien se haya cancelado la acción o bien haya habido algún error.
En el siguiente fragmento podemos ver el callback de la actividad ACTION_REQUEST_ENABLE, que es llamada por Android cuando finaliza la actividad a la que se llamó a través de startActivityForResult():


En caso de que éste callback reciba como requestCode la constante REQUEST_ENABLE_BT, comprobará que el valor devuelto por la actividad (resultCode) corresponde con RESULT_OK para continuar la ejecución. Si por el contrario el valor devuelto esRESULT_CANCELED, la actividad finalizará.
Búsqueda de dispositivos
A través de la clase BluetoothAdapter se puede buscar dispositivos bluetooth remotos de dos formas distintas, o bien haciendo un descubrimiento de qué dispositivos hay en el radio de alcance, o bien obteniendo la lista de dispositivos emparejados al nuestro.
Una vez que se tiene identificado el dispositivo al que se quiere conectar (por cualquiera de los dos métodos comentados anteriormente), se puede intentar la conexión.
A continuación se describe como realizar los dos procesos, el de descubrimiento de nuevos dispositivos y el de consulta de los dispositivos emparejados.
Consultando los dispositivos emparejados
El proceso de descubrimiento de nuevos dispositivos es un proceso lento y costoso en términos de batería. Por tanto cuando se quiere conectar con otro dispositivo es recomendable consultar primero si dicho dispositivo está emparejado y si no lo está, entonces proceder al descubrimiento.
Para conocer qué dispositivos están emparejados con el nuestro, se invoca el método getBondedDevices(). Éste método devolverá un Set de objetos de tipo BluetoothDevice que representarán a los dispositivos emparejados. En el siguiente fragmento de código podemos ver un ejemplo:

Después de obtener el representante del dispositivo bluetooth se buscan todos los dispositivos remotos que están emparejados con él llamando a getBondedDevices(). El resultado se guarda en un Set<BluetoothDevice> que después podrá ser recorrido para obtener toda la información que necesitemos.
Descubrimiento de dispositivos

En caso de que el dispositivo con el que queramos conectar no esté emparejado con nuestro dispositivo, habrá que hacer un descubrimiento de dispositivos para localizarlo y emparejarlo.
Ésto se hace llamando a la función startDiscovery(), función que retornará inmediatamente devolviendo un booleano que indicará si la operación ha sido iniciada correctamente o ha habido algún problema.
Cómo se puede suponer, ésta función es asíncrona. El componente que la invoque deberá registrarse en un BroadcastReceiver para recibir todas las información de los dispositivos que se vayan descubriendo así como para saber cuando ha finalizado el proceso de descubrimiento.
Cada vez que se descubre un nuevo dispositivo, se recibirá a través de un broadcast un intent ACTION_FOUND. Éste Intent portará los campos EXTRA_DEVICE y EXTRA_CLASS, que contendrán, respectivamente, un objeto de la clase BluetoothDevice y otro de la claseBluetoothClass.
A continuación se muestra un ejemplo. Aquí, lo primero que se hace es registrarse en los broadcasts correspondientes.
Después se programan las acciones a realizar cuando se recibe cada broadcast:
Conectando dispositivos

Para que puede llevarse a cabo una conexión bluetooth es necesario disponer tanto de un cliente, que será quien inicie la conexión, como de un servidor, que será quien la atienda. Aunque en general dichos roles suelen estar bien diferenciados, la tendencia habitual en los dispositivos bluetooth es implementar tanto un cliente como un servidor en un mismo nodo, de modo que el dispositivo pueda recibir una conexión entrante pero a la vez pueda iniciarla él si se le pide. De ésta forma el nodo podrá adoptar cualquier rol.
Aunque lo comentando anteriormente es lo mas habitual, nada impide que una aplicación concreta actúe solo como cliente o solo como servidor.
Cuando tanto un cliente como un servidor tienen un objeto de la clase BluetoothSocket asociado al mismo RFCOMM se puede decir que existe una conexión entre ambos. En ése momento ambos dispositivos pueden pedir un stream de datos tanto de entrada como de salida a su socket correspondiente, de modo que se puedan intercambiar datos.
Servidor
Para la implementación de un servidor ha de mantenerse abierto un socket de tipo BluetoothServerSocket que será el encargado de escuchar todas las solicitudes de conexión entrantes.
Cuando se produce una solicitud y ésta es aceptada, BluetoothServerSocket proporciona un socket BluetoothSocket representando a la nueva conexión. Si es posible recibir mas conexiones, el BluetoothServerSocket deberá seguir escuchando, pero en caso de que el dispositivo solo vaya a manejar una conexión (que por otro lado suele ser lo mas habitual), es muy recomendable cerrar el BluetoothServerSocket y liberar sus recursos.
Concretando un poco, los pasos a seguir para implementar un servidor son los siguientes:
  • Obtener un BluetoothServerSocket a través de una llamada a listenUsingRfcommWithServiceRecord().
    listenUsingRfcommWithServiceRecord() creará un nuevo DSP que quedará registrado en nuestro sistema y que definirá de forma unívoca al servicio ofrecido por nuestra aplicación, de modo que cualquier cliente que lo necesite pueda pedir información sobre ése servicio concreto al servidor siempre y cuando conozca su nombre y obviando el resto de servicios disponibles. Una vez que el servidor muera, el DSP generado será eliminado del sistema.
    listenUsingRfcommWithServiceRecord() necesita dos parámetros: un nombre y un UUID. El primero sirve para identificar al servicio de modo que, como se ha comentado hace un momento, el cliente pueda obtener información acerca de él. El segundo es usado por el cliente para intentar conectar. Si en la petición de conexión el cliente no envía el UUID correspondiente al servicio, la conexión será rechazada.
  • Comenzar a escuchar conexiones entrantes llamando a accept().
    El funcionamiento de ésta llamada es prácticamente el mismo que en el caso de un socket TCP/IP. El sistema quedará bloqueado hasta que se acepte una conexión o se lance una excepción (es aconsejable que accept() esté dentro de un bloque try/catch). Si la conexión tiene éxito, accept() devolverá un objeto BluetoothSocket representando la conexión.
  • Si no se espera recibir ninguna conexión mas, puede llamarse a close().
    close() provocará que el BluetoothServerSocket se cierre, pero sin afectar en absoluto al socket que nos devolvió.

Como se comentó anteriormente, la llamada a accept() es bloqueante, motivo por el cual es no es muy aconsejable que se realice en la misma actividad que gestiona la ventana visible en ése momento.
Lo mas correcto es implementar algún tipo de servicio que gestione toda la infraestructura bluetooth y hacer que corra en otro hilo distinto. De ésa forma, además, dicho servicio podría ser utilizado por cualquier otra aplicación del sistema que lo conozca y lo necesite.
Cliente
Para poder realizar una conexión como cliente a otro dispositivo, deberemos obtener primero el BluetoothDevice que respresentará al nodo hacia el que queremos conectar (ver sección Búsqueda de dispositivos). Una vez que tenemos dicho objeto, podremos obtener un socket llamado al método createRfcommSocketToServiceRecord. Ésta llamada devolverá un BluetoothSocket representando la conexión.
createRfCommSocketToServiceRecord toma como argumento un UUID que debe coincidir con el que se utilizó en el servidor para inicializar el BluetoothServerSocket. Además, ésta llamada es bloqueante, por lo que al igual que en el caso del servidor, es aconsejable utilizar un servicio que corra en otro hilo.

Si createRfCommSocketToServiceRecord tiene éxito se devolverá un BluetoothSocket. En caso de que se rechace la conexión (o expire el timeout de unos 12 segundos), se devolverá también un BluetoothSocket, pero ésta vez nulo. Una vez que se consigue obtener un socket válido, la conexión se inicia con una llamada a connect().


Bibliografía: 
http://developer.android.com/resources/samples/BluetoothChat/index.html

12 comentarios:

  1. Una pregunta rápida.
    Estoy utilizando el bluetooth en diferentes Activitys, y tengo un error que me conecto a un dispositivo en una Ativity, y cuando salto a otra, se desconecta, y no quiero eso, necesito que siga conectado. ¿Sabes que error puede ser?
    Gracias por la atención prestada.

    ResponderEliminar
    Respuestas
    1. hola se que ya es una publicacion muy vieja pero me gustaria saber si lograste resolver tu problema

      Eliminar
  2. de gran ayuda ese aporte si fueran tan amables de pasarme ese ejemplo por favor les tendría muy agradecido de mi parte.

    ResponderEliminar
  3. Muchas gracias por compartir todo este conocimiento!!!

    Me gustaría preguntarte si también es posible hacer un programa que "identifique" los intentos de conexión via bluetooth. Es decir, si las solicitudes de conexión con nuestro movil las gestiona únicamente el SO o si es posible crear una aplicación que "monitorice" esas solicitudes de conexión. Es para un pequeño proyecto que tratamos de hacer en el colegio(la idea es contar el número de intentos de conexiones via bluetooth y establecer unas gráficas de cómo esos intentos de conexión afectan a la duración de la batería).

    Muchas gracias de antemano!!
    muchas gracias

    ResponderEliminar
  4. Hola, interesante el articulo, tendrias disponible el codigo en linea ? gracias de antemano

    ResponderEliminar
  5. Excelente articulo!! Buenisimo para tener una idea y comenzar a desarrollar haciendo uso de la API. Gracias!

    ResponderEliminar
  6. Una pregunta, si alguien usa esos códigos para otra aplicación diferente, no tendría problema o si??

    ResponderEliminar
  7. Estoy intentando hacer una aplicación que use del bluetooth pero me seria de gran ayuda poder usar algún tipo de emulador. El emulador por defecto que viene con adt parece no soportar la emulación bluetooth podríais decirme alguna solución para esto. Conocéis algún sistema para emular bluetooth con adt. Muchas gracias por la colaboración y muchas gracias por el post me ha sido de gran ayuda.

    ResponderEliminar
    Respuestas
    1. Disculpen si en vez de responder, pregunto, pero es que yo no sé como hacerle para saber si un dispositivo bluetooth está prendido o apagado, y es que tengo el siguiente código pero el programa no responde como se lo espera.
      String address = "00:42:FE:9F:C8:37";
      BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
      //bluetooth.
      BluetoothSocket tmp = null;
      try {
      Log.v(TAG,"INTENTANDO CREAR RFSOCKE");
      tmp = device.createRfcommSocketToServiceRecord(ConexionBT.MY_UUID);
      try {
      // Aqui solo recibiremos o una conexion establecida o una excepcion
      Log.v(TAG,"INTENTANDO CONECTAR");
      tmp.connect();
      hardware.setDETECTOR_activado(true);
      } catch (IOException e1)
      {
      Log.v(TAG,"DD APAGADO");
      hardware.setDETECTOR_activado(false);
      try {
      Log.v(TAG,"INTENTO DE CIERRE");
      tmp.close();
      } catch (IOException e2) {
      Log.e(TAG, "NO SE PUEDE CERRAR"+e2.getMessage());
      }
      }
      if(tmp.isConnected())
      {
      Log.v(TAG,"Conexion está establecida");
      try {

      tmp.close();
      } catch (IOException e2) {
      Log.e(TAG, "NO SE PUEDE CERRAR", e2);
      }
      }
      } catch (IOException e)
      {
      // TODO Auto-generated catch block
      Log.v(TAG,"SE DIO UNA ECEPCION");
      }

      Eliminar
    2. Y el log que me sale en el siguiente orden:
      INTENTANDO CREAR RFSOCKE
      INTENTANDO CONECTAR
      DD APAGADO
      INTENTO DE CIERRE

      Eliminar
  8. Porque mi aplicacion se cierra al intentar crea el socket.

    ResponderEliminar