← Retour au portfolio English

snake_differ

Vue d'ensemble de l'architecture — module Puara pour le Seeed XIAO ESP32-S3 Sense

Objectif

Un firmware de détection de mouvement qui observe un flux caméra, calcule les différences de pixels image par image, et diffuse les résultats sous forme de messages OSC via WiFi — afin qu'un DAW, un synthétiseur, ou un environnement génératif puisse réagir à la présence, la position et la forme d'objets en mouvement dans le cadre. Conçu à l'origine pour suivre des formes sombres (sculptures cinétiques en bobines) sur un fond clair, mais fonctionne pour toute source de mouvement sombre sur fond clair.

Matériel

CarteSeeed XIAO ESP32-S3 Sense
CaméraOV2640 (intégrée)
MicrophonePDM (intégré, GPIO 41/42)
Sorties moteurD2 = GPIO 3, D3 = GPIO 4 (PWM, 1 kHz, 10 bits)
ConnexionWiFi 2,4 GHz
AlimentationUSB-C

Aucun câblage requis pour la caméra ou le micro ; les sorties moteur nécessitent un pilote externe pour tout ce qui dépasse un petit signal.

Vue d'ensemble du système

La caméra et le microphone alimentent le firmware, qui soit diffuse un paquet OSC vers un logiciel récepteur, soit sert une petite interface web (avec un aperçu MJPEG en direct) dans un navigateur pour calibrer la caméra avant un spectacle. Les deux chemins partagent la même connexion WiFi et le même stockage de paramètres sur l'appareil.

Vue d'ensemble du système snake_differ La caméra et le microphone intégrés alimentent le firmware ESP32-S3, qui soit diffuse un paquet OSC via WiFi vers un logiciel récepteur, soit sert une interface web et un aperçu MJPEG dans un navigateur pour le calibrage avant un spectacle. Caméra OV2640 + micro PDM Intégrés, QVGA niveauxde gris ~30 fps Firmware snake_differ Module Puara sur ESP32-S3 Paquet OSC sortant UDP, ~30 Hz Interface web + aperçu MJPEG Ports 80 / 81 Logiciel récepteur DAW, Max/MSP, Logic, Protokol Navigateur Config, scan, réglages, aperçu Les deux chemins partagent la même connexion WiFi et le même stockage de réglages
Vue d'ensemble : capteurs → firmware → sortie OSC ou interface web de calibrage

Structure des tâches du firmware

En interne, trois morceaux de code s'exécutant en parallèle — deux tâches FreeRTOS plus la boucle principale Arduino — partagent leur état via des structures protégées par mutex :

  1. Tâche caméra (~30 fps) — pour chaque image, un seul passage pixel par pixel calcule deux ensembles de métriques indépendants à la fois :
  2. Tâche micro (~100 Hz) — lit 512 échantillons PDM à 16 kHz, calcule le RMS, et applique un gain linéaire micGain plafonné à 1,0.
  3. Boucle principale (~30 Hz) — lit les deux ensembles de métriques sous leurs verrous, applique un comportement de maintien (« latch ») (la plupart des valeurs spatiales conservent leur dernier état connu quand la scène s'immobilise, plutôt que de revenir brusquement à zéro ; la vitesse, l'indicateur de mouvement et le RMS du micro restent toujours en direct), puis soit :

Le serveur web (port 80, issu du module Puara) et le serveur de capture MJPEG optionnel (port 81) fonctionnent indépendamment de cette boucle, lisant la dernière image brute et les paramètres actuels au besoin.

Structure des tâches du firmware snake_differ Deux tâches FreeRTOS concurrentes calculent les métriques dérivées de la caméra et du microphone dans un état partagé protégé par mutex. La boucle principale Arduino lit cet état à environ 30 Hz, applique le comportement de maintien, et soit envoie le paquet OSC via UDP, soit, sur réception d'OSC entrant, pilote les sorties PWM des moteurs. Tâche caméra (~30 fps) Diff + analyse de pixels en un passage Tâche micro (~100 Hz) Lecture PDM, RMS avec gain Métriques partagées État diff/pixel/micro protégé par mutex Boucle principale (~30 Hz) Applique le maintien, construit le paquet OSC Paquet OSC sortant UDP vers oscIP:oscPORT OSC entrant threshold, motor1, motor2 Pilote le PWM des moteurs (D2/D3)
Structure des tâches du firmware : tâches concurrentes, état partagé et boucle principale

Référence des paramètres

Tous les paramètres se trouvent sur la page web Settings, s'appliquent immédiatement sans reflashage, et persistent au redémarrage.

