Hace un par de años, cuando encontré por primera vez Bluetooth en un proyecto en funcionamiento, encontré este artículo, que me ayudó mucho a entender cómo funciona, a encontrar un punto de "inicio". Espero que sea útil para principiantes.
Sobre el autor: Yoav Schwartz es un desarrollador líder de iOS en Donkey Republic, un sistema de intercambio de moteros en Copenhague, que se esfuerza por cambiar las actitudes hacia el ciclismo. A continuación, hablaremos en nombre del autor.
En este artículo, hablaré sobre técnicas prácticas para trabajar con CoreBluetooth. Primero sobre Bluetooth Low Energy (BLE) porque no todos están familiarizados con esta tecnología, luego sobre CoreBluetooth, el marco de trabajo de Apple que nos permite interactuar con dispositivos BLE. También te contaré algunos de los trucos de desarrollo que aprendí yo mismo mientras depuraba, lloraba y me arrancaba el pelo de la cabeza.
Bluetooth de baja energía
Para empezar, ¿qué es BLE? Es algo así como Bluetooth, que todos usamos en altavoces, auriculares, etc., pero hay una diferencia: este protocolo consume muy poca energía. Por lo general, una sola carga de batería para un dispositivo habilitado para BLE puede durar meses o incluso años (dependiendo, por supuesto, de cómo se use el dispositivo). Esto nos permite hacer cosas que antes no estaban disponibles para Bluetooth "normal". Este estándar se llama Bluetooth 4.0, todo comenzó con una tecnología llamada Smart Bluetooth, que luego se convirtió en BLE. Hay un
manual de 200 páginas, puede leer antes de acostarse, lectura emocionante.
BLE es muy económico en términos de consumo de energía, y el protocolo en sí no es muy complejo. Entonces, ¿por qué ble? ¿Cómo podemos usarlo? El primer y más común ejemplo es un sensor de frecuencia cardíaca. Por lo general, este dispositivo mide y transmite su frecuencia cardíaca a través del protocolo. También hay todo tipo de sensores a los que puede conectarse a través de BLE y leer los datos que recopilan. Finalmente, hay iBeacons que pueden decirle "proximidad" a un lugar. Entre comillas porque en los iPhone Apple bloquea la capacidad de detectar iBeacons como dispositivos Bluetooth normales, por lo que tenemos que trabajar con CoreLocation. En general, este es el Internet de las cosas: puede conectarse a un televisor o aire acondicionado y comunicarse con él mediante este protocolo.
Como funciona
Tenemos un perifer: estos son los dispositivos que usan el protocolo Bluetooth. Cada periférico tiene servicios, puede haber cualquier número de ellos y cada uno de ellos tiene características. Puede considerar el perifer como un servidor. Con todas las consecuencias resultantes: a veces se apaga, a veces lleva tiempo transferir datos, y a veces estos datos no llegan en absoluto.
En general, tenemos un servicio con muchas características, cada una de las cuales contiene un valor, tipo, etc. Para trabajar con CoreBluetooth, no necesita saberlo todo, lo más importante es leer los datos. Esto es lo que estamos tratando de obtener, modificar o usar para nuestros propios fines. Necesitamos estos datos y conocimiento de lo que podemos hacer con ellos.
Aquí hay una introducción rápida a BLE porque hay miles de recursos que explican las características técnicas mejor que yo.
Núcleo bluetooth
Apple introdujo Core Bluetooth hace mucho tiempo, en iOS 5. Apple comenzó a trabajar para introducir BLE en sus dispositivos mucho antes que Android y la creciente popularidad de la tecnología. Muchos desarrolladores usan este marco en sus aplicaciones, en general es solo un contenedor, ya que los protocolos BLE son bastante complejos. En realidad no, pero créeme, esto no es algo con lo que me gustaría trabajar todos los días. Al igual que muchas otras cosas, Apple lo envolvió en un paquete hermoso y conveniente, permitiéndole usar términos que todos nosotros, los estúpidos desarrolladores, podemos entender.
Ahora es el turno de decirle lo que realmente necesita saber sobre las clases involucradas en la comunicación con el marco. Nuestro actor principal es CBCentralManager, créalo:
manager = CBCentralManager(delegate:self, queue:nil, options: nil)
Arriba, creamos un nuevo administrador, que indica su delegado, de lo contrario no podremos usarlo. También indicamos la cola, en nuestro caso nula, lo que significa que toda la comunicación con el gerente se llevará a cabo en la cola principal.
Debe comprender qué es exactamente lo que va a hacer: usar una cola separada complicará la aplicación, pero, por supuesto, los usuarios lo amarán más. Si planea comunicarse con un solo dispositivo, no puede molestarse y usar la cola principal. Si aún desea experimentar, cree una cola, especifíquela en el constructor y no olvide volver a la principal antes de utilizar los resultados en otro lugar.
Opciones No hay nada particularmente interesante aquí, tal vez lo principal: cuando crea un administrador y Bluetooth está desactivado para el usuario, la aplicación le informará al respecto, pero casi todo el mundo hace clic en "Aceptar" (que en realidad no incluye Bluetooth), por eso también estoy usando esta opción Yo no uso
En primer lugar, después de crear el administrador, el delegado llama al método:
func centralManagerDidUpdateState(_ central: CBCentralManager)
Entonces obtenemos una respuesta del hardware, ya sea que el usuario tenga Bluetooth habilitado o no.
Primer consejo: el administrador es inútil hasta que obtengamos una respuesta de que bluetooth está activado, su estado es. PoweredOn. Otros estados solo se pueden usar para pedirle al usuario que active Bluetooth.
Búsqueda de dispositivos
Ahora que nuestro gerente está funcionando correctamente, podemos ver,
lo que nos rodea (después de recibir el estado .PoweredOn - llamamos a la función scanForPeripheralsWithServices :)
manager.scanForPeripheralsWithServices([CBUUID], options: nil)
En cuanto a los servicios, esta es una matriz CBUUID (una clase que es un identificador único universal de 128 bits para los atributos utilizados por Bluetooth Low Energy aprox. Per.), Que utilizamos como filtro para encontrar dispositivos con solo este conjunto de UID, esta es una práctica común en CoreBluetooth .
Si pasa nil como argumento, podemos ver todos los dispositivos. Para el rendimiento, por supuesto, es mejor especificar una matriz de los parámetros que necesitamos, pero en el caso de que no los conozca, no pasará nada terrible si pasa cero, nadie muere.
Desde que lanzamos la búsqueda de dispositivos, debemos detenerlo. De lo contrario, la búsqueda continuará y aterrizará la batería del usuario hasta que la detengamos. Tan pronto como encontremos el dispositivo correcto, o la necesidad de búsqueda desaparezca, dejaremos de:
manager.stopScan()
Cada vez que se descubre un nuevo dispositivo, el delegado del administrador llamará a la función didDiscoverPeripheral en la cola que especificamos cuando se inicializó. La función nos transmite el dispositivo encontrado (periférico), información sobre él (publicidadData, algo que los desarrolladores de chips decidieron mostrar cada vez) y el nivel relativo de señal RSSI en decibelios.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
Segundo consejo: mantenga siempre un vínculo fuerte con el perifer detectado. Si esto no se hace, el sistema decidirá que no necesitamos el dispositivo encontrado y lo descartará. Ella lo recordará, pero ya no tendremos acceso a él. De lo contrario, no podremos trabajar con el dispositivo.
Conexión del dispositivo
Encontramos el dispositivo que nos interesa: así es como ir a una fiesta y ver a una chica bonita. Queremos conectarnos, llamamos a la función connectPeripheral: ofrecemos "comprar una bebida". Por lo tanto, tratamos de conectarnos al dispositivo deseado (periférico), y puede decirnos "sí" o "no", pero nuestro iPhone es realmente bueno, por lo que escucharemos una respuesta positiva.
manager.connectPeripheral(peripheral, options: nil)
Aquí recurrimos al gerente responsable de las conexiones, le decimos a qué dispositivo en particular nos estamos conectando, y nuevamente damos cero a las opciones (si realmente está muy interesado en conocer las opciones, lea la documentación, pero generalmente puede prescindir de ellas). Cuando termine de trabajar con el dispositivo, puede desconectarse de él, bueno, ya sabe, en la mañana - cancelPeripheralConnection:
//called to cancel and/or disconnect manager.cancelPeripheralConnection(peripheral)
Después de conectarnos o desconectarnos, el delegado nos informará de esto:
//didConnect func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) //didDisconnect func centralManager(central: CBCentralManager!, didDisconnectPeripheral peripheral: CBPeripheral!, error: NSError!)
Ahora, dos consejos más importantes. El protocolo Bluetooth supone un tiempo de espera de conexión, pero a Apple no le importa. iOS intentará conectarse una y otra vez y no se detendrá hasta que llame a cancelPeripheralConnection. Este proceso puede llevar demasiado tiempo, por lo que es necesario limitarlo a tiempo, y si, al final, no recibimos un mensaje de conexión exitoso (didConnectPeripheral), debemos informar al usuario que algo salió mal.
Si no mantiene un enlace fuerte al periférico, iOS simplemente restablecerá la conexión. Desde su punto de vista, esto significará que no lo necesita, y mantenerlo es una tarea bastante intensiva en energía para la batería, y sabemos cómo Apple se relaciona con el consumo de energía.
Hagamos que el dispositivo sea útil
Y así, nos conectamos al dispositivo, hagamos algo con él. Anteriormente, mencioné los servicios y características, los valores que contienen, eso es lo que necesitamos. Ahora tenemos un dispositivo, está conectado y podemos obtener sus servicios llamando a periferral.discoverServices.
peripheral.discoverServices(nil) func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) peripheral.services
Ahora sonará un poco confuso, pero se llama al delegado en el hilo que definimos al crear el administrador, a pesar de que este es un delegado en la periferia. Es decir, el sistema recuerda con qué transmisión funciona, y toda nuestra comunicación Bluetooth tiene lugar en esa transmisión. Es importante no olvidar volver al principal si no lo usó.
Recibimos los servicios, pero todavía no tenemos nada con qué trabajar. A continuación, debe llamar a peripral.discoverCharacteristics, el delegado nos dará todas las características disponibles para los servicios recibidos en didDiscoverCharacteristicsForService. Ahora podemos leer los valores,
que están contenidos allí (readValueForCharacteristic) o para pedirnos que nos informen tan pronto como algo cambie allí: setNotifyValue.
peripheral.discoverCharacteristics(nil, forService: (service as CBService)) func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) peripheral.readValueForCharacteristic(characteristic) peripheral.setNotifyValue(true, forCharacteristic: characteristic) func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!)
A diferencia de Android, Apple no distingue entre lectura y notificación. Es decir, no sabemos lo que está sucediendo: estamos leyendo algo del dispositivo o este dispositivo nos está diciendo algo.
Grabar en un dispositivo
Tenemos un dispositivo, leemos información de él, lo gestionamos. Por lo tanto, podemos registrar información sobre él, como regla, - NSData ordinario. Solo necesita averiguar qué espera este dispositivo de nosotros y qué será aceptado por él.
La mayoría de los dispositivos BLE vienen con una especificación, un tipo de API desde la cual está claro cómo "comunicarse" con ellos. Puede extraer datos de las características para obtener al menos una idea aproximada de lo que el dispositivo espera de nosotros.
A partir de las especificaciones, aprendemos en qué características, qué propiedades leemos y en qué escribimos, si seremos notificados de los cambios (isNotifying). La mayoría de las veces, aquí encontraremos todo lo que se requiere para trabajar.
peripheral.writeValue(data: NSData!, forCharacteristic: CBCharacteristic!, type: CBCharacteristicWriteType) characteristic.properties - OptionSet type characteristic.isNotifying func peripheral(peripheral: CBPeripheral!, didWriteValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!)
Durante el proceso de grabación, el delegado nos informará que todo salió bien (didWriteValueForCharacteristics), que se actualizó el valor deseado y que podemos informar al usuario al respecto o usar esta información de manera diferente.
Consideramos el tema en una sección muy estrecha, confiando en la implementación de Apple, por lo que hay una serie de problemas que deberán enfrentarse. Por ejemplo, una dependencia muy fuerte de la delegación, tan querida Apple.
¿Herencia de CBP periférico? Si todo fuera tan fácil
Parece que, dado que tenemos un dispositivo, podemos comenzar a usarlo, pero de hecho no nos dirá nada sobre sí mismo. Quizás queremos controlar la cerradura, el aire acondicionado o el sensor de frecuencia cardíaca. Necesita saber con qué dispositivo nos estamos comunicando.
Parece herencia: tenemos un caso especial de algo en común. Según mi experiencia, puedo decir que cuando se usa la herencia, algo no funcionará en absoluto como se esperaba, algo no funcionará en absoluto y no sabrá por qué. En general, le advertiría contra la idea de heredar CBPeripheral. Que hacer
Le aconsejo que agregue CBPeripheral al constructor del objeto que lo administrará. Esto lo encapsula dentro de esta clase. Úselo para interactuar con el dispositivo, mantenga un enlace fuerte para que iOS no rompa la conexión. Pero lo más importante es que esta clase se utilizará como delegado, de lo contrario será difícil administrar todos los dispositivos en un solo lugar, lo que amenaza un montón de otros.
Conexión y trabajo con CBPeripheralDelegate
Y así nos conectamos al dispositivo y queremos ser CBPeripheralDelegate. Hay un matiz más: mientras trabaja con el dispositivo, "interrogando" sus servicios y características, leyéndoles y escribiéndoles, casi toda la comunicación se realiza con el periférico. Todo excepto la conexión.
Naturalmente, nos gustaría concentrar toda la comunicación en un solo lugar, pero el gerente debe estar al tanto de lo que sucede con el dispositivo. Y la dificultad es tener una fuente de verdad, para asegurarse de que todos estén informados de manera oportuna sobre lo que está sucediendo con el dispositivo. Para hacer esto, supervisaremos el estado del periférico; puede cambiar de desconectado, conectado y conectado. Siempre te informa sobre la situación actual. Queda por suscribirse a un cambio de estado en nuestra instalación de control, de la que hablé anteriormente, esto permitirá comunicarse con el dispositivo desde un solo lugar.
Proximidad
Un punto muy importante, ya que es difícil encontrar documentación normal sobre este tema. En el caso de Apple y sus iBeacons, todo es simple, nos dicen lo cerca que estamos del dispositivo bluetooth.
Desafortunadamente, no se nos brinda una forma tan fácil de trabajar con dispositivos de terceros. Y más de una vez sucedió que era necesario determinar el dispositivo más cercano. También es difícil entender si el dispositivo está dentro del rango disponible o no. A veces, cuando busca dispositivos, puede informarle sobre sí mismo solo una vez y desaparecer, luego el intento de conexión no tendrá éxito.
Utilizamos el siguiente método: guardar una pila con etiquetas de fecha y potencia de señal (RSSI) para cada mensaje recibido en discoverPeripheral. Si alguien se encuentra con CoreLocation, nuestro método es similar a cómo las marcas de tiempo y las coordenadas correspondientes se almacenan allí. Por lo general, cuanto mayor es la señal (RSSI), más cerca está el dispositivo. Es más difícil entender si un dispositivo está dentro de un rango accesible o no, en parte porque este concepto en sí es bastante flexible. Para esto, uso el promedio ponderado de la señal. Tenga en cuenta que la intensidad de la señal del dispositivo conectado debe solicitarse manualmente cada vez que necesite saberlo.
Que sigue
Desafortunadamente, este artículo no lo convertirá en un experto si también se lo lee.
se volvió interesante: preste atención a
la Guía de programación CoreBluetooth de Apple , la guía no es muy grande, pero es muy útil. Todavía hay un par de transmisiones de WWDC 2012 (
básico y
avanzado ) y una de
2013 , pero no te preocupes, poco ha cambiado desde entonces.
También hay un video de
Altconf 2015 publicado en el sitio web de Realm, que comparte la experiencia de John Sher, un gran tipo y especialista.