Ciel de Courroux : savoir enfin quel avion passe au-dessus du village

Ciel de Courroux : savoir enfin quel avion passe au-dessus du village

Vous connaissez ce réflexe. Vous êtes dans le jardin, un avion passe assez bas pour qu'on l'entende bien, et vous levez la tête en vous demandant qui c'est et où il va. Chez nous à Courroux, ça arrive plusieurs fois par jour : le village est pile sous l'axe d'approche sud de Bâle-Mulhouse. Les moyen-courriers descendent vers l'EuroAirport en passant à la verticale des maisons.

Jusqu'ici, ma réponse c'était de sortir Flightradar24 sur le téléphone. Ça marche, mais bon.

Du coup j'ai fait ma propre carte : cieldecourroux.trachsel.info

La page d'accueil de Ciel de Courroux : l'appareil le plus proche et la carte du secteur en direct


La contrainte de départ : pas d'antenne

Normalement, pour faire ça, on achète une clé RTL-SDR et une antenne 1090 MHz, on branche le tout sur un Raspberry Pi et on décode soi-même les signaux ADS-B que les avions diffusent en permanence.

Je n'avais pas envie de commencer par de la quincaillerie. J'ai donc pris le problème dans l'autre sens : les agrégateurs communautaires — adsb.lol et adsb.fi — mettent gratuitement à disposition les données captées par leurs milliers de bénévoles, avec des API publiques. Il suffit d'aller les chercher.

Sauf que ces gens font tourner leurs serveurs sur leur temps et leur argent. La règle non négociable du projet est donc devenue : le navigateur du visiteur ne doit jamais appeler une API tierce. Toutes les requêtes sortantes partent du serveur, à cadence fixe. Dix visiteurs sur le site ne doivent pas générer dix fois plus de trafic chez adsb.lol qu'un seul.

C'est ce qui a dicté toute l'architecture.


La chaîne logicielle

Le seul composant que j'ai vraiment développé, c'est la passerelle. Tout le reste, c'est de l'assemblage de briques existantes et excellentes.

   API adsb.lol (secours : adsb.fi)
        │  une requête toutes les 2 secondes, depuis le serveur
        ▼
   adsb-bridge ──── SBS-1 ───┐
   (le seul code maison)     │
                             ▼
                    tar1090 [readsb intégré]
                        │        │
              SBS ──────┘        └── aircraft.json
               │                         │
               ▼                         ▼
          Planefence                 enricher
               │                         │
               └────► nginx ◄────────────┘
                        │
                   Caddy (déjà en place à la maison)

adsb-bridge — Python, une centaine de lignes utiles. Il interroge l'API, convertit le JSON en messages SBS-1 BaseStation (un vieux format CSV texte, mais c'est l'espéranto de l'ADS-B : tout le monde le parle) et sert ce flux sur un port TCP. Avec back-off exponentiel en cas d'erreur, bascule automatique sur la source de secours après trois échecs, et retour à la source principale toutes les cinq minutes.

