Signaux : couleurs, sons et codes ================================= .. important:: **Cette page fait autorité.** Les couleurs, les sons et les codes de bips sont définis ici et nulle part ailleurs. ``App/signal.h``, ``App/drivers/buzzer.h``, ``App/blackbox.h`` et ``led_states.yaml`` y renvoient, ils ne la recopient pas. Ce n'est pas une précaution théorique : la table des sons avait été recopiée en commentaire dans le yaml, elle a divergé, et ``low_battery`` jouait « ok ». La version utilisateur de cette page — ce qu'on voit et entend, et quoi en faire — est dans le guide utilisateur, page « Voyant et sons ». Les cinq principes ------------------ Tout ce qui suit en découle. Quand un cas nouveau se présente, c'est à ces règles qu'il faut le confronter, pas à la table. 1. **Le son dit le changement, la lumière dit l'état.** Un son marque un instant : on l'entend une fois, il ne sert à rien de le répéter. Une couleur se lit à tout moment. Seuls deux états se répètent au son : le danger et l'absence de Pi, parce qu'ils demandent quelqu'un. 2. **Chaque étage signale ce qu'il sait.** La carte signale ce qu'elle voit sans la Pi — et elle doit le faire même Pi éteinte, câble débranché, agent mort. La Pi signale ce que seule elle sait : mission, navigation, batterie estimée en pourcentage. 3. **La priorité est absolue, jamais mélangée.** Danger > panne > pas de Pi > couleur de la Pi. Une seule chose s'affiche à la fois, la plus grave. Mêler deux informations sur une LED donne une troisième couleur qui ne veut rien dire. 4. **La couleur donne la catégorie, l'animation l'urgence.** Rouge = danger, orange = panne, vert = tout va bien, et ce que la Pi envoie pour le reste. Clignotement rapide = maintenant, lent = à surveiller, respiration = j'attends. 5. **Le signal dit « regarde », ``/diagnostics`` dit « quoi ».** On ne cherche pas à coder le détail d'une panne dans une couleur. Le signal amène quelqu'un devant le robot ou devant un terminal ; le détail exact est dans ``/diagnostics`` et dans le journal de bord. Qui écrit quoi -------------- .. list-table:: :header-rows: 1 :widths: 22 20 58 * - organe - propriétaire - remarque * - LED RGB - tâche **IHM**, ``signal.c`` - seule écriture de ``board_rgb_set()``. La couleur venant de ROS est *déposée* par ``signal_pi_rgb()`` et affichée seulement si rien de plus grave n'occupe la LED. * - buzzer **passif** - tâche **IHM** - mélodies et codes de bips ; ``buzzer_play()`` dépose une demande, la tâche IHM l'arme à 50 Hz * - buzzer **actif** - tâche **traction** - sécurité seulement : ``safety_step()``, et le SOS des gestionnaires de faute. **Aucun chemin ROS.** La signalisation n'a **pas de tâche à elle** : elle vit dans la tâche IHM, qui tournait déjà à 50 Hz pour les boutons et le buzzer. Une tâche de plus aurait ajouté un ordonnancement et un partage de LED pour un travail qui tient en quelques microsecondes par tour. Le voyant RGB, par priorité --------------------------- .. list-table:: :header-rows: 1 :widths: 8 26 22 44 * - ordre - situation - couleur - animation * - 1 - arrêt d'urgence verrouillé, ou batterie critique - **rouge** - clignotement **rapide, 4 Hz** — et le buzzer actif sonne * - 2 - panne présente (roue, capteur, flash, quartz) - **orange** - clignotement **lent, 1 Hz** ; le code est rejoué quand la panne apparaît * - 3 - démarrage sans panne - **vert** - un **fondu de 2 s**, une seule fois * - 4 - pas d'agent micro-ROS - **rouge** - **respiration** lente sur 3 s * - 5 - agent présent - *ce que la Pi envoie* - ``status/led`` ; LED éteinte tant qu'elle n'a rien envoyé Le bilan de démarrage --------------------- Entre **3 et 10 secondes** après la mise sous tension — dès que les capteurs et la configuration ont répondu — la carte fait son bilan, **une seule fois**. - **Aucune panne** : le fondu vert. Pas de son, la mélodie de démarrage l'a déjà dit. - **Une ou plusieurs pannes** : les codes de bips ci-dessous, à la file, puis la LED passe à l'orange clignotant tant que la panne dure. Les codes de bips, comme un BIOS -------------------------------- Un code se compte : des **bips longs** donnent la famille, des **bips courts** le détail. Même hauteur de note pour tous (LA5, 880 Hz) — c'est la **durée** qui distingue, pas la mélodie, parce qu'un code se compte à l'oreille sans être musicien. .. list-table:: :header-rows: 1 :widths: 14 22 14 50 * - longs - famille - courts - détail * - 1 - traction - 1 / 2 / 3 - roue gauche / roue droite / les deux * - 2 - capteurs - 1 / 2 / 3 - centrale inertielle / température / batterie * - 3 - mémoire - 1 - flash illisible : le robot tourne sur les réglages par défaut * - 4 - horloge - 1 - quartz 32 kHz arrêté : pas d'heure sur pile * - 5 - **plantage précédent** - 1 / 2 / 3 / 4 - pile débordée / tas plein / faute matérielle / ``Error_Handler`` * - 6 - **sécurité inertielle** - 1 / 2 / 3 / 4 - renversé / roue bloquée / patinage vu par le gyroscope / choc (pic horizontal ≥ 5 m/s²) — robot coupé et verrouillé jusqu'au réarmement (``motion_guard.c``, appelé par ``safety_step``). Jamais au démarrage : joué au déclenchement. Voyant au niveau 2 (orange), pas d'alarme. Mode banc : éclair blanc 120 ms toutes les 2 s, par-dessus les niveaux 3 à 5 Durées : long **450 ms**, court **120 ms**, **200 ms** entre deux bips, **600 ms** entre les longs et les courts, **1,2 s** entre deux codes. Un code à 5 longs est joué au démarrage suivant un plantage, puis **acquitté** : il ne revient pas au démarrage d'après. Une panne qui apparaît en cours de route ---------------------------------------- Le bilan ne dit que ce qui n'allait pas **au démarrage**. Une centrale débranchée pendant que le robot tourne ne faisait donc que changer la couleur du voyant — et personne ne regarde un voyant en permanence. Le son dit le **changement** (principe 1) : le code est donc rejoué **une fois**, au moment où la panne apparaît. Une panne qui **disparaît** ne fait aucun bruit, elle n'appelle personne ; la LED cesse simplement de clignoter. Trois signaux qui ne sont pas des codes --------------------------------------- .. list-table:: :header-rows: 1 :widths: 26 24 50 * - signal - son - quand * - **relance par le watchdog** - 3 notes graves rapides (``SOUND_WATCHDOG``) - au démarrage, **avant** les codes, si la carte a été relancée par le watchdog * - **rappel « pas de Pi »** - une note brève (``SOUND_NO_PI``) - **toutes les 5 s** tant qu'il n'y a pas d'agent * - **SOS** - ``···---···`` sur le buzzer **actif** - la carte a planté : voir la boîte noire, page « Journal de bord » Le rappel « pas de Pi » a un **délai de grâce de 3 minutes** au démarrage à froid : allumer le robot avant la Pi est normal, et se faire klaxonner dessus pendant qu'elle démarre ne l'est pas. Le délai saute dans deux cas : la carte a déjà vu la Pi une fois dans ce tour, ou elle vient d'être relancée par le chien de garde — là, le silence serait un mensonge. Le rappel se tait aussi pendant un code, pendant une mélodie, et pendant un danger : le buzzer actif parle, on ne lui coupe pas la parole. Deux buzzers, deux rôles ------------------------ La carte en porte deux, et ils ne servent pas à la même chose. .. list-table:: :header-rows: 1 :widths: 14 20 30 36 * - - broche - rôle - piloté par * - **passif** - PB9, TIM17_CH1 - mélodies : information, confirmation, événements de mission - ``status/sound`` → ``ros_ui.c`` * - **actif** - PC13 - **un seul sens** : « arrête-toi et regarde » - ``safety_step()``, tâche traction. **Aucun chemin ROS.** Le buzzer actif n'est **jamais commandé depuis ROS**, et c'est délibéré : un signal de sécurité qu'on peut faire taire à distance n'en est plus un. Il doit aussi fonctionner quand il n'y a **aucun agent** — au démarrage, agent tombé, câble débranché. Il ne demande qu'une écriture GPIO depuis la tâche la plus prioritaire : ni timer, ni table de notes, ni séquenceur qui pourrait se coincer. La Pi le **voit** (``status/emergency_stop``, ``/diagnostics``) mais ne le commande pas. Si elle avait une raison assez grave de le faire sonner, la bonne réponse serait de demander l'**arrêt**, pas un son. Il sonne dans exactement deux cas, tous deux connus de la carte seule : - **arrêt d'urgence verrouillé** — contact de J8 ouvert ; - **batterie critique** — sous **9,6 V**, soit 3,2 V par élément d'une 3S. En dessous, décharger davantage abîme les cellules de façon irréversible. Ce seuil est câblé dans la carte parce que c'est la seule chose qui protège la batterie quand la Pi est éteinte ou plantée. Motif : **200 ms toutes les secondes**. Audible sans être continu — un signal continu, on cesse de l'entendre au bout d'une minute. Table des mélodies ------------------ Sur le buzzer **passif**, publiées sur ``status/sound`` (``std_msgs/UInt8``). Les numéros sont **groupés par famille** et les trous sont volontaires : ils laissent la place aux ajouts sans tout renuméroter. .. list-table:: :header-rows: 1 :widths: 8 24 24 44 * - n° - constante - déclenché par - description * - 0 - ``SOUND_SILENCE`` - n'importe qui - coupe immédiatement le son en cours * - **1-9** - *accusés de réception* - - * - 1 - ``SOUND_BEEP`` - accusé de réception - une note brève * - 2 - ``SOUND_OK`` - confirmation - deux notes montantes * - 3 - ``SOUND_ERROR`` - valeur refusée - deux notes descendantes * - **10-19** - *états de la carte* - **la carte, sans agent** - * - 10 - ``SOUND_BOOT`` - mise sous tension - arpège ; joué avant même que l'agent existe * - 11 - ``SOUND_AGENT_UP`` - session micro-ROS établie - deux notes qui montent haut * - 12 - ``SOUND_AGENT_LOST`` - session tombée - le même motif à l'envers * - 13 - ``SOUND_WATCHDOG`` - relance par le watchdog - trois notes graves rapides, avant les codes du bilan * - 14 - ``SOUND_NO_PI`` - pas d'agent, toutes les 5 s - une note brève ; discret, il revient souvent * - **20-29** - *mission* - la Pi - * - 20 - ``SOUND_NAV_START`` - navigation démarrée - trois notes qui montent, franches * - 21 - ``SOUND_GOAL`` - étape intermédiaire atteinte - deux notes courtes, discrètes — ça arrive souvent * - 22 - ``SOUND_MISSION_OK`` - mission accomplie - la seule mélodie un peu longue, elle marque la fin * - 23 - ``SOUND_MISSION_KO`` - mission échouée - descente résolue ; un échec, pas une panne * - 24 - ``SOUND_PAUSE`` - mise en pause - deux notes égales, suspendues * - 25 - ``SOUND_RESUME`` - reprise - la pause résolue vers le haut * - **30-39** - *alertes* - le robot continue, mais quelque chose demande attention - * - 30 - ``SOUND_OBSTACLE`` - bloqué, chemin impossible - deux notes basses répétées ; contrarié, pas affolé * - 31 - ``SOUND_BATTERY_LOW`` - seuil bas franchi (seuil dans le yaml) - trois notes descendantes, lentes * - 32 - ``SOUND_ALERT`` - alerte générique - sirène, deux tons alternés .. note:: L'arrêt d'urgence et la batterie **critique** ne figurent pas dans cette table : ils sonnent sur le buzzer actif. Les doubler ici ne ferait que couvrir le signal de sécurité par une mélodie. Personne ne manipule ces numéros -------------------------------- Un nœud de la Pi ne publie **jamais** un numéro. Il publie un **nom d'état** sur ``status/set_state``, et ``led_states.yaml`` fait la correspondance vers une couleur et un son : .. code-block:: text nœud nav ──"mission_ok"──► status/set_state ──► led_states.yaml ──► status/sound: 22 (le seul endroit) Le numéro ne circule donc qu'entre le yaml et la carte. Le son est joué à l'**entrée** dans l'état ; ``set_state_hold`` maintient l'état, donc un nœud qui republie le même nom en boucle ne redéclenche pas la mélodie. Essayer une mélodie ------------------- .. code-block:: bash ros2 topic pub --once /ratbot_1/status/sound std_msgs/msg/UInt8 "{data: 22}" ros2 topic pub --once /ratbot_1/status/sound std_msgs/msg/UInt8 "{data: 0}" # couper Un identifiant inconnu ou situé dans un trou de numérotation **coupe le son et est compté** dans le bloc ``buzzer`` de ``/diagnostics`` (``unknown_ids``) : un son qui ne sort pas doit se voir, sinon on cherche du côté du haut-parleur pendant une heure. .. code-block:: bash ros2 topic echo /diagnostics | grep -A12 "buzzer" Essayer le buzzer actif ----------------------- Il n'y a pas de commande — c'est le principe. Il se teste par le vrai chemin, ce qui vaut mieux : ça valide toute la chaîne et pas seulement le composant. **Actionner le contact de J8 (PE12).** Attendu : le buzzer actif bipe, ``status/emergency_stop`` passe à ``true``, ``/diagnostics`` l'annonce, les roues s'arrêtent. Avec le champignon **1NC** monté depuis le 2026-10-27, on l'enfonce : c'est l'ouverture du circuit qui déclenche. Débrancher J8 doit faire de même — c'est la sécurité positive (voir :doc:`topics`, section *La polarité du contact*). .. code-block:: bash # tourner le champignon (ou rebrancher J8), PUIS ros2 service call /ratbot_1/rearm std_srvs/srv/Trigger Ou, devant le robot : **5 appuis sur SW1** en moins de 4 s. Un bip passif par appui compté, puis ``SOUND_OK`` si c'est réarmé, ``SOUND_ERROR`` si c'est refusé — voir ``App/rearm_gesture.h``. Le réarmement est **refusé** tant que le contact est ouvert : redémarrer le robot sous les mains de quelqu'un est exactement ce qu'il ne faut pas faire. Le compteur ``rearms_refused`` le dit, et ``rearms_local`` compte les réarmements faits par SW1. Ajouter une mélodie ------------------- 1. un identifiant dans la bonne famille, dans ``App/drivers/buzzer.h`` ; 2. la table de notes et son entrée dans ``melodies[]``, dans ``buzzer.c`` — la table est **indexée par identifiant**, avec des trous : chercher la mélodie 22 reste une indexation directe ; 3. une ligne **ici** ; 4. si un état doit la déclencher, une ligne dans ``led_states.yaml``. Une mélodie se termine par une note de durée nulle. Elles sont **non bloquantes** : ``buzzer_play()`` dépose une demande, ``buzzer_spin()`` avance d'une note dans la tâche IHM à 50 Hz.