# La conversation, pas à pas

[![chat.png](https://librenard.fr/wiki/uploads/images/gallery/2026-10/scaled-1680-/chat.png)](https://librenard.fr/wiki/uploads/images/gallery/2026-10/chat.png)

Maintenant qu'on connaît la grammaire, on peut écouter une conversation complète. Toute la séquence est écrite dans une seule fonction, `downloadSamples`, dans `session.cpp`. Elle suit un ordre strict : si on saute une étape ou qu'on inverse deux messages, le lecteur abandonne.

Pour illustrer, j'utilise une **trace** : un fichier texte où chaque ligne est un transfert USB. `>` veut dire "le PC envoie", `<` veut dire "le lecteur répond", `c` c'est le transfert de contrôle du début. Celle-ci vient des tests (`tests/fixtures/meals.trace`) : elle est construite par un simulateur calqué sur un vrai Accu-Chek Guide, avec des mesures inventées.

<p class="callout warning">Une trace enregistrée sur un vrai lecteur contient **toutes** les mesures de la personne. Ce sont des données de santé : on ne la publie pas, on ne la met pas dans un dépôt Git. Le `.gitignore` d'accuchek ignore d'ailleurs les fichiers `*.trace` en dehors des exemples de test.</p>

#### Vue d'ensemble

<table id="bkmrk-%C3%89tape-sens-message-1"><thead><tr><th>Étape</th><th>Sens</th><th>Message</th></tr></thead><tbody><tr><td>1</td><td>PC → lecteur</td><td>"toc toc" : requête USB <code>GET&#95;STATUS</code></td></tr><tr><td>2</td><td>lecteur → PC</td><td>demande d'association</td></tr><tr><td>3</td><td>PC → lecteur</td><td>association acceptée</td></tr><tr><td>4</td><td>lecteur → PC</td><td>description de ses objets (configuration)</td></tr><tr><td>5</td><td>PC → lecteur</td><td>"configuration bien reçue"</td></tr><tr><td>6-7</td><td>aller-retour</td><td>identité et horloge du lecteur (MDS)</td></tr><tr><td>8-9</td><td>aller-retour</td><td>liste des segments de la mémoire</td></tr><tr><td>(10-11)</td><td>aller-retour</td><td>mise à l'heure, seulement si demandée et utile</td></tr><tr><td>12...</td><td>boucle</td><td>transfert des glycémies, avec accusé de réception à chaque paquet</td></tr><tr><td>...</td><td>boucle</td><td>transfert des marqueurs repas</td></tr><tr><td>fin</td><td>aller-retour</td><td>fin d'association, on raccroche</td></tr></tbody></table>

### 1. Le "toc toc"

```bash
c 0000
```

On commence par une requête USB standard `GET_STATUS` sur le périphérique. Le lecteur répond deux octets à zéro. On ne se sert pas de cette réponse, mais c'est ce que fait le pilote Tidepool avant de commencer, et quand on reproduit un protocole qu'on ne connaît pas par cœur, on évite de retirer des étapes "pour voir". Dans le doute, je fais tout pareil.

### 2 et 3. La poignée de main 

```bash
< E2000032800000000001002A0000...
> E300002C0003507900268000000280008000000000000000800000000008123456780000000000000000000000000000
```

Le lecteur ouvre la discussion avec une demande d'association (`E2 00`). On lui répond (`E3 00`) avec un message entièrement construit par `buildAssociationResponse`. Décortiqué, ça donne :

```bash
E3 00          réponse d'association
00 2C          longueur : 44 octets
00 03          résultat : "accepté, configuration inconnue"
50 79          protocole 20601 (0x5079), la norme ISO/IEEE 11073-20601
00 26          longueur des infos de protocole
80 00 00 02    version du protocole
80 00          règles d'encodage : MDER
80 00 00 00    version de la nomenclature
00 00 00 00    unités fonctionnelles : association normale
80 00 00 00    type de système : "manager"
00 08          longueur de l'identifiant système
12 34 56 78 00 00 00 00   notre identifiant (fictif)
...
```

Le point important est le résultat `00 03`, "accepted-unknown-config" : on accepte la connexion mais on dit au lecteur qu'on ne connaît pas encore sa configuration. Du coup, il va nous la décrire. C'est exactement ce qu'on veut : on ne suppose rien, on lui demande de se présenter.

### 4 et 5. Le lecteur se décrit

```bash
< E700003A00380010010100320000FFFFFFFF0D1C00284000000200220006000100010006092F00027144003D01000002000C092100020100095100020004
> E7000016001400100201000E0000000000000D1C000440000000
```

Le lecteur envoie un événement `0D 1C` (MDC\_NOTI\_CONFIG) : la liste de ses objets. On n'a besoin que d'un seul d'entre eux, le **PM-Store**, la mémoire des mesures. `parseConfigInfo` le cherche par sa classe :

```bash
00 3D          classe 61 : MDC_MOC_VMO_PMSTORE, c'est lui
01 00          son handle : 0x0100
00 02          il a 2 attributs
00 0C          sur 12 octets
09 21 00 02 01 00    attribut 2337 (ID_HANDLE) = 0x0100
09 51 00 02 00 04    attribut 2385 (NUM_SEG)   = 4 segments
```

On retient le handle `0x0100` : toutes les questions sur la mémoire seront adressées à cet objet. Puis on accuse réception de la configuration (`02 01` + `0D 1C`, résultat `00 00` = acceptée), en reprenant l'invoke-id du lecteur (`00 10`).

### 6 et 7. Qui es-tu ?

```bash
> E700000E000C001101030006000000000000
< E70000AE00AC0011020300A6...
```

On demande tous les attributs de l'objet MDS (handle 0), c'est-à-dire le lecteur lui-même. Remarquez l'invoke-id : `00 11`, soit le `00 10` précédent plus un. La réponse contient le fabricant, le modèle, le numéro de série, le firmware et l'horloge. Je la décortique au chapitre suivant, parce qu'elle mérite qu'on s'y attarde.

### 8 et 9. Qu'est-ce que tu as en mémoire ?

```bash
> E7000014001200120107000C01000C0D0006000100020000
< E700012E012C00120207012601000C0D0120...
```

On demande au PM-Store (`01 00`) l'action `0C 0D` (SEG\_GET\_INFO) pour tous ses segments. Le lecteur répond avec la description de chacun d'eux. Sur le vrai Accu-Chek Guide, on en trouve quatre, avec des noms assez parlants :

<table id="bkmrk-instance-nom-contenu"><thead><tr><th>Instance</th><th>Nom</th><th>Contenu</th></tr></thead><tbody><tr><td>0</td><td><code>PMSegGluc</code></td><td>les glycémies</td></tr><tr><td>1</td><td><code>PMSegCtrl</code></td><td>les mesures de contrôle (solution de test)</td></tr><tr><td>2</td><td><code>PMSegError</code></td><td>les erreurs</td></tr><tr><td>3</td><td><code>PMSegCMeal</code></td><td>les marqueurs repas</td></tr></tbody></table>

C'est une des grosses différences avec la version d'origine, qui lisait simplement le segment 0. Le code ne se fie pas aux noms ni aux numéros : `segmentKind` lit la "carte" de chaque segment (l'attribut `PM_SEG_MAP`), qui décrit le format exact de ses entrées :

- **glycémies** : objet numérique, partition 2, qui n'est pas une mesure de contrôle, avec une valeur sur 2 octets et un statut sur 2 octets ;
- **marqueurs repas** : objet "énumération", partition 128, code `MDC_CTXT_GLU_MEAL`, avec un code repas sur 2 octets.

Au passage, on récupère le compteur d'entrées de chaque segment (`SEG_USAGE_CNT`). Ça permettra de vérifier à la fin qu'on a bien tout reçu : "le lecteur annonce 638 mesures, on en a reçu 638".

### 12. Le transfert des glycémies

On demande le transfert du segment des glycémies avec l'action `0C 1C` (SEG\_TRIG\_XFER) :

```bash
> E7000010000E00130107000801000C1C00020000
< E7000012001000130207000A01000C1C000400000000
```

La réponse fait 22 octets. Les deux derniers sont le verdict : `00 00` veut dire "c'est parti", `00 03` veut dire "ce segment est vide" (un lecteur tout neuf, par exemple). Tout autre code est une erreur. Puis le lecteur commence à envoyer ses paquets de données :

```bash
< E700003800360020010100300100FFFFFFFF0D2100260000000000000000000280000018202609300731120000700000202609301302400000B60000
```

C'est le message le plus intéressant de toute la conversation. Le voici découpé :

```bash
E7 00 00 38 00 36    message de données, longueurs
00 20                invoke-id choisi par le lecteur
01 01                événement à confirmer
00 30                longueur
01 00                objet : le PM-Store
FF FF FF FF          temps relatif (non utilisé)
0D 21                événement MDC_NOTI_SEGMENT_DATA : "voici des mesures"
00 26                longueur
00 00                instance du segment (0 = glycémies)
00 00 00 00          index de la première entrée de ce paquet
00 02                nombre d'entrées dans ce paquet : 2
80 00                statut : 0x80 = premier paquet (0x40 = dernier)
00 18                taille des entrées : 24 octets = 2 x 12

20 26 09 30 07 31 12 00   date BCD : 2026/09/30 07:31:12
00 70                     valeur : 0x70 = 112 mg/dL
00 00                     statut : 0, mesure normale

20 26 09 30 13 02 40 00   date BCD : 2026/09/30 13:02:40
00 B6                     valeur : 0xB6 = 182 mg/dL
00 00                     statut : 0
```

Chaque glycémie tient donc sur **12 octets** : 8 pour la date, 2 pour la valeur en mg/dL, 2 pour le statut. Les mmol/L sont simplement calculés en divisant par 18.

Après chaque paquet, il faut renvoyer un accusé de réception, sinon le lecteur n'envoie pas la suite :

```bash
> E700001E001C0020020100160100FFFFFFFF0D21000C000000000000000000020080
```

On y recopie l'invoke-id du lecteur (`00 20`), l'instance, l'index et le nombre d'entrées du paquet, suivis du statut `00 80`, qui signifie "le manager confirme".

<p class="callout info">Petite anecdote de reverse engineering : dans le code d'origine (et dans Tidepool), ces champs s'appelaient simplement `u0`, `u1` et `u2`, des valeurs "qu'on renvoie telles quelles" sans trop savoir ce qu'elles sont. En les comparant à la norme, on comprend que ce sont l'instance du segment, l'index de la première entrée, le nombre d'entrées et le statut du paquet. Le code garde ces noms, mais on sait maintenant ce qu'il recopie.</p>

La boucle continue jusqu'au paquet dont le statut a le bit `0x40` ("dernier paquet"). Détail qui m'a fait chercher un moment : pendant cette boucle, les invoke-id sont ceux du **lecteur** (`00 20`, `00 21`...). Une fois le segment terminé, il faut revenir à *notre* numérotation, celle de la réponse au déclenchement du transfert. C'est le rôle de la variable `answerInvokeId` dans `transferSegment`.

### Le transfert des marqueurs repas

Si le lecteur a un segment de marqueurs repas non vide, on recommence exactement la même danse avec son instance (ici la 3). Les entrées sont plus petites, **10 octets** :

```bash
20 26 09 30 07 31 12 00   date BCD : 2026/09/30 07:31:12
72 54                     code repas : 29268 = à jeun
```

Il n'y a pas de valeur de glycémie ici : juste une date et un code. Comment on relie ce marqueur à la bonne mesure ? Je l'explique au chapitre suivant.

### Fin. On raccroche proprement

```bash
> E40000020000
< E50000020000
```

On envoie une demande de fin d'association (`E4 00`, "fin normale") et le lecteur confirme (`E5 00`). Transfert terminé s'affiche sur l'écran du glucomètre

Un choix que j'ai fait ici : si cette toute dernière étape échoue (lecteur débranché un poil trop tôt, par exemple), on **garde** quand même les mesures. Tous les paquets ont déjà été confirmés, les données sont complètes, il n'y a aucune raison de les jeter pour un "au revoir" manqué.

### Et si le lecteur coupe en plein milieu ?

À n'importe quelle étape, le lecteur peut envoyer un abandon (`E6 00`) au lieu de la réponse attendue. La fonction `receive` le détecte à chaque message reçu et arrête tout avec un message précis, du genre `received association abort request instead of MDS attribute answer`. Pas de mesures à moitié lues : on recommencera.