Nébuliseur (ou atomiseur) à ultrasons

Récemment, j'ai pu jouer avec un nouvel appareil : un nébuliseur (ou atomiseur) d'eau. Ce sont ces humidificateurs qui créent une petite brume d'eau. Il est possible d'ajouter quelques gouttes de parfum, par exemple sous forme d'huile essentielle pour parfumer l'atmosphère en même temps.

Celui-ci est un peu particulier puisqu'il est pilotable par Bluetooth, au vu de ses fonctionnalités :

Principe de fonctionnement

Il y a plusieurs mécanismes intéressants mis en jeu dans cet appareil. Chacune des parties ci-après va brièvement expliquer le fonctionnement.

Nébuliseur à ultrasons

Ce dispositif créé véritablement une fumée de gouttelettes d'eau (et non de la vapeur d'eau car l'eau n'est pas sous forme de gaz), qui sont projeté en l'air. Pour faire jaillir des gouttelettes de taille microscopique, cet appareil utilise un transducteur piézo-électrique à ultrasons. Typiquement, pour ce genre d'application, il s'agit de petits disques en céramique qui valent quelques euros pièce maximum.

L'appareil en question a en plus un petit ventilateur pour forcer l'extraction du brouillard par la sortie.

Enceinte Bluetooth

Cet appareil présente en fait deux facettes lors d'un scan Bluetooth (Aromamusic et Aroma31) :

L'un est pour la partie de contrôle de l'appareil, et l'autre est exclusivement dédié à l'audio. Du coup, l'application fournie par le fabricant est non-nécessaire pour ce qui est de la partie musicale.

Commande Bluetooth

L'application de télécommande est téléchargeable pour Android et iOS. Elle est relativement simple, ce qui est compréhensible au vu du peu de fonctionnalités offertes. Néanmoins, elle n’est pas très stable, et pose parfois des soucis de fiabilité en témoigne les avis sur le Play Store.

💡
Parenthèse vie privée : il s'agit d'une application presque exemplaire : elle n'accède pas d'accès à Internet (et donc, pas de compte à créer). Pour ce qui est des permissions, en voici la liste :

  • Localisation : nécessaire pour utiliser le Bluetooth, car en connaissant le lieu d'émetteurs Bluetooth déterminés, il est possible de savoir où est l'utilisateur, et ce sans GPS
    • ACCESS_COARSE_LOCATION
    • ACCESS_FINE_LOCATION
  • Bluetooth : pour l'interaction avec la puce Bluetooth (allumage, association, scan, communication, etc.)
    • BLUETOOTH
    • STATE_CHANGED
    • BLUETOOTH_ADMIN
  • Stockage : absolument inutile dans ce cas
    • READ_EXTERNAL_STORAGE
    • WRITE_EXTERNAL_STORAGE
  • État et identité du téléphone : absolument inutile encore une fois
    • READ_PHONE_STATE

