Tout est parti d'un carton
J'avais des ESP32 qui trainaient. Un Raspberry Pi 4 inutilisé. Un appartement avec du WiFi partout. Et une question qui m'avait trotté dans la tête : est-ce qu'on peut détecter la présence d'un humain uniquement avec des signaux radio — sans caméra, sans capteur de mouvement classique, juste avec le WiFi ambiant ?
La réponse courte : oui, en partie. La réponse longue : c'est ce projet.
C'est une expérience avant d'être un produit. Pas de modèle d'apprentissage automatique pour l'instant — tout est basé sur des heuristiques et du traitement de signal classique. L'idée c'était de voir jusqu'où on peut aller sans IA, pour mieux comprendre ce qu'elle apporterait ensuite.
Le WiFi, c'est quoi concrètement ?
Un rapide cours de rattrapage pour ceux qui n'ont pas fait télécom (moi non plus, j'ai lu).
Le WiFi envoie des données en découpant le signal sur plusieurs fréquences simultanément — c'est ce qu'on appelle l'OFDM. Plutôt qu'une seule fréquence qui transmet tout, le WiFi utilise des dizaines de petites sous-porteuses en parallèle. Sur un canal 20 MHz standard, il y en a 64.
Chacune de ces sous-porteuses arrive avec une amplitude et une phase légèrement différente selon les obstacles qu'elle a traversés — murs, meubles, et corps humains. L'ensemble de ces informations s'appelle le CSI : Channel State Information. C'est l'empreinte du canal radio à un instant donné.
Imagine un égaliseur de sono avec 64 curseurs. Le RSSI — la mesure classique de « force du signal » — c'est juste la moyenne de tous les curseurs en un seul chiffre. Le CSI, c'est les 64 curseurs individuellement.
Pourquoi le RSSI ne sert à rien ici
Première idée naïve : mesurer la puissance du signal reçu (RSSI) depuis plusieurs points et trilatérer.
Problème : le RSSI fluctue de 10 à 20 dBm pour absolument rien. Une main qui bouge, un micro-ondes qui démarre, une trame retransmise — et hop, le signal bondit. C'est inutilisable pour estimer une distance de façon fiable.
Le CSI, lui, est beaucoup plus stable sur chaque sous-porteuse individuellement. Et surtout : il change de façon cohérente quand quelqu'un se déplace dans la pièce. On peut mesurer la variance temporelle — à quel point les valeurs bougent d'une trame à l'autre — et en déduire s'il y a du mouvement.
Les ESP32 en mode espion
Pour capturer le CSI, j'utilise trois ESP32-DevKitC positionnés aux coins de la pièce. Chacun tourne en mode « promiscuous » : il écoute toutes les trames WiFi qui passent dans l'air, même celles qui ne lui sont pas destinées, sans se connecter à aucun réseau.
Pour chaque trame captée, le firmware extrait les données CSI et les envoie par câble série (UART) au Raspberry Pi central qui centralise tout.
Le firmware tourne sur FreeRTOS. Et là, premier piège classique : printf() n'est pas thread-safe. À fort débit de trames, j'avais des corruptions mémoire impossibles à reproduire de façon déterministe — le genre de bug qui disparaît si tu l'observes trop attentivement. Après une soirée à chasser des fantômes, j'ai mis en place un ring buffer dans une tâche d'écriture dédiée. Problème réglé, données propres.
Trilatérer : la bonne idée pas si simple
Avec trois capteurs, on peut en théorie calculer une position en 2D : c'est la trilatération. On mesure la distance depuis chaque capteur, on trace trois cercles, l'intersection donne la position. C'est comme ça que le GPS fonctionne (avec des satellites).
En pratique, « mesurer la distance » avec du WiFi dans un appartement c'est compliqué. On utilise un modèle mathématique qui dit que la puissance du signal diminue avec la distance selon une courbe logarithmique — le path-loss. Le problème : ce modèle suppose un espace vide. Dans un appartement avec des murs, des meubles et un chat, le signal rebondit dans tous les sens.
Résultat : la trilatération fonctionne bien en espace ouvert avec une calibration soigneuse. Dans un appartement réel, c'est plus une estimation qu'une mesure précise. L'interface permet de calibrer les capteurs en drag-and-drop sur la scène 3D, ce qui aide.
L'interface : parce que les données brutes c'est illisible
Le backend Python sur le Raspberry Pi agrège les flux des trois ESP32, calcule les positions estimées, et expose tout via FastAPI. Les données de capteurs sont stockées dans DuckDB — une base de données analytique embarquée, parfaite pour des séries temporelles légères.
L'interface est construite avec React + Three.js. Une scène 3D avec une grille au sol graduée en mètres, les capteurs repositionnables, les devices détectés représentés par des blobs dont l'opacité reflète la confiance de localisation (trois capteurs = 100%, un seul capteur = ~22%). Un waterfall CSI montre l'activité radio en temps réel.
La fonctionnalité qui m'a pris 30 minutes mais qui change tout : le mode Solo sur un device. Il dim tous les autres et filtre le waterfall sur cet appareil uniquement. Petit détail, gros impact sur la lisibilité.
Ce qui marche vraiment, et la suite
Soyons honnêtes : le README contient une section « Ce qui est réel vs décoratif ».
Ce qui est solide : la détection de mouvement par variance CSI est fiable. La détection de présence (quelqu'un est dans la pièce ?) fonctionne bien. La collecte et visualisation des données temps réel aussi.
Ce qui est approximatif : la localisation précise en 2D dans un appartement avec des obstacles. C'est fonctionnel, pas magique.
La suite logique, c'est le machine learning. Les chercheurs utilisent des réseaux de neurones pour apprendre à interpréter les patterns CSI — identifier des activités, compter des personnes, détecter des chutes. Avec un dataset enregistré dans cet appartement précis, un modèle apprendrait les « signatures » de l'espace bien mieux qu'un modèle path-loss générique.
C'est le prochain chantier. Pour l'instant, voir jusqu'où les heuristiques classiques pouvaient aller — c'était déjà le but.