dimanche 10 septembre 2023

Teledistribution

Ayant connu quelques problèmes de réception de chaînes TV sur le câble, je me suis un peu intéressé à ce qui s'y passait...
La première idée a été de regarder le spectre avec un analyseur de spectre TinySA, un étonnant bidule à quelques dizaines d'euros qui affiche des spectres de 0 à quasi 1 GHz. Par défaut, il affiche le spectre de 0 à 350 MHz. On voit quelque chose du côté de 100 MHz, probablement la FM puis les signaux numériques commencent un peu avant 300 MHz. Si l'on zoome du côté de 330 MHz (la fréquence recommandée pour un scan rapide lors de l'installation d'un nouveau téléviseur), on voit bien les bandes de signaux de 8 MHz propres au DVB-C.

Le TinySA permet également d'obtenir les mesures via l'USB. Sur Linux, avec Minicom,
$ minicom -D /dev/ttyACM0

scan 333M 343M 290 3


Tout cela c'est très bien. En regardant vers les fréquences plus hautes, on pouvait constater une baisse de la puissance du signal dûe à la qualité du câble coaxial à l'intérieur du bâtiment. Mais cela n'expliquait pas pourquoi le signal était bon à certains moments et insuffisant à d'autres moments. Une fonction de la T24D390EW/EN permet d'afficher les caractéristiques d'un signal (fréquence, qualité du signal,...).
Un technicien est venu remplacer quelques connecteurs et a fini par me laisser un amplificateur. Cela a peut-être un peu boosté le signal et je n'ai plus eu d'images avec des blocs mais la veille de sa venue, je n'ai subitement plus eu de programme du tout, sauf sur La Trois, ...seule chaîne non cryptée dans le bouquet standard. Et cela, c'était un autre problème, sûrement un problème de module CAM ou de carte CI... et le technicien m'a invité à aller dans une boutique du téléopérateur. Là, on me donne une 'nouvelle carte' (usagée) et on m'invite à téléphoner au support technique pour l'activer. Là, chipotage, seconde ligne, blabla... Je dois contacter le service technique de Samsung, faire une mise à jour du firmware de la TV,... Je fais la mise à jour avec un mystérieux T-NT14LDEUCM_1013.0.exe trouvé sur le site de Samsung. En fait un .rar autoextractible sur lequel je peux faire un unrar(1) et je passe donc ma TV de la version 1005 à la version 1013 (tout en constantant qu'il y a du Linux à l'intérieur...). Mais rien n'y fait. Comme l'opérateur s'entête à m'encourager à contacter Samsung, je finis par le faire et la personne suspecte qu'ils sont passé de CI+ 1.3 à CI+ 1.4 que ma TV ne peux malheureusement pas traiter... Hum... L'opérateur finit par me re-contacter pour savoir comment s'est passé le changement de signal... C'est la première fois qu'ils me parlent d'un 'changement de signal'. J'avais donc raison de ne pas croire au hasard, ils ont bien changé quelque chose. Curieusement, la solution ne semble pas consister à me remettre en CI+ 1.3 (ou je ne sais quoi d'autre) mais à changer ma formule d'abonnement, d'acquérir un décodeur et de prendre Internet et le téléphone chez eux... Il y en a qui ne manquent pas d'air.

Là-dessus, je me suis demandé comment fonctionnait exactement cette histoire de bouquets cryptés sur DVB-C et j'ai acquis un bidule USB, Hauppauge SoloHD qui permet de capter les chaînes non cryptées et est bien supporté sous Linux (j'ai d'abord tenté un dongle bon marché DVBT2 qui promettait le DVB-C mais la démodulation 64-QAM semble se faire sur le PC à partir des signaux IQ bruts et ce n'est pas supporté par Linux). Hauppauge fournit de la documentation pour installer sous Linux (notamment pour Raspberry Pi). En gros, c'est supporté par défaut, il faut juste télécharger et installer le firmware (deux fichiers à copier dans /lib/firmware/).


Ensuite, il reste à trouver les utilitaires qui vont bien. Par exemple, w_scan(1) (...qui se trouve dans le package w-scan)
$ sudo apt-get install w-scan
...
$ w_scan -fc	# pour scanner toutes les fréquences DVB-C


Ce qui est étonnant, c'est le nombre de chaînes que l'on parvient à passer en haute définition dans un seul MPEG-TS en 64QAM de 8 MHz. Il semble y avoir 18 chaînes en clair sur le canal de 8 MHz à 314 MHz! (6875 kilo-symboles par seconde avec les 6 bits par symbole du 64QAM, cela fait environ 41 Mbits/seconde).

J'aimerais bien comprendre la structure de tout cela. À 330 MHz, il y a des informations qui renseignent les chaînes disponibles sur différentes fréquences, par exemple. Et j'aimerais bien extraire le guide électronique des programmes (EPG)...

à suivre...

En tout cas, rien de tel qu'une panne de télédistribution pour se rendre compte à quel point la télévision est une drogue dure et prendre la mesure du temps que l'on perd à regarder des programmes sans aucun intérêt.



















samedi 6 mai 2023

Triomphe de la Lune

Le 26 avril 2023, la Nasa publiait une photo de la Lune occupant presque toute la voûte de l'Arc de Tromphe de l'Étoile comme photo du jour.

Une occasion d'utiliser PyEphem pour vérifier tout ça...

C'est simple, amusant et vraisemblablement très précis. On a un observateur positionné dans l'espace et le temps et un astre dont la position dans un repère géographique nous intéresse...

Pour le calcul de l'azimut entre la Porte Maillot et l'Arc, ChatGPT me proposait 'geopy' mais le code qu'il proposait n'était pas correct... Google m'a proposé geographiclib...
pip install pyephem
pip install geographiclib
(avec ces modules, le code Python est compact et lisible) Pour les coordonnées GPS des points d'intérêt, j'utilise wikimapia qui a un curseur au centre et affiche sa position dans l'URL.

