Este artículo presenta una aproximación al modelo de desarrollo
seguido durante la implementación de esas librerías.
Orígenes y necesidades
de Bluetooth
Bluetooth surgió en la multinacional sueca Ericsson, en 1994, a
partir de un estudio inicialmente orientado a la búsqueda de
alternativas a las comunicaciones de dispositivos con el teléfono
móvil en un contexto inalámbrico.
La utilización de Bluetooth como tecnología de comunicación entre
dispositivos tuvo una buena acogida entre la comunidad tecnológica,
y tanto es así que en 1998 se creó un SIG (Special Interest Group),
compuesto por las multinacionales IBM, Intel, Nokia, Toshiba y
Ericsson, para el desarrollo conjunto y expansión de dicha
tecnología. Unos meses más tarde se anunció también la entrada de
Microsoft, Agere Systems (después Lucent), 3Com y Motorola,
sentándose así las bases para la generalización del uso de
Bluetooth en un futuro no muy lejano, como efectivamente ha
ocurrido.
Especificación
Bluetooth es una tecnología de radio de corto alcance. Su
finalidad es la comunicación entre dispositivos, sean teléfonos
móviles, agendas electrónicas, ordenadores, portátiles, manos
libres, GPS y otros, utilizando la frecuencia libre de licencia 2,4
Ghz ISM (Industries, Scientific and Medical). Su desarrollo se
orientó hacia el bajo precio, el bajo consumo y el uso de la
tecnología de radio. No necesita la proximidad que requiere IrDA,
por ejemplo, con lo que su alcance puede incluso "atravesar"
obstáculos como paredes (según tipo y profundidad). Soporta tanto
la conexión punto a punto como multipunto, lo cual puede dar lugar
a redes PAN (Personal Area Network), también conocidas como
Bluetooth Piconet. El precio de Bluetooth como módulo independiente
era en el año 2000 de 20$, y ha bajado en la actualidad hasta los
5$ (datos orientativos). Su consumo es mínimo, unos 0.3 mA en modo
espera y 30 mA durante su funcionamiento, otra razón por la que una
gran cantidad de teléfonos móviles utilizan hoy en día
Bluetooth.
Su tasa de transferencia no es muy alta, y como en cualquier tipo
de comunicación inalámbrica, depende del medio. Ésta no supera 1
Mbps (la velocidad óptima de rendimiento es de 721 Kbps), y aunque
baja, supera la velocidad soportada por los actuales puertos serie
(hasta ocho veces más rápido) y paralelo (el triple de rápido).
Bluetooth utiliza el espectro de frecuencia 2,4 Ghz, como otros
aparatos tan cotidianos como los teléfonos inalámbricos o routers
WiFi LAN (IEEE 802.11), y otros que no lo son tanto y se utilizan
en medicina o en laboratorios. Si está usted pensando en si tanto
dispositivo no podría interferir en las comunicaciones Bluetooth
(dado que la lista de productos cotidianos es grande; podríamos
incluir el microondas, o el mando a distancia de un garaje...), le
diré que Bluetooth transmite paquetes muy pequeños a una velocidad
tan rápida (hasta 1600 paquetes por segundo) que prácticamente hace
imposible la interferencia entre ellos. Sin embargo, existen
algunas connotaciones en el caso de interferencia con las redes
WiFi. A la pregunta si pueden coexistir, la respuesta es sí, y el
porqué lo explicaré de manera muy ligera: Bluetooth utiliza una
modulación de señal de espectro expandido por salto de
frecuencia (FHSS) y el estándar WLAN IEEE 802.11b. WiFi, además de
la mencionada, también utiliza una modulación de señal de espectro
expandido por secuencia directa (DSSS), y puede utilizar ambas
modulaciones, no dando lugar a interferencias. La técnica de
modulación FHSS conforma en sí una parte importante de la seguridad
de conexión entre dispositivos Bluetooth debido a su
funcionamiento. Este tema, aunque interesante, es algo
complejo. Su comprensión puede ayudar a entender mejor tanto la
comunicación entre dispositivos como la seguridad entre ellos.
Podrá saber más acerca de este tema en los enlaces recomendados al
final de este artículo.
El rango de alcance de Bluetooth está catalogado en 3 clases en
función de su potencia. En la clase 1 podemos obtener rangos de
unos 100 metros a una potencia de antena nominal de 20 dBm. En la
clase 2 y la clase 3, los rangos son ambos de unos 10 metros, con
la diferencia de que en la clase 2 la potencia de antena es de 4
dBm, mientras que en la clase 3 es de 0 dBm.
Además de la adaptabilidad y flexibilidad, Bluetooth posee varias
capas de seguridad de encriptación de hasta 128 bits y de
autenticación de usuarios. Bluetooth utiliza un número de
identificación personal (PIN) que junto a su propia dirección se
utiliza para la detección y autenticación de otros dispositivos a
su alcance. A quien tenga dudas de si Bluetooth es realmente
seguro, le puedo asegurar que sí lo es. Además de la encriptación,
en este contexto entra en escena la modulación FHSS antes
mencionada, que provee una técnica de comunicación segura por la
que un dispositivo sólo aceptará paquetes de una dirección
Bluetooth remota específica. Ver figura 1.
En este mismo contexto, también me gustaría resaltar lo que en la
especificación oficial de Bluetooth se conoce como Baseband, que
describe la especificación del controlador de enlace de Bluetooth
(Bluetooth Link Controller) que es quien administra los protocolos
e implementa la capa física de Bluetooth, siendo el encargado de
las transmisiones por canales mediante FHSS. Además, es importante
desde el punto de vista programático, ya que es quien especifica
las direcciones de los dispositivos Bluetooth, como más adelante
veremos. Maneja dos tipos de conexiones, SCO (Synchronous
Connection-Oriented), conexiones punto a punto síncronas, y ACL
(Asynchronous Connection-Less), conexiones multipunto asíncronas;
de ésta última veremos un ejemplo de conexión mediante una función
por código. Esta base física es la interfaz del protocolo L2CAP
(Logical Link Control and Adaptation Protocol), que se encarga de
"interpretar" y administrar los paquetes en transmisión.
El protocolo RFCOMM (Serial Cable Emulation Protocol) ofrece una
emulación de un puerto serie (basado en RS232) para la comunicación
con L2CAP; desde Windows CE podremos crear un puerto virtual que
interactúe con RFCOMM. Este punto también es interesante, puesto
que RFCOMM será en muchas ocasiones nuestro conducto de
comunicación con los perfiles Bluetooth. Digo muchas veces, ya que
también se puede interactuar mediante SDP (Service Discovery
Protocol), el cual ofrece un mecanismo de interconexión a Bluetooth
para el descubrimiento de dispositivos a nuestro alcance y sus
servicios.
Desarrollo
Como hemos visto hasta ahora, toda la especificación Bluetooth
definida desde el SIG hace de esta tecnología un estándar de
mercado que permite utilizarla siguiendo dichas especificaciones;
sin embargo, desde el punto de vista de desarrollo dicha
estandarización no es tal. Me explico: las maneras de acceder a
Bluetooth desde nuestras aplicaciones vienen determinadas por la
"pila" que el sistema operativo implementa. Windows Mobile (a
partir de la versión 4.0), por ejemplo, viene con Microsoft
Bluetooth Stack (así como Windows XP SP1 y versiones posteriores,
incluyendo evidentemente Windows Vista); sin embargo, y como sucede
con otras características del sistema operativo, los fabricantes de
dispositivos móviles, sean smartphones o PDA, tienen la opción de
mantener dicha "pila" o sustituirla sin que podamos hacer nada,
puesto que ésta reside en la memoria ROM del dispositivo. Por esta
razón, podemos encontrar dispositivos cuya "interfaz" o "pila" es
la que implementa Microsoft, y otros que la sustituyen por otras
disponibles en el mercado, como por ejemplo BROADCOM/WIDCOMM.
El problema que se plantea es que, en función de la interfaz,
desarrollaremos código específico o propietario para dicha
interfaz, no pudiendo reutilizarla luego en otros dispositivos con
una interfaz distinta. Por poner un par de ejemplos, los PC de
bolsillo de Hewlett Packard vienen en su totalidad equipados con
WIDCOMM, no siendo esto así en algunos modelos del fabricante Qtek.
Aún habiendo pilas distintas éstas se comunican perfectamente entre
sí; es decir, que podemos crear dos aplicaciones específicas, cada
una para cada pila diferente, y a la hora de comunicarse lo harán a
la perfección, ya que no olvidemos que Bluetooth es un estándar,
aunque las librerías utilizadas serán distintas.
En este artículo nos centraremos en el desarrollo para Microsoft
Bluetooth Stack. No entraré en detalles de qué interfaz o pila es
mejor o peor y por qué. La principal razón para utilizar ésta es
que Microsoft Bluetooth Stack está implementada no solo en Windows
Mobile, sino además en los sistemas operativos Windows XP SP1 y
posteriores, para los que muchas de las funciones y librerías aquí
mencionadas funcionarán (eso sí, con sus particularidades).
El planteamiento y/o alcance de la aplicación nos permite saber
qué servicio de un dispositivo Bluetooth remoto queremos utilizar.
Por ejemplo, si nuestra intención es conectarnos con un GPS, este
planteamiento será distinto al caso en que queramos conectarnos a
un dispositivo manos libres. Aunque los mecanismos de
descubrimiento y emparejado sean iguales, la forma de comunicarse
puede ser distinta. Lo que debemos tener bien claro es que
Bluetooth es un medio de transmisión, no el fin de nuestra
aplicación, y que en él nos basaremos para comunicarnos con otro
dispositivo.
Por último, en Windows CE, la principal vía de acceso a Bluetooth
es mediante sockets que exponen el protocolo RFCOMM, aunque también
puede hacerse directamente con las interfaces Bluetooth sin pasar
por sockets (posibilidad ésta algo más compleja). Para el
seguimiento de este artículo, el código al que se hace referencia
está escrito por Microsoft y puede encontrarse en Windows Embedded
Source Tools for Bluetooth Technology [3]. Además de hacer
referencia a dicho código, mostraremos otras funcionalidades que
éste no ofrece y que están basadas en el criterio de desarrollo con
el que se implementó la librería gratuita Bluetooth de
desarrolloMobile.NET.
1. Inicializar la radio Bluetooth
Al activar la radio es un buen momento para inicializar también la
librería WS2.dll, encargada de las comunicaciones por sockets. La
función de inicialización requiere dos parámetros: el primero es de
tipo int e indica la versión más alta, mientras que el segundo es
un puntero a la estructura de datos WSDATA que recibirá los
detalles de la implementación de Windows Sockets. Como se ve en el
fuente 1, esta estructura se implementa como una matriz de tipo
byte de 512 posiciones. Esta función debe devolver cero para que la
inicialización sea satisfactoria. Del mismo modo que en el
constructor hemos inicializado WS2, en el destructor la
descargaremos.
- Obtención de información
de la radio local
Pese a que no es un paso estrictamente necesario, podemos llegar a
obtener la clase de dispositivo, la versión e incluso el fabricante
de nuestro dispositivo. Esta información la obtenemos de las
funciones BthReadCOD para la clase de dispositivo y
BthReadLocalVersion para la versión y código del fabricante (ver
fuente 1). Estas funciones tienen sus análogas para la petición de
dicha información para un dispositivo remoto, BthReadRemoteVersion.
En la obtención del fabricante y clase de dispositivo se devuelven
identificadores únicos asignados por la especificación Bluetooth
SIG.
Hasta aquí y como se muestra en el fuente 1, hemos hecho que la
clase BTHRadio englobe tanto la gestión del estado de la radio como
la información del dispositivo.
- Mostrar los dispositivos
ya emparejados
Una vez que hemos realizado la inicialización de la radio, se
trata de contactar o emparejar con el dispositivo remoto. Para ello
tenemos dos escenarios posibles. El primero parte de que dicho
dispositivo ya haya sido emparejado anteriormente, mientras que el
segundo implica empezar una búsqueda de todos los dispositivos que
estén en nuestro alcance.
En lo que a los dispositivos ya emparejados se refiere, esta
información se almacena en el Registro (clave
HKEYLOCALMACHINE\SOFTWARE\Microsoft\Bluetooth\Device), donde se
guardan el nombre y la dirección del dispositivo remoto.
El recorrido por los valores guardados bajo esa clave para obtener
los dispositivos emparejados nos crea la necesidad de tener una
clase BluetoothDevice. Si se estudia la clase BluetoothRadio del
ejemplo de Microsoft, se verá que realiza una iteración por cada
uno de los dispositivos y construye para cada uno un nuevo objeto
BluetoothDevice con dos parámetros: dirección y nombre. La función
que devuelve los dispositivos emparejados es miembro de la clase
BluetoothRadio y devuelve una matriz de BluetoothDevice, lo cual da
lugar a la posibilidad de contemplar una nueva clase
BluetoothDeviceCollection que implemente las interfaces IList e
ICollection.
Suponiendo que el emparejamiento se haya producido mediante
autenticación por medio de un PIN, que es lo más probable, y
ninguno de los dos dispositivos lo haya revocado, no será necesario
volvernos a autenticar para comunicarlos entre ellos. Sobre los
aspectos relacionados con la seguridad hablaremos más
adelante.
Descubrir dispositivos a mi alcance
Cuando lo que queremos es descubrir los dispositivos Bluetooth con
estado activado o detectable que estén bajo nuestro radio de
acción, según especificaciones, la cosa cambia. Hasta ahora en el
código que hemos visto había una llamada más o menos simple a
alguna API de Bluetooth que nos ayudaba; sin embargo, en el proceso
de búsqueda de dispositivos tenemos que echar mano de la librería
de sockets. Para la búsqueda de dispositivos, debemos inicializar
una consulta a través de sockets, especificando en una estructura
de datos (nativa) que lo que buscamos son dispositivos Bluetooth.
De un modo similar también podemos buscar las conexiones de
red disponibles en nuestro sistema.
La referencia de Bluetooth en MSDN [2] indica que, a pesar de que
las tres funciones claves para esta enumeración son
BthNsLookupServiceBegin, BthNsLookupServiceNext y
BthNsLookupServiceEnd, para conservar la compatibilidad con Win32
se recomienda utilizar WSALookUpServiceBegin, WSALookUpServiceNext
y WSALookUpServiceEnd, respectivamente. Mediante la función
WSALookUpServiceBegin inicializamos la búsqueda; debemos
suministrarle una estructura WSAQUERYSET con los parámetros
deseados para la búsqueda de dispositivos o redes disponibles, como
dije antes. Recomiendo estudiar los documentos relativos a
WSAQUERYSET (Bluetooth and WSAQUERYSET for Device Inquiry,
Bluetooth and WSAQUERYSET for Service Inquiry), para así entender
mejor la complejidad e importancia de esta estructura.
¿Cómo indicamos la búsqueda de dispositivos Bluetooth? Dentro de
la estructura WSAQUERYSET hay otras dos estructuras, CSADDRINFO y
BLOB; ésta última a su vez apunta a otra estructura,
BTHQUERY_
DEVICE (si lo que buscamos son servicios, esta estructura es
BTHQUERYSERVICE). Es en este punto donde debemos utilizar los
sockets orientados a las peculiaridades de Bluetooth. Por ejemplo,
cuando desde nuestro código creemos un objeto del tipo
System.Net.Sockets.Socket, al constructor deberemos indicarle que
la familia de direcciones es Bluetooth (mediante el valor 32 en
AddressFamily), que el tipo de socket utilizado es
SocketType.Stream, y el tipo de protocolo es RFCOMM (su valor es
0x0003).
El puntero a CSADDRINFO es de salida, y a través de él la función
devolverá la información de la dirección "bluetooth socket" del
resultado de la búsqueda según las especificaciones comentadas. El
puntero a BLOB, por su parte, contendrá el tamaño del puntero a
BTHQUERY_DEVICE y el puntero al mismo, en el que debemos
especificar a la entrada los parámetros de búsqueda que queremos
llevar a cabo mediante el flag LAP (su valor es 0x9e8b33).
Hasta aquí hemos identificado las estructuras y funciones que
intervienen. El funcionamiento es el siguiente:
• Creación de la estructura BLOB y BTHQUERYSERVICE
• Creación de la estructura WSAQUERYSET
• Llamada a WSALookUpServiceBegin
• Llamada a WSALookUpServiceNext mientras se vayan
detectando dispositivos (hay un límite por defecto de 16
dispositivos).
• Al detectar un dispositivo, creamos un objeto de tipo
BluetoothDevice en base a la dirección recibida.
• Lo añadimos a nuestra lista
BluetoothDeviceCollection.
• Al finalizar la búsqueda, llamada a
WSALookUpServiceEnd.
Siento no poder profundizar más sobre este tema. La explicación en
detalle del mecanismo de descubrimiento requeriría probablemente un
artículo íntegro debido a su complejidad. La codificación en .NET
es compleja y requiere de un ejercicio importante de
interoperabilidad con las API y de utilización de los cálculos de
referencia en cuanto a asignación dinámica de estructuras de
memoria y destrucción de las mismas. La utilización de lo que en un
marco de código nativo conocemos como estructuras debería ser
reemplazada por el uso de clases en código manejado. Otra opción es
manipularlo todo en una librería nativa, un entorno más propenso
para desarrollos a más bajo nivel. Lamentablemente, Microsoft no
ofrece un ejemplo de código específico, pero en la clase
BluetoothService, método PublishService, encontrará un ejemplo de
publicación de servicio en el que se tratan estructuras BLOB desde
.NET; también puede encontrar librerías totalmente funcionales que
implementan el descubrimiento de dispositivos como 32feet,
InTheHand o desarrolloMobile.NET.
Emparejar
El proceso de emparejado requiere del conocimiento de la dirección
del dispositivo Bluetooth con el que queremos emparejarnos y consta
de un secuencia de pasos. En primer lugar, establecemos nuestro PIN
mediante una llamada a BthSetPin. A continuación, creamos una
conexión ACL mediante BthCreateACLConnection (para una conexión SCO
se utiliza BthCreateSCOConnection), de donde obtendremos el
manejador (handle) de la conexión que utilizaremos. Luego debemos
llamar a la función BthAutenticate para forzar el emparejamiento.
Será en esta llamada donde el dispositivo remoto cuya dirección se
indica recibirá el aviso de petición de emparejamiento. Si el PIN
es correcto, el emparejamiento se realizará satisfactoriamente
(fuente 2). Por último, podemos cerrar la conexión.
En Windows Mobile 5, la función BthPairRequest engloba todas estas
llamadas en una, haciéndose cargo ella misma del establecimiento
del PIN, la conexión ACL, la autenticación y el cierre.
Recibir petición
de emparejamiento
En el otro extremo, y al hilo del anterior apartado, está el
dispositivo que recibe una petición de emparejamiento. Para obtener
la dirección del dispositivo que ha realizado una petición se
utiliza BthGetPINRequest. En caso de que queramos rechazar la
petición, nuestra función es BthRefusePinRequest.
Microsoft Bluetooth Stack incorpora un mecanismo de evento que
permite mediante una interfaz gráfica mostrar una petición entrante
por pantalla. Se trata de un notificador de tipo globo (balloon)
que permite al usuario aceptar o rechazar la petición. La función
encargada de esta llamada es BthSetSecurityUI.
Seguridad
En estos últimos apartados hemos visto diferentes contextos y
funciones que ofrece Bluetooth en cuanto a seguridad. Hay
dispositivos GPS Bluetooth por ejemplo, en los que no son
necesarios PIN para el emparejamiento. También cabe la posibilidad
de que dos aplicaciones se comuniquen por Bluetooth sin ningún tipo
de establecimiento de PIN. Bluetooth es, gracias al modelado de
frecuencia que utiliza, una tecnología muy segura, que hace muy
difícil interceptar comunicaciones ajenas. De todas formas, y más
aún en la utilización de servicios específicos de los dispositivos,
no está de más establecer un número PIN para evitar usos no
deseados.
Perfiles
Si la especificación nos muestra cómo trabaja Bluetooth, los
perfiles nos muestran cómo se utiliza Bluetooth, es decir, con qué
fin (figura 2).
Existe una gran cantidad de perfiles, en muchos casos dependientes
entre sí. Por ejemplo, Obex Push (OBject EXchange) depende del
Perfil de acceso genérico (Generic Access Profile), del Perfil de
puerto serie (Serial Port Profile) y del Perfil genérico de
intercambio de objetos (Generic Object Exchange Profile). Aún así,
a día de hoy siguen apareciendo nuevos perfiles debido al gran
impacto que está teniendo esta tecnología en el mundo y a las
nuevas utilidades que se le está dando.
Como se puede ver en la figura 2, podemos encontrar perfiles de
acceso a redes PAN, distribución de audio y vídeo, así como de
comunicación con manos libres, auriculares e intercambio de
objetos, entre otros.
Desarrollo (perfiles)
Hasta ahora tenemos todas las herramientas para poder detectar y
emparejar dispositivos desde código. Es hora utilizar algún
servicio. Pero antes debemos conectarnos. En este punto ambos
dispositivos se reconocerán mutuamente y podrán comunicarse entre
sí.
Es en este momento donde la utilización de uno u otro servicio
marca el alcance de nuestra aplicación. Evidentemente, éste será
distinto si lo que queremos es obtener información de un GPS,
redirigir voz a un manos libres, crear una plataforma de
intercambio de archivos o tarjetas de visita, etcétera.
Aquí presentaremos la conexión simple mediante sockets. En el
apartado dedicado a cómo descubrir dispositivos Bluetooth hablamos
de una clase Socket con unos parámetros específicos en el
constructor. Una clase Socket necesita de otra clase EndPoint
(punto terminal), que no es más que la dirección remota a la que
quiere conectarse. Pero como es habitual, la clase EndPoint para
Bluetooth tiene algunas particularidades. Dichas peculiaridades son
definidas por la estructura SOCKADDR_BTH:
En la que se indica la familia de direcciones que se utilizará, la
dirección remota (BTHADDR) del dispositivo a conectar, el servicio
a conectar (definido por un valor GUID) y el puerto remoto de
conexión. Si se indica cero como puerto remoto, el servicio
indicado obtendrá el puerto remoto a conectar, por lo cual aconsejo
mantener siempre dicho valor.
Los identificadores de servicio (GUID) están identificados en la
especificación Bluetooth del SIG (vea la clase StandardServices del
ejemplo de Microsoft). En cualquier caso, si no queremos utilizar
ningún servicio, digamos estándar, podemos crear un GUID propio
para que dos aplicaciones creadas por nosotros se comuniquen vía
sockets. Un ejemplo típico que podemos encontrar en el código de
Microsoft es una aplicación para chat.
Una vez que hayamos obtenido la dirección remota, la pasamos
mediante el método Connect conjuntamente con el GUID del servicio
deseado en forma de BluetoothEndPoint (clase heredada de EndPoint)
y, si todo es correcto, la conexión se establecerá. Observación: si
estudia la clase BluetoothEndPoint de Microsoft, verá dos métodos
reemplazados: Serialize y Create. Como hemos dicho, Bluetooth
utiliza la clase EndPoint siguiendo la estructura SOCKADDRBTH, con
lo que estos dos métodos contemplan, en un ámbito EndPoint, las
peculiaridades de Bluetooth.
Ahora lo que tenemos es una conexión por sockets; es decir, que
con dos aplicaciones a ambos lados de la conexión, utilizando los
métodos Write y Read de una clase de tipo Stream (System.IO),
obtenida de la conexión preestablecida, podremos enviar y
recibir datos. Incluso si el dispositivo remoto es un GPS, verá que
en la lectura se apreciarán fragmentos de información del estándar
NMEA (National Marine Electronics Association) remitidos por el
GPS. Pese a que esta técnica funciona, en algunos casos nos
interesará crear un puerto COM virtual para bien obtener o bien
enviar información al dispositivo (por ejemplo, si éste es un
teléfono móvil y queremos enviar los comandos AT para el envío de
un SMS, por ejemplo). En el ejemplo de Microsoft se puede ver cómo
el método Connect devuelve un NetworkStream, en el que nos
basaremos para las comunicaciones. Además, la clase
BluetoothService ofrece las posibilidades que brinda la clase
Socket en el contexto de Bluetooth (figura 3).
Conclusión
Este artículo es resultado del desarrollo de las librerías
desarrolloMobile.NET para interoperar con Bluetooth, tanto en la
versión para Windows Mobile como para Windows XP, que podrán
encontrar en [1]. Espero que el lector haya podido hacerse una idea
aproximada de cómo utilizar Bluetooth en sus aplicaciones. Como se
habrá podido apreciar, la implementación de Bluetooth en su
totalidad es muy extensa y algo difícil desde .NET, así que el
primer paso a dar es determinar cómo y con quién queremos
comunicarnos y qué perfil o servicio satisfará mejor esas
necesidades. Si se decide profundizar en el funcionamiento de
Bluetooth a través de Microsoft Bluetooth Stack, es aconsejable
tener conocimientos de programación con sockets; de todos modos, no
deja de ser muy satisfactorio desarrollar una aplicación utilizando
Bluetooth y sacar el máximo provecho a éste.
Quisiera agradecer a David Carmona de Microsoft Ibérica, a Peter
Foot (MVP Windows Mobile) y a Daniel Bouie y especialmente a Mikel
Zintel (.NET Compact Framework Group Manager) de Microsoft Corp.
por la ayuda e intercambio de impresiones que seguramente han
contribuido de forma positiva al desarrollo de este artículo.