mirror of
https://github.com/deunix-educ/voice-assistant.git
synced 2026-10-13 00:11:57 +02:00
Assistant vocal local ESP32 : étapes 1 à 15, et 15b écrite
État du projet au premier commit : - firmware ESP32 : micro I2S, lecture anti-gigue, MQTT (état retenu, testament), mains libres, domotique de démonstration, chien de garde, présence du serveur ; - serveur Python : sessions, VAD, faster-whisper, pyannote, identification, droits par profil, domotique, mot de réveil, Piper ; - broker Mosquitto : configuration, comptes et ACL (non activés en développement) ; - étape 15b écrite, en attente d'essai : agent MQTT pour machines Linux. Étapes 1 à 15 validées. 203 tests, pyright sans erreur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @file config.h
|
||||
* @brief Constantes matérielles et audio du firmware.
|
||||
*
|
||||
* Toutes les valeurs réglables du firmware sont ici : aucune constante magique
|
||||
* dans le code. Les broches évitent les broches de « strapping » (0, 2, 5, 12, 15)
|
||||
* pour les signaux audio, car leur état au démarrage change le mode de boot.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------- Format audio
|
||||
|
||||
/// Fréquence d'échantillonnage unique de tout le système (Hz).
|
||||
static constexpr uint32_t AUDIO_SAMPLE_RATE = 16000;
|
||||
|
||||
/// Durée d'un chunk audio (ms). 50 ms = compromis latence / surcoût protocole.
|
||||
static constexpr uint32_t AUDIO_CHUNK_MS = 50;
|
||||
|
||||
/// Nombre d'échantillons 16 bits dans un chunk : 16000 x 0,050 = 800.
|
||||
static constexpr size_t AUDIO_CHUNK_SAMPLES = (AUDIO_SAMPLE_RATE * AUDIO_CHUNK_MS) / 1000;
|
||||
|
||||
/// Taille d'un chunk en octets : 800 x 2 = 1600.
|
||||
static constexpr size_t AUDIO_CHUNK_BYTES = AUDIO_CHUNK_SAMPLES * sizeof(int16_t);
|
||||
|
||||
// --------------------------------------------------------- Broches micro INMP441
|
||||
|
||||
static constexpr int PIN_MIC_SCK = 25; ///< Horloge bit (BCLK) du micro
|
||||
static constexpr int PIN_MIC_WS = 26; ///< Sélection de mot (LRCL / WS)
|
||||
static constexpr int PIN_MIC_SD = 33; ///< Donnée série sortante du micro (DOUT)
|
||||
|
||||
// ------------------------------------------------------ Broches ampli MAX98357A
|
||||
|
||||
static constexpr int PIN_AMP_BCLK = 27; ///< Horloge bit
|
||||
static constexpr int PIN_AMP_LRC = 14; ///< Sélection de mot
|
||||
static constexpr int PIN_AMP_DIN = 22; ///< Donnée série entrante de l'ampli
|
||||
|
||||
// ------------------------------------------------------------ Interface utilisateur
|
||||
|
||||
static constexpr int PIN_BUTTON_PTT = 4; ///< Bouton « Push-To-Talk », pull-up interne
|
||||
static constexpr int PIN_STATUS_LED = 2; ///< LED d'état (broche de strapping, sortie seule : acceptable)
|
||||
/// LED de démonstration domotique (étape 11) : « la lumière » de la pièce de la carte.
|
||||
/// GPIO 21 : libre, sans rôle au démarrage. LED + résistance 330 Ω vers GND.
|
||||
static constexpr int PIN_DEMO_LIGHT = 21;
|
||||
|
||||
/// Durée d'anti-rebond du bouton (ms).
|
||||
static constexpr uint32_t BUTTON_DEBOUNCE_MS = 30;
|
||||
|
||||
// ------------------------------------------------------------ Conversion micro
|
||||
|
||||
/**
|
||||
* Gain numérique appliqué après le passe-haut, avant la réduction à 16 bits.
|
||||
*
|
||||
* Valeur **mesurée**. Une phrase à 20-30 cm donne, une fois le grondement
|
||||
* infrasonore retiré par le passe-haut, une crête vers -25 dBFS : un gain de 4
|
||||
* la place vers -13 dBFS, soit ~22 % de la pleine échelle, avec 13 dB de marge.
|
||||
*
|
||||
* Les valeurs 16 puis 1 retenues plus tôt venaient de mesures faussées (cadrage
|
||||
* des bits erroné, puis infrasons dominants). L'auto-test annonce la valeur à
|
||||
* retenir à la fin de chaque session ; elle dépend du module et de la distance.
|
||||
*
|
||||
* Le gain est numérique : il ne dégrade pas le rapport signal/bruit, il replace
|
||||
* seulement le signal dans la plage utile.
|
||||
*/
|
||||
static constexpr int MIC_GAIN = 4;
|
||||
|
||||
/**
|
||||
* Durée de stabilisation du micro après activation du canal I2S (ms).
|
||||
*
|
||||
* L'INMP441 s'endort dès que ses horloges s'arrêtent, et chaque réveil produit
|
||||
* une composante continue qui dérive lentement. Mesuré sur le banc : encore
|
||||
* +23 % de pleine échelle et en mouvement 600 ms après le réveil. On jette donc
|
||||
* ce début, UNE seule fois au démarrage : ensuite, les horloges ne s'arrêtent
|
||||
* plus jamais (flush() vide le DMA par lecture).
|
||||
*/
|
||||
static constexpr uint32_t MIC_SETTLE_MS = 1500;
|
||||
|
||||
/**
|
||||
* Pull-down interne sur la ligne SD du micro.
|
||||
*
|
||||
* Le datasheet de l'INMP441 exige une résistance de 100 kOhms entre SD et la
|
||||
* masse : le micro met sa sortie en haute impédance hors de sa moitié de trame.
|
||||
* Beaucoup de modules bon marché ne la portent pas. Le pull-down interne de
|
||||
* l'ESP32 (environ 45 kOhms) remplit le même rôle sans composant à ajouter.
|
||||
*/
|
||||
static constexpr bool MIC_SD_PULLDOWN = true;
|
||||
|
||||
/// Affiche l'analyse détaillée des bits au démarrage (utile tant que l'étape 1 n'est pas validée).
|
||||
static constexpr bool MIC_SELFTEST_VERBOSE = true;
|
||||
|
||||
/// Durée de l'auto-test du micro au démarrage (ms).
|
||||
static constexpr uint32_t MIC_PROBE_MS = 300;
|
||||
|
||||
/**
|
||||
* Filtre passe-haut du micro, du premier ordre : bas += (x - bas) >> SHIFT ;
|
||||
* sortie = x - bas. Fréquence de coupure : fc = fs / (2 pi 2^SHIFT).
|
||||
*
|
||||
* 5 donne fc = 16000 / (2 pi 32) = 80 Hz. La voix n'a rien d'utile en dessous.
|
||||
* Mesuré sur le banc : avec l'ancien réglage (12, soit 0,6 Hz, qui ne retirait
|
||||
* que la composante continue), 87 % de l'énergie d'un enregistrement était
|
||||
* infrasonore — mouvements de la plaque d'essai quand on tient le bouton,
|
||||
* souffle de la parole. Avec 5, il en reste 0,6 %, et la voix 99 %.
|
||||
*
|
||||
* Calculé sur les 24 bits du micro : sur 16 bits, un pas de 1/32 laisserait une
|
||||
* zone morte de 31 LSB, source d'erreur continue et de distorsion à bas niveau.
|
||||
*/
|
||||
static constexpr int MIC_HPF_SHIFT = 5;
|
||||
|
||||
// ------------------------------------------------------------ Sortie audio (étape 2)
|
||||
|
||||
/**
|
||||
* Cadrage I2S attendu par l'ampli. Le MAX98357**A** suit la norme Philips, le
|
||||
* MAX98357**B** le cadrage MSB (c'est la lettre après le numéro, sur la puce).
|
||||
* Si le bip de démarrage sort râpeux ou soufflé au lieu d'être pur : basculer.
|
||||
*/
|
||||
static constexpr bool AMP_FORMAT_PHILIPS = true;
|
||||
|
||||
/// Bip de démarrage : teste la sortie SEULE, sans PC, sans FIFO, sans tâche.
|
||||
static constexpr uint32_t BOOT_TONE_HZ = 440;
|
||||
static constexpr uint32_t BOOT_TONE_MS = 600;
|
||||
|
||||
/**
|
||||
* Amplitude du bip : 8192 = 25 % de pleine échelle = -12 dBFS.
|
||||
* Avec le gain de 9 dB du MAX98357A sur 4 ohms, environ 0,15 W : net sans agresser.
|
||||
*/
|
||||
static constexpr int16_t BOOT_TONE_AMPLITUDE = 8192;
|
||||
|
||||
/**
|
||||
* FIFO de lecture : 16384 octets = 8192 échantillons = 512 ms à 16 kHz.
|
||||
* Elle contient la réserve anti-gigue (PLAYBACK_START_MS) et encore 212 ms pour
|
||||
* les rafales : après un retard, le réseau livre souvent plusieurs chunks d'un coup.
|
||||
*/
|
||||
static constexpr size_t PLAYBACK_FIFO_BYTES = 16384;
|
||||
|
||||
/// Bloc lu par la tâche audio : 256 échantillons = 16 ms = 512 octets.
|
||||
static constexpr size_t PLAYBACK_BLOCK_SAMPLES = 256;
|
||||
|
||||
/**
|
||||
* Tampon anti-gigue (étape 12) : réserve à constituer avant de jouer le premier
|
||||
* échantillon, et à reconstituer après une famine. 300 ms = 6 chunks = 9600 octets,
|
||||
* soit l'avance que le serveur envoie d'emblée : la lecture démarre dès qu'elle est
|
||||
* arrivée. Un retard réseau plus court que la réserve ne s'entend pas.
|
||||
*/
|
||||
static constexpr uint32_t PLAYBACK_START_MS = 300;
|
||||
|
||||
/// Tâche audio : au-dessus de loop() (priorité 1), sur le cœur 1 (le Wi-Fi prendra le 0).
|
||||
static constexpr int AUDIO_TASK_PRIORITY = 5;
|
||||
static constexpr int AUDIO_TASK_CORE = 1;
|
||||
static constexpr uint32_t AUDIO_TASK_STACK_BYTES = 4096;
|
||||
|
||||
/**
|
||||
* Tampon de réception série : 8192 octets = 256 ms de PCM au débit de lecture.
|
||||
* Le tampon par défaut (256 octets) déborderait en 8 ms pendant qu'on enregistre.
|
||||
*/
|
||||
static constexpr size_t SERIAL_RX_BUFFER_BYTES = 8192;
|
||||
|
||||
// ------------------------------------------------------------ Capture vers le réseau (étape 4)
|
||||
|
||||
/**
|
||||
* FIFO de capture : 32768 octets = 1,02 s de PCM. L'envoi MQTT peut bloquer quand
|
||||
* le Wi-Fi peine ; la tâche de capture, elle, ne s'arrête jamais. Au-delà d'une
|
||||
* seconde de retard réseau, des chunks sont perdus — et comptés.
|
||||
*/
|
||||
static constexpr size_t CAPTURE_FIFO_BYTES = 32768;
|
||||
|
||||
/// Tâche de capture : même priorité et même cœur que la tâche de lecture.
|
||||
static constexpr int CAPTURE_TASK_PRIORITY = 5;
|
||||
static constexpr int CAPTURE_TASK_CORE = 1;
|
||||
static constexpr uint32_t CAPTURE_TASK_STACK_BYTES = 4096;
|
||||
|
||||
// ------------------------------------------------------------ Liaison série (étape 1)
|
||||
|
||||
/// Débit série : 921 600 baud = ~92 kB/s, largement au-dessus des 32 kB/s d'audio.
|
||||
static constexpr uint32_t SERIAL_BAUD = 921600;
|
||||
|
||||
// ------------------------------------------------------------ Wi-Fi et MQTT (étape 3)
|
||||
|
||||
/// Identifiant du nœud, utilisé dans les topics MQTT : voice/<DEVICE_ID>/...
|
||||
/// Sert aussi d'identifiant client MQTT : deux cartes ne doivent jamais le partager,
|
||||
/// sinon le broker déconnecte l'une dès que l'autre se connecte, en boucle.
|
||||
static constexpr const char* DEVICE_ID = "esp32-01";
|
||||
|
||||
/// Préfixe commun de tous les topics.
|
||||
static constexpr const char* MQTT_TOPIC_PREFIX = "voice";
|
||||
|
||||
/**
|
||||
* Intervalle de maintien de connexion MQTT (s). Sans nouvelle de l'ESP32 pendant
|
||||
* 1,5 x cet intervalle, le broker publie le testament « offline ». 10 s : une
|
||||
* coupure de courant est signalée en 15 s au plus, pour 1 petit paquet toutes
|
||||
* les 10 s.
|
||||
*/
|
||||
static constexpr uint16_t MQTT_KEEPALIVE_S = 10;
|
||||
|
||||
/// Tampon de PubSubClient : un chunk de 1600 octets + topic + en-tête MQTT (étape 4).
|
||||
static constexpr uint16_t MQTT_BUFFER_BYTES = 2048;
|
||||
|
||||
/// Délai entre deux tentatives de connexion au broker (ms).
|
||||
static constexpr uint32_t MQTT_RETRY_MS = 5000;
|
||||
|
||||
/**
|
||||
* Attente maximale d'une réponse du broker (s). Une tentative de connexion bloque
|
||||
* loop() au plus MQTT_CONNECT_TIMEOUT_MS + ce temps, soit 3 s, et seulement quand le
|
||||
* broker est injoignable : l'audio, lui, tourne dans ses propres tâches (étape 15 :
|
||||
* borné et mesuré plutôt que déplacé dans une tâche, PubSubClient n'étant pas prévu
|
||||
* pour être partagé entre tâches).
|
||||
*/
|
||||
static constexpr uint16_t MQTT_SOCKET_TIMEOUT_S = 2;
|
||||
|
||||
/// Délai d'ouverture de la connexion TCP au broker (ms). 3000 par défaut dans le core ;
|
||||
/// sur un réseau local, un broker présent répond en quelques millisecondes.
|
||||
static constexpr uint32_t MQTT_CONNECT_TIMEOUT_MS = 1000;
|
||||
|
||||
/**
|
||||
* Présence du serveur (étape 15) : « online » retenu, remplacé par son testament
|
||||
* « offline » s'il disparaît. Suit MQTT_TOPIC_PREFIX. Serveur absent = micro coupé :
|
||||
* rien ne part plus sur le réseau sans personne pour l'écouter.
|
||||
*/
|
||||
static constexpr const char* MQTT_SERVER_STATUS_TOPIC = "voice/server/status";
|
||||
|
||||
/// Période de republication de l'état (ms) : IP, RSSI, durée de fonctionnement.
|
||||
static constexpr uint32_t STATE_PERIOD_MS = 30000;
|
||||
|
||||
// ------------------------------------------------------------ Fiabilisation (étape 15)
|
||||
|
||||
/**
|
||||
* Chien de garde (watchdog) des tâches : une tâche surveillée (loop, capture, audio)
|
||||
* qui ne donne plus signe de vie pendant ce délai fait redémarrer la carte, avec la
|
||||
* cause « watchdog » dans l'événement boot. Mieux vaut 2 s de redémarrage qu'une
|
||||
* carte figée pour toujours.
|
||||
*
|
||||
* 15 s : au-dessus du pire blocage LÉGITIME trouvé dans le code réseau du core — un
|
||||
* envoi TCP attend jusqu'à 10 x 1 s que le Wi-Fi accepte des données
|
||||
* (NetworkClient::write). Le délai par défaut (5 s) redémarrerait la carte sur un
|
||||
* simple Wi-Fi faible.
|
||||
*/
|
||||
static constexpr uint32_t WATCHDOG_TIMEOUT_S = 15;
|
||||
|
||||
/// Wi-Fi absent depuis ce délai (ms) : on relance la connexion de zéro. Filet de
|
||||
/// sécurité, si la reconnexion automatique n'aboutit pas (point d'accès redémarré...).
|
||||
static constexpr uint32_t WIFI_RESTART_MS = 30000;
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file secrets.h.example
|
||||
* @brief Modèle de configuration sensible.
|
||||
*
|
||||
* Copier ce fichier en « secrets.h » et renseigner les valeurs réelles :
|
||||
* cp include/secrets.h.example include/secrets.h
|
||||
*
|
||||
* « secrets.h » est ignoré par git : il ne doit JAMAIS être versionné.
|
||||
* Ces valeurs ne sont utilisées qu'à partir de l'étape 3 (Wi-Fi + MQTT).
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------ Wi-Fi
|
||||
|
||||
/// Réseau 2,4 GHz obligatoire : l'ESP32 ne sait pas se connecter en 5 GHz.
|
||||
static constexpr const char* WIFI_SSID = "mon-reseau";
|
||||
static constexpr const char* WIFI_PASSWORD = "mon-mot-de-passe";
|
||||
|
||||
// ------------------------------------------------------------------------- MQTT
|
||||
|
||||
/// Adresse IP du broker Mosquitto (Raspberry Pi ou PC). Une adresse IP plutôt
|
||||
/// qu'un nom : pas de résolution DNS, donc pas d'attente supplémentaire.
|
||||
/// Sur Linux : `ip -4 addr` sur la machine du broker.
|
||||
static constexpr const char* MQTT_HOST = "192.168.1.104";
|
||||
static constexpr uint16_t MQTT_PORT = 1883;
|
||||
|
||||
/// Compte MQTT de la carte (étape 15), créé par « make mqtt-user NAME=esp32-01 ».
|
||||
/// MQTT_USER vide = DEVICE_ID (config.h) : c'est ce nom que les droits du broker
|
||||
/// (mosquitto/acl) attendent. MQTT_PASSWORD vide = connexion anonyme, refusée par
|
||||
/// un broker sécurisé.
|
||||
static constexpr const char* MQTT_USER = "";
|
||||
static constexpr const char* MQTT_PASSWORD = "";
|
||||
Reference in New Issue
Block a user