On positionne le programme en début de soirée (UTC!), Porte Maillot et on demande à pyephem quand a lieu le lever suivant. On y positionne l'observateur pour obtenir la position de la Lune sur l'horizon à ce moment-là.

Le programme ci-dessous donne les résultats suivants :
(https://apod.nasa.gov/apod/ap230426.html - EXIF 'Fr, 07 April 2023 22:21:14')
----
Lever Lune :  2023/4/7 20:16:01 112.160414663
Maillot->Etoile : distance =  1065.04410143 ; azimut =  115.71367909
distance * sin(0.5 deg) =  9.29414515847 m
----
https://fr.wikipedia.org/wiki/Arc_de_Triomphe_de_Paris
http://wikimapia.org/#lang=en&lat=48.873776&lon=2.295027&z=16&m=o

Remarquez que la Porte Maillot, à un peu plus d'un kilomètre, c'est un peu juste. La Lune ne fait que 9.3 mètres à l'Arc alors que la voûte en fait 14.6.

Je ne suis ni photographe ni observateur. J'ignore si on peut obtenir quelque chose d'aussi net à cette distance-là. Ni si on peut voir la Lune aussi nette si près de l'horizon en ville. Mais, sinon, c'est tout-à-fait plausible. La Lune, levée depuis quelques minutes, a parcouru quelques degrés pour se trouver dans le bon azimut légèrement au dessus de l'horizon.

Après, on peut refaire l'exercice pour le Cinquantenaire à Bruxelles en faisant une boucle sur 365 jours et en alternant next_rising(moon) et next_setting(moon). Comme le comportement de la Lune est un peu erratique, il vaut peut-être mieux s'exercer avec le Soleil... (sun).

Dans le 'Hacker's Dictionary', vers 1980, pour désigner un programme dont les résultats étaient incertains, on trouvait :

> POM n. Phase of the moon (q.v.).
> Usage: usually used in the phrase "POM dependent"
> which means flakey (q.v.).

( https://www.dourish.com/goodies/jargon.html )
#!/usr/bin/python
import math
import ephem
from geographiclib.geodesic import Geodesic

moon = ephem.Moon()

#
# Porte Maillot (48.877938, 2.281962)
# Arc de Triomphe (48.873782, 2.295043)
# altitude : 59m
# https://commons.wikimedia.org/wiki/File:Rep%C3%A8re_d%27altitude_sur_l%27Arc_de_Triomphe.jpg
#
x = Geodesic.WGS84.Inverse(48.877938, 2.281962, 48.873782, 2.295043)
azimut = x['azi1']
distance = x['s12']

Pt_Maillot = ephem.Observer()
Pt_Maillot.lat = '48.877938'
Pt_Maillot.lon = '2.281962'
Pt_Maillot.elevation = 55    # ?

Pt_Maillot.date = ephem.Date('2023/04/07 18:00')
Pt_Maillot.date = Pt_Maillot.next_rising(moon)

print "(https://apod.nasa.gov/apod/ap230426.html - EXIF 'Fr, 07 April 2023  22:21:14')"
print "----"
print "Lever Lune : ", Pt_Maillot.date, moon.az * 180 / math.pi
print "Maillot->Etoile : distance = ", distance, "; azimut = ", azimut
print "distance * sin(0.5 deg) = ", distance * math.sin(.5 * math.pi/180), "m"
print "----"
print "https://fr.wikipedia.org/wiki/Arc_de_Triomphe_de_Paris"
print "http://wikimapia.org/#lang=en&lat=48.873776&lon=2.2950&z=16&m=o"

Pour une doc rapide, voir :

dimanche 8 janvier 2023

Beid



En 2007, j'avais fait quelques expériences de lecture de cartes d'identité belges avec un lecteur de cartes utilisant un ACR38 : myACR38.blogspot.com... J'avais écrit un petit programme en 'C' d'un peu plus de 300 lignes n'utilisant que la librairie usb pour extraire les données d'identité et la photo sous Linux. Aujourd'hui, je ne l'ai plus mais je peux utiliser le Digipass 870 de Vasco fourni par la banque Belfius pour effectuer des opérations bancaires. Les opérations bancaires ne nécessitent pas que le lecteur soit connecté à un ordinateur mais le lecteur est équipé d'un port USB qui permet cette connexion (contrairement au lecteur de carte Digipass 810 fourni par Bpostbanque).

Curieusement, il n'est pas possible de se connecter à une administration belge sans que le lecteur de carte ne soit connecté à un ordinateur équipé d'un logiciel qui ne fonctionne qu'avec certains logiciels particuliers sur une sélection particulières de systèmes d'exploitation (compatibilité Linux)... Par exemple, il n'est pas possible d'utiliser un vieil Ubuntu, un Ubuntu trop récent ou un Raspberry Pi. On vous invite alors à partir des sources, eid-mw-5.1.4-v5.1.4.tar.gz qui, bien sûr, nécessite un tas de dépendances qui en rendent la compilation fort hasardeuse.

Était-ce bien nécessaire? Je ne pense pas. Je ne suis pas un spécialiste en cryptographie mais c'est l'occasion de regarder d'un peu plus près ce qui se passe exactement avec cette carte d'identité électronique. On trouve assez bien d'informations. Par exemple, sur un wiki de Philippe Teuwen qui réference une présentation de Danny De Cock (pdf) contenant des informations techniques. On trouve également des choses sur github.com/Fedict. Par exemple, le contenu de la carte : Belgian Electronic Identity Card content v5_4.pdf. On voit que la carte respecte la norme de cartes électroniques ISO-7816 et le standard de cryptographie PKCS#15.

Sur Linux, on peut utiliser le package opensc pour lire le contenu de la carte (sources sur github). Il y a deux 'applications'. L'une reprenant des données d'identité sous 'DF01' et l'autre, 'BELPIC' ('DF00'), la partie cryptographique. Dans chacune, il y a plusieurs fichiers. Parfois respectant un format 'BER' (Basic Encoding Rules d'ASN.1), parfois transparent comme, par exemple, pour la photo qui est un fichier JPEG d'environ 4 kilo-octets (3?) d'une définition de 140x200 pixels avec 16 niveaux de gris.

$ sudo apt-get install opensc
$ opensc-explorer <<END
cd df01
get 4031 4031-id_rn.bin
get 4032 4032-sgn_rn.bin
get 4033 4033-id_address.bin
get 4034 4034-sgn_rn.bin
get 4035 4035-id_photo.jpg
get 4038 4038-puk_7_ca.bin
get 4039 4039-pref.bin

cd ..
cd df00
get 5031 5031-odf.bin
get 5032 5032-token_info.bin
get 5034 5034-aodf.bin
get 5035 5035-pr_kdf.bin
get 5037 5037-cdf.bin
get 5038 5038-cert_2_auth.der
get 5039 5039-cert_3_sign.der
get 503a 503a-cert_4_ca.der
get 503b 503b-cert_6_root.der
get 503c 503c-cert_8_rn.der
END


Les certificats, en format DER, peuvent être affichés ou transformés en base64 avec openssl(1) :
$ # display beid certificate
$ openssl x509 -in 5038-cert_2_auth.der -inform DER -text
$ # convert
$ openssl x509 -inform der -in cert.der -outform pem -out cert.pem


Avec opensc-explorer, on peut aussi effectuer l'opération MVP:VERIFY. C'est le clavier du lecteur de carte qui est utilisé:
$ opensc-explorer
OpenSC [3F00]> verify CHV1 
Please enter PIN on the reader's pin pad.
Code correct.
Bizarrement, le code source de opensc-explorer:do_verify est complexe. Cela passe par sc_pin_cmd() et part10_build_verify_pin_block() pour utiliser l'API du lecteur de carte (...qui doit correspondre à un standard documenté quelque part) qui ne semble pas être piqué des vers. Et même pour un lecteur de carte sans clavier, ce n'est pas évident. OpenSC/src/libopensc/sec.c nous apprend, à la ligne 269, fonction sc_build_pin(), que les 8 octets de data de l'apdu sont 24:nn:nn:ff:ff:ff:ff:ff:ff (!) pour un PIN de 4 chiffres. Par exemple, pour '1234', on aura 'apdu 00:20:00:01:08:24:12:34:ff:ff:ff:ff:ff'.

Avec un lecteur muni d'un clavier, je ne peux pas utiliser l'apdu MVP:VERIFY dans opensc-explorer apdu (ni en 'C' avec libpcsclite), le code de retour est 6985 et non 9000. Je peux néanmoins utiliser 'apdu' pour tester d'autres commandes après avoir utilisé la commande 'verify' de opensc-explorer.
OpenSC [3F00]> apdu 00:20:00:01:00       # MVP:VERIFY with 'lc' = 0
Sending: 00 20 00 01 00
Received (SW1=0x69, SW2=0x85)
Failure: Not allowed
OpenSC [3F00]> apdu 80:E6:00:00          # LOG OFF
Sending: 80 E6 00 00
Received (SW1=0x90, SW2=0x00)
Success!
OpenSC [3F00]> apdu 80:E4:00:00:1C       # GET CARD DATA
Sending: 80 E4 00 00 1C
Received (SW1=0x90, SW2=0x00):
53 4C 47 90 61 28 00 00 2E CD 12 BF 12 92 61 54 SLG.a(........aT
F3 36 01 25 01 17 00 03 00 21 01 0F             .6.%.....!..
Success!
Le fin du GET CARD DATA donne 17 (Applet Version 1.7), 0003 (Belpic V-1.7), 00 (interface version), 21 (PKCS#1 version 2.1), 0F (Personalized state). SW1=0x90 et SW2=0x00 signifient que la commande s'est terminée normalement... (On se demande bien ce que les gars qui développent ce genre de standard ont dans la tête...)

Préparons un petit copion pour effectuer des opérations avec opensc-explorer... D'abord, sélectionner la paire de clé et l'algorithme de signature. Il n'y a pas 36 possibilités : on peut exécuter 6 algorithme avec deux clés privées (la clé d'authentification et la clé de non-répudiation (signature)). Ensuite, appliquer l'algorithme sur un 'hash' dont la longueur dépend de l'algorithme (16, 20, 32 octets pour MD5, SHA1, SHA256). Ces 'hashes' peuvent être obtenus au moyen des commandes Linux md5sum(1), sha1sum(1) et sha256sum(1).
# MSE:SET	Manage Security Environment : Set
#		le dernier :82 ou :83 sélectionne la clé (Auth ou Sign)
#		l'avant-pénultième octet sélectionne l'algorithme (en fait, la longueur des données à signer)

apdu 00:22:41:B6:05:04:80:01:84:82	# MSE:SET Auth - RSASSA-PKCS1
apdu 00:22:41:B6:05:04:80:02:84:82	# MSE:SET Auth - RSASSA-PKCS1-v1.5 SHA1
apdu 00:22:41:B6:05:04:80:04:84:82	# MSE:SET Auth - RSASSA-PKCS1-v1.5 MD5
apdu 00:22:41:B6:05:04:80:08:84:82	# MSE:SET Auth - RSASSA-PKCS1-v1.5 SHA256
apdu 00:22:41:B6:05:04:80:10:84:82	# MSE:SET Auth - RSASSA-PSS PKCS1-v2.1 SHA1
apdu 00:22:41:B6:05:04:80:20:84:82	# MSE:SET Auth - RSASSA-PSS PKCS1-v2.1 SHA256

apdu 00:22:41:B6:05:04:80:01:84:83	# MSE:SET Sign - RSASSA-PKCS1
apdu 00:22:41:B6:05:04:80:02:84:83	# MSE:SET Sign - RSASSA-PKCS1-v1.5 SHA1
apdu 00:22:41:B6:05:04:80:04:84:83	# MSE:SET Sign - RSASSA-PKCS1-v1.5 MD5
apdu 00:22:41:B6:05:04:80:08:84:83	# MSE:SET Sign - RSASSA-PKCS1-v1.5 SHA256
apdu 00:22:41:B6:05:04:80:10:84:83	# MSE:SET Sign - RSASSA-PSS PKCS1-v2.1 SHA1
apdu 00:22:41:B6:05:04:80:20:84:83	# MSE:SET Sign - RSASSA-PSS PKCS1-v2.1 SHA256

# PSO:CDS	Perform Security Operation : Compute Digital Signature

apdu 00:2A:9E:9A:LL::00		# LL(MD5)=16; LL(SHA1)=20; LL(SHA256)=32


# test
apdu 00:22:41:B6:05:04:80:04:84:82      # MSE:SET Auth - RSASSA-PKCS1-v1.5 MD5
verify CHV1
apdu 00:2A:9E:9A:10:ed:9e:7e:69:f8:da:f8:8d:44:ef:78:30:41:42:1d:b6:00		# cal 2023 |md5sum|sed 's/../&:/g' -> ed:9e:7e...

# verify PIN=1234
apdu 00:20:00:01:08:24:12:34:ff:ff:ff:ff:ff

Utilisons maintenant ce copion avec la carte insérée dans le lecteur : choix de l'algorithme, introduction du PIN, demande de calcul de la signature :
$ opensc-explorer
OpenSC Explorer version 0.17.0
Using reader with a card: VASCO DIGIPASS 870 [CCID] 00 00
OpenSC [3F00]> apdu 00:22:41:B6:05:04:80:04:84:82
Sending: 00 22 41 B6 05 04 80 04 84 82
Received (SW1=0x90, SW2=0x00)
Success!
OpenSC [3F00]> verify CHV1
Please enter PIN on the reader's pin pad.
Code correct.
OpenSC [3F00]> apdu 00:2A:9E:9A:10:ed:9e:7e:69:f8:da:f8:8d:44:ef:78:30:41:42:1d:b6:00
Sending: 00 2A 9E 9A 10 ED 9E 7E 69 F8 DA F8 8D 44 EF 78 30 41 42 1D B6 00
Received (SW1=0x90, SW2=0x00):
18 CA 07 F8 78 3C 12 37 DB 15 CB D9 30 2F 0E A4 ....x<.7....0/..
BB 59 AA 7B 1E 42 DA A3 B2 91 7F CE 2D 3F 3F 41 .Y.{.B......-??A
7D 13 2F 24 D9 58 43 E0 E1 30 99 44 D6 AD 4D C5 }./$.XC..0.D..M.
08 B9 66 BA 31 75 29 8E 9B 75 7A 4A CA 36 0A BB ..f.1u)..uzJ.6..
E3 C9 12 31 78 12 7E F9 4D BA 34 27 82 8E 12 E3 ...1x.~.M.4'....
D8 BD 21 1A F6 3D 38 D2 F0 35 C0 C6 B6 7C AD B2 ..!..=8..5...|..
DE 6E 87 1A DB 78 E7 85 84 BF D1 9F 65 31 7E 68 .n...x......e1~h
1B F9 34 A1 7F 2E 9F A3 9E 9D B1 A2 F1 31 8E E3 ..4..........1..
45 D6 4E 71 7F E2 26 9A 7B F7 C4 2A 04 5E 7E EB E.Nq..&.{..*.^~.
E7 EA 50 76 C1 C0 AF C9 52 AC A2 9F AD 9F D4 5D ..Pv....R......]
C5 0D 52 20 77 BD 2B 5D 22 52 57 91 73 D0 8A 96 ..R w.+]"RW.s...
F1 38 A3 B7 6D 49 7B A0 91 91 A9 8C 20 43 83 69 .8..mI{..... C.i
18 B4 3B 8A AA 85 33 D4 50 09 6A 1F 3D 16 6B 33 ..;...3.P.j.=.k3
61 9A F0 A3 98 4F C7 FC 73 91 6C 66 08 1A B6 FF a....O..s.lf....
5C E2 F1 06 5B FE 32 F9 61 DF F9 1B 3E 85 53 7C \...[.2.a...>.S|
08 89 CF 65 58 75 BD C5 09 7A 6E 26 48 51 D7 36 ...eXu...zn&HQ.6
Success!
OpenSC [3F00]>
Bingo! Cela fonctionne comme prévu. Donc, concrètement, rien de bien compliqué. On peut se demander pourquoi il faut triturer la documentation dans tous les sens pour obtenir ce résultat. Cela ressemble fort à de l'offuscation...

Reste à vérifier que l'on peut confirmer avec le certificat contenant la clé publique correspondante que le 'hash' a bien été signé par la clé privée...

D'abord, transformer le certificat de non-repudiation .der en .pem
$ openssl x509 -inform der -in 5039-cert_3_sign.der \
    -outform pem -out 5039-cert_3_sign.pem
En extraire la clé publique
$ openssl x509 -noout -pubkey -in 5039-cert_3_sign.pem \
    -out cert_3_sign-pubkey.pem 
Enfin, vérifier la signature
$ openssl pkeyutl -verify \
	-in cal2023.sha256 \
    -inkey cert_3_sign-pubkey.pem \
    -sigfile cal2023-sha256.sig \
    -pubin \
    -pkeyopt rsa_padding_mode:pss -pkeyopt digest:sha256
Signature Verified Successfully
cal2023.sha256 est un fichier binaire contenant le résultat de 'cal 2023 | sha256sum'.
cal2023-sha256.sig est la signature avec l'algorithme 0x20 du hash sha256 sous forme de fichier binaire de 256 octets.
cert_3_sign-pubkey.pem est la clé publique extraite du certificat.

Les fichiers binaires ont été obtenu par un petit programme en 'C' interagissant avec la carte en utilisant la libpcsclite (voir https://github.com/xofc/my_beid).



À suivre...

mercredi 2 novembre 2022

Tensiometre Bluetooth sur Linux

Suite à une visite médicale, on m'a conseillé de suivre ma tension... J'avais bien un tensiomètre de poignet qui traînait dans un tiroir depuis des années mais il paraît que c'est mieux d'en utiliser un avec un brassard... Petit tour dans une grande surface de trucs électroniques, je repère un Medisana BU-546 Connect (542~546; ~50.00 euros) qui a une connexion Bluetooth. Petit tour sur Internet. Cela n'a pas l'air trop mauvais, c'est une société allemande de l'ouest qui semble avoir bonne réputation. Rien sur Linux en lien avec le bidule, ça va être l'occasion de découvrir Bluetooth et d'expérimenter. Bref, c'est irrésistible...

Le tensiomètre fontionne avec une application (gratuite) pour smartphone, Vitadock+ ...qui demande d'activer la géolocalisation et envoie toutes les données (somme toute assez confidentielles) dans le cloud. Ça, ce n'est pas une bonne idée. En fait, c'est un peu scandaleux, c'est pire que le driver d'imprimante dont Richard Stallman voulait les sources. Pour dispositif médical, je dois pouvoir contrôler l'utilisation des données.

D'abord, découvrir Bluetooth sous Linux. Les outils et le b-a-ba de la communication. La première idée était de trouver un autre gadget compatible Linux et d'observer son fonctionnement. En fait, je pouvais déjà commencer à essayer de communiquer entre mon laptop et un Raspberry Pi. Mettons que je prenne le RPi comme 'device' (serveur) :
$ sudo btmgmt le on              # pour activer le Low Energy
$ sudo hciconfig hci0 piscan     # pour se laisser scanner (?)
$ bluetoothctl                   # pour le rendre découvrable et activer l'annonce (advertising)
> help
> discoverable on
> advertise on
> show
Du côté du laptop (client), je peux utiliser gatttool(1) pour me connecter et interroger le Rpi :
$ gatttool -I
> connect 11:22:33:44:55:66
> help
> primary
> characteristics
> char-desc
> char-read-hdn 0x0001
> char-read-uuid 0x2a01
...
C'est très bien. Maintenant, il faut essayer de faire la même chose avec le tensiomètre... D'abord, connaître sa mac-address. Ce n'est évidemment marqué nulle part. Après une mesure de la tension, il active le Bluetooth et s'annonce (advertising) pendant une minute. En principe, je devrais pouvoir le détecter avec hcitool(1)
$ sudo hcitool lescan
Mais pour une raison qui m'échappe, le lescan me renvoie un tas de mac-addresses dont quelques unes seulement sont associées à un nom d'appareil (la TV des voisins, une voiture qui passe, mon smartphone si j'active le Bluetooth,...) mais rien qui ressemble à Medisana ou Transtek, la société chinoise qui fabrique le bidule. Pas plus que le manufacturer-id du constructeur chinois (Medisana n'en a pas). En utilisant le smartphone (Samsung A40), les parasites sont absents et un BS1490 apparaît mystérieusement à la fin d'une mesure de tension. C'était donc ça! Maclookup m'apprend que c'est une 'Localy Administred Address' (adresse aléatoire utilisée par discrétion pour éviter le repérage). Et, de fait, on retrouve TMB-190-BS dans le manuel du tensiomètre et une recherche Internet mène à la page du constructeur chinois. En plus, il y a moyen de trouver des documents concernant la demande d'autorisation au FCC américain.

Avec la MAC-address, je peux maintenant me connecter sur le tensiomètre et lire différentes choses mais rien ne semble correspondre à une tension. Tout est quasi constant d'une mesure à l'autre. La seule chose qui varie (une horloge?) varie même à l'intérieur d'une seule session. Là, ça va être difficile... Si il faut écrire quelque chose quelque part et lire le résultat ailleurs, il va falloir capturer ue session entre le tensiomètre et l'application du smartphone. Sniffer une communication Bluetooth n'est pas aussi simple que de capturer du traffic Ethernet avec tcpdump(8). Bluetooth fait du saut de fréquence (frequency hopping) et il est difficile de capturer toute la bande ISM 2.4 GHz. Heureusement, il existe un bidule génial de Great Scott Gadgets dont Michael Ossmann décrit la genèse dans une conférence au Shmoocon de 2011 le Ubertooth One. Radin, comme d'habitude, je commence par commander un clone chinois qui se révélera fort capricieux. Je fais des tests entre le laptop et le Raspberry Pi et cela ne fonctionne correctement que très rarement. Un forum finit par me convaincre que cela fonctionne mieux avec la version officielle. Au diable l'avarice, j'en commande un vrai (aux Pays-Bas) que je reçois deux jours plus tard. Et cela fonctionne du premier coup entre le tensiomètre et le laptop (j'utilise l'Ubertooth One sur le Raspberry Pi pour avoir une version plus récente des outils (sur le laptop, je suis toujours en Ubuntu 18.04...). J'ai juste dû faire une mise à jour avec ubertooth-dfu(1) du dernier firmware. Quelques messages d'erreurs, mais cela fonctionne.

$ sudo ubertooth-btle -f -t 11:22:33:44:55:66 -r tensio.pcapng
Et là, surprise! Wireshark(1) décode les packets contenant la tension.
On peut aussi utiliser tshark(1) pour produire un fichier JSON que l'on peut imprimer, traiter avec grep(1), jq(1),... :
$ tshark -r tensio.pcapng -T json > tensio.json
À noter que le fichier JSON est un peu foireux, il n'y a pas de racine unique, il faut encadrer le contenu par quelque chose comme '{ "results": [' et ']}' pour pouvoir utiliser jq(1) et, dans jq, il faut utiliser des '"' (double quotes) parce qu'il y a des '.' dans les identifiants. Par exemple,
$ cat medisana.json |jq '.results[]._source.layers.frame."frame.protocols"' |less
$ cat medisana.json |jq '.results[]._source.layers.btatt."btatt.blood_pressure_measurement.compound_value.systolic.mmhg"' |less
Mais sinon, par exemple, dans une indication contenant une mesure, on peut lire:
        "btatt": {
          "btatt.opcode": "0x0000001d",
          "btatt.opcode_tree": {
            "btatt.opcode.authentication_signature": "0",
            "btatt.opcode.command": "0",
            "btatt.opcode.method": "0x0000001d"
          },
          "btatt.handle": "0x0000000b",
          "btatt.handle_tree": {
            "btatt.service_uuid16": "6160",
            "btatt.uuid16": "10805"
          },
          "btatt.blood_pressure_measurement.flags": "0x0000001e",
          "btatt.blood_pressure_measurement.flags_tree": {
            "btatt.blood_pressure_measurement.flags.reserved": "0x00000000",
            "btatt.blood_pressure_measurement.flags.measurement_status": "1",
            "btatt.blood_pressure_measurement.flags.user_id": "1",
            "btatt.blood_pressure_measurement.flags.pulse_rate": "1",
            "btatt.blood_pressure_measurement.flags.timestamp": "1",
            "btatt.blood_pressure_measurement.flags.unit": "0x00000000"
          },
          "btatt.blood_pressure_measurement.compound_value.systolic.mmhg": "120",
          "btatt.blood_pressure_measurement.compound_value.diastolic.mmhg": "62",
          "btatt.blood_pressure_measurement.compound_value.arterial_pressure.mmhg": "91",
          "btatt.blood_pressure_measurement.compound_value.timestamp": {
            "btatt.year": "2022",
            "btatt.month": "10",
            "btatt.day": "27",
            "btatt.hours": "17",
            "btatt.minutes": "52",
            "btatt.seconds": "0"
          },
          "btatt.blood_pressure_measurement.pulse_rate": "58",
          "btatt.blood_pressure_measurement.user_id": "0x00000001",
          "btatt.blood_pressure_measurement.status": "0x00000000",
          "btatt.blood_pressure_measurement.status_tree": {
            "btatt.blood_pressure_measurement.status.reserved": "0x00000000",
            "btatt.blood_pressure_measurement.status.improper_measurement_position": "0",
            "btatt.blood_pressure_measurement.status.pulse_rate_range_detection": "0x00000000",
            "btatt.blood_pressure_measurement.status.irregular_pulse": "0",
            "btatt.blood_pressure_measurement.status.cuff_fit_too_loose": "0",
            "btatt.blood_pressure_measurement.status.body_movement": "0"
          }
        }
Ce qui est curieux, c'est que tout ce décodage semble être fait par du code ad-hoc dans packet-btatt.c Il n'y aurait donc pas de définitions formelles comme les MIBs du SNMP. Et parmi les UUID-16, on trouve 0x2A35 pour la notification d'une caractéristique concernant la pression sanguine... (voir aussi Bluetooth Assigned numbers - HealthDeviceProfiles.pdf) Le tensiomètre répond donc bien à un standard : Blood-pressure-profile-1-1-1. Ce n'est pas très compréhensible. C'est comme le bouquin 'Bluetooth Low Energy: The Developer's Handbook' de Robin Heydon. C'est intéressant, très détaillé mais pas très pratique. Il faut le lire plusieurs fois, expérimenter avec les outils Linux et Wireshark, parcourir des blogs et des forums pour finir par avoir une idée de quoi il est question. Et, il n'y a, curieusement pas de références (ou de guide pour s'orienter dans la documention étendue de bluetooth.com)

Ainsi, avec Wireshark (en éliminant les packets sans intérêt avec le filtre 'btle.data_header.length > 0 || btle.advertising_header.pdu_type == 0x05', après une découverte du tensiomètre par l'application Vitadock+, elle active les 'indications' en écrivant 0200 dans un canal particulier. Les mesures non encore transmises sont alors envoyées une par une, avec un timestamp comme 'indication'. On peut reproduire le mécanisme avec gatttool(1) On reçoit alors des listes de 19 caractères en hexadécimal contenant la pression systolique, diatolique, la pression, le temps de la mesure (année, mois, jour, heure, minute, seconde) et le nombre de pulsations par minute) qu'il est facile de décoder en regardant la correspondance sur Wireshark(1).
$ gatttool -b 11:22:33:44:55:66 -t random -I
[11:22:33:44:55:66][LE]> connect
Attempting to connect to 11:22:33:44:55:66
Connection successful
[11:22:33:44:55:66][LE]> char-write-req 0x000c 0200
Characteristic value was written successfully
Indication   handle = 0x000b value: 1e 71 00 42 00 59 00 e6 07 0a 1c 0b 2b 00 3f 00 01 00 00 
Indication   handle = 0x000b value: 1e 85 00 49 00 67 00 e6 07 0a 1c 0c 1d 00 51 00 01 00 00 
...
                                    XX SS SS DD DD PP PP YY YY MM dd HH MM SS pp pp id
(SSSS=Systolic mmhg, DDDD=Diatolic, PPPP pressure, YYYY=year, MM=month, dd=day, HH=hour, MM=minute, SS=seconds, id=person)
Vers le milieu, on retrouve l'année 2022 (0x07E6), octobre (0x0A), 28 (0x1C), etc...

Reste maintenant à écrire une petite application qui fait juste ça : connect, write(0200) et accepte/affiche les indications reçues. Ce qui n'est pas spécialement trivial parce que la documentation de l'API Bluetooth sur Linux est inexistante. La stratégie la plus simple serait de partir des sources de gatttool(1) mais il y a une masse de dépendances... On trouve beaucoup de choses en Python aussi mais Python est infernal au niveau des versions et des dépendances.

Ce qui serait bien aussi, ce serait de remettre le pointeur de lecture à zéro (ou à une valeur arbitraire) parce que là, le tensiomètre n'envoie que les nouvelles mesures, effectuées après le dernier transfert. Alors qu'il peut contenir jusqu'à 250 mesures par utilisateur. Il semblerait cependant que cela ne soit pas possible parce qu'on ne trouve pas de 'Record Access Control Point' (0x2a52) dans le caractéristiques du tensiomètre (voir ci-dessous) et que la fonctionalité est optionnelle.

Exploration avec gatttool(1)

(gatttool(1) serait à l'abandon et remplacé par bluetoothctl(1) (tellement 'intuitif' que le manuel est virtuellement inexistant... (il faut taper 'menu gatt' pour avoir le sous-menu gatt...)).

Les 'services' du tensiomètre

$ gatttool -b 11:22:33:44:55:66 -t random -I
[11:22:33:44:55:66][LE]> connect
Attempting to connect to 11:22:33:44:55:66
Connection successful
[11:22:33:44:55:66][LE]> primary
attr handle: 0x0001, end grp handle: 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0008, end grp handle: 0x0008 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0009, end grp handle: 0x000e uuid: 00001810-0000-1000-8000-00805f9b34fb
attr handle: 0x000f, end grp handle: 0x0012 uuid: 0000180f-0000-1000-8000-00805f9b34fb
attr handle: 0x0013, end grp handle: 0x0021 uuid: 0000180a-0000-1000-8000-00805f9b34fb
attr handle: 0x0022, end grp handle: 0xffff uuid: 00001805-0000-1000-8000-00805f9b34fb
[11:22:33:44:55:66][LE]> 
La commande 'primary' liste les 'services' disponibles sur le tensiomètre. La partie variable des UUIDs sont des UUID-16 de 'services' dont on trouve la liste dans https://www.bluetooth.com/specifications/assigned-numbers/ : assigned_numbers_release.pdf :
0x1800  Generic Access service 
0x1801  Generic Attribute service
0x1810  Blood Pressure service
0x180f  Battery service
0x180a  Device Information service
0x1805  Current Time service
Ce sont des informations que l'on peut aller chercher dans le tensiomètre. Ces 'services' sont décrits dans les documents Specifications and Tests Documents List/Services. Par exemple, Blood Pressure Service (blood-pressure-service-1-1-1.pdf et d'autres documents).

La manière d'accéder à ces informations est décrite dans des documents 'Profile' : Specifications and Tests Documents List/Profiles. Par exemple, Blood Pressure Profile (blood-pressure-profile-1-1-1.pdf et d'autres documents).

Il faut noter que tous les bidules Bluetooth ne sont pas forcément 'conformes' à un standard accepté. Ceux qui ont passé les tests de conformance se retrouvent dans la base de données cherchable : https://launchstudio.bluetooth.com/Listings/Search. La recherche avancée permet de trouver les bidules qui ont passé les tests avec succès. On peut, par exemple, lister ceux qui implémente le 'Blood Pressure Service' (BLS) et le 'Blood Pressure Profile' (BLP) ou, éventuellement regarder quels bidules d'une marque données ont un profil standard. Dans ce cas-ci, on arrive à une fiche concernant le BU-546 (en fait, un TMB-1490-TS de Transtek) où on ne trouve (bizarrement?) pas de qualification 'blood pressure service/profile'...

Les 'caractéristiques' du tensiomètre

$ gatttool -b 11:22:33:44:55:66 -t random -I
[11:22:33:44:55:66][LE]> connect
Attempting to connect to 11:22:33:44:55:66
Connection successful
[11:22:33:44:55:66][LE]> char-desc
/* 0x1800  Generic Access service */ 
handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0006, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0007, uuid: 00002a04-0000-1000-8000-00805f9b34fb
     /* 0x1801  Generic Attribute service */
handle: 0x0008, uuid: 00002800-0000-1000-8000-00805f9b34fb
     /* 0x1810  Blood Pressure service */
handle: 0x0009, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000a, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x000b, uuid: 00002a35-0000-1000-8000-00805f9b34fb
handle: 0x000c, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x000d, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x000e, uuid: 00002a49-0000-1000-8000-00805f9b34fb
     /* 0x180f  Battery service */
handle: 0x000f, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0010, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0011, uuid: 00002a19-0000-1000-8000-00805f9b34fb
handle: 0x0012, uuid: 00002902-0000-1000-8000-00805f9b34fb
     /* 0x180a  Device Information service */
handle: 0x0013, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0014, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0015, uuid: 00002a29-0000-1000-8000-00805f9b34fb
handle: 0x0016, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0017, uuid: 00002a24-0000-1000-8000-00805f9b34fb
handle: 0x0018, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0019, uuid: 00002a25-0000-1000-8000-00805f9b34fb
handle: 0x001a, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001b, uuid: 00002a27-0000-1000-8000-00805f9b34fb
handle: 0x001c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001d, uuid: 00002a26-0000-1000-8000-00805f9b34fb
handle: 0x001e, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001f, uuid: 00002a28-0000-1000-8000-00805f9b34fb
handle: 0x0020, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0021, uuid: 00002a23-0000-1000-8000-00805f9b34fb
     /* 0x1805  Current Time service */
handle: 0x0022, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0023, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0024, uuid: 00002a2b-0000-1000-8000-00805f9b34fb
handle: 0x0025, uuid: 00002902-0000-1000-8000-00805f9b34fb
[11:22:33:44:55:66][LE]> 
De nouveau, on retrouve les UUID-16 dans assigned_numbers_release.pdf :
0x2800  Primary Service
0x2803  Characteristic
0x2902  Client Characteristic Configuration
0x2a00  Device Name
0x2a01  Appearance
0x2a04  Peripheral Preferred Connection Parameters
0x2a19  Battery Level
0x2a23  System ID
0x2a24  Model Number String
0x2a25  Serial Number String
0x2a26  Firmware Revision String
0x2a27  Hardware Revision String
0x2a28  Software Revision String
0x2a29  Manufacturer Name String
0x2a2b  Current Time
0x2a35  Blood Pressure Measurement
0x2a49  Blood Pressure Feature
Bref, un peu de tout... On notera cependant qu'on écrit 0200 dans le 'Client Characteristic Configuration' (0x2902, handle 0x000c) du 'Blood Pressure service' (0x1810) pour activer les indications 'Blood Pressure Measurement' (0x2a35, handle 0x000b).



lundi 15 août 2022

Ais : ESAR

Le 28 juin 2022, rtl-sdr.com a présenté un article ESAR – Extraordinarily Simple AIS Receiver written in C avec un lien vers le code que l'on trouve aussi sur github.

Une partie radio de juste une centaine de lignes (lignes 307 à 402) nettoie le signal
  • Downsampling par 3 (340-344) parce que le rtl-sdr ne peut faire 100 kps, on utilise 300 kps; I1/Q1 est ré-utilisé.
  • Shift de 25 kHz (sample rate/4). Le signal est à 162 MHz +/- 25 kHz; cela le ramène à 0 en I1/Q1 et shift de 50 kHz pour avoir l'autre canal (346-364) en I2/Q2
  • Filtrage et décimation par deux sur charque canal (avec recyclage de I1/Q1 et I2/Q2) (371-377)
  • Démodulation AM & FM (379-387)
  • Pour chaque canal, recherche des trames
La recherche des trames consiste en :
  • Recherche de signal basé sur l'amplitude (161-171)
  • Recherche du maximum de corrélation du préambule (197-237)
  • Échantillonnage de la fréquence et transformation en bits (NRZI & bit stuffing) (251-289)


À suivre...

samedi 26 mars 2022

Mesure CO2 avec Raspberry Pi et SCD30

Après la mésaventure précédente, j'ai craqué pour un module SCD30 de Sensirion. Là, il ne suffit pas de l'allumer et de regarder l'affichage, il faut souder un connecteur, le raccorder à quelque chose et trouver un programme qui interagit avec. J'avais dans l'idée d'utiliser le I2C d'un Raspberry Pi mais j'ai eu un peu de mal à trouver quelque chose qui fonctionne et les outils de base i2c ne fonctionne pas parce que le module nécessite du clock stretching. Du coup, j'ai opté pour le protocole Modbus sur la liaison RS-232. Et là, c'est relativement trivial. Cinq fils à connecter et un petit programme en 'C' et c'est parti. Voir my_scd30 sur GitHub.
Il est assez facile d'injecter les données dans une 'timeseries database' Influxdb et d'afficher les mesures avec Grafana:
Il y a eu une coupure de courant et j'ai oublié de rallumer le Raspberry Pi... Je ne suis pas très sûr de la calibration. J'ignore quel est le niveau de base en ville. J'imagine que si la référence planétaire se trouve à Hawaï, il doit y avoir une raison.

vendredi 25 mars 2022

CO2 Mesaventure chinoise

Lors de l'épidémie de covid-19, les capteurs de CO2 sont devenus à la mode...
Tous ne se valent pas. De petits margoulins se sont précipités pour proposer des appareils en ayant l'apparence mais pas la fonctionalité. Ainsi, un joli capteur acheté pas trop cher sur AliExpress affiche un taux de ppm qui varie en fonction du temps mais un petit test simple introduit un gros doute sur son fonctionnement. Si je mets le bidule dans un sac en plastique et que je souffle dedans, je devrais dépasser de loin la limite supérieure du capteur, 5000 ppm parce que j'expire 4% de CO2, soit 40000 ppm et... il ne se passe rien. J'ouvre la fenêtre, la mesure monte en flèche et génère une alarme. Tout cela est hautement suspect et je ne peux résister à regarder ce que contient ce bidule...

C'est difficile à ouvrir, c'est collé/vissé ou thermosoudé. Cela fait un peu mal au coeur de charcuter un joli gadget tout neuf mais là, il y a visiblement quelque chose qui ne va pas. Et, de fait, le bidule ne contient aucun composant capable de mesurer le CO2. Il y a juste un DHT11 qui mesure l'humidité et la température. C'est vraiment curieux et incompréhensible.

Ce bidule est soigné, cela a demandé des gens intelligents et capables pour concevoir le boîtier, l'affichage, la carte électronique, le programme,... et le tout est complètement factice. En fait, à la base, cela probablement été un instrument fonctionnel. Quelque margoulin a dû se l'approprier, faire l'économie d'un capteur coûteux et soudoyer un programmeur de seconde zone pour faire un programme qui simule le fonctionnement. À moins qu'ils aient eu l'intention de faire un vrai capteur et qu'ils n'y soient jamais parvenus; qu'ils aient développé un truc factice pour obtenir des capitaux et aient laissé l'affaire en l'état, laissant l'investisseur avec un truc inutilisable.