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.

+6 lignes de C dans le correctif final
13 jours entre l'ouverture de la PR et son intégration
v3.4 rétroporté sur la branche stable

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énementCe que ça signifie
17,926 s+CGEV: ME PDN ACT 0,0Contexte 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 1Un second contexte (CID 1) apparaît, que personne n'a demandé
18,809 sApplicationRéseau disponible : ouverture de la session DTLS
19,203 s+CGEV: ME PDN DEACT 1CID 1 désactivé, en pleine poignée de main
19,303 s+CNEC_ESM: 36,1Cause ESM 36 : « regular deactivation », pas une erreur
21,948 sApplicationPremier 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_if en é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.

lib/nrf_modem_lib/lte_net_if/lte_net_if.c+6 −0
@@ -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é.

  1. 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 dans lte_link_control pour 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.
  2. 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.
  3. 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 de lte_link_control changeait son comportement public pour du code qui s'appuyait dessus. La question remonte à l'équipe modem de Nordic.
  4. 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_control doit 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.
  5. 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 la v3.4-branch pour 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, Codium
Position Codium — Nov. 2026

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

Un produit connecté sur Zephyr ou nRF Connect SDK ?
De la conception de la carte au firmware, en passant par la connectivité cellulaire : on développe et on débogue sur toute la pile. Parlez-nous de votre projet.
Discuter de votre projet Notre bureau d'études