<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.connexity.fr/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.connexity.fr/" rel="alternate" type="text/html" hreflang="fr" /><updated>2026-10-07T11:40:14+02:00</updated><id>https://www.connexity.fr/feed.xml</id><title type="html">Sami Lahamel</title><subtitle>Responsable Technique chez CITEOS. Régulation de trafic, informatique industrielle et supervision de réseaux et d&apos;équipements de terrain.</subtitle><author><name>Sami Lahamel</name></author><entry><title type="html">Une passerelle DIASER / LCR pour panneaux à messages variables</title><link href="https://www.connexity.fr/blog/2026/10/passerelle-diaser-lcr-pmv/" rel="alternate" type="text/html" title="Une passerelle DIASER / LCR pour panneaux à messages variables" /><published>2026-10-05T00:00:00+02:00</published><updated>2026-10-05T00:00:00+02:00</updated><id>https://www.connexity.fr/blog/2026/10/passerelle-diaser-lcr-pmv</id><content type="html" xml:base="https://www.connexity.fr/blog/2026/10/passerelle-diaser-lcr-pmv/"><![CDATA[<p>Les panneaux à messages variables (PMV) que nous exploitons dialoguent en LCR, le langage de commande routier, sur TCP. Notre superviseur de trafic, lui, dialogue en DIASER, sur UDP. Entre les deux, il faut un traducteur : une passerelle applicative. Celle que proposait le fabricant des panneaux nous a posé des problèmes de fiabilité. Nous avons donc écrit la nôtre, en C, pour la faire tourner dans un boîtier industriel Teltonika TRB140.</p>

<h2 id="le-besoin">Le besoin</h2>

<p>Le superviseur de trafic commande un panneau comme il commande n’importe quel équipement du réseau de régulation : par des transactions DIASER, courtes, en texte, avec acquittement. Il décrit un panneau en termes de lignes, de caractères, de durée de validité, d’état et de défauts.</p>

<p>Le panneau, lui, attend des commandes LCR : un message par ligne, avec ses attributs de clignotement, d’alternance, de durée de validité et d’éclairage, et il répond à des interrogations d’état, de cellules photosensibles et de pixels. Les deux protocoles ne se ressemblent ni dans leur transport, ni dans leur vocabulaire. La passerelle doit donc faire plus que relayer des octets : elle traduit, tient un état et surveille le panneau.</p>

<h2 id="pourquoi-une-passerelle-maison">Pourquoi une passerelle maison</h2>

<p>Une passerelle est un maillon critique. Quand elle défaille, le superviseur perd la main sur le panneau, et un message d’alerte peut rester affiché, ou ne jamais s’afficher. Les problèmes de fiabilité rencontrés avec la passerelle du fabricant nous ont décidés à reprendre la maîtrise de la chaîne.</p>

<p>Écrire la passerelle nous-mêmes a donné trois choses :</p>

<ul>
  <li><strong>un comportement connu</strong> : nous savons ce qu’elle fait à chaque transaction, et nous pouvons le tracer ;</li>
  <li><strong>un logiciel qui se corrige</strong> : un défaut observé sur le terrain se traite directement, sans attendre un tiers ;</li>
  <li><strong>un déploiement simple</strong> : le même programme sert pour tous les panneaux, paramétré à son lancement.</li>
</ul>

<h2 id="architecture">Architecture</h2>

<p>La passerelle est un unique programme en C, découpé en trois fils d’exécution (threads) qui ne se bloquent pas les uns les autres.</p>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart TB
    SUP["Superviseur de trafic"] &lt;--&gt;|"DIASER · UDP"| GW
    subgraph TRB["Boîtier industriel Teltonika TRB140"]
        GW["Fil DIASER&lt;br/&gt;répond au superviseur"] &lt;--&gt; ETAT[("État du panneau&lt;br/&gt;en mémoire")]
        ETAT &lt;--&gt; LCR["Fil LCR&lt;br/&gt;dialogue avec le panneau"]
        ETAT &lt;--&gt; DEF["Fil défauts&lt;br/&gt;analyse des réponses"]
    end
    LCR &lt;--&gt;|"LCR · TCP"| PMV["Panneau à messages variables"]
    style PMV fill:#fff4e0,stroke:#e9a23b,color:#27262b
    style ETAT fill:#e3f5ea,stroke:#2fb37a,color:#27262b
</code></pre>

<ul>
  <li><strong>Le fil DIASER</strong> écoute les transactions du superviseur en UDP, les décode, met à jour l’état du panneau en mémoire et répond aussitôt.</li>
  <li><strong>Le fil LCR</strong> tient une connexion TCP permanente avec le panneau, lui envoie les messages à afficher et l’interroge régulièrement.</li>
  <li><strong>Le fil défauts</strong> lit les réponses du panneau, en extrait les défauts, les cellules photosensibles et la synthèse de l’état technique, compare l’affichage réel au message demandé, et met à jour l’état en mémoire.</li>
</ul>

<p>Le point clé est l’<strong>état tenu en mémoire</strong>. Tant que le panneau répond, le superviseur n’attend jamais lui : sa transaction est traitée à partir de ce que la passerelle sait déjà, et la réponse part immédiatement. Et quand le panneau ne répond plus, ou n’affiche pas ce qui lui est demandé, la passerelle ne répond plus non plus : c’est le silence qui avertit le superviseur (voir plus bas).</p>

<h2 id="les-fonctions-diaser-prises-en-charge">Les fonctions DIASER prises en charge</h2>

<p>Les fonctions prises en charge couvrent l’application « Panneaux » de DIASER, identifiée par la lettre <code class="language-plaintext highlighter-rouge">P</code>. Une fonction de lecture se note en majuscule, sa forme d’écriture en minuscule.</p>

<table>
  <thead>
    <tr>
      <th>Fonction</th>
      <th>Forme</th>
      <th>Code</th>
      <th>Traduction côté panneau</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Identification</td>
      <td>lecture</td>
      <td><strong>Z</strong></td>
      <td>Répondue par la passerelle, sans dialogue avec le panneau</td>
    </tr>
    <tr>
      <td>Horodate</td>
      <td>lecture</td>
      <td><strong>Y</strong></td>
      <td>Heure de la passerelle</td>
    </tr>
    <tr>
      <td>Configuration du panneau</td>
      <td>écriture / lecture</td>
      <td><strong>a</strong> / <strong>A</strong></td>
      <td>Mémorise les temporisations (clignotement, alternance, durée de validité)</td>
    </tr>
    <tr>
      <td>Commande de veille</td>
      <td>écriture</td>
      <td><strong>b</strong></td>
      <td>Met le panneau en veille</td>
    </tr>
    <tr>
      <td>Luminosité</td>
      <td>écriture</td>
      <td><strong>c</strong></td>
      <td>Choix de l’éclairage : jour, nuit, surbrillance ou automatique</td>
    </tr>
    <tr>
      <td>Message fixe</td>
      <td>écriture</td>
      <td><strong>e</strong></td>
      <td>Affichage d’un message, ligne par ligne</td>
    </tr>
    <tr>
      <td>Message clignotant</td>
      <td>écriture</td>
      <td><strong>f</strong></td>
      <td>Même message, avec clignotement</td>
    </tr>
    <tr>
      <td>Message alterné</td>
      <td>écriture</td>
      <td><strong>g</strong></td>
      <td>Deux messages qui alternent</td>
    </tr>
    <tr>
      <td>Contrôle d’état du panneau</td>
      <td>lecture</td>
      <td><strong>D</strong> / <strong>d</strong></td>
      <td>État, luminosité, cellules ; la forme <code class="language-plaintext highlighter-rouge">d</code> signale un changement</td>
    </tr>
    <tr>
      <td>Défauts en cours</td>
      <td>lecture</td>
      <td><strong>P</strong></td>
      <td>Défauts de pixel, d’alimentation, de cellule, fin de validité</td>
    </tr>
  </tbody>
</table>

<p>Les fonctions qui n’ont pas de sens pour un panneau reçoivent le code d’erreur normalisé « code fonction non reconnu », plutôt qu’un silence qui laisserait le superviseur attendre.</p>

<h2 id="du-message-diaser-à-la-commande-lcr">Du message DIASER à la commande LCR</h2>

<p>Un message à afficher arrive en DIASER sous la forme d’une durée de validité, d’un nombre de lignes, d’un nombre de caractères par ligne et du texte. La passerelle le découpe ligne par ligne et construit une commande LCR d’affichage, avec les attributs que le superviseur a demandés. Voici un exemple pour un panneau de deux lignes de quinze caractères, les points figurant des espaces (valeurs d’exemple) :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DIASER   Pe 0A 2 0F BOUCHON........RALENTISSEZ....
LCR      PA ID=... AM=1.1 AF="BOUCHON        " CL=0 DV=00:10:00 EC=AU
                       AM=1.2 AF="RALENTISSEZ    " CL=0 DV=00:10:00 EC=AU
</code></pre></div></div>

<p>Chaque ligne du panneau devient un message adressé (<code class="language-plaintext highlighter-rouge">AM</code>), avec son texte (<code class="language-plaintext highlighter-rouge">AF</code>), son clignotement (<code class="language-plaintext highlighter-rouge">CL</code>), sa durée de validité (<code class="language-plaintext highlighter-rouge">DV</code>) et son éclairage (<code class="language-plaintext highlighter-rouge">EC</code>). Un message alterné ajoute l’alternance (<code class="language-plaintext highlighter-rouge">AT</code>), avec les deux textes et leurs durées d’affichage.</p>

<p>Le message est envoyé <strong>une seule fois</strong>, avec sa durée de validité : c’est le panneau qui gère l’échéance. Si la passerelle ou le réseau devait s’interrompre, le panneau retire de lui-même le message à l’issue de sa durée de validité, au lieu de rester bloqué sur une information périmée.</p>

<h2 id="surveiller-le-panneau">Surveiller le panneau</h2>