ParamètreDéfautNotes
oscIP192.168.4.2IP de l'ordinateur recevant l'OSC
oscPORT8000Port UDP sur lequel le récepteur écoute
localPORT8000Port UDP sur lequel l'appareil écoute pour l'OSC entrant
diffThreshold30Delta de luminosité de pixel (0–255) comptant comme mouvement ; plus bas = plus sensible
videoStream0Mettre à 1 pour activer l'aperçu MJPEG sur le port 81 ; désactiver pendant la performance
camContrast0Contraste matériel de l'OV2640, plage -2 à 2 ; plus élevé aide les formes sombres à ressortir
micGain10Gain linéaire sur le RMS brut du micro, plafonné à 1,0 ; plage pratique 1–100
pixThreshold100Luminosité absolue (0–255) en dessous de laquelle un pixel compte comme matière de bobine sombre

Référence OSC

Paquet sortant — ~30 Hz

Envoyé comme un seul paquet OSC vers oscIP:oscPORT ; contient toujours tous les messages ci-dessous. Le nom de l'appareil provient de config.json (par défaut snake_differ_0).

AdresseArgsMaintienDescription
/<device>/motionmotion (int), magnitude (float)mag seulementÉtat de mouvement actuel et fraction du cadre en mouvement
/<device>/bboxx_min, y_min, x_max, y_max (float×4)ouiBoîte englobante du mouvement, normalisée 0–1
/<device>/centroidcx, cy (float×2)ouiCentre de masse des pixels en mouvement, normalisé
/<device>/velocityvx, vy (float×2)nonDéplacement du centroïde par image ; zéro à l'arrêt
/<device>/spreadsx, sy (float×2)ouiÉcart-type des pixels en mouvement autour du centroïde
/<device>/fillfill (float)ouiDensité de mouvement dans la boîte englobante
/<device>/micrms (float)nonRMS du micro intégré, normalisé 0–1, indépendant du mouvement visuel
/<device>/coveragecoverage (float)nonFraction de l'ensemble du cadre sous pixThreshold
/<device>/masscx, cy (float×2)nonCentroïde de tous les pixels sombres, suit la masse de bobine même à l'arrêt
/<device>/grid/coverage24 flottantsnonFraction de pixels sombres par zone (6×4, ligne par ligne, ligne 0 = haut)
/<device>/grid/density24 flottantsnonMagnitude de gradient/contour par zone — étroitesse des bobines
/<device>/grid/texture24 flottantsnonVariance locale par zone — richesse de texture

Messages entrants

AdresseArgDescription
/snake_differ/thresholdfraction (float)Met à jour diffThreshold en direct ; la valeur est une fraction de 255
/snake_differ/motor1vitesse (int 0–1000)Rapport cyclique PWM sur D2 / GPIO 3
/snake_differ/motor2vitesse (int 0–1000)Rapport cyclique PWM sur D3 / GPIO 4

Première configuration

1. Flasher le firmware (PlatformIO)

pio run --target upload
pio run --target uploadfs

Nécessite la plateforme pioarduino (ESP32 Arduino 3.x) — la plateforme PlatformIO ESP32 2.x standard a un plantage connu à la réception UDP ; platformio.ini pointe déjà vers la bonne plateforme.

2. Se connecter au WiFi de l'appareil

Au premier démarrage (ou sans routeur configuré), l'appareil crée son propre point d'accès (SSID snake_differ_0, mot de passe garnetwillis).

3. Ouvrir l'interface web

http://snake_differ_0.local (repli : l'IP par défaut du point d'accès, 192.168.4.1, ou celle affichée par le moniteur série). Les pages Config / Scan / Settings s'y trouvent toutes.

4. Rejoindre éventuellement un routeur de studio

Sur la page Config, saisir le SSID/mot de passe du routeur et Enregistrer ; l'appareil se connecte au prochain redémarrage et le point d'accès reste actif en secours.

Déroulement du calibrage

  1. Positionner l'appareil pour que la caméra voie toute la zone d'intérêt.
  2. Régler videoStream = 1, ouvrir Config, vérifier le cadrage.
  3. Ajuster camContrast jusqu'à ce que la forme suivie apparaisse clairement plus sombre que le fond.
  4. Scène immobile, surveiller le moniteur série (pio device monitor) pour un [diff] no motion continu.
  5. Bouger la forme — des lignes [diff] mag=... devraient apparaître immédiatement.
  6. Ajuster diffThreshold jusqu'à une détection propre avec un minimum de bruit.
  7. Régler videoStream = 0, saisir oscIP/oscPORT, Enregistrer.
  8. Confirmer que le paquet arrive à ~30 Hz dans Protokol ou le récepteur visé.

Réception de l'OSC

Aperçu caméra

Quand videoStream = 1, un flux MJPEG est servi sur http://<ip_appareil>:81/stream (aussi intégré directement dans la page Config) via une seule connexion HTTP multipart persistante — pas de sondage, pas d'aller-retour par image. L'aperçu est la même image en niveaux de gris utilisée pour le calcul de différence, et camContrast affecte les deux. Désactiver videoStream avant un spectacle — l'encodage JPEG tourne en continu tant qu'un client est connecté.

Notes techniques

Construit avec