Avec l'utilisation de l'excellente application nRF Connect, il s'avère que la commande sur fait donc avec l'appareil Aroma31, avec une communication Bluetooth Low Energy (LE only sur la capture d'écran ci-dessous). Effectivement, il y a très peu de données à transmettre, c'est une utilisation tout à fait adéquate.

Une fois connecté, nous pouvons voir les différents services Bluetooth mis à disposition, et pour chacun d'eux, chaque caractéristique (voir ça comme un canal de communication thématique, par exemple) :

Nous y voyons 3 services, avec peu de caractéristiques :

  1. Generic Access (défini par le standard Bluetooth) (0x1800) : profil Bluetooth de base (GAP)
    • Device Name (0x2A00) : le nom de l'appareil
  2. Generic Attribute (lui aussi standard) : profil Bluetooth permettant de définir ses propres attributs sous forme de services et caractéristiques (GATT)
    • Service Changed (0x2A05) : permet de notifier des modifications de services et caractéristiques lors de l'utilisation. Ici, en pratique, ce n'est pas utilisé
  3. Unknown service (0xAE00) : service créé par l'appareil pour pouvoir être télécommandé. J'ai choisi de le nommer Aroma Service
    • Unknown characteristic (0xAE01) en écriture uniquement : permet d'envoyer des données, sans en recevoir. C'est donc pour envoyer les commandes à l'appareil. Je l'ai renommée Command
    • Unknown characteristic (0xAE02) en lecture et notification : permet de recevoir en continu des données, dès qu'elles sont envoyées par l'appareil. C'est donc le canal utilisé pour dire à l'app quelle est la configuration actuelle de l'appareil. Je l'ai renommée State

Analyse de la communication

Nous savoir quoi chercher et où, reste donc à avoir un enregistrement de la communication, et des outils pour l'analyser. Nous allons commencer à faire de l'ingénierie inverse (reverse-engineering) du protocole de communication.

Capture des paquets Bluetooth HCI

Maintenant qu'on sait quoi chercher (une communication Bluetooth Low Energy), on va pouvoir interagir avec le brumisateur via l'app, et demander à Android de conserver l'historique de toute l'interaction avec l'adaptateur Bluetooth (HCI). Pour cela, on va utiliser l'application pour générer un trafic suffisamment spécifique pour tenter d'identifier quel paquet de la capture correspond à quelle action. Après avoir activé les options développeur d'Android et installé ADB pour communiquer avec le téléphone, on va demander gentiment l'historique :

ℹ️
adb bugreport

On se retrouve avec une archive zip qu'on va extraire, pour aller chercher le fichier FS/data/log/bt/btsnoop_hci.log (dans mon cas, ça peut changer en fonction du téléphone utilisé).

Préparation de l'analyse avec Wireshark

Le fichier de capture récupéré lors de l'étape précédente est un fichier de capture au format pcap. On va pouvoir l'étudier avec Wireshark. La première chose après avoir ouvert le fichier est à faire à mon avis de mettre un peu de couleur en changeant le profil :

Maintenant, dans la mesure où l'on sait que toute la communication se fait avec l'Aroma Service 0xAE00, on va filtrer uniquement ce qu'on veut :

ℹ️
Comment trouver la syntaxe pour faire un filtre ? Très bonne question, qui n'est pas triviale quand on ne le sait pas. Il faut commencer par trouver un paquet qui nous intéresse. Dans le panneau d'information sur le paquet, sélectionner l'élément qui sera utilisé pour le filtrage. Par exemple, pour filtrer les écritures sur une caractéristique, j'ai sélectionné Write Request, puis dans le menu contextuel, choisir Copy -> As Filter :

Dernier point pour faciliter l'étude des messages envoyés : les afficher dans une nouvelle colonne dans la liste des paquets. Pour cela, on ouvre le menu contextuel des têtes de colonnes et choisir Columns Preferences.... Avec le bouton +, créer une nouvelle colonne avec comme titre Value (par exemple), de type Custom, qui affiche le champ field btatt.value.

Analyse du trafic

J'ai commencé par une séquence Allumage - Extinction - Allumage - Extinction - Allumage, sans chercher à être particulièrement rapide ni lent. Très rapidement, il semble y avoir ce motif qui se dégage :

Les trois groupes de trois lignes sélectionnés sont probablement l'allumage de l'appareil, avec les deux groupes de 3 lignes intermédiaires correspondant à l'extinction, car le message envoyé (colonne Value) se répète. La colonne Delta affiche le temps en secondes depuis le dernier paquet envoyé : ici, il semblerait que j'ai attendu entre une demi-seconde et deux secondes entre chaque action.

Il est temps d'essayer ! Plutôt que de coder une application pour faire juste cette tentative, on va ressortir nRF Connect déjà utilisé avant : en cliquant sur la petite flèche vers le haut au niveau de la caractéristique Command, on peut choisir d'envoyer un message. Je renseigne donc un byte array qui contient ce que la capture Wireshark indique : 730101000075 et j'envoie. Bingo ! Ça fonctionne, même sans les deux messages qui suivent. J'essaie de la même manière le 1er message du trio que je suppose être l'extinction, et une fois encore, ça fonctionne.

Je vais créer un nouveau filtre de couleur View -> Coloring Rules... pour ces nouvelles découvertes pour rendre la liste de paquets plus lisible et chatoyante. Je continue ce travail pour la suite des messages, et c'est relativement facile d'identifier les différentes choses en jeu. J'obtiens un résultat plutôt satisfaisant :

Voici les règles de couleur utilisées :

💡
La première règle de coloration de la liste utilise une syntaxe pas encore rencontrée : btatt.value[0:2] == 73:03. Les crochets permettent de découper un bout de btatt.value, conservant uniquement les deux premiers octets.

Résumé des commandes

Maintenant que presque tout est connu, il est possible de faire un récapitulatif :

Communication Bluetooth Low Energy, via le profil GATT, avec un service propriétaire d'UUID 0xAE00, et une caractéristique en écriture-seule d'UUID 0xAE01. Chaque commande contient un message sur six octets :

Maintenant, il ne reste plus qu'à faire une app qui fonctionne mieux ! 😄