mercredi 15 janvier 2025

Linux et la balance de cuisine SilverGear

La semaine dernière, la semaine d'Action (en Belgique) faisait la promotion d'une balance de cuisine Silvergear 'connectée' à 5.99 euros... Impossible de résister. L'occasion de jouer un peu avec Bluetooth. L'application conseillée est 'Silvergear fit'. Après avoir choisi le type de nourriture, elle calcule l'apport en calories, matières grasses, protéines, glucides,... de la pesée. L'application est plutôt mal cotée. J'ignore si c'est justifié ou non, je n'ai pas vraiment l'intention de l'utiliser.

Pour jouer avec en Bluetooth, le premier problème est d'obtenir son adresse MAC. J'avais déjà eu des problèmes avec le pèse-personne SilverGear à 10 euros la semaine précédente. Je dois être entouré de voisins fans de bidules connectés et c'est difficile de retrouver mon bidule dans toutes ces adresses. Le pèse-personne s'annonçait comme 'TY' (pour tuya) et il y en avait déjà 3 dans l'environnement... Dans un environnement moins fréquenté, j'ai découvert que mon pèse-personne avait une adresse DC:23:50:XX:XX:XX. La balance de cuisine s'annonce comme '4454' (cela apparaît également avec l'application du smartphone lors du 'pairing'). Ce n'est pas très explicite mais l'information figure sur l'étiquette à l'arrière. Son adresse MAC est BC:A5:46:XX:XX:XX.

linux$ bluetoothctl scan le     # could have been 'sudo hcitool lescan ?
...
[bluetooth]# scan on
...
[NEW] Device BC:A5:46:XX:XX:XX 4454
...
^D


Une fois son adresse MAC connue, on peut utiliser gatttool(1) pour aller plus loin et découvrir ce que le bidule propose. Un truc à essayer, par exemple, c'est '--primary':
linux$ gatttool -t public -b BC:A5:46:XX:XX:XX --primary # Cuisine
attr handle = 0x0001, end grp handle = 0x000f uuid: 0000180a-0000-1000-8000-00805f9b34fb
attr handle = 0x0010, end grp handle = 0xffff uuid: 0000ffb0-0000-1000-8000-00805f9b34fb
Voilà qui est bien mais ne nous avance pas beaucoup. Si le '180a' se trouve bien dans les Bluetooth Assigned Numbers (Device Information Service), 'ffb0' n'y est pas... Essayons de nous connecter en mode interactif pour pouvoir l'interroger...
linux$ gatttool -t public -b BC:A5:46:17:F5:07 --interactive # Cuisine
[BC:A5:46:XX:XX:XX7][LE]> connect
Attempting to connect to BC:A5:46:XX:XX:XX
Connection successful
Notification handle = 0x0014 value: ac 40 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a6 a7 
Notification handle = 0x0014 value: ac 40 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a6 a7 
...
Notification handle = 0x0014 value: ac 40 00 00 01 0d 4c 00 00 00 00 00 00 00 00 00 00 00 a6 00 
[BC:A5:46:XX:XX:XX][LE]> ^C
Et là, bonne surprise! La balance envoie des 'notifications' dont la valeur change lorsque l'on dépose un poids dessus, sans aucune autre intervention que la connexion. Là, ce n'est pas encore trop pratique mais c'est encourageant. Le plus simple, maintenant, cela doit être d'écrire un petit script Python utilisant le module pygatt pour interagir avec le Bluetooth LE Generic Attribute Profile (GATT). Et, justement, dans la doc, il y a un exemple de captation des notifications. Le problème, c'est qu'il faut associer le 'callback' à un UUID et que nous n'avons que le 'handle' 0x0014... C'est là que la commande '--char-desc' de gatttool intervient. On trouve l'UUID correspondant au handle 0x0014, '0000ffb2-0000-1000-8000-00805f9b34fb' que l'on peut utiliser dans le script.
linux$ gatttool -t public -b BC:A5:46:XX:XX:XX --char-desc        # Cuisine
handle = 0x0001, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x0002, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0003, uuid = 00002a23-0000-1000-8000-00805f9b34fb
handle = 0x0004, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0005, uuid = 00002a24-0000-1000-8000-00805f9b34fb
handle = 0x0006, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0007, uuid = 00002a25-0000-1000-8000-00805f9b34fb
handle = 0x0008, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0009, uuid = 00002a26-0000-1000-8000-00805f9b34fb
handle = 0x000a, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x000b, uuid = 00002a27-0000-1000-8000-00805f9b34fb
handle = 0x000c, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x000d, uuid = 00002a28-0000-1000-8000-00805f9b34fb
handle = 0x000e, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x000f, uuid = 00002a29-0000-1000-8000-00805f9b34fb
handle = 0x0010, uuid = 00002800-0000-1000-8000-00805f9b34fb
handle = 0x0011, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0012, uuid = 0000ffb1-0000-1000-8000-00805f9b34fb
handle = 0x0013, uuid = 00002803-0000-1000-8000-00805f9b34fb
handle = 0x0014, uuid = 0000ffb2-0000-1000-8000-00805f9b34fb
handle = 0x0015, uuid = 00002902-0000-1000-8000-00805f9b34fb

Et donc, le script devient :
#!/usr/bin/python3
import pygatt, time

adapter = pygatt.GATTToolBackend()
old_weight = 0

def handle_data(handle, value):
    """
    handle -- integer, characteristic read handle the data was received on
    value -- bytearray, the data returned in the notification
    """
    global old_weight
    weight = ((value[3]*256+value[4])*256+value[5])*256+value[6]
    if weight != old_weight :
        print(weight, flush=True)
        old_weight = weight

try:
    adapter.start()
    device = adapter.connect('BC:A5:46:XX:XX:XX')	# balance cuisine
    device.subscribe("0000ffb2-0000-1000-8000-00805f9b34fb",
                     callback=handle_data)
    while True:
        time.sleep(10)
finally:
    adapter.stop()
Il y a visiblement encore des choses à explorer mais en utilisant les octets de 3 à 6, on obtient un nombre de milligrammes qui correspond à la valeur affichée sur la balance en grammes. La bidouille avec 'old_weight', c'est pour éviter les mesures redondantes plusieurs fois (combien?) par seconde. Pas sûr que cela soit stable au niveau du milligramme. Il faudrait peut-être faire quelque chose de plus sophistiqué...
linux$  ./kichen_notif.py 
72110
Et la balance affiche '72' grammes.

L'intérêt d'avoir accès aux mesures indépendamment de l'application, c'est que l'on peut maintenant faire ce que l'on veut. Je peux compter des boulons, multiplier le poids par le prix des bananes,...

Il faudrait aussi faire des expériences pour savoir dans quelle mesure le résultat est reproductible, correct, linéaire,... À quel point. Et, par exemple, si 3 grammes à partir de X grammes est plus précis qu'à partir de 0,...