<p>Quand aucun message n’est en attente, le fil LCR ne reste pas inactif : il interroge le panneau, environ une fois par seconde, en alternant cinq questions. Elles s’appuient sur des commandes propres au fabricant du panneau, comme la commande <code class="language-plaintext highlighter-rouge">ST STR</code>, qui renvoie la synthèse de l’état technique.</p>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart TB
    MSG["Message à afficher&lt;br/&gt;reçu du superviseur"] --&gt; LCR
    LCR["&lt;b&gt;Fil LCR&lt;/b&gt; · une requête par seconde&lt;br/&gt;affichage, si un message est en attente&lt;br/&gt;sinon une interrogation à tour de rôle :&lt;br/&gt;défauts · état technique · cellules · pixels · affichage"] --&gt;|"LCR · TCP"| PAN
    PAN["Panneau à messages variables"] --&gt;|"réponses"| DEF
    DEF["&lt;b&gt;Fil défauts&lt;/b&gt; · analyse des réponses&lt;br/&gt;pixels · alimentation · cellules&lt;br/&gt;fin de validité · affichage réel"] --&gt; ETAT
    ETAT[("État du panneau en mémoire&lt;br/&gt;lu par le superviseur")]
    style LCR fill:#eef0ff,stroke:#5c5adc,color:#27262b
    style PAN fill:#fff4e0,stroke:#e9a23b,color:#27262b
    style ETAT fill:#e3f5ea,stroke:#2fb37a,color:#27262b
</code></pre>

<p>Le fil défauts exploite les réponses :</p>

<ul>
  <li><strong>Défauts de pixel</strong> : le panneau indique la position des pixels défaillants, que la passerelle convertit en défauts DIASER avec leur position.</li>
  <li><strong>Synthèse technique</strong> : la réponse à la synthèse de l’état technique donne notamment l’état de l’alimentation. Un défaut de batterie ou d’alimentation devient un défaut DIASER d’alimentation.</li>
  <li><strong>Fin de durée de validité</strong> : signalée au superviseur, pour qu’il sache qu’un message s’est éteint de lui-même.</li>
  <li><strong>Cellules photosensibles</strong> : leurs mesures donnent le niveau de luminosité de jour, de nuit ou de surbrillance, que le superviseur lit dans l’état du panneau quand l’éclairage est laissé en mode local.</li>
</ul>

<p>Dès que le nombre de défauts change, la passerelle lève un indicateur de changement : la prochaine lecture de l’état répond par la forme « changement », et le superviseur vient lire la liste des défauts. Il n’a pas besoin d’interroger les défauts en permanence pour ne rien rater.</p>

<h2 id="vérifier-que-le-panneau-affiche-ce-qui-est-demandé">Vérifier que le panneau affiche ce qui est demandé</h2>

<p>Un panneau qui acquitte une commande ne prouve pas qu’il affiche le message : une erreur de module, une coupure d’alimentation ou une commande locale peuvent laisser un autre texte à l’écran. Un acquittement ne suffit donc pas, la passerelle doit relire l’affichage.</p>

<p>Elle mémorise le message demandé par le superviseur, puis, dans sa rotation d’interrogations, <strong>relit l’affichage réel du panneau</strong> avec la commande <code class="language-plaintext highlighter-rouge">PE AM=*</code>, qui renvoie une ligne par module avec son texte. La relecture est comparée au message attendu, ligne à ligne, en ignorant les espaces de fin.</p>

<ul>
  <li><strong>Concordance</strong> : rien à faire, le compteur de tentatives repart de zéro.</li>
  <li><strong>Discordance</strong> : après un court délai laissé au panneau pour afficher, la passerelle <strong>renvoie le message</strong>, jusqu’à trois fois.</li>
  <li><strong>Discordance persistante</strong> : si le panneau n’affiche toujours pas ce qui est demandé, la passerelle <strong>coupe la communication DIASER</strong>, comme pour une liaison perdue, et continue de renvoyer le message. Dès que l’affichage est conforme, la communication reprend d’elle-même.</li>
</ul>

<p>Pourquoi couper plutôt que répondre par une erreur ? Parce que le fonctionnement est asynchrone : la réponse DIASER part avant que le panneau ait eu le temps d’afficher, on ne peut donc pas la conditionner à la relecture. En revanche, quand la discordance est établie, le silence est un signal que le superviseur sait déjà lire : la station est indisponible, il ne doit pas croire qu’un message est affiché.</p>

<p>Trois cas ne sont pas comparés, pour éviter les fausses alertes : un message dont la durée de validité est écoulée, un message alterné dont l’affichage change à chaque instant, et une lecture dont le nombre de modules ne correspond pas à la taille du panneau.</p>

<h2 id="quand-le-panneau-ne-répond-plus">Quand le panneau ne répond plus</h2>

<p>La liaison TCP avec le panneau peut tomber : coupure réseau, redémarrage du panneau, boîtier qui perd sa liaison. Plus insidieux encore, la connexion peut rester ouverte alors que le panneau ne répond plus. La passerelle surveille donc la <strong>date de la dernière réponse du panneau</strong>, quelle qu’elle soit.</p>

<p>Au-delà d’un délai de dix secondes sans réponse, la liaison est déclarée perdue. La passerelle fait alors deux choses :</p>

<ul>
  <li><strong>elle cesse de répondre aux transactions DIASER</strong>, sans les traiter et sans renvoyer de code d’erreur. Le superviseur interprète l’absence de réponse comme une station injoignable, ce qu’elle est : mieux vaut un silence franc qu’une réponse rassurante qui décrirait un état que la passerelle ne peut plus vérifier ;</li>
  <li><strong>elle reconstruit la connexion</strong> : elle ferme la socket, attend cinq secondes, puis se reconnecte et reprend ses interrogations.</li>
</ul>

<p>Dès que le panneau répond de nouveau, les transactions DIASER sont traitées comme avant, sans intervention.</p>

<h2 id="un-banc-dessai-pour-tout-voir">Un banc d’essai pour tout voir</h2>

<p>Une passerelle se met au point en regardant les trois acteurs en même temps : celui qui commande, celui qui exécute et celui qui traduit. Notre banc d’essai les met côte à côte.</p>

<figure class="post-figure">
<img src="/assets/images/blog/pmv-banc-essai.webp" alt="Deux écrans : à gauche le superviseur de trafic avec le message commandé, à droite la page web de pilotage du panneau montrant le message appliqué, et en dessous la passerelle en mode débogage" />
<figcaption>Le banc d'essai. À gauche, le superviseur de trafic avec le message commandé. À droite, en haut, la page web de pilotage du panneau, qui montre le message appliqué ; en bas, la passerelle en mode débogage, pour voir ce qui se passe.</figcaption>
</figure>

<p>Dans le journal de la passerelle, on suit chaque échange : la transaction DIASER reçue, la réponse renvoyée au superviseur, puis les requêtes envoyées au panneau et leurs acquittements. Quand le message commandé à gauche s’affiche à droite sur la page du panneau, la chaîne est validée.</p>

<h2 id="dans-un-boîtier-teltonika-trb140">Dans un boîtier Teltonika TRB140</h2>

<p>Le programme est embarqué dans une passerelle industrielle Teltonika TRB140, un produit pensé pour héberger ce type d’application au plus près de l’équipement. Il se paramètre à son lancement : port d’écoute DIASER, adresse et port LCR du panneau, identifiant de la station, nombre de lignes et de caractères par ligne, niveau de journalisation. Le même programme sert donc pour des panneaux de tailles différentes.</p>

<figure class="post-figure">
<img src="/assets/images/blog/passerelle-trb140.webp" alt="Sur un plan de travail : le boîtier Teltonika TRB140 relié par câble réseau, la carte de pilotage du panneau dans son boîtier vert à capot transparent, une alimentation sur rail et un boîtier industriel Siemens" />
<figcaption>Le boîtier Teltonika TRB140, en bas à droite, et le pilote du panneau, la carte verte sous capot transparent.</figcaption>
</figure>

<p>Un mode bavard, activable au lancement, journalise chaque transaction DIASER et chaque échange LCR avec un horodatage à la milliseconde. C’est l’outil qui rend un défaut observé sur le terrain reproductible : on voit ce que le superviseur a demandé, ce que la passerelle a envoyé et ce que le panneau a répondu.</p>

<h2 id="technologies">Technologies</h2>

<p>C (threads POSIX, sockets UDP et TCP), Linux embarqué, boîtier industriel Teltonika TRB140, DIASER (UDP, application panneaux), LCR (TCP, NF P 99-341), panneaux à messages variables.</p>

