Nos produits embarqués tournent sur Zephyr et sur le nRF Connect SDK de Nordic Semiconductor. Ces bibliothèques, d'autres les écrivent, mais c'est nous qui les livrons dans nos produits — alors quand elles flanchent, c'est à nous de comprendre pourquoi. En voici un bon exemple.
Une de nos passerelles LTE-M, construite autour d'un nRF9151, fonctionnait parfaitement au labo. Puis un lot de cartes SIM est arrivé, et certaines refusaient tout simplement de se connecter à notre serveur applicatif. Même carte électronique, même firmware, même APN, même serveur. Seule la carte SIM changeait.
L'enquête nous a fait passer par les traces AT du modem, la couche de gestion de session EPS, le provisionnement à distance des cartes SIM M2M, et finalement par le code du SDK qu'on utilise tous les jours. Elle s'est terminée par un correctif qui fait aujourd'hui partie du nRF Connect SDK, pour tous les développeurs qui l'utilisent.
Le symptôme : un bug qui suit la carte SIM
La passerelle ouvre une session sécurisée DTLS (CoAP) vers notre serveur dès que le réseau cellulaire est disponible. Avec certaines cartes SIM, ça échouait à chaque fois : la poignée de main peinait, puis le tout premier envoi de données était refusé.
Le premier réflexe, quand un produit se comporte différemment d'un exemplaire à l'autre, c'est de soupçonner le matériel. On a donc échangé les cartes SIM entre passerelles. Le verdict est sans appel : le problème suit la carte SIM, pas la carte électronique. Quatre cartes du lot le présentaient, systématiquement. Une cinquième, du même forfait et sur le même APN, fonctionnait sans accroc.
À ce stade, il est tentant de conclure « carte SIM défectueuse » et de renvoyer le lot. On a préféré comprendre.
Lire les traces AT du modem
Le modem du nRF9151 signale son état via des notifications AT. En mettant côte à côte la trace d'une carte concernée et celle de la carte de référence, la différence saute aux yeux en quelques centaines de millisecondes.
| Uptime | Événement | Ce que ça signifie |
|---|---|---|
| 17,926 s | +CGEV: ME PDN ACT 0,0 | Contexte PDN par défaut (CID 0) activé |
| 18,428 s | +CGPADDR: 0,"10.x.x.x" | Adresse IP obtenue sur CID 0 |
| 18,804 s | +CGEV: ME PDN ACT 1 | Un second contexte (CID 1) apparaît, que personne n'a demandé |
| 18,809 s | Application | Réseau disponible : ouverture de la session DTLS |
| 19,203 s | +CGEV: ME PDN DEACT 1 | CID 1 désactivé, en pleine poignée de main |
| 19,303 s | +CNEC_ESM: 36,1 | Cause ESM 36 : « regular deactivation », pas une erreur |
| 21,948 s | Application | Premier envoi de données : échec |
Carte SIM concernée (SGP.02). Avec la carte de référence, la même séquence se déroule sans aucun événement CID 1, et la session s'établit normalement.
Un contexte PDN secondaire — un second « tuyau » de données vers le réseau — est ouvert puis refermé environ 400 ms plus tard, sans que notre firmware ne l'ait jamais demandé. Aucune commande AT+CGDCONT, aucune gestion de contexte secondaire de notre côté.
Qui ouvre ce contexte ?
La notification indique ME PDN ACT (Mobile Equipment) et non NW PDN ACT : c'est le terminal lui-même qui active le contexte, pas le réseau. Ajouté à une cadence de nouvelles tentatives typique d'une logique côté client (toutes les ~30 secondes, puis de plus en plus espacées), tout désigne la carte SIM : les cartes M2M compatibles GSMA SGP.02 (provisionnement SIM à distance) peuvent demander au modem, via une commande SIM Toolkit (OPEN CHANNEL, protocole BIP), d'ouvrir leur propre contexte de données pour joindre leur plateforme de provisionnement.
Cette activité est donc légitime, et elle n'est pas de notre ressort. Mais elle soulève une question bien plus intéressante.
La vraie question : pourquoi un contexte qu'on n'utilise pas casse notre connexion ?
Notre session passe par le contexte par défaut, CID 0. CID 1 appartient à la carte SIM. Sa désactivation devrait être un non-événement pour nous — et les traces modem confirment que CID 0 reste actif tout du long. Pourtant, notre application se comporte comme si le réseau avait disparu.
Quelque chose, entre le modem et notre application, interprétait donc mal cet événement. Direction le code du SDK.
Dans le nRF Connect SDK, la bibliothèque lte_link_control analyse les notifications +CGEV du modem et les transforme en événements LTE_LC_EVT_PDN. Ces événements sont notamment consommés par lte_net_if, la couche qui expose le modem comme une interface réseau Zephyr (net_if) et la raccorde au gestionnaire de connectivité (conn_mgr).
Et c'est là que se trouvait le bug : lte_net_if traitait tous les événements PDN comme s'ils concernaient le contexte par défaut, sans jamais vérifier l'identifiant de contexte (CID). À la désactivation de CID 1, elle faisait exactement ce qu'elle aurait fait si la connexion principale avait été perdue :
- suppression des adresses IPv4/IPv6 de l'interface ;
- passage de la
net_ifen état dormant ; - propagation d'un événement de déconnexion L4 (
NET_EVENT_L4_DISCONNECTED) vers Zephyr et l'application.
Notre application, faisant exactement ce qu'elle doit faire quand le réseau disparaît, fermait sa session DTLS. Le bug n'était pas dans notre code — mais il fallait connaître notre code, la pile réseau de Zephyr et la couche modem du SDK pour le trouver.
Pourquoi personne ne l'avait vu avant ? La plupart des cartes SIM n'ouvrent jamais de contexte secondaire. Le bug ne se manifeste qu'avec celles qui le font — les cartes M2M SGP.02, mais aussi toute application qui gère elle-même plusieurs contextes PDN. Un cas d'école de bug qui se cache dans la combinaison des composants, pas dans l'un d'eux en particulier.
Le correctif : six lignes
Une fois le diagnostic posé, le correctif est presque décevant de simplicité. lte_net_if ne gère que le contexte par défaut : il lui suffit d'ignorer les autres.
@@ -332,6 +332,12 @@ static void lte_evt_handler(const struct lte_lc_evt *const evt) break; case LTE_LC_EVT_PDN:+ /* We only handle default PDN (CID 0), ignore others */+ if (evt->pdn.cid != 0) {+ LOG_DBG("Filtered PDN CID %d event: %d", evt->pdn.cid, evt->pdn.type);+ break;+ }+ switch (evt->pdn.type) { case LTE_LC_EVT_PDN_ACTIVATED: LOG_DBG("PDN connection activated");
On a validé le correctif sur nos passerelles avec les quatre cartes SIM concernées : la session applicative ne tombe plus, et CID 0 reste actif du début à la fin. On aurait pu s'arrêter là, avec un patch local dans notre manifeste west. On a choisi d'aller plus loin.
L'upstreaming : du patch à la pull request
Corriger un bug dans son propre fork, c'est l'affaire d'un après-midi. Le faire accepter par les mainteneurs d'un SDK utilisé par des milliers de produits, c'est un autre exercice : le correctif doit être juste pour tout le monde, pas seulement pour nous. Voici comment ça s'est passé.
-
24 juil. 2026
Ouverture de la PR sur nrfconnect/sdk-nrf
Première version, plus ambitieuse : une garde dans
lte_net_if, et une seconde danslte_link_controlpour qu'elle ne remonte que les événements des contextes qu'elle gère elle-même. Contribution externe oblige : signature du CLA, et la CI ne se déclenche qu'après autorisation explicite d'un mainteneur Nordic. - Le jour même Premier retour : le format des commits Le contrôle de conformité refuse nos messages de commit : des lignes de plus de 72 caractères. Anodin, mais ça rappelle que chaque projet a ses conventions, et qu'elles ne sont pas optionnelles.
-
27 juil.
Les tests unitaires nous rattrapent
La suite de tests
tests/lib/lte_lc_pdnéchoue. On va être honnêtes : on n'avait pas lancé Twister en local sur cette partie. Le test a révélé que notre modification delte_link_controlchangeait son comportement public pour du code qui s'appuyait dessus. La question remonte à l'équipe modem de Nordic. -
28 juil. → 6 août
La revue : moins, c'est mieux
Recommandation des relecteurs : ne garder que la modification de
lte_net_if.lte_link_controldoit continuer à remonter tous les événements PDN — d'autres applications peuvent légitimement en avoir besoin. C'est au consommateur des événements de filtrer ce qui le concerne. -
6 août 2026
Approuvée, intégrée, rétroportée
Un commit, un fichier, six lignes. Trois approbations d'ingénieurs Nordic, CI au vert, intégration dans
main. Comme la modification n'impose aucune adaptation aux applications existantes (ni API, ni Kconfig, ni devicetree), elle est rétroportée sur lav3.4-branchpour la prochaine version corrective.
La version finale est plus petite, et meilleure, que celle qu'on avait proposée. C'est exactement à ça que sert la revue de code : notre correctif initial réglait notre problème ; le correctif intégré règle le problème sans en créer pour les autres.
Bonus : corriger la pile a révélé le vrai impact
Une fois que la session ne tombait plus à chaque événement CID 1, on a pu observer la passerelle sur de longues captures — et voir ce que le bug masquait jusque-là.
La carte SIM retente son contexte secondaire environ toutes les 30 secondes, quatre fois, avant d'espacer ses tentatives. Chaque tentative réveille la radio. Avec un minuteur actif (T3324) de 3 minutes négocié avec le réseau, ces réveils retardent de plusieurs minutes l'entrée en PSM, le mode de veille le plus profond du modem, à chaque cycle. Pour un équipement sur batterie ou sur panneau solaire, ça se mesure directement sur le bilan énergétique.
Ce volet-là ne se règle pas depuis le terminal : il dépend du profil de provisionnement de la carte SIM. Mais grâce au correctif, on a pu transmettre au fournisseur des cartes un rapport précis, décodé et chiffré, plutôt qu'un vague « certaines cartes ne marchent pas ».
Pourquoi on contribue en amont
Garder un patch local aurait été plus rapide à court terme. Voici pourquoi on ne s'en contente jamais :
- Un patch local, c'est de la dette technique. À chaque mise à jour du SDK, il faut le reporter, le retester, et espérer que le code autour n'a pas bougé.
- Le bug n'est pas qu'à nous. Les cartes SIM SGP.02 sont courantes dans l'IoT industriel. Tous les produits nRF91 qui les utilisent étaient exposés au même problème.
- La revue améliore le correctif. Notre patch final est meilleur grâce aux personnes qui connaissent ce code mieux que quiconque.
- C'est la preuve qu'on maîtrise notre pile. Zephyr, le nRF Connect SDK, le modem, la carte SIM : quand quelque chose casse, on descend jusqu'à la cause racine, quelle que soit la couche où elle se trouve.
« Les bibliothèques sur lesquelles on construit ne sont pas des boîtes noires. Quand on y trouve un bug, on le corrige — et on le corrige pour tout le monde. »
— Axel Moinet, Docteur en informatique, CodiumUtiliser de l'open source dans des produits industriels implique une responsabilité : le comprendre, et y contribuer en retour. Un correctif de six lignes accepté en amont vaut mieux que cent lignes de patchs maintenus dans notre coin.
Chez Codium, firmware, électronique et RF cohabitent sous le même toit, à Langres. C'est ce qui nous permet de suivre un bug de l'antenne jusqu'au code applicatif. Vous développez un produit connecté sur nRF91, nRF54 ou Zephyr ? Parlons-en.