mercredi 28 mai 2025

Chauves-souris

Je possède depuis un certain temps un Batseeker qui rend audibles le cris des chauves-souris. J'ignore de quelle version il s'agit. En principe, les ultrasons sont captés par un microphone 'mems' et la fréquence en serait divisée par 10. Le résultat, c'est qu'on entend une espèce de chr-chr-chr-chr... rapide quand une chauve-souris passe à proximité. En les entendant, c'est plus facile de les repérer.

Récemment, à je ne sais plus quelle occasion, j'ai vu qi'il y avait moyen d'acquérir un enregistreur AudioMoth pas trop cher. Du genre 100€ mais en ajoutant divers frais (taxes et port), comptez 140e, sans compter les piles et la carte SD. Cela reste accessible. Le bidule est configurable et autonome. On peut aussi l'utiliser 'à la demande' (une fois le taux d'échantillonnage sélectionné). Pour les pipistrelles, 192 kilo-échantillons par seconde permet d'enregistrer jusqu'à 96 kHz, ce qui est bien suffisant. À 16 bits par échantillon, on est à quelque chose du genre 20 MB par minutes mais les PC modernes traitent cela sans problème et les cartes SD permettent de stocker des dizaines de giga-octets. En mode autonome, avec des pages horaires programmées, il y a deux contraintes : l'énergie fournie par les piles et la capacité mémoire de la carte SD. Dans les deux cas, cela se compte en jours. L'enregistrement en continu se compose de fichiers .WAV de tailles raisonnables (qu'il faut extraire avec un lecteur de carte).
Audacity permet d'inspecter les enregistrements. Ci-dessus, on peut voir les cris d'écholocalisation (avec un maximum vers 48 kHz) et un 'buzz' de capture typique des pipistrelles.



Batseeker suggère d'agiter un trousseau de clés pour tester le détecteur. Celles-ci génèreraient des ultrasons que le détecteur rendrait audibles. Et, en effet, si on enregistre l'agitation des clés avec l'AudioMoth à 192 kilo-échantillons par seconde, on trouve beaucoup de signal au dessus de 20 kHz. L'audiogramme ci-dessous montre l'enregistrement sans batseeker à guche et avec batseeker à droite. On observe clairement l'apparition d'un signal basse fréquence. Pour voir si il y a réellement division des fréquence par 10, il faudrait un signal d'entrée plus pur. Par exemple, le sinus d'un générateur de fréquences dans un haut-parleur allant des les ultrasons. Il faudrait que je retrouve mes pièces de modules HC-SR04...
Un aute truc amusant, c'est d'avoir enregistré le 'chime' de l'application de configuration. Le smartphone jour une petite musique qui permet de démarrer un enregistrement 'custom' avec des coordonnées GPS (mais sans pouvoir spécifier d'autres paramètres comme le taux d'échantillonnage ou ou le gain). Quand on observe l'audiogramme, on s'aperçoit que la petite musique est décorative et que le signal utile se situe à 18 kHz.


Je n'ai pas encore trouvé d'insectes (grillons, sauterelles) à enregistrer (ni de batraciens ou autres animaux). Cela fonctionne pour les oiseaux mais l'application BirdNet pour smartphone est beaucoup plus pratique et elle est associée à un logiciel qui permet de déterminer l'espèce à partir de son chant.

Finalement si. Probablement un 'criquet' stridulant autour de 23 kHz (?).




dimanche 23 février 2025

Cassette de brodeuse Toyota ESP-510

(xofc sur Wikipédia)

La brodeuse Toyota Expert SP-510 (1986) peut lire des motifs à broder à partir de cassettes audio. L'encodage semble être identique à celui utilisé dans les Datassettes de Commodore. Une cassette peut contenir plusieurs designs et chaque design est constitué de plusieurs sections.

Une cassette de démonstration comprenant 8 motifs (1/letter A; 2/Dancing; 3/ Bouquet; 4/ Pigeon; 5/ Butterfly; 6/ Pisces; 7/ Rosetted ribbon; 8/ Star) a été lue avec un lecteur de cassette USB (Super USB Cassette Capture / Cassette Converter vendu chez Aldi (?)) que Linux voit comme un micro USB (alsamixer(1) et gnome-sound-recorder(1)) et convertie en fichier .mp3. La compression du fichier ne semble pas poser problème (sinon, on pourrait le sauver en .flac). On peut alors analyser le contenu de l'enregistrement avec audacity(1), par exemple.

Via Audacity.Tools->Frequency_analysis, sur un bout d'enregistrement, on peut deviner qu'il s'agit d'un encodage AFSK avec du signal à 1000 et 2000 Hz.


L'ensemble de la bande montre les huit motifs séparés par des silences de 30 secondes.

Un motif particulier étant composé de plusieurs sections. Par exemple, le cinquième motif, qui est un papillon (Butterfly) assez complexe, est composé de nombreuses sections séparées par des silences de 2.5 secondes.

En zoomant encore sur une section, on voit apparaître le signal. Ici, au moment ou une succession de cycles à 1000 Hertz utile à la synchronisation fait place aux données proprement dites.

En zoomant davantage, on voit apparaître l'échantillonnage à 44100 Hertz (standard des CD audio) des signaux à 1000 et 2000 Hertz.

Certaines sections affichent des signaux différents qui ne semble contenir aucune information particulière, c'est juste du remplissage.

Il faut noter que Audacity affiche des valeurs entre +1 et -1 mais quand on transforme le fichier .mp3 en .wav (16 bits signés à 44100 échantillons par seconde) pour pouvoir l'analyser numériquement facilement, on obtient des valeurs entre 32767 et -32768. Avec le module audio de Octave(1), on peut également lire les fichiers audio (en .flac, .wav et .ogg mais pas .mp3).
octave> [y,fs]=audioread('file.ogg');
octave> x = [0:length(y)-1];
octave> plot(x,y)
# or
octave> fd = fopen('file.wav', 'rb');
octave> y = fread(fd, 'int16');   # ?!should skip the header...
octave> x = [0:length(y)-1];
octave> plot(x,y)

mpg123(1) permet de convertir les différents formats. Typiquement, pour lire des données .wav (sans header) en entrée standard de 'mon_programme', on va faire :
linux$ mpg123 -s -0 mp3/butterfly.mp3 | ./mon_programme

Après, il ne restera plus qu'à essayer de deviner ce que contiennent ces sections... Ce n'est pas gagné parce que, curieusement, il existe une masse de formats de fichier propriétaires pour brodeuses alors qu'on aurait pu penser qu'il aurait existé une espèce de G-Code universel ne contenant que des dx et dy entre les piqures. Pour se faire une idée de la matière, on peut analyser comment Ink/Stich, un module pour Inkscape(1), approche le sujet.





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,...