<p><a href="/contact/" class="btn btn-primary">Me contacter</a></p>]]></content><author><name>Sami Lahamel</name></author><category term="PMV" /><category term="LCR" /><category term="DIASER" /><summary type="html"><![CDATA[Une passerelle relie un superviseur de trafic (DIASER) à des panneaux à messages variables (LCR) : traduction des commandes, contrôle de l'affichage, surveillance de la liaison.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.connexity.fr/assets/images/og-card.png" /><media:content medium="image" url="https://www.connexity.fr/assets/images/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Une station de comptage sur boucles avec un Siemens IOT2050</title><link href="https://www.connexity.fr/blog/2026/10/comptage-boucles-iot2050/" rel="alternate" type="text/html" title="Une station de comptage sur boucles avec un Siemens IOT2050" /><published>2026-10-04T00:00:00+02:00</published><updated>2026-10-04T00:00:00+02:00</updated><id>https://www.connexity.fr/blog/2026/10/comptage-boucles-iot2050</id><content type="html" xml:base="https://www.connexity.fr/blog/2026/10/comptage-boucles-iot2050/"><![CDATA[<p>Les boucles inductives sciées dans la chaussée restent le capteur de référence pour compter les véhicules et mesurer l’occupation d’une voie : éprouvées, précises et peu coûteuses à exploiter. Elles ont leur propre usure, puisqu’elles vieillissent avec l’enrobé dans lequel elles sont installées, mais ce sont aussi les automates qui les lisent qui finissent par ne plus être maintenus. Nous avons dû renouveler un parc de 14 stations de comptage sur boucles, construites à l’origine autour de l’automate Orion de FARECO, un produit en fin de vie et qui n’est plus maintenu. Plutôt que de chercher un remplaçant à l’identique, nous avons conçu notre propre automate de comptage sur une passerelle industrielle Siemens SIMATIC IOT2050, avec un logiciel écrit en C qui prend en charge les fonctions de mesure du trafic du protocole DIASER (<span style="white-space: nowrap">NF P 99-071</span>).</p>

<h2 id="le-besoin">Le besoin</h2>

<p>Le chantier porte sur l’automate, pas sur le capteur : les boucles inductives sont déjà dans la chaussée. Le remplaçant doit donc se brancher sur les détecteurs existants et continuer à dialoguer, en DIASER, comme l’équipement qu’il remplace.</p>

<p>Car un superviseur de trafic ne veut pas des signaux bruts d’une boucle, il veut des <strong>compteurs</strong> : un nombre de véhicules et un temps d’occupation, qu’il interroge à son rythme et dont il tire lui-même les débits et les taux d’occupation. La station a donc deux métiers : transformer des fronts électriques en compteurs fiables, et exposer ces compteurs dans le dialogue normalisé du trafic, sans que le superviseur ait à savoir que l’automate a changé.</p>

<h2 id="de-la-boucle-à-lentrée-numérique">De la boucle à l’entrée numérique</h2>

<p>Chaque boucle est reliée à un détecteur, qui bascule à l’arrivée d’un véhicule puis revient à l’état libre à son départ. La sortie du détecteur est raccordée à une entrée numérique de l’IOT2050. Deux fronts portent toute l’information :</p>

<ul>
  <li>le <strong>front montant</strong> : un véhicule entre sur la boucle ;</li>
  <li>le <strong>front descendant</strong> : il la quitte. La durée entre les deux est son temps de présence.</li>
</ul>

<p>Une station gère dix entrées, soit autant de boucles ou de voies à surveiller en parallèle.</p>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart TB
    B["Boucles inductives&lt;br/&gt;sciées dans l'enrobé"] --&gt; D["Détecteurs de boucle"]
    D --&gt;|"contacts · fronts montants / descendants"| IN
    subgraph IOT["Siemens IOT2050 · Debian"]
        IN["Entrées numériques"] --&gt; TH["Un thread par entrée&lt;br/&gt;attente passive des fronts"]
        TH --&gt; CP["Compteurs&lt;br/&gt;véhicules · occupation"]
        CP --&gt; SRV["Serveur DIASER&lt;br/&gt;application M · UDP"]
    end
    SRV &lt;--&gt;|"questions / réponses"| SUP["Superviseur de trafic"]
</code></pre>

<p>Sur le terrain, la station tient dans une armoire installée en bordure de chaussée. Quant à la boucle, elle est invisible : une fois le sillon scié dans l’enrobé puis rempli, il n’en reste que le tracé.</p>

<div class="carousel" data-carousel="">
  <div class="carousel-track" tabindex="0" aria-label="Captures d'écran">
    <figure class="carousel-slide">
      <a href="/assets/images/blog/comptage-station-bordure-voie.webp?v=20261007114014" target="_blank" rel="noopener" title="Ouvrir en taille réelle"><img src="/assets/images/blog/comptage-station-bordure-voie.webp?v=20261007114014" width="2000" height="1170" alt="Armoire de la station de comptage installée en bordure de chaussée, au pied d'un muret, avec la route et un pont haubané en arrière-plan" /></a>
      <figcaption>La station : une armoire en bordure de chaussée.</figcaption>
    </figure>
    <figure class="carousel-slide">
      <a href="/assets/images/blog/comptage-sciage-boucle.webp?v=20261007114014" target="_blank" rel="noopener" title="Ouvrir en taille réelle"><img src="/assets/images/blog/comptage-sciage-boucle.webp?v=20261007114014" width="2000" height="1170" alt="Chaussée vue de près, avec des sillons sciés dans l'enrobé dessinant le tracé d'une boucle de détection" loading="lazy" /></a>
      <figcaption>Le capteur, lui, est dans la chaussée : seuls les sillons sciés dans l'enrobé trahissent le tracé de la boucle.</figcaption>
    </figure>
  </div>
  <button type="button" class="carousel-btn prev" aria-label="Capture précédente"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5l-7 7 7 7" /></svg></button>
  <button type="button" class="carousel-btn next" aria-label="Capture suivante"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 5l7 7-7 7" /></svg></button>
  <div class="carousel-dots"><button type="button" aria-label="Capture 1"></button><button type="button" aria-label="Capture 2"></button></div>
</div>
<script>
  document.querySelectorAll('[data-carousel]').forEach(function (c) {
    if (c.dataset.ready) return; c.dataset.ready = '1';
    var track = c.querySelector('.carousel-track'), dots = c.querySelectorAll('.carousel-dots button');
    if (dots.length < 2) return;
    function idx() { return Math.round(track.scrollLeft / track.clientWidth); }
    function go(i) { i = Math.max(0, Math.min(dots.length - 1, i)); track.scrollTo({ left: i * track.clientWidth, behavior: 'smooth' }); }
    function sync() { var i = idx(); dots.forEach(function (d, k) { d.classList.toggle('on', k === i); });
      c.querySelector('.prev').disabled = i === 0; c.querySelector('.next').disabled = i === dots.length - 1; }
    c.querySelector('.prev').addEventListener('click', function () { go(idx() - 1); });
    c.querySelector('.next').addEventListener('click', function () { go(idx() + 1); });
    dots.forEach(function (d, k) { d.addEventListener('click', function () { go(k); }); });
    track.addEventListener('scroll', function () { window.requestAnimationFrame(sync); });
    track.addEventListener('keydown', function (e) { if (e.key === 'ArrowRight') go(idx() + 1); if (e.key === 'ArrowLeft') go(idx() - 1); });
    sync();
  });
</script>

<h2 id="acquisition-par-événements">Acquisition par événements</h2>

<p>La tentation, avec des entrées numériques, est de les lire en boucle à intervalle fixe. Ce serait gaspiller le processeur et, surtout, risquer de rater un front court, par exemple celui d’une moto ou d’un véhicule rapide sur une petite boucle.</p>

<p>Le logiciel de la station procède autrement. Chaque entrée est exposée par le noyau Linux et configurée pour signaler les deux fronts. Un thread dédié par entrée s’endort sur un <code class="language-plaintext highlighter-rouge">select()</code>, donc sans consommer de CPU, et n’est réveillé que par un changement d’état. À ce moment seulement, il lit la nouvelle valeur et prend l’horloge. Un mutex protège l’état de chaque capteur, partagé avec le thread qui répond au superviseur.</p>

<p>Le résultat est une acquisition pilotée par les événements, qui ne dépend pas d’une période de scrutation et ne coûte rien tant qu’aucun véhicule ne passe.</p>

<h2 id="des-compteurs-bruts-comme-le-veut-la-norme">Des compteurs bruts, comme le veut la norme</h2>

<p>La norme DIASER fixe une règle simple et exigeante : la station ne transmet <strong>jamais de quotient</strong>, seulement des compteurs <strong>cumulatifs, non remis à zéro à la lecture</strong>. Le superviseur calcule le débit ou le taux d’occupation par différence entre deux relevés.</p>

<p>La station entretient donc deux familles de compteurs :</p>

<ul>
  <li>le <strong>comptage</strong> : un véhicule détecté sur le front montant, c’est une unité ajoutée ;</li>
  <li>l’<strong>occupation</strong> : le temps pendant lequel la boucle est occupée, cumulé par pas de 0,25 s, l’unité d’échange de la norme.</li>
</ul>

<p>Chaque compteur est échangé sur <strong>un octet</strong> et boucle donc à 256. Ce n’est pas un défaut : le superviseur travaille par différence, y compris au passage du zéro, à condition d’interroger la station assez souvent pour que le compteur ne fasse jamais plus d’un tour entre deux relevés. C’est une contrainte de dimensionnement à garder en tête, côté période de scrutation comme côté trafic attendu.</p>

<p>Le choix d’un compteur sans remise à zéro rend aussi le système tolérant : si un relevé est perdu, rien n’est perdu avec lui, le suivant rattrape la différence.</p>

<h2 id="le-dialogue-diaser">Le dialogue DIASER</h2>

<p>Toute la station se pilote par l’application « Mesure du trafic » de DIASER, identifiée par la lettre <code class="language-plaintext highlighter-rouge">M</code>. Chaque transaction est une question suivie d’une réponse, encadrée par les caractères de début et de fin de trame et un caractère de contrôle, avec des valeurs codées en hexadécimal ASCII. Le logiciel de la station répond en UDP.</p>

<p>Les fonctions prises en charge couvrent le cycle de vie complet d’une station. Une fonction de lecture se note en majuscule et sa forme d’écriture en minuscule, comme le veut DIASER.</p>

<p><strong>Fonctions communes à toutes les applications (<code class="language-plaintext highlighter-rouge">*</code>)</strong></p>

<table>
  <thead>
    <tr>
      <th>Fonction</th>
      <th>Forme</th>
      <th>Code DIASER</th>
      <th>Remarques</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Identification</td>
      <td>lecture</td>
      <td><strong>Z</strong></td>
      <td>Identifiant de la station, version de configuration, constructeur, modèle et version du logiciel</td>
    </tr>
    <tr>
      <td>Horodate</td>
      <td>lecture</td>
      <td><strong>Y</strong></td>
      <td>Date et heure de la station, au dixième de seconde, avec le jour de la semaine</td>
    </tr>
    <tr>
      <td>Horodate</td>
      <td>écriture</td>
      <td><strong>y</strong></td>
      <td>Mise à l’heure par le superviseur, refusée si une source de référence a recalé l’horloge depuis moins de 12 heures</td>
    </tr>
  </tbody>
</table>

<p><strong>Fonctions de l’application Mesures (<code class="language-plaintext highlighter-rouge">M</code>)</strong></p>

<table>
  <thead>
    <tr>
      <th>Fonction</th>
      <th>Forme</th>
      <th>Code DIASER</th>
      <th>Remarques</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Configuration mesures</td>
      <td>lecture</td>
      <td><strong>B</strong></td>
      <td>Relecture de la configuration d’un compteur, pour vérifier ce que la station a retenu</td>
    </tr>
    <tr>
      <td>Configuration mesures</td>
      <td>écriture</td>
      <td><strong>b</strong></td>
      <td>Occupation (A), comptage de couloir sur 1 à 9 entrées (B) et vitesse sur couple de boucles (C), pour 32 compteurs au plus. Les comptages N, N+ et 2N-1 (D, E, H) sont refusés</td>
    </tr>
    <tr>
      <td>RàZ configuration mesures</td>
      <td>écriture</td>
      <td><strong>d</strong></td>
      <td>Libère tous les compteurs, acquittement <code class="language-plaintext highlighter-rouge">M!</code></td>
    </tr>
    <tr>
      <td>Lecture mesures (temps réel)</td>
      <td>lecture</td>
      <td><strong>A</strong> / <strong>a</strong></td>
      <td>Valeurs cumulatives de tous les compteurs configurés, sur un octet, avec indication de changement de défauts</td>
    </tr>
    <tr>
      <td>Lecture défauts en cours</td>
      <td>lecture</td>
      <td><strong>P</strong></td>
      <td>Défauts en cours de la station</td>
    </tr>
  </tbody>
</table>

<p>Les autres fonctions de l’application Mesures, comme la classification, les comptages différés ou la lecture multiple, ne sont pas encore prises en charge. La station y répond par le code d’erreur normalisé « code fonction non reconnu » plutôt que de rester muette, ce qui permet au superviseur de le détecter proprement.</p>

<p>Voici un échange typique, où le superviseur déclare un compteur de comptage sur la première entrée, puis le lit (valeurs données à titre d’exemple) :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Question  Mb 00 B1 00      déclare le compteur 0 : comptage, 1 entrée, entrée 0
Réponse   M!               acquittement
Question  MA               lecture des compteurs
Réponse   MA 02 1F 0C      2 compteurs : le premier à 0x1F, le second à 0x0C
</code></pre></div></div>

<p>La configuration et les valeurs des compteurs sont enregistrées dans des fichiers et restaurées au démarrage : un redémarrage de la passerelle ne fait ni perdre la programmation reçue du superviseur, ni repartir les compteurs de zéro.</p>

<h2 id="industrialiser-la-passerelle">Industrialiser la passerelle</h2>

<p>Le logiciel se compile directement sur la passerelle, qui embarque son propre compilateur sous Debian. Pour déployer, nous préparons une image de référence une fois, puis nous la clonons sur la mémoire eMMC de chaque station en réglant l’ordre de démarrage dans le chargeur de démarrage. Une nouvelle station est ainsi prête sans reconfiguration manuelle, ce qui compte quand on en multiplie le nombre.</p>

<figure class="post-figure" style="max-width: 24rem; margin-left: auto; margin-right: auto;">
<img src="/assets/images/blog/comptage-armoire-interieur.webp" alt="Intérieur d'une armoire de station de comptage : rails de montage avec disjoncteurs, alimentation, passerelle Siemens IOT2050 en haut à droite et borniers de raccordement" />
<figcaption>L'intérieur d'une station : protections, alimentation, passerelle IOT2050 en haut à droite et raccordements.</figcaption>
</figure>

<h2 id="la-suite--le-débit-classifié">La suite : le débit classifié</h2>

<p>Avec un couple de boucles par voie, on peut aller plus loin que le simple comptage et ranger chaque véhicule dans une classe de vitesse ou de longueur. C’est le développement que nous étudions, et la norme comme l’automate que nous remplaçons en fixent le principe.</p>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart LR
    V(["Véhicule&lt;br/&gt;sens de circulation"]) ==&gt; P
    subgraph VOIE["Une voie de mesure"]
        direction LR
        P["Boucle principale&lt;br/&gt;entrée n"] ==&gt;|"distance inter-boucles"| A["Boucle associée&lt;br/&gt;entrée n+1"]
    end
    A ==&gt; F(["Fin de zone"])
    style V fill:#fff4e0,stroke:#e9a23b,color:#27262b
    style F fill:#f4f5ff,stroke:#8a8da8,color:#27262b
    style P fill:#e3f5ea,stroke:#2fb37a,color:#27262b
    style A fill:#eef0ff,stroke:#5c5adc,color:#27262b
</code></pre>

<ul>
  <li>La <strong>vitesse</strong> découle du temps séparant les deux fronts montants, sur la boucle principale puis sur la boucle associée, rapporté à la distance qui les sépare.</li>
  <li>La <strong>longueur</strong> découle du temps de présence sur la boucle principale, combiné à cette vitesse.</li>
  <li>La <strong>prise en compte</strong> du véhicule se déclenche sur le front descendant de la boucle principale : c’est à cet instant que le compteur est incrémenté et que la classe est déterminée.</li>
</ul>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart LR
    subgraph SV["1 · Mesure de la vitesse"]
        direction TB
        T1["t1&lt;br/&gt;front montant&lt;br/&gt;boucle principale"] --&gt; T2["t2&lt;br/&gt;front montant&lt;br/&gt;boucle associée"]
        T2 --&gt; VIT["Vitesse&lt;br/&gt;distance ÷ (t2 - t1)"]
    end
    subgraph SL["2 · Mesure de la longueur"]
        direction TB
        T3["t3&lt;br/&gt;front descendant&lt;br/&gt;boucle principale"] --&gt; LON["Longueur&lt;br/&gt;présence × vitesse"]
    end
    SV ==&gt; SL
    SL ==&gt; PRI(["3 · Prise en compte&lt;br/&gt;compteur + 1&lt;br/&gt;classe de vitesse&lt;br/&gt;classe de longueur"])
    style T1 fill:#e3f5ea,stroke:#2fb37a,color:#27262b
    style T2 fill:#e3f5ea,stroke:#2fb37a,color:#27262b
    style T3 fill:#fde8e8,stroke:#e5484d,color:#27262b
    style VIT fill:#eef0ff,stroke:#5c5adc,color:#27262b
    style LON fill:#eef0ff,stroke:#5c5adc,color:#27262b
    style PRI fill:#fff4e0,stroke:#e9a23b,color:#27262b
</code></pre>

<p>Le superviseur ne déclare que l’entrée principale, l’entrée associée étant par convention la suivante. Il programme ensuite des <strong>histogrammes</strong> : chacun est rattaché à un compteur source et à un critère, classe de vitesse en km/h ou classe de longueur en décimètres, avec des bornes croissantes, la dernière classe n’ayant pas de limite haute. Le superviseur lit ces histogrammes en continu ; comme les compteurs de classes sont cumulatifs, il en tire le débit par classe par différence entre deux relevés, exactement comme pour le comptage simple.</p>

<p>Deux points de conception se dégagent :</p>

<ul>
  <li><strong>Les paramètres physiques restent locaux.</strong> La distance inter-boucles et la longueur de boucle ne passent pas par DIASER : l’automate d’origine les fait saisir dans son outil de programmation, par point de mesure. Notre station doit donc offrir sa propre configuration locale pour ces deux valeurs.</li>
  <li><strong>La géométrie est un compromis.</strong> Une boucle longue améliore la précision, mais si elle est trop longue, deux véhicules qui se suivent de près l’occupent en même temps et faussent la mesure.</li>
</ul>

<p>Une dernière particularité vaut d’être notée : la norme n’est pas parfaitement cohérente avec elle-même sur le codage du nombre de classes, selon qu’on lit le texte ou son exemple, et les automates du marché ont chacun leur convention. Une station de remplacement doit donc être validée contre le superviseur réel, en relisant systématiquement la configuration qu’il vient d’écrire.</p>

<h2 id="technologies">Technologies</h2>

<p>Siemens SIMATIC IOT2050, Debian (aarch64), C (threads POSIX, <code class="language-plaintext highlighter-rouge">select()</code>, sockets UDP), boucles inductives et détecteurs, DIASER (<span style="white-space: nowrap">NF P 99-071</span>, application Mesure du trafic).</p>

<p><a href="/contact/" class="btn btn-primary">Me contacter</a></p>]]></content><author><name>Sami Lahamel</name></author><category term="Comptage routier" /><category term="DIASER" /><category term="Systèmes embarqués" /><summary type="html"><![CDATA[Comment une passerelle industrielle Siemens IOT2050 devient une station de comptage routier : fronts des boucles, compteurs cumulatifs et dialogue DIASER avec le superviseur.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.connexity.fr/assets/images/og-card.png" /><media:content medium="image" url="https://www.connexity.fr/assets/images/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Contrôle d’accès des trémies : un portail pour piloter les barrières</title><link href="https://www.connexity.fr/blog/2026/10/controle-acces-tremies/" rel="alternate" type="text/html" title="Contrôle d’accès des trémies : un portail pour piloter les barrières" /><published>2026-10-04T00:00:00+02:00</published><updated>2026-10-04T00:00:00+02:00</updated><id>https://www.connexity.fr/blog/2026/10/controle-acces-tremies</id><content type="html" xml:base="https://www.connexity.fr/blog/2026/10/controle-acces-tremies/"><![CDATA[<p>Une trémie est un passage routier en contrebas, sous un ouvrage ou une voie. Il faut pouvoir en interdire l’accès vite, à distance, sans qu’un agent ait à se déplacer : en cas d’urgence, pour une opération de maintenance ou à l’occasion d’un événement. Pour cinq trémies d’une métropole, nous avons déployé des barrières automatiques commandées par un contrôleur, et un portail de contrôle-commande pour les piloter à distance.</p>

<h2 id="sur-le-terrain">Sur le terrain</h2>

<p>À l’entrée de chaque trémie, une <strong>barrière automatique</strong> ferme la voie. À côté d’elle, des <strong>feux rouges clignotants</strong> de type R24 signalent l’interdiction d’accès, accompagnés du panneau « Rouge clignotant, accès interdit ». Ces feux sont raccordés à la barrière : le contrôleur se contente d’en déclencher le clignotement par un relais. Une caméra de vidéosurveillance, sur le réseau de la régulation, permet de vérifier l’état de l’ouvrage à distance.</p>

<div style="display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center; margin: 1.25rem 0;">
<figure class="post-figure" style="flex: 1 1 15rem; max-width: 22rem; margin: 0;">
<img src="/assets/images/blog/tremie-barriere-feux.webp" alt="Barrière automatique relevée, feux rouges clignotants et panneau Rouge clignotant accès interdit à l'entrée d'une trémie, avec un portique limitant la hauteur" />
<figcaption>La barrière relevée, ses feux R24 et le panneau d'interdiction à l'entrée de la trémie.</figcaption>
</figure>
<figure class="post-figure" style="flex: 1 1 15rem; max-width: 22rem; margin: 0;">
<img src="/assets/images/blog/tremie-vue-ensemble.webp" alt="Vue d'ensemble d'un site de trémie : mât de la caméra, mât des feux et barrière relevée en bordure de chaussée" />
<figcaption>Vue d'ensemble : le mât de la caméra, celui des feux et la barrière en bordure de voie.</figcaption>
</figure>
</div>

<p>Le matériel se résume à peu de choses : une barrière dont la lisse mesure 3 mètres, 8 mètres pour l’une des cinq trémies, un coffret de commande avec ses relais, un transformateur et des protections électriques, un commutateur industriel avec sa liaison fibre. Quatre coffrets suffisent pour cinq trémies, deux barrières voisines partageant le même.</p>

<h2 id="un-contrôleur-de-feux-utilisé-comme-automate">Un contrôleur de feux utilisé comme automate</h2>

<p>Le cœur d’un coffret est un contrôleur de feux. Mais ici il ne pilote aucun feu de circulation : il joue le rôle d’<strong>automate programmable</strong> qui commande la barrière. Conçu à l’origine pour la régulation du trafic, ce matériel implémente le protocole DIASER : il s’intègre ainsi au réseau de la régulation et se supervise comme n’importe quel autre contrôleur.</p>

<p>Cet usage particulier se voit dans sa configuration. On lui déclare <strong>deux lignes de feux fictives</strong>, sans charge, avec une matrice de dégagement nulle. Aucun rack de puissance n’est installé, puisqu’aucune ligne de feux réelle n’est pilotée : la gestion de la barrière n’en a pas besoin.</p>

<p>Ses entrées et sorties se limitent à l’essentiel :</p>

<table>
  <thead>
    <tr>
      <th>Type</th>
      <th>Désignation</th>
      <th>Rôle</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Entrée</td>
      <td>Retour d’ouverture</td>
      <td>Retour de position de la barrière, remonté au portail</td>
    </tr>
    <tr>
      <td>Entrée</td>
      <td>Commande manuelle locale</td>
      <td>Permet à un agent d’agir sur place, sans passer par le réseau</td>
    </tr>
    <tr>
      <td>Sortie</td>
      <td>Relais d’ouverture / fermeture</td>
      <td>Commande la montée ou la descente de la lisse</td>
    </tr>
    <tr>
      <td>Sortie</td>
      <td>Relais de clignotement</td>
      <td>Active les feux R24 avant la descente de la barrière</td>
    </tr>
    <tr>
      <td>Variable DIASER</td>
      <td>Variable de commande</td>
      <td>Ordre de montée ou de descente, écrit par le portail</td>
    </tr>
  </tbody>
</table>

<p>La logique de la barrière tient en une phrase : <strong>un ordre à ON descend la barrière, un ordre à OFF la remonte.</strong> C’est la variable de commande, écrite par le portail, qui porte cet ordre.</p>

<h2 id="le-portail-de-contrôle-commande">Le portail de contrôle-commande</h2>

<p>Le portail est l’unique point d’entrée pour commander les barrières. Deux familles d’utilisateurs s’en servent, chacune avec ses droits :</p>

<ul>
  <li><strong>le poste central de régulation</strong>, qui commande la descente de la barrière en cas d’urgence, de maintenance ou d’événement ;</li>
  <li><strong>des intervenants externes</strong>, comme la police municipale ou une entreprise intervenant sur l’ouvrage, qui disposent d’un accès limité à la trémie qui les concerne.</li>
</ul>

<p>Derrière le portail, un programme de pilotage dialogue avec chacun des contrôleurs.</p>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart TB
    subgraph USR["Utilisateurs"]
        REG["Poste central&lt;br/&gt;de régulation"]
        EXT["Intervenants externes&lt;br/&gt;police municipale · entreprises"]
    end
    subgraph DED["Portail de contrôle-commande · Debian"]
        WEB["Portail web PHP"] --&gt; DB[("PostgreSQL")]
        DB &lt;--&gt; PIL["Programme de pilotage&lt;br/&gt;en C"]
    end
    subgraph TRE["Coffret de trémie"]
        CTL["Contrôleur&lt;br/&gt;utilisé comme automate"] --&gt; REL["Relais"]
    end
    REL --&gt; BAR["Barrière&lt;br/&gt;+ feux R24"]
    REG --&gt; WEB
    EXT --&gt; WEB
    PIL &lt;--&gt;|"DIASER · UDP · variable de commande"| CTL
    style BAR fill:#fde8e8,stroke:#e5484d,color:#27262b
    style DB fill:#e3f5ea,stroke:#2fb37a,color:#27262b
</code></pre>

<h2 id="le-déroulé-dune-fermeture">Le déroulé d’une fermeture</h2>

<p>Pour chaque ordre reçu, le contrôleur déroule la même séquence. Un <strong>préavis de 6 secondes</strong> laisse aux usagers le temps de voir l’interdiction : <strong>les feux R24 clignotent avant que la barrière ne descende</strong>, jamais l’inverse.</p>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart LR
    subgraph ORD["1 · Ordre"]
        direction TB
        PORT["Portail&lt;br/&gt;variable DIASER"]
    end
    subgraph SEQ["2 · Séquence du contrôleur"]
        direction TB
        CTL["Contrôleur"] --&gt; R24["Feux R24&lt;br/&gt;clignotement alterné"]
        R24 --&gt;|"après 6 s"| REL["Relais de commande&lt;br/&gt;fermeture / ouverture"]
        REL --&gt; BAR["Barrière automatique"]
    end
    subgraph RET["3 · Retour"]
        direction TB
        BUT["Butée basse ou haute&lt;br/&gt;atteinte"] --&gt; RIN["Relais de retour&lt;br/&gt;d'information"]
        RIN --&gt; VAR["Variable de retour&lt;br/&gt;lue par le portail"]
    end
    ORD ==&gt; SEQ
    SEQ ==&gt; RET
    style R24 fill:#fde8e8,stroke:#e5484d,color:#27262b
    style BAR fill:#fde8e8,stroke:#e5484d,color:#27262b
    style VAR fill:#e3f5ea,stroke:#2fb37a,color:#27262b
</code></pre>

<table>
  <thead>
    <tr>
      <th>Ordre</th>
      <th>Étapes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Fermeture</strong></td>
      <td>1. Les feux R24 se mettent à clignoter.<br />2. Après 6 secondes, le relais commande la fermeture et la barrière descend.<br />3. À la butée basse, le retour d’information remonte au portail.</td>
    </tr>
    <tr>
      <td><strong>Ouverture</strong></td>
      <td>1. Le relais commande l’ouverture et la barrière monte.<br />2. À la butée haute, le retour d’information remonte au portail et les feux R24 s’éteignent.</td>
    </tr>
  </tbody>
</table>

<p>Le retour d’information est ce qui fait la différence entre un ordre envoyé et une barrière réellement en position : le portail lit la position atteinte, et non le simple fait que la commande a été émise.</p>

<h2 id="le-pilotage--programme-et-interface">Le pilotage : programme et interface</h2>

<h3 id="un-programme-qui-dialogue-avec-chaque-contrôleur">Un programme qui dialogue avec chaque contrôleur</h3>

<p>Un programme écrit en C fait le lien entre la base de données du portail et le terrain. Il tourne en boucle, avec un cycle d’environ sept secondes. À chaque cycle, il relit la table des trémies, puis lance <strong>un fil d’exécution (thread) par trémie</strong> pour deux échanges successifs en DIASER sur UDP :</p>

<ol>
  <li>une <strong>lecture d’état</strong> du contrôleur, retentée trois fois à une demi-seconde d’intervalle avant de conclure à un défaut ;</li>
  <li>l’<strong>envoi de la commande</strong> voulue par l’opérateur : blocage en ouverture ou en fermeture.</li>
</ol>

<p>Les <strong>trames DIASER sont stockées en base</strong>, trémie par trémie. Modifier un ordre ou raccorder une sixième trémie ne demande pas de recompiler le programme.</p>

<h3 id="un-journal-qui-ne-garde-que-lutile">Un journal qui ne garde que l’utile</h3>

<p>Le programme ne journalise que les <strong>changements d’état</strong>, sous la forme « commande ouverture vers commande fermeture » ou « défaut de communication ». La date de dernière mise à jour ne change, elle aussi, que lorsque l’état change : un opérateur qui regarde l’écran sait depuis quand la barrière est dans cet état, et non l’heure du dernier échange.</p>

<h3 id="linterface">L’interface</h3>

<p>Pour chaque barrière, le portail affiche trois retours : le <strong>statut</strong> (absence de forçage ou forçage en fermeture), le <strong>retour de mode</strong> et le <strong>retour de communication</strong> (<em>en service</em> ou <em>en défaut</em>). Deux boutons commandent le blocage en ouverture ou en fermeture, chacun derrière une fenêtre de confirmation qui rappelle la conséquence de l’ordre avant tout envoi. L’écran se rafraîchit toutes les deux secondes, et chaque consultation ou commande est inscrite au journal de bord.</p>

<div class="carousel" data-carousel="">
  <div class="carousel-track" tabindex="0" aria-label="Captures d'écran">
    <figure class="carousel-slide">
      <a href="/assets/images/solutions/urbain-tremies.webp?v=20261007114014" target="_blank" rel="noopener" title="Ouvrir en taille réelle"><img src="/assets/images/solutions/urbain-tremies.webp?v=20261007114014" width="2000" height="1170" alt="Supervision des trémies : pour chaque barrière, statut, retour de mode, retour de communication et commandes de blocage en ouverture ou en fermeture" /></a>
      <figcaption>Le portail : statut, retour de mode et de communication, commandes de blocage.</figcaption>
    </figure>
  </div>
</div>
<script>
  document.querySelectorAll('[data-carousel]').forEach(function (c) {
    if (c.dataset.ready) return; c.dataset.ready = '1';
    var track = c.querySelector('.carousel-track'), dots = c.querySelectorAll('.carousel-dots button');
    if (dots.length < 2) return;
    function idx() { return Math.round(track.scrollLeft / track.clientWidth); }
    function go(i) { i = Math.max(0, Math.min(dots.length - 1, i)); track.scrollTo({ left: i * track.clientWidth, behavior: 'smooth' }); }
    function sync() { var i = idx(); dots.forEach(function (d, k) { d.classList.toggle('on', k === i); });
      c.querySelector('.prev').disabled = i === 0; c.querySelector('.next').disabled = i === dots.length - 1; }
    c.querySelector('.prev').addEventListener('click', function () { go(idx() - 1); });
    c.querySelector('.next').addEventListener('click', function () { go(idx() + 1); });
    dots.forEach(function (d, k) { d.addEventListener('click', function () { go(k); }); });
    track.addEventListener('scroll', function () { window.requestAnimationFrame(sync); });
    track.addEventListener('keydown', function (e) { if (e.key === 'ArrowRight') go(idx() + 1); if (e.key === 'ArrowLeft') go(idx() - 1); });
    sync();
  });
</script>

<h2 id="ce-que-cela-apporte">Ce que cela apporte</h2>

<p>Une seule interface de contrôle-commande simplifie l’exploitation : un seul endroit pour commander, un seul journal, un seul retour d’état. Le portail ouvre un <strong>accès maîtrisé aux intervenants externes</strong>, avec des droits par rôle, et garde la trace de chaque commande, de chaque état et de chaque défaut de communication. Comme tous les ordres suivent le même chemin vers le même contrôleur, l’état réel de la barrière reste unique et sans ambiguïté.</p>

<h2 id="technologies">Technologies</h2>

<p>Contrôleur de carrefour à feux utilisé comme automate programmable, DIASER (UDP), barrière automatique et feux R24, relais 24 V, commutateur industriel et fibre optique, caméra IP, C (threads POSIX, sockets UDP), PHP, PostgreSQL, nginx, Debian.</p>

<p><a href="/contact/" class="btn btn-primary">Me contacter</a></p>]]></content><author><name>Sami Lahamel</name></author><category term="Contrôle d&apos;accès" /><category term="DIASER" /><summary type="html"><![CDATA[Cinq trémies, des barrières automatiques commandées par un contrôleur de feux utilisé comme automate, et un portail de contrôle-commande utilisé par la régulation et par des intervenants externes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.connexity.fr/assets/images/og-card.png" /><media:content medium="image" url="https://www.connexity.fr/assets/images/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Détecter un bouchon sur une bretelle : de la caméra au panneau</title><link href="https://www.connexity.fr/blog/2026/10/detection-bouchon-bretelle-sortie/" rel="alternate" type="text/html" title="Détecter un bouchon sur une bretelle : de la caméra au panneau" /><published>2026-10-04T00:00:00+02:00</published><updated>2026-10-04T00:00:00+02:00</updated><id>https://www.connexity.fr/blog/2026/10/detection-bouchon-bretelle-sortie</id><content type="html" xml:base="https://www.connexity.fr/blog/2026/10/detection-bouchon-bretelle-sortie/"><![CDATA[<p>Sur une route nationale, une bretelle de sortie qui se remplit finit par déborder sur la voie principale. Pour un gestionnaire de réseau, savoir tôt que la file se forme vaut mieux que le constater ensuite. Nous avons développé DAB (Détection Automatique de Bouchons) pour fournir cette information sous la forme la plus simple possible : un état binaire par bretelle surveillée, fiable et lisible par le superviseur qui pilote le panneau d’alerte.</p>

<h2 id="le-besoin">Le besoin</h2>

<p>Une caméra dotée d’une analyse vidéo embarquée sait compter les véhicules à l’arrêt dans une zone qu’on lui définit. Mais un compte de véhicules n’est pas un signal d’exploitation : MiVisu, le superviseur qui pilote le panneau d’alerte, attend un état net et stable, dans un protocole industriel. DAB fait ce travail de traduction : il reçoit les détections de la caméra, décide s’il y a bouchon, et expose le résultat en Modbus TCP.</p>

<p>DAB ne traite aucune image. Toute l’intelligence de détection reste dans la caméra ; DAB ne gère que la décision, l’historique et l’exposition.</p>

<h2 id="la-détection-dans-la-caméra">La détection, dans la caméra</h2>

<p>La règle de détection se programme directement dans l’interface d’analyse de la caméra, une SWISSTRAFFIC AI. On y dessine sur l’image la zone à surveiller, puis on assemble des opérateurs sous forme de graphe : la zone alimente un filtre de catégorie de véhicule, puis un opérateur de durée d’arrêt. Un véhicule qui reste immobile dans la zone plus longtemps que la durée programmée, 5 secondes dans cette configuration, déclenche une détection. C’est ce nombre de véhicules détectés que la caméra remonte à DAB.</p>

<figure class="post-figure">
<img src="/assets/images/blog/dab-camera-config.webp" alt="Interface de programmation de la caméra : une zone dessinée sur la chaussée, puis un graphe d'opérateurs avec un filtre de catégorie et une durée d'arrêt supérieure à 5 secondes" />
<figcaption>La détection dans la caméra : une zone dessinée sur l'image, puis un graphe d'opérateurs. Ici, un véhicule arrêté plus de 5 secondes dans la zone déclenche une détection.</figcaption>
</figure>

<h2 id="architecture">Architecture</h2>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart TB
    CAM["Caméra SWISSTRAFFIC AI&lt;br/&gt;zone de détection&lt;br/&gt;véhicule à l'arrêt &gt; durée programmée"] --&gt;|"HTTP POST JSON&lt;br/&gt;(webhook)"| WEB
    subgraph SRV["Serveur DAB · Debian"]
        WEB["nginx + PHP-FPM&lt;br/&gt;logique de décision"] --&gt;|"écriture"| DB[("SQLite&lt;br/&gt;mode WAL")]
        DB --&gt;|"lecture toutes les secondes"| MB["dab-modbus&lt;br/&gt;serveur Modbus C · libmodbus"]
        UI["Interface web&lt;br/&gt;supervision · historique&lt;br/&gt;forçage · rôles"] &lt;--&gt; DB
    end
    MB --&gt;|"Modbus TCP · fonction 2&lt;br/&gt;1 bit par site"| MIV["MiVisu&lt;br/&gt;superviseur · LABOCOM"]
    MIV --&gt;|"LCR"| PAN["Panneau de police dynamique&lt;br/&gt;pictogramme BOUCHON"]
</code></pre>

<p>Côté DAB, la chaîne tient en quatre maillons :</p>

<ul>
  <li><strong>La caméra</strong> notifie DAB par un message JSON qui porte l’identifiant du site et le nombre de véhicules détectés.</li>
  <li><strong>L’ingestion</strong> est assurée par nginx (POST uniquement, taille des requêtes bornée) puis PHP-FPM. Un identifiant de site inconnu est rejeté : seuls les sites déclarés dans l’interface sont acceptés.</li>
  <li><strong>L’état</strong> est stocké dans SQLite en mode WAL, partagé entre PHP et le serveur Modbus : une ligne par site pour l’état courant, une table d’historique à part.</li>
  <li><strong>L’exposition</strong> est confiée à un logiciel dédié, écrit en C, qui lit l’état et le publie sur le port Modbus.</li>
</ul>

<h2 id="une-décision-asymétrique">Une décision asymétrique</h2>

<p>La règle de décision tient en deux phrases : dès qu’au moins un véhicule à l’arrêt est détecté, le site passe en BOUCHON immédiatement ; il ne retombe en LIBRE qu’après un délai sans aucune détection, de l’ordre de la minute et réglable dans l’interface.</p>

<pre><code class="language-mermaid">%%{init: {'theme':'base','themeVariables':{'primaryColor':'#eef0ff','primaryTextColor':'#27262b','primaryBorderColor':'#5c5adc','lineColor':'#8a8da8','secondaryColor':'#f4f5ff','tertiaryColor':'#f4f5ff','edgeLabelBackground':'#eef0ff','clusterBkg':'#f4f5ff','clusterBorder':'#5c5adc'}}}%%
flowchart LR
    LIBRE(["LIBRE"]) --&gt;|"véhicule à l'arrêt détecté&lt;br/&gt;(immédiat)"| BOUCHON(["BOUCHON"])
    BOUCHON --&gt;|"nouvelle détection :&lt;br/&gt;le délai repart à zéro"| BOUCHON
    BOUCHON --&gt;|"aucune détection pendant&lt;br/&gt;le délai d'hystérésis"| LIBRE
    style LIBRE fill:#e3f5ea,stroke:#2fb37a,color:#27262b
    style BOUCHON fill:#fde8e8,stroke:#e5484d,color:#27262b
</code></pre>

<p>L’hystérésis ne porte donc que sur la retombée. Sans elle, un véhicule qui redémarre, une occultation passagère ou une mesure à zéro entre deux voitures ferait osciller le bit, et donc l’affichage du panneau. Avec elle, DAB ne retarde jamais l’alerte, et seul le retour à la normale est lissé.</p>

<p>Trois détails de conception comptent :</p>

<ul>
  <li><strong>Le retour à LIBRE est géré côté serveur.</strong> En plus de la logique du webhook, le serveur Modbus vérifie lui-même chaque seconde si le délai d’hystérésis est écoulé : un site ne reste donc pas bloqué en BOUCHON si la caméra cesse d’émettre.</li>
  <li><strong>La vie de la caméra est suivie, à titre informatif.</strong> Chaque trame reçue, y compris une simple trame de vie sans comptage de véhicules, rafraîchit l’horodate du site. L’interface en déduit un état (en ligne, silencieuse, hors ligne) d’après l’âge de la dernière trame, visible dans la surveillance et dans la table de correspondance Modbus. Cet indicateur alerte l’exploitant mais n’agit pas sur le bit exposé, qui ne dépend que des détections et du forçage.</li>
  <li><strong>Seules les transitions sont journalisées</strong>, pas chaque mesure. L’historique reste compact et chaque ligne correspond à un événement qu’un exploitant peut lire.</li>
  <li><strong>Un forçage manuel</strong> par site (automatique, libre ou bouchon) prend le pas sur la détection, y compris sur le bit exposé en Modbus. Il est réservé au rôle administrateur, protégé contre le CSRF, et tout changement d’état qu’il provoque est tracé dans l’historique. Il permet par exemple de tester la chaîne de bout en bout avec le superviseur sans attendre un vrai bouchon, ou de neutraliser un site le temps d’une intervention.</li>
</ul>

<h2 id="le-serveur-modbus-en-c">Le serveur Modbus en C</h2>

<p>Modbus TCP demande un serveur à l’écoute en permanence sur le port 502, avec des connexions persistantes : ce n’est pas le métier de PHP-FPM. D’où un logiciel dédié, écrit en C et construit sur libmodbus.</p>

<p>Il tient une boucle <code class="language-plaintext highlighter-rouge">select()</code> qui sert plusieurs clients simultanés, relit la base chaque seconde, et alimente deux tables en mémoire : les Discrete Inputs (fonction 2), un bit par site à l’adresse choisie dans l’interface, et un miroir en Holding Registers (fonction 3) pour tolérer un client Modbus mal configuré qui interrogerait en lecture de registres. Voici l’échange pour huit sites, où seul le site d’adresse 0 est en BOUCHON (unit id 1 à titre d’exemple) :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Requête   00 01 00 00 00 06 01 02 00 00 00 08   lecture de 8 entrées à partir de l'adresse 0
Réponse   00 01 00 00 00 04 01 02 01 01         1 octet de données : bit 0 à 1
</code></pre></div></div>

<p>Quelques choix d’exploitation :</p>

<ul>
  <li>Le statut du serveur (PID, durée de fonctionnement, dernière lecture de la base, nombre de sites) est écrit dans un fichier JSON de façon atomique, par écriture dans un fichier temporaire puis renommage, pour que l’interface web ne lise jamais un fichier à moitié écrit.</li>
  <li>Il s’arrête proprement sur SIGTERM et ignore SIGPIPE, pour qu’une déconnexion brutale d’un client ne le fasse pas tomber.</li>
  <li>L’unité systemd est durcie : utilisateur non privilégié, <code class="language-plaintext highlighter-rouge">NoNewPrivileges</code>, <code class="language-plaintext highlighter-rouge">PrivateTmp</code>, système de fichiers en lecture seule hors des deux répertoires de travail, et la capacité <code class="language-plaintext highlighter-rouge">CAP_NET_BIND_SERVICE</code> pour écouter sur le port 502 sans être root.</li>
</ul>

<h2 id="en-aval--mivisu-et-le-panneau">En aval : MiVisu et le panneau</h2>

<p>DAB ne pilote aucun panneau : il ne connaît ni l’équipement ni son protocole. C’est le superviseur MiVisu, édité par la société LABOCOM, qui s’en charge. MiVisu scrute l’état de chaque site de détection en Modbus TCP auprès de DAB, puis commande en LCR un panneau de police dynamique. Quand un site passe en BOUCHON, le panneau affiche à l’attention des usagers un pictogramme de danger représentant une file de véhicules, avec la mention BOUCHON.</p>

<p>Cette séparation garde DAB indépendant du panneau : changer d’afficheur n’oblige pas à toucher à la détection.</p>

<figure class="post-figure">
<img src="/assets/images/blog/dab-panneau-police.jpg" alt="Panneau de police dynamique éteint, monté sur un pylône treillis en bordure de route, au milieu de la végétation" />
<figcaption>Un panneau de police dynamique en bordure de route, ici éteint : il affiche le pictogramme BOUCHON lorsque MiVisu le commande.</figcaption>
</figure>

<h2 id="linterface-dexploitation">L’interface d’exploitation</h2>

<p>L’interface web, en PHP, couvre l’ensemble de la vie du système :</p>

<ul>
  <li>un <strong>tableau de bord</strong> : sites surveillés, bouchons actifs, voies libres, état du serveur Modbus ;</li>
  <li>une <strong>surveillance en temps réel</strong>, rafraîchie toutes les 5 secondes, avec l’état de chaque caméra ;</li>
  <li>un <strong>historique</strong> des transitions avec graphiques ;</li>
  <li>une page <strong>Interface Modbus</strong> : paramètres d’écoute et table de correspondance entre chaque adresse et l’état du site, avec la valeur réellement exposée ;</li>
  <li>la <strong>configuration des sites</strong> (identifiant de la caméra, nom, adresse Modbus, unique par site) et le réglage de l’hystérésis ;</li>
  <li>la <strong>gestion des utilisateurs</strong> avec trois rôles : administrateur, opérateur, lecteur.</li>
</ul>

<div class="carousel" data-carousel="">
  <div class="carousel-track" tabindex="0" aria-label="Captures d'écran">
    <figure class="carousel-slide">
      <a href="/assets/images/blog/dab-ui-tableau-de-bord.webp?v=20261007114014" target="_blank" rel="noopener" title="Ouvrir en taille réelle"><img src="/assets/images/blog/dab-ui-tableau-de-bord.webp?v=20261007114014" width="2000" height="1170" alt="Tableau de bord de DAB : sites surveillés, bouchons actifs, voies libres et état du serveur Modbus" /></a>
      <figcaption>Tableau de bord : sites surveillés, bouchons actifs, voies libres et état du serveur Modbus.</figcaption>
    </figure>
    <figure class="carousel-slide">
      <a href="/assets/images/blog/dab-ui-surveillance.webp?v=20261007114014" target="_blank" rel="noopener" title="Ouvrir en taille réelle"><img src="/assets/images/blog/dab-ui-surveillance.webp?v=20261007114014" width="2000" height="1170" alt="Surveillance temps réel de DAB : état de chaque site, véhicules détectés et état de la caméra" loading="lazy" /></a>
      <figcaption>Surveillance en temps réel : état de chaque site, véhicules détectés et état de la caméra.</figcaption>
    </figure>
    <figure class="carousel-slide">
      <a href="/assets/images/blog/dab-ui-modbus.webp?v=20261007114014" target="_blank" rel="noopener" title="Ouvrir en taille réelle"><img src="/assets/images/blog/dab-ui-modbus.webp?v=20261007114014" width="2000" height="1170" alt="Interface Modbus de DAB : état du serveur Modbus, paramètres d'écoute, correspondance entre adresses et sites, mode de forçage" loading="lazy" /></a>
      <figcaption>Interface Modbus : état du serveur Modbus, paramètres d'écoute, correspondance adresse - site et mode de forçage (automatique, libre, bouchon).</figcaption>
    </figure>
    <figure class="carousel-slide">
      <a href="/assets/images/blog/dab-ui-configuration.webp?v=20261007114014" target="_blank" rel="noopener" title="Ouvrir en taille réelle"><img src="/assets/images/blog/dab-ui-configuration.webp?v=20261007114014" width="2000" height="1170" alt="Configuration des sites de DAB : ajout d'un site, hystérésis de détection et liste des sites" loading="lazy" /></a>
      <figcaption>Configuration des sites : identifiant de la caméra, nom, adresse Modbus et réglage de l'hystérésis.</figcaption>
    </figure>
  </div>
  <button type="button" class="carousel-btn prev" aria-label="Capture précédente"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5l-7 7 7 7" /></svg></button>
  <button type="button" class="carousel-btn next" aria-label="Capture suivante"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 5l7 7-7 7" /></svg></button>
  <div class="carousel-dots"><button type="button" aria-label="Capture 1"></button><button type="button" aria-label="Capture 2"></button><button type="button" aria-label="Capture 3"></button><button type="button" aria-label="Capture 4"></button></div>
</div>
<script>
  document.querySelectorAll('[data-carousel]').forEach(function (c) {
    if (c.dataset.ready) return; c.dataset.ready = '1';
    var track = c.querySelector('.carousel-track'), dots = c.querySelectorAll('.carousel-dots button');
    if (dots.length < 2) return;
    function idx() { return Math.round(track.scrollLeft / track.clientWidth); }
    function go(i) { i = Math.max(0, Math.min(dots.length - 1, i)); track.scrollTo({ left: i * track.clientWidth, behavior: 'smooth' }); }
    function sync() { var i = idx(); dots.forEach(function (d, k) { d.classList.toggle('on', k === i); });
      c.querySelector('.prev').disabled = i === 0; c.querySelector('.next').disabled = i === dots.length - 1; }
    c.querySelector('.prev').addEventListener('click', function () { go(idx() - 1); });
    c.querySelector('.next').addEventListener('click', function () { go(idx() + 1); });
    dots.forEach(function (d, k) { d.addEventListener('click', function () { go(k); }); });
    track.addEventListener('scroll', function () { window.requestAnimationFrame(sync); });
    track.addEventListener('keydown', function (e) { if (e.key === 'ArrowRight') go(idx() + 1); if (e.key === 'ArrowLeft') go(idx() - 1); });
    sync();
  });
</script>

<p>Côté réseau, nginx limite le débit des tentatives de connexion et des appels d’API, applique HSTS, et refuse l’accès direct à la configuration et aux fichiers cachés.</p>

<h2 id="technologies">Technologies</h2>

<p>Analyse vidéo embarquée (caméra SWISSTRAFFIC AI), HTTP (webhook JSON), Modbus TCP (fonctions 2 et 3), LCR (panneau, via MiVisu), nginx, PHP-FPM, SQLite (WAL), C (libmodbus), systemd, Debian.</p>

<p><a href="/contact/" class="btn btn-primary">Me contacter</a></p>]]></content><author><name>Sami Lahamel</name></author><category term="Détection IA" /><category term="Modbus" /><category term="LCR" /><summary type="html"><![CDATA[De la caméra de détection au pilotage du panneau : comment un bouchon détecté devient un bit Modbus scruté par MiVisu. Webhook, hystérésis, serveur Modbus en C et SQLite.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.connexity.fr/assets/images/og-card.png" /><media:content medium="image" url="https://www.connexity.fr/assets/images/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Le jalonnement de notre parking : la première supervision CiteosVision</title><link href="https://www.connexity.fr/blog/2026/09/jalonnement-parking-premiere-supervision-citeosvision/" rel="alternate" type="text/html" title="Le jalonnement de notre parking : la première supervision CiteosVision" /><published>2026-09-27T00:00:00+02:00</published><updated>2026-09-27T00:00:00+02:00</updated><id>https://www.connexity.fr/blog/2026/09/jalonnement-parking-premiere-supervision-citeosvision</id><content type="html" xml:base="https://www.connexity.fr/blog/2026/09/jalonnement-parking-premiere-supervision-citeosvision/"><![CDATA[<p>Avant de superviser l’éclairage public ou les bornes de recharge, CiteosVision a commencé beaucoup plus modestement : par le parking de notre propre entreprise, à Le Petit-Quevilly. Pas un client, pas un cahier des charges : un besoin qu’on vivait tous les jours.</p>

<h2 id="le-besoin">Le besoin</h2>

<p>Le parking est découpé en trois zones : Collaborateur, Visiteur et PMR. Sans visibilité sur les places libres, on tournait pour rien, surtout aux heures d’affluence. L’idée de départ était simple : un capteur par place, un panneau à l’entrée qui indique combien de places restent dans chaque zone.</p>

<figure class="post-figure">
<img src="/assets/images/blog/parking-zones.png" alt="Écran de gestion des zones de parking, listant les capteurs affectés à chaque zone (Collaborateur, PMR, Visiteur)" />
<figcaption>Chaque capteur est rattaché à une zone. Le nombre de places libres est recalculé zone par zone.</figcaption>
</figure>

<h2 id="la-solution">La solution</h2>

<p>Chaque place reçoit un capteur LoRaWAN à détection magnétique et radar 60 GHz (référence SPS200, IP68, IK7), qui combine les deux techniques pour fiabiliser la détection quelle que soit la météo, et remonte son état par radio avec sa charge de batterie, jusqu’à dix ans d’autonomie annoncés. Les trames sont captées par une antenne fibre de verre omnidirectionnelle 8 dBi, IP67, reliée à une passerelle LoRaWAN qui fait aussi office de concentrateur sur site (modèle OptimaGate, alimentée en PoE). Ces mesures arrivent ensuite sur notre superviseur, qui les enregistre et recalcule en continu le nombre de places libres par zone. Un panneau d’affichage à l’entrée du site montre ce compte en temps réel, zone par zone, jusqu’à afficher « COMPLET » quand il n’y a plus de place.</p>

<pre><code class="language-mermaid">flowchart LR
    A["Capteur LoRaWAN&lt;br/&gt;magnétique + radar 60 GHz&lt;br/&gt;SPS200 · IP68 / IK7"] --&gt;|"LoRaWAN 868 MHz"| B["Antenne 8 dBi&lt;br/&gt;omnidirectionnelle · IP67"]
    B --&gt; C["Passerelle LoRaWAN&lt;br/&gt;OptimaGate · PoE"]
    C --&gt;|"HTTP webhook"| D["Superviseur&lt;br/&gt;nginx + PHP + PostgreSQL&lt;br/&gt;VM Debian 13 sur Proxmox VE"]
    D --&gt;|"TCP 4001 · LCR NF P99-341"| E["Panneau d'affichage&lt;br/&gt;3 modules × 7 caractères"]
</code></pre>

<h2 id="comment-ça-fonctionne">Comment ça fonctionne</h2>

<p>Le capteur envoie une trame à chaque changement d’état (véhicule détecté ou parti), pas en continu : ça préserve sa batterie, pensée pour durer plusieurs années. Côté serveur, chaque trame devient une ligne dans une table d’événements, jamais écrasée : on peut ainsi reconstruire l’historique d’occupation d’une place, pas seulement son état du moment. Ce service tourne dans une VM Debian 13, sur un hyperviseur Proxmox VE.</p>

<p>Le panneau d’affichage, lui, pose un problème différent : il doit toujours montrer une valeur juste, même si le réseau a un accroc. Il est composé de trois modules de sept caractères, un par zone, pilotés en TCP par le protocole LCR (Langage de Commande Routier, norme NF P99-341), chez le fabricant TTS. Le panneau ne fait aucune intelligence : il affiche tel quel ce qu’on lui envoie, une trame texte par zone :</p>

<figure class="post-figure">
<img src="/assets/images/blog/parking-panneau-photo.jpg" alt="Le totem CITEOS à l'entrée du parking, avec le panneau LED trois modules affichant les places libres par zone (Collaborateurs, Visiteurs, PMR)" />
<figcaption>Le panneau installé à l'entrée du parking, un module par zone (Collaborateurs, Visiteurs, PMR).</figcaption>
</figure>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PA AM=1.0 AF="9 LIBRE" EC=AU AT=0 CL=0 AM=2.0 AF="1 LIBRE" EC=AU AT=0 CL=0 AM=3.0 AF="COMPLET" EC=AU AT=0 CL=0
</code></pre></div></div>

<p>Cette trame est renvoyée toutes les 3 secondes, qu’elle ait changé ou non : c’est ce battement régulier, plus que le calcul d’occupation lui-même, qui garantit qu’un panneau ne reste jamais figé sur une ancienne valeur après un accroc réseau.</p>

<figure class="post-figure">
<img src="/assets/images/blog/parking-afficheur.png" alt="Console de supervision de l'afficheur : prévisualisation des trois modules, état de la connexion TCP et dernière trame LCR envoyée" />
<figcaption>La console de supervision du panneau : connexion, dernière trame envoyée, et contrôle manuel par zone en cas de besoin.</figcaption>
</figure>

<p>Maintenir cet affichage synchronisé, sans jamais laisser une ancienne valeur s’y figer, s’est révélé être la partie la plus délicate du projet, bien plus que le calcul d’occupation lui-même : un panneau qui affiche une information fausse est pire qu’un panneau éteint.</p>

<h2 id="ce-quon-en-a-retenu">Ce qu’on en a retenu</h2>

<p>Ce premier déploiement, grandeur nature et sur notre propre terrain, a posé des questions qu’on n’aurait pas anticipées sur le papier : que faire d’un capteur dont la batterie faiblit ? Comment distinguer un capteur en maintenance d’un capteur en panne ? Comment garantir qu’un panneau ne reste jamais bloqué sur une ancienne valeur ?</p>

<p>Ce sont ces questions, pas la technologie LoRa elle-même, qui ont façonné la suite : un compteur de places libres est un cas particulier d’un problème plus général, celui de superviser n’importe quel équipement de terrain de façon fiable.</p>

<h2 id="et-aujourdhui">Et aujourd’hui</h2>

<p>Cette première brique a donné naissance à CiteosVision, la même plateforme qui supervise aujourd’hui notre <a href="/projets/eclairage-public/">éclairage public</a>. Le principe reste le même : un cœur générique qui ne connaît que des équipements, des mesures et des commandes, et des connecteurs qui dialoguent chacun dans le protocole du terrain, qu’il s’agisse d’un capteur de parking, d’un point lumineux ou d’une caméra.</p>

<p>Le parking, lui, continue de tourner : la carte ci-dessous montre l’état de chaque place en temps réel, capteur par capteur.</p>

<figure class="post-figure">
<img src="/assets/images/blog/parking-synoptique.png" alt="Synoptique du parking : vue aérienne avec l'état de chaque place (libre, occupée, batterie faible, en travaux, hors ligne) et le détail d'un capteur" />
<figcaption>Le synoptique du parking aujourd'hui : chaque place affiche son état en temps réel, avec le détail du capteur au survol.</figcaption>
</figure>

<h2 id="technologies">Technologies</h2>

<p>LoRaWAN, HTTP (webhook), TCP, LCR (NF P99-341), nginx, PHP, C, PostgreSQL, Debian, Proxmox VE.</p>

<p><a href="/contact/" class="btn btn-primary">Me contacter</a></p>]]></content><author><name>Sami Lahamel</name></author><category term="IoT" /><category term="LoRa" /><category term="Parking" /><summary type="html"><![CDATA[Retour sur le tout premier projet de CiteosVision : des capteurs LoRa et un panneau d'affichage pour guider vers les places libres, sur le parking de notre propre entreprise.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.connexity.fr/assets/images/og-card.png" /><media:content medium="image" url="https://www.connexity.fr/assets/images/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Pourquoi ce site</title><link href="https://www.connexity.fr/blog/2026/09/pourquoi-ce-site/" rel="alternate" type="text/html" title="Pourquoi ce site" /><published>2026-09-20T00:00:00+02:00</published><updated>2026-09-20T00:00:00+02:00</updated><id>https://www.connexity.fr/blog/2026/09/pourquoi-ce-site</id><content type="html" xml:base="https://www.connexity.fr/blog/2026/09/pourquoi-ce-site/"><![CDATA[<p>Bienvenue. Ce site est l’endroit où je partage mon expérience et les projets passionnants sur lesquels j’ai eu la chance de travailler.</p>

<p>Mon métier a une particularité : il se situe entre l’ingénierie de trafic et le développement logiciel. Peu de gens font les deux, et c’est pourtant là que naissent les solutions les plus utiles. Quand on comprend le terrain et qu’on sait aussi écrire le logiciel, on peut construire l’outil exact dont les exploitants ont besoin, au lieu de contourner celui qu’on nous vend.</p>

<p>Je voudrais donc partager ici :</p>

<ul>
  <li><strong>des retours d’expérience</strong> sur des projets de régulation, de supervision et d’équipements de voirie ;</li>
  <li><strong>des idées de conception</strong> : comment rendre une supervision fiable, comment faire cohabiter plusieurs protocoles, comment tenir quand la liaison coupe ;</li>
  <li><strong>des essais et des passions</strong> : ce que je construis parce que j’ai envie de comprendre, pas parce qu’on me l’a demandé.</li>
</ul>

<h2 id="ce-que-vous-ne-trouverez-pas-ici">Ce que vous ne trouverez pas ici</h2>

<p>Aucune donnée confidentielle : ni données de clients ou d’utilisateurs, ni informations d’exploitation sensibles, ni secrets d’accès. Je reste sur le terrain technique : les architectures, les protocoles et les choix de conception.</p>

<h2 id="la-suite">La suite</h2>

<p>Les prochains articles parleront de supervision, de protocoles et de choix techniques faits sur des projets réels. Si un sujet vous intéresse, ou si vous travaillez sur des problèmes proches, <a href="/contact/">contactez-moi via la page Contact</a> : les meilleures idées viennent souvent d’une conversation.</p>

<p><a href="/contact/" class="btn btn-primary">Me contacter</a></p>]]></content><author><name>Sami Lahamel</name></author><category term="présentation" /><summary type="html"><![CDATA[Partager mon expérience et les projets passionnants sur lesquels j'ai eu la chance de travailler.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.connexity.fr/assets/images/og-card.png" /><media:content medium="image" url="https://www.connexity.fr/assets/images/og-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>