readsb — le décodeur de référence. Il agrège, déduplique, calcule les distances et tient l'historique des traces. Il est embarqué dans l'image tar1090 (j'y reviens, il y a une histoire).

tar1090 — la carte de wiedehopf, celle que tout le monde utilise dans le milieu. Traces, filtres, historique. Je ne l'ai pas touchée d'une ligne : elle vit à l'adresse /carte/.

La carte détaillée tar1090 : les cercles de 5, 10 et 25 km autour de Courroux, et la table des appareils avec route, altitude et vitesse

Planefence — journalise les avions passés à moins de 3 km sous 10 000 pieds, et publie la liste du jour. Toutes ses fonctions de publication automatique sur les réseaux sociaux sont coupées : ce service ne poste nulle part.

Le journal des survols : chaque appareil passé à moins de 3 km du village, avec sa compagnie, son immatriculation et sa route

enricher — encore du Python maison. Il croise le flux avec adsbdb.com et hexdb.io pour obtenir l'immatriculation, le type d'appareil, la compagnie et la route origine → destination. Avec un cache SQLite persistant : permanent pour ce qui ne change jamais (un code hex désigne le même avion à vie), 30 jours pour les routes, et 24 heures même pour les réponses négatives — inutile de marteler une API pour un callsign qu'elle ne connaît pas. Objectif tenu : moins de 100 requêtes d'enrichissement par heure.

veilleur — un petit chien de garde qui m'envoie une alerte sur webhook si plus aucune donnée valide n'arrive pendant dix minutes. Une seule alerte par épisode, et un message quand ça repart.

Le tout en Docker Compose, tous les volumes dans un sous-dossier DATA/ pour que la sauvegarde soit triviale, un seul port exposé sur le LAN, et le reverse proxy Caddy que j'ai déjà à la maison qui s'occupe du TLS.


Développé avec Claude Code, en TDD

Là aussi je suis transparent : je n'ai pas écrit ce code à la main. J'ai rédigé un cahier des charges sérieux — une trentaine de pages, avec les exigences numérotées, les limites de débit, les obligations d'attribution, les tests de recette — puis j'ai lancé Claude Code (Opus 5) dessus.

Ce qui change tout par rapport à du vibe-coding classique : j'ai imposé le développement piloté par les tests. Les tests d'abord, le code ensuite. À l'arrivée : 202 tests unitaires pour environ 2 000 lignes de Python. Chaque étape commitée et poussée, et le déploiement fait par git pull sur le serveur de prod.

Et honnêtement, c'est là que ça devient intéressant, parce que les tests ne sont pas la partie difficile. La partie difficile, c'est le réel.


Les trois trucs que le cahier des charges ne pouvait pas prévoir

1. readsb ne relaie pas ce qu'il reçoit en SBS. Le schéma prévoyait une passerelle alimentant un readsb central, qui alimenterait ensuite la carte et Planefence. Le décodeur remplissait bien sa base interne… et n'envoyait strictement rien sur ses sorties. Logique après coup : ses sorties réémettent des trames radio, or une entrée SBS ne contient que des données déjà décodées. Un conteneur de supprimé et une architecture simplifiée.

2. adsb.lol refusait 45 % de nos requêtes. À une requête par seconde — la cadence maximale que je m'étais fixée — l'agrégateur renvoyait 429 presque une fois sur deux. Le service tenait quand même grâce au back-off, mais c'était impoli. En passant à une requête toutes les deux secondes : zéro rejet. Une seconde de latence en plus, et des serveurs bénévoles qui respirent.

3. Le piège qui ne se voit qu'à l'écran. J'avais demandé l'en-tête Referrer-Policy: no-referrer pour la vie privée des visiteurs. Sauf que la politique d'usage des tuiles OpenStreetMap exige un Referer. Résultat : une carte entièrement pavée de « Access blocked ». Le plus vicieux, c'est qu'en ligne de commande tout répondait HTTP 200 — seul le trafic identifié comme navigateur est filtré. Tous les tests serveur étaient au vert. Il a fallu que j'ouvre la page et que j'envoie la capture d'écran pour que le défaut apparaisse.

Ce troisième point est ma vraie leçon du projet. Un agent qui code vite et bien reste aveugle à ce qu'il ne peut pas regarder. La boucle de contrôle, c'est encore vous.


Ce que ça ne fait pas

Autant l'écrire franchement, c'est d'ailleurs marqué sur le site :

  • Sans récepteur local, les avions légers, planeurs et hélicoptères de la région sont invisibles. Il n'y a pas de bénévole assez proche pour les capter. C'est la limite structurelle du projet.
  • Les routes origine → destination viennent de bases communautaires : plausibles, pas certaines.
  • Quelques secondes de retard sur la réalité.
  • Aucune valeur opérationnelle. À ne pas utiliser pour la navigation ou la sécurité aérienne, évidemment.

L'architecture est prête pour le jour où j'ajouterai une antenne : il suffira de changer une variable d'environnement pour que le décodeur local prenne le relais, sans rien toucher en aval. C'est écrit dans la doc, on verra si le futur moi confirme.


Pour finir

C'est un projet de village, gratuit, sans pub, sans traceur et sans le moindre cookie. Les données viennent de bénévoles, et le pied de page les cite comme leurs licences l'exigent.

Si vous habitez Courroux ou dans le coin, la carte est là : cieldecourroux.trachsel.info. La prochaine fois qu'un truc passe en grondant au-dessus du toit, vous saurez que c'est le vol de Palma qui entame sa descente sur Bâle.

Et si vous bricolez aussi de l'ADS-B, de l'auto-hébergement ou du développement assisté par IA, ça m'intéresse d'en discuter. 👇


Transparence : l'application et cet article ont été rédigés avec l'aide de Claude Code, sous ma supervision. Les choix, les tests sur le terrain et les bêtises racontées plus haut restent les miens.