Il y a quelques années, lorsque j'ai rencontré Bluetooth pour la première fois dans un projet de travail, j'ai trouvé cet article, qui a grandement aidé à comprendre comment cela fonctionne, pour trouver un point de départ. J'espère que cela sera utile pour les débutants.
À propos de l'auteur: Yoav Schwartz est l'un des principaux développeurs iOS de Donkey Republic, un système de partage de motards à Copenhague, s'efforçant de changer les attitudes envers le cyclisme. Ensuite, nous parlerons au nom de l'auteur.
Dans cet article, je parlerai des techniques pratiques pour travailler avec CoreBluetooth. Tout d'abord, sur Bluetooth Low Energy (BLE), car tout le monde n'est pas familier avec cette technologie, puis sur CoreBluetooth, le cadre d'Apple qui nous permet d'interagir avec les appareils BLE. Je vais également vous parler de certaines des astuces de développement que j'ai moi-même apprises en déboguant, en pleurant et en arrachant les cheveux de ma tête.
Bluetooth basse énergie
Pour commencer, qu'est-ce que le BLE? C'est un peu comme Bluetooth, que nous utilisons tous dans les haut-parleurs, les casques, etc., mais il y a une différence - ce protocole consomme très peu d'énergie. En règle générale, une seule charge de batterie pour un appareil compatible BLE peut durer des mois, voire des années (selon, bien sûr, la façon dont l'appareil est utilisé). Cela nous permet de faire des choses qui n'étaient pas disponibles auparavant pour le Bluetooth «normal». Ce standard est appelé Bluetooth 4.0, tout a commencé avec une technologie appelée Smart Bluetooth, qui est ensuite devenue BLE. Il y a un
manuel de 200 pages, vous pouvez lire l'heure du coucher, une lecture passionnante.
Le BLE est très économique en termes de consommation d'énergie, et le protocole lui-même n'est pas très complexe. Alors pourquoi ble? Comment pouvons-nous l'utiliser? Le tout premier et le plus courant exemple est un émetteur de fréquence cardiaque. En règle générale, cet appareil mesure et transmet votre fréquence cardiaque via le protocole. Il existe également toutes sortes de capteurs auxquels vous pouvez vous connecter via BLE et lire les données qu'ils collectent. Enfin, il existe des iBeacons qui peuvent vous indiquer la «proximité» d'un lieu. Entre guillemets, car sur les iPhones, Apple bloque la capacité de détecter les iBeacons en tant qu'appareils Bluetooth standard, nous devons donc travailler avec CoreLocation. En général, c'est l'Internet des objets: vous pouvez vous connecter à un téléviseur ou à un climatiseur et communiquer avec lui à l'aide de ce protocole.
Comment ça marche?
Nous avons un perifer - ce sont les appareils qui utilisent le protocole Bluetooth. Chaque périphérique a des services, il peut y en avoir n'importe quel nombre et chacun a des caractéristiques. Vous pouvez considérer le perifer comme un serveur. Avec toutes les conséquences qui en découlent: parfois, il s'éteint, parfois il faut du temps pour transférer des données, et parfois ces données ne viennent pas du tout.
En général, nous avons un service avec de nombreuses caractéristiques, chacune contenant une valeur, un type, etc. Pour travailler avec CoreBluetooth, vous n'avez pas besoin de tout savoir, le plus important est de lire les données. C'est ce que nous essayons d'obtenir, de modifier ou d'utiliser à nos propres fins. Nous avons besoin de ces données et de la connaissance de ce que nous pouvons en faire.
Voici une introduction rapide à BLE car il existe des milliers de ressources qui expliquent mieux que moi les fonctionnalités techniques.
Bluetooth de base
Le noyau Bluetooth a été introduit par Apple il y a longtemps, dans iOS 5. Apple a commencé à travailler sur l'introduction du BLE dans ses appareils beaucoup plus tôt qu'Android et la popularité croissante de la technologie. De nombreux développeurs utilisent ce cadre dans leurs applications, dans l'ensemble, il ne s'agit que d'un wrapper, car les protocoles BLE eux-mêmes sont assez complexes. Pas vraiment, mais croyez-moi, ce n'est pas quelque chose avec lequel j'aimerais travailler tous les jours. Comme beaucoup d'autres choses, Apple l'a enveloppé dans un bel emballage pratique, vous permettant d'utiliser des termes que nous, les développeurs stupides, pouvons comprendre.
Maintenant, c'est au tour de vous dire ce que vous devez vraiment savoir sur les classes impliquées dans la communication avec le framework. Notre acteur principal est CBCentralManager, créez-le:
manager = CBCentralManager(delegate:self, queue:nil, options: nil)
Ci-dessus, nous avons créé un nouveau manager, en indiquant son délégué, sinon nous ne pourrons pas l'utiliser. Nous indiquons également la file d'attente, dans notre cas nulle, ce qui signifie que toutes les communications avec le gestionnaire seront effectuées sur la file d'attente principale.
Vous devez comprendre exactement ce que vous allez faire - l'utilisation d'une file d'attente séparée compliquera l'application, mais, bien sûr, les utilisateurs vous aimeront davantage. Si vous prévoyez de communiquer avec un seul appareil, vous ne pouvez pas déranger et utiliser la file d'attente principale. Si vous souhaitez toujours expérimenter, créez une file d'attente, spécifiez-la dans le constructeur et n'oubliez pas de revenir à la principale avant d'utiliser les résultats dans un autre endroit.
Les options Il n'y a rien de particulièrement intéressant ici, peut-être l'essentiel - lorsque vous créez un gestionnaire et que le Bluetooth est désactivé pour l'utilisateur - l'application lui en parlera, mais presque tout le monde clique sur "OK" (qui n'inclut pas vraiment le Bluetooth), c'est pourquoi j'utilise également cette option Je n'utilise pas.
Tout d'abord, après avoir créé le gestionnaire, le délégué appelle la méthode:
func centralManagerDidUpdateState(_ central: CBCentralManager)
Nous obtenons donc une réponse du matériel - que l'utilisateur ait activé Bluetooth ou non.
Premier conseil: le gestionnaire est inutile jusqu'à ce que nous obtenions une réponse indiquant que le Bluetooth est activé, son état est. D'autres états peuvent être utilisés uniquement pour demander à l'utilisateur d'activer le Bluetooth.
Recherche d'appareils
Maintenant que notre manager fonctionne correctement, nous pouvons regarder,
ce qui nous entoure (après avoir reçu l'état .PoweredOn - nous appelons la fonction scanForPeripheralsWithServices :)
manager.scanForPeripheralsWithServices([CBUUID], options: nil)
En ce qui concerne les services, il s'agit d'un tableau de CBUUID (une classe qui est un identifiant unique universel 128 bits pour les attributs utilisés par Bluetooth Low Energy approximativement par.), Que nous utilisons comme filtre pour trouver des appareils avec uniquement cet ensemble d'UID, c'est une pratique courante dans CoreBluetooth .
Si vous passez nil en argument, nous pouvons voir tous les appareils autour. Pour les performances, bien sûr, il est préférable de spécifier un tableau des paramètres dont nous avons besoin, mais dans le cas où vous ne les connaissez pas, rien de terrible ne se passera si vous passez à zéro, personne ne meurt.
Depuis que nous avons lancé la recherche d'appareils, nous devons l'arrêter. Sinon, la recherche se poursuivra et déchargera la batterie de l'utilisateur jusqu'à ce que nous l'arrêtions. Dès que nous trouverons le bon appareil, ou que la nécessité de la recherche disparaîtra, nous arrêterons:
manager.stopScan()
Chaque fois qu'un nouveau périphérique est découvert, le délégué du gestionnaire appellera la fonction didDiscoverPeripheral dans la file d'attente que nous avons spécifiée lors de son initialisation. La fonction nous transmet le périphérique trouvé (périphérique), des informations à ce sujet (AdvertisementData - quelque chose que les développeurs de puces ont décidé de montrer à chaque fois) et le niveau de signal RSSI relatif en décibels.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
Deuxième astuce: gardez toujours un lien fort avec le perifère détecté. Si cela n'est pas fait, le système décidera que nous n'avons pas besoin de l'appareil trouvé et le rejettera. Elle se souviendra de lui, mais nous n'aurons plus accès à lui. Sinon, nous ne pourrons pas travailler avec l'appareil.
Connexion d'appareil
Nous avons trouvé l'appareil qui nous intéresse - voici comment venir à une fête et voir une jolie fille. Nous voulons nous connecter, nous appelons la fonction connectPeripheral - nous proposons «acheter un verre». Ainsi, nous essayons de nous connecter à l'appareil souhaité (périphérique), et il peut nous dire «oui» ou «non», mais notre iPhone est vraiment bon, nous entendrons donc une réponse positive.
manager.connectPeripheral(peripheral, options: nil)
Ici, nous nous sommes tournés vers le gestionnaire qui est responsable des connexions, nous lui disons à quel appareil particulier nous nous connectons, et encore une fois nous donnons zéro aux options (si vous êtes vraiment très intéressé à en apprendre davantage sur les options, lisez la documentation, mais vous pouvez généralement vous en passer). Lorsque vous avez fini de travailler avec l'appareil, vous pouvez vous déconnecter de celui-ci, eh bien, vous savez, le matin - cancelPeripheralConnection:
//called to cancel and/or disconnect manager.cancelPeripheralConnection(peripheral)
Après nous être connectés ou déconnectés, le délégué nous en informera:
//didConnect func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) //didDisconnect func centralManager(central: CBCentralManager!, didDisconnectPeripheral peripheral: CBPeripheral!, error: NSError!)
Maintenant, deux autres conseils importants. Le protocole Bluetooth suppose un délai de connexion, mais Apple s'en fiche. iOS essaiera de se connecter encore et encore et ne s'arrêtera pas tant que vous n'aurez pas appelé cancelPeripheralConnection. Ce processus peut prendre trop de temps, il est donc nécessaire de le limiter dans le temps, et si, au final, nous ne recevons pas de message de connexion réussi (didConnectPeripheral), nous devons informer l'utilisateur que quelque chose s'est mal passé.
Si vous ne conservez pas de lien solide avec le périphérique, iOS réinitialisera simplement la connexion. De son point de vue, cela signifie que vous n'en avez pas besoin, et le maintenir est une tâche plutôt énergivore pour la batterie, et nous savons comment Apple se rapporte à la consommation d'énergie.
Rendons l'appareil utile
Et donc, nous nous sommes connectés à l'appareil, faisons quelque chose avec. Plus tôt, j'ai mentionné les services et fonctionnalités, les valeurs qu'ils contiennent, c'est ce dont nous avons besoin. Maintenant que nous avons un appareil, il est connecté et nous pouvons obtenir ses services en appelant périphérique.discoverServices.
peripheral.discoverServices(nil) func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) peripheral.services
Cela peut sembler un peu déroutant, mais le délégué est appelé sur le thread que nous avons défini lors de la création du gestionnaire, bien qu'il s'agisse d'un délégué à la périphérie. Autrement dit, le système se souvient du flux avec lequel il fonctionne, et toutes nos communications Bluetooth ont lieu sur ce flux. Il est important de ne pas oublier de revenir au principal si vous ne l'avez pas utilisé.
Nous avons obtenu les services, mais nous n'avons toujours rien avec qui travailler. Ensuite, vous devez appeler périphérique.discoverCharacteristics, le délégué nous donnera toutes les caractéristiques disponibles pour les services reçus dans didDiscoverCharacteristicsForService. Maintenant, nous pouvons lire les valeurs,
qui y sont contenus (readValueForCharacteristic) ou pour demander à nous informer dès que quelque chose y change - 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!)
Contrairement Ă Android, Apple ne fait pas de distinction entre la lecture et la notification. Autrement dit, nous ne savons pas ce qui se passe - nous lisons quelque chose sur l'appareil ou cet appareil nous dit quelque chose.
Enregistrement sur un appareil
Nous avons un appareil, nous en lisons des informations, nous le gérons. Donc, nous pouvons enregistrer des informations dessus, en règle générale, - NSData ordinaire. Il vous suffit de savoir ce que cet appareil attend de nous et ce qu'il acceptera.
La plupart des appareils BLE sont livrés avec une spécification, une sorte d'API à partir de laquelle il est clair comment «communiquer» avec eux. Vous pouvez extraire des données des caractéristiques pour avoir au moins une idée approximative de ce que l'appareil attend de nous.
À partir des spécifications, nous apprenons dans quelles caractéristiques quelles propriétés nous lisons et dans lesquelles nous écrivons, si nous serons informés des modifications (isNotifying). Plus souvent qu'autrement, nous trouverons ici tout ce qui est nécessaire pour travailler.
peripheral.writeValue(data: NSData!, forCharacteristic: CBCharacteristic!, type: CBCharacteristicWriteType) characteristic.properties - OptionSet type characteristic.isNotifying func peripheral(peripheral: CBPeripheral!, didWriteValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!)
Au cours du processus d'enregistrement, le délégué nous informera que tout s'est bien passé (didWriteValueForCharacteristics), que la valeur souhaitée a été mise à jour et que nous pouvons en informer l'utilisateur ou utiliser ces informations différemment.
Nous considérons le sujet dans une section très étroite, en nous appuyant sur la mise en œuvre d'Apple, il y a donc un certain nombre de problèmes qui devront être rencontrés. Par exemple, une très forte dépendance vis-à -vis de la délégation, si chère Apple.
Héritage de CBPeripheral? Si tout était si facile
Il semblerait que puisque nous avons un appareil, nous pouvons commencer à l'utiliser, mais en fait, il ne nous dira rien sur lui-même. Peut-être que nous voulons contrôler la serrure, la climatisation ou le capteur de fréquence cardiaque. Vous devez savoir avec quel appareil nous communiquons.
Cela ressemble à un héritage: nous avons un cas particulier de quelque chose en commun. D'après mon expérience, je peux dire que lorsque vous utilisez l'héritage, quelque chose ne fonctionnera pas du tout comme prévu, quelque chose ne fonctionnera pas du tout, et vous ne saurez pas pourquoi. En général, je vous déconseille l'idée d'hériter de CBPeripheral. Que faire?
Je vous conseille d'ajouter CBPeripheral au constructeur de l'objet qui va le gérer. Cela l'encapsule dans cette classe. Utilisez-le pour interagir avec l'appareil, gardez un lien fort vers lui afin qu'iOS ne coupe pas la connexion. Mais la chose la plus importante est que cette classe sera utilisée en tant que délégué, sinon il sera difficile de gérer tous les appareils en un seul endroit, ce qui menace un tas d'autres.
Se connecter et travailler avec CBPeripheralDelegate
Et donc nous nous connectons à l'appareil et voulons être CBPeripheralDelegate. Il y a une autre nuance: pendant que vous travaillez avec l'appareil, en «interrogeant» ses services et ses caractéristiques, en leur lisant et en leur écrivant, presque toutes les communications ont lieu avec le périphérique. Tout sauf la connexion.
Naturellement, nous aimerions concentrer toutes les communications en un seul endroit, mais le gestionnaire doit être conscient de ce qui se passe avec l'appareil. Et la difficulté est d'avoir une source de vérité, pour s'assurer que tout le monde est informé en temps opportun de ce qui se passe avec l'appareil. Pour ce faire, nous surveillerons l'état du périphérique - il peut changer de déconnecté, connecté et connecté. Il vous informe toujours de la situation actuelle. Reste à souscrire à un changement d'état dans notre centre de contrôle, dont j'ai parlé plus tôt, cela permettra de communiquer avec l'appareil depuis un seul endroit.
Proximité
Un point très important, car il est difficile de trouver une documentation normale sur ce sujet. Dans le cas d'Apple et de leurs iBeacons, tout est simple, ils nous disent à quel point nous sommes proches de l'appareil Bluetooth.
Malheureusement, nous ne disposons pas d'un moyen aussi simple de travailler avec des appareils tiers. Et plus d'une fois, il s'est avéré nécessaire de déterminer l'appareil le plus proche. Il est également difficile de comprendre si l'appareil est dans la plage disponible ou non. Parfois, lors de la recherche de périphériques, il peut vous faire connaître une seule fois et disparaître, puis une tentative de connexion sera infructueuse.
Nous utilisons la méthode suivante: enregistrer une pile avec des étiquettes de date et d'intensité du signal (RSSI) pour chaque message reçu dans DiscoverPeripheral. Si quelqu'un rencontre CoreLocation, notre méthode est similaire à la façon dont les horodatages et les coordonnées correspondantes y sont stockés. En règle générale, plus le signal (RSSI) est élevé, plus l'appareil est proche. Il est plus difficile de comprendre si un appareil est dans une plage accessible ou non, en partie parce que ce concept lui-même est assez flexible. Pour cela, j'utilise la moyenne pondérée du signal. Gardez à l'esprit que la force du signal de l'appareil connecté doit être demandée manuellement chaque fois que vous en avez besoin.
Et ensuite?
Malheureusement, cet article ne fera pas de vous un expert si vous le lisez également.
c'est devenu intéressant - faites attention au
Guide de programmation CoreBluetooth d'
Apple , le guide n'est pas très grand, mais très utile. Il y a encore quelques émissions de la WWDC 2012 (de
base et
avancées ) et une de
2013 , mais ne vous inquiétez pas, peu de choses ont changé depuis.
Il y a aussi une vidéo d'
Altconf 2015 publiée sur le site Web de Realm, qui partage l'expérience de John Sher, un gars formidable et spécialiste.