mirror of
https://github.com/deunix-educ/voice-assistant.git
synced 2026-10-13 00:11:57 +02:00
277 lines
14 KiB
Makefile
277 lines
14 KiB
Makefile
# Makefile — toutes les opérations système du projet assistant vocal.
|
|
# Cible par défaut : help.
|
|
|
|
SHELL := /bin/bash
|
|
VENV := server/.venv
|
|
PY := $(VENV)/bin/python
|
|
PIP := $(VENV)/bin/pip
|
|
# PlatformIO vit dans son propre environnement : ses dépendances entrent sinon en
|
|
# conflit avec celles du serveur vocal (constaté avec click / esptool).
|
|
PIO_VENV := .pio-venv
|
|
PIO := $(PIO_VENV)/bin/pio
|
|
FW_DIR := firmware
|
|
SERIAL_PORT ?= /dev/ttyUSB0
|
|
BAUD ?= 921600
|
|
OUT ?= recordings/test.wav
|
|
TONE ?= 440
|
|
TEXT ?= Quelle heure est-il ?
|
|
LAST ?= 5
|
|
JITTER ?= 0
|
|
VOICE ?= fr_FR-tom-medium
|
|
SPEAKER ?=
|
|
SAY_OUT ?= recordings/voix-test/phrase.wav
|
|
WAKE ?=
|
|
WAKE_VOICE ?= en_GB-alan-medium
|
|
# Adresse du broker : UN seul réglage, mqtt.host de server/config.yaml, relu ici pour les
|
|
# cibles mqtt-* (surcharge ponctuelle : make mqtt-watch MQTT_HOST=...).
|
|
MQTT_HOST ?= $(shell awk '/^mqtt:/{m=1;next} m && $$1=="host:"{print $$2; exit}' server/config.yaml)
|
|
MSG ?= {"cmd":"ping"}
|
|
MOSQ_DIR ?= $(HOME)/srv/mosquitto
|
|
MOSQ_COMPOSE ?= $(HOME)/srv/docker-compose.mosquitto.yml
|
|
# Clients MQTT : ceux du système s'ils sont installés, sinon ceux de l'image Docker.
|
|
MOSQ_RUN := $(shell command -v mosquitto_sub >/dev/null 2>&1 || echo "docker run --rm --network host eclipse-mosquitto:2.0")
|
|
# Comptes MQTT (étape 15) : fichier de mots de passe hachés, jamais versionné.
|
|
MOSQ_PASSWD := $(shell command -v mosquitto_passwd >/dev/null 2>&1 && echo "mosquitto_passwd mosquitto/passwd" \
|
|
|| echo "docker run --rm -it --user $$(id -u):$$(id -g) -v $(CURDIR)/mosquitto:/work eclipse-mosquitto:2.0 mosquitto_passwd /work/passwd")
|
|
# Les cibles mqtt-* prennent les identifiants du serveur dans server/.env. Leurs
|
|
# recettes commencent par @ : make n'affiche pas la ligne, donc pas le mot de passe.
|
|
-include server/.env
|
|
MQTT_AUTH = $(if $(MQTT_USERNAME),-u '$(MQTT_USERNAME)' -P '$(MQTT_PASSWORD)')
|
|
MQTT_AS = @echo "(compte MQTT : $(if $(MQTT_USERNAME),$(MQTT_USERNAME) de server/.env,anonyme))"
|
|
|
|
.DEFAULT_GOAL := help
|
|
.PHONY: help install-system mosquitto-config mosquitto-docker mqtt-user agent-run agent-install agent-log mqtt-agents mqtt-watch mqtt-transcript mqtt-home mqtt-control venv install install-dev install-speech install-diarization install-wakeword models \
|
|
say speech-check transcribe vad vad-all diarize diarize-demo enroll profiles forget run test lint record play inspect play-esp tone-esp play-mqtt tone-mqtt run-echo pio-venv fw-build fw-check fw-upload \
|
|
fw-monitor clean
|
|
|
|
## ------------------------------------------------------------------ aide
|
|
|
|
help: ## Affiche cette aide
|
|
@echo "Assistant vocal local ESP32 — cibles disponibles :"
|
|
@echo ""
|
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) \
|
|
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[1m%-18s\033[0m %s\n", $$1, $$2}'
|
|
@echo ""
|
|
@echo "Variables : SERIAL_PORT=$(SERIAL_PORT) BAUD=$(BAUD) OUT=$(OUT) TONE=$(TONE) MQTT_HOST=$(MQTT_HOST) JITTER=$(JITTER)"
|
|
@echo " TEXT=\"$(TEXT)\""
|
|
|
|
## ------------------------------------------------------- installation système
|
|
|
|
install-system: ## Installe les paquets Debian nécessaires (sudo)
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
mosquitto mosquitto-clients \
|
|
ffmpeg \
|
|
python3 python3-venv python3-pip \
|
|
git
|
|
@echo "Ajout de l'utilisateur au groupe dialout (accès au port série)."
|
|
sudo usermod -aG dialout $$USER
|
|
@echo "→ Déconnectez/reconnectez votre session pour activer le groupe dialout."
|
|
|
|
mosquitto-config: mosquitto/passwd ## Installe voice.conf, acl et passwd dans /etc/mosquitto et redémarre le broker
|
|
sed 's|/mosquitto/config/|/etc/mosquitto/|' mosquitto/voice.conf | sudo tee /etc/mosquitto/conf.d/voice.conf >/dev/null
|
|
sudo install -o mosquitto -g mosquitto -m 0600 mosquitto/acl /etc/mosquitto/acl
|
|
sudo install -o mosquitto -g mosquitto -m 0600 mosquitto/passwd /etc/mosquitto/passwd
|
|
sudo systemctl restart mosquitto
|
|
sudo systemctl --no-pager status mosquitto | head -n 5
|
|
|
|
mosquitto-docker: mosquitto/passwd ## Installe voice.conf, acl et passwd dans le conteneur Mosquitto et le relance
|
|
@test -f $(MOSQ_COMPOSE) || { echo "Fichier compose introuvable : MOSQ_COMPOSE=$(MOSQ_COMPOSE)"; exit 1; }
|
|
-sudo cp $(MOSQ_DIR)/config/mosquitto.conf $(MOSQ_DIR)/config/mosquitto.conf.bak
|
|
sudo install -o 1883 -g 1883 -m 0644 mosquitto/voice.conf $(MOSQ_DIR)/config/mosquitto.conf
|
|
sudo install -o 1883 -g 1883 -m 0600 mosquitto/acl $(MOSQ_DIR)/config/acl
|
|
sudo install -o 1883 -g 1883 -m 0600 mosquitto/passwd $(MOSQ_DIR)/config/passwd
|
|
docker compose -f $(MOSQ_COMPOSE) restart
|
|
@sleep 2
|
|
docker compose -f $(MOSQ_COMPOSE) logs --tail 5
|
|
|
|
mosquitto/passwd:
|
|
@echo "mosquitto/passwd absent : creez d'abord les comptes (make mqtt-user NAME=voice-server, puis NAME=esp32-01)"; exit 1
|
|
|
|
mqtt-user: ## Crée ou change le compte MQTT NAME (voice-server, ou le DEVICE_ID d'une carte) dans mosquitto/passwd
|
|
@test -n "$(NAME)" || { echo "usage : make mqtt-user NAME=voice-server puis make mqtt-user NAME=esp32-01"; exit 1; }
|
|
@touch mosquitto/passwd && chmod 600 mosquitto/passwd
|
|
@echo "mot de passe de $(NAME) (idee : openssl rand -hex 16), a reporter dans $(if $(filter voice-server,$(NAME)),server/.env,$(if $(findstring esp32,$(NAME)),firmware/include/secrets.h,/etc/voice-assistant/agent.yaml)) :"
|
|
@$(MOSQ_PASSWD) $(NAME)
|
|
@echo "compte $(NAME) enregistre, hache, dans mosquitto/passwd. Installation : make mosquitto-docker (ou mosquitto-config)."
|
|
|
|
## ------------------------------------------------------------- agents Linux (étape 15b)
|
|
|
|
agent-run: agent/agent.yaml ## Lance l'agent de CETTE machine au premier plan (agent/agent.yaml, essai)
|
|
$(PY) agent/voice_agent.py --config agent/agent.yaml
|
|
|
|
agent/agent.yaml:
|
|
sed 's/^dry_run: false/dry_run: true/' agent/agent.yaml.example > $@
|
|
@echo "agent/agent.yaml cree en MODE ESSAI (dry_run: true) : verifiez id et mqtt.host"
|
|
|
|
agent-install: ## Installe l'agent comme service sur CETTE machine (autre machine : copier agent/, make -C agent install)
|
|
$(MAKE) -C agent install
|
|
|
|
agent-log: ## Journal de l'agent installé sur cette machine
|
|
$(MAKE) -C agent log
|
|
|
|
mqtt-agents: ## Affiche états, commandes et comptes rendus des agents : agent/#
|
|
$(MQTT_AS)
|
|
@$(MOSQ_RUN) mosquitto_sub -h $(MQTT_HOST) $(MQTT_AUTH) -v -t 'agent/#'
|
|
|
|
mqtt-watch: ## Affiche les messages texte voice/+/... et home/# (sans l'audio binaire), Ctrl-C pour quitter
|
|
$(MQTT_AS)
|
|
@$(MOSQ_RUN) mosquitto_sub -h $(MQTT_HOST) $(MQTT_AUTH) -v -t 'voice/+/state' -t 'voice/+/event' \
|
|
-t 'voice/+/control' -t 'voice/+/transcript' -t 'voice/server/status' -t 'home/#'
|
|
|
|
mqtt-home: ## Affiche seulement les commandes domotiques home/# (étape 11)
|
|
$(MQTT_AS)
|
|
@$(MOSQ_RUN) mosquitto_sub -h $(MQTT_HOST) $(MQTT_AUTH) -v -t 'home/#'
|
|
|
|
mqtt-transcript: ## Affiche seulement « qui a dit quoi » : voice/+/transcript (étape 10)
|
|
$(MQTT_AS)
|
|
@$(MOSQ_RUN) mosquitto_sub -h $(MQTT_HOST) $(MQTT_AUTH) -v -t 'voice/+/transcript'
|
|
|
|
mqtt-control: ## Publie MSG sur voice/esp32-01/control
|
|
$(MQTT_AS)
|
|
@$(MOSQ_RUN) mosquitto_pub -h $(MQTT_HOST) $(MQTT_AUTH) -q 1 -t voice/esp32-01/control -m '$(MSG)'
|
|
|
|
## ----------------------------------------------------------- environnement Python
|
|
|
|
venv: ## Crée l'environnement virtuel Python
|
|
python3 -m venv $(VENV)
|
|
$(PIP) install --upgrade pip setuptools wheel
|
|
|
|
install: venv ## Installe le serveur Python (dépendances de base)
|
|
$(PIP) install -e server
|
|
@test -f server/.env || { cp server/.env.example server/.env; echo "server/.env créé depuis .env.example — renseigner MQTT_USERNAME/PASSWORD et HF_TOKEN si besoin."; }
|
|
|
|
install-dev: install pio-venv ## Installe en plus les outils de développement (pyright, pytest, platformio)
|
|
$(PIP) install -e "server[dev]"
|
|
|
|
install-speech: install ## Installe faster-whisper et Piper (étape 6)
|
|
$(PIP) install -e "server[speech]"
|
|
|
|
install-diarization: install-speech ## Installe pyannote et torch, version CPU (étape 8, ~1,3 Go)
|
|
$(PIP) install --index-url https://download.pytorch.org/whl/cpu \
|
|
--extra-index-url https://pypi.org/simple -e "server[diarization]"
|
|
|
|
install-wakeword: install-speech ## Installe openWakeWord, en mode ONNX (étape 13)
|
|
$(PIP) install -e "server[wakeword]"
|
|
# Sans dépendances : openwakeword exige tflite-runtime, absent pour Python 3.13 (inutile en ONNX).
|
|
$(PIP) install --no-deps "openwakeword==0.6.0"
|
|
|
|
models: ## Télécharge les modèles de config.yaml dans server/models (étapes 6, 8 et 13)
|
|
$(PY) tools/download_models.py
|
|
|
|
say: ## Fait dire [WAKE (voix anglaise) puis] TEXT par VOICE (SPEAKER) : WAV + MP3 dans SAY_OUT, pour un téléphone
|
|
$(PY) tools/say.py "$(TEXT)" --voice $(VOICE) $(if $(SPEAKER),--speaker $(SPEAKER)) \
|
|
$(if $(WAKE),--wake "$(WAKE)" --wake-voice $(WAKE_VOICE)) --out $(SAY_OUT)
|
|
|
|
speech-check: ## Synthèse, transcription et RÉPONSE de l'assistant à TEXT, sans ESP32 (étape 6)
|
|
$(PY) tools/speech_check.py "$(TEXT)"
|
|
|
|
transcribe: ## VAD, transcription et réponse du WAV OUT, sans ESP32 (étapes 6-7)
|
|
$(PY) tools/speech_check.py --wav $(OUT)
|
|
|
|
vad: ## Montre la parole trouvée par la VAD dans OUT, écrit la parole seule (étape 7)
|
|
$(PY) tools/vad_check.py $(OUT)
|
|
|
|
vad-all: ## Passe la VAD sur tous les enregistrements du serveur (étape 7)
|
|
$(PY) tools/vad_check.py server/recordings/*.wav
|
|
|
|
diarize: ## Qui parle quand dans OUT, qui est-ce, qui a dit quoi (étapes 8-10)
|
|
$(PY) tools/diarize_check.py $(OUT)
|
|
|
|
diarize-demo: ## Diarise une séquence à deux voix : la vôtre et celle de Piper (étape 8)
|
|
$(PY) tools/diarize_check.py --demo
|
|
|
|
enroll: ## Crée le profil vocal de NAME avec ses LAST dernières sessions (étape 9)
|
|
@test -n "$(NAME)" || { echo "Usage : make enroll NAME=Denis [LAST=5]"; exit 1; }
|
|
$(PY) tools/enroll.py --name "$(NAME)" --last $(LAST)
|
|
|
|
profiles: ## Liste les profils vocaux (étape 9)
|
|
$(PY) tools/enroll.py --list
|
|
|
|
forget: ## Supprime le profil vocal de NAME : donnée biométrique (étape 9)
|
|
@test -n "$(NAME)" || { echo "Usage : make forget NAME=Denis"; exit 1; }
|
|
$(PY) tools/enroll.py --forget "$(NAME)"
|
|
|
|
## ----------------------------------------------------------------- serveur
|
|
|
|
run: ## Lance l'assistant vocal : répond à chaque session (étape 6, Ctrl-C pour arrêter)
|
|
cd server && .venv/bin/python -m voice_server --config config.yaml
|
|
|
|
run-echo: ## Serveur en mode echo : chaque session est rejouee par l'ESP32 (etape 5)
|
|
cd server && .venv/bin/python -m voice_server --config config.yaml --echo
|
|
|
|
test: ## Exécute les tests Python
|
|
$(VENV)/bin/pytest server/tests -v
|
|
|
|
lint: ## Vérifie le typage Python avec pyright
|
|
$(VENV)/bin/pyright --project server/pyrightconfig.json
|
|
|
|
## ----------------------------------------------------------------- outils
|
|
|
|
play: ## Joue OUT en cherchant un lecteur qui fonctionne
|
|
@for player in paplay pw-play ffplay aplay; do \
|
|
command -v $$player >/dev/null 2>&1 || continue; \
|
|
echo "-> $$player $(OUT)"; \
|
|
case $$player in \
|
|
ffplay) ffplay -autoexit -nodisp -loglevel error "$(OUT)" && exit 0 ;; \
|
|
*) $$player "$(OUT)" && exit 0 ;; \
|
|
esac; \
|
|
done; \
|
|
echo "Aucun lecteur n'a abouti. Utilisez 'make inspect' pour verifier sans ecouter."; \
|
|
exit 1
|
|
|
|
inspect: ## Analyse OUT sans le jouer : enveloppe ASCII et niveaux
|
|
$(PY) tools/wav_inspect.py $(OUT)
|
|
|
|
play-esp: ## Joue OUT sur le haut-parleur de l'ESP32, au rythme reel (etape 2)
|
|
$(PY) tools/wav_to_serial.py --port $(SERIAL_PORT) --baud $(BAUD) $(OUT)
|
|
|
|
tone-esp: ## Joue un son pur de TONE Hz sur le haut-parleur de l'ESP32 (etape 2)
|
|
$(PY) tools/wav_to_serial.py --port $(SERIAL_PORT) --baud $(BAUD) --tone $(TONE) --seconds 3
|
|
|
|
play-mqtt: ## Joue OUT sur l'ESP32, par MQTT, avec bilan ; JITTER=ms simule un Wi-Fi irrégulier (étapes 5, 12)
|
|
$(PY) tools/wav_to_mqtt.py --jitter $(JITTER) $(OUT)
|
|
|
|
tone-mqtt: ## Joue un son pur de TONE Hz sur l'ESP32, par MQTT ; JITTER=ms simule un Wi-Fi irrégulier (étapes 5, 12)
|
|
$(PY) tools/wav_to_mqtt.py --tone $(TONE) --seconds 5 --jitter $(JITTER)
|
|
|
|
record: ## Enregistre une session depuis l'ESP32 vers un WAV (étape 1)
|
|
@mkdir -p $$(dirname $(OUT))
|
|
$(PY) tools/serial_to_wav.py --port $(SERIAL_PORT) --baud $(BAUD) --output $(OUT)
|
|
|
|
## ---------------------------------------------------------------- firmware
|
|
|
|
pio-venv: ## Installe PlatformIO dans son propre environnement virtuel
|
|
python3 -m venv $(PIO_VENV)
|
|
$(PIO_VENV)/bin/pip install --upgrade pip
|
|
$(PIO_VENV)/bin/pip install platformio
|
|
# click 8.3+ casse l'esptool 5.0 livré avec le core Arduino 3.x : on épingle 8.1.
|
|
$(PIO_VENV)/bin/pip install "click==8.1.8"
|
|
|
|
fw-build: ## Compile le firmware ESP32
|
|
@test -x $(PIO) || { echo "PlatformIO absent : lancez 'make pio-venv'."; exit 1; }
|
|
$(PIO) run -d $(FW_DIR)
|
|
|
|
fw-check: ## Compile sans secrets.h personnel (secrets factices hors du dépôt)
|
|
@test -x $(PIO) || { echo "PlatformIO absent : lancez 'make pio-venv'."; exit 1; }
|
|
@if [ -f $(FW_DIR)/include/secrets.h ]; then \
|
|
echo "secrets.h present : compilation normale"; $(PIO) run -d $(FW_DIR); \
|
|
else \
|
|
tmp=$$(mktemp -d); cp $(FW_DIR)/include/secrets.h.example $$tmp/secrets.h; \
|
|
echo "secrets.h absent : secrets factices dans $$tmp (firmware/include/ n'est pas touche)"; \
|
|
PLATFORMIO_BUILD_FLAGS="-I$$tmp" $(PIO) run -d $(FW_DIR); status=$$?; rm -rf $$tmp; exit $$status; \
|
|
fi
|
|
|
|
fw-upload: ## Compile et téléverse le firmware
|
|
$(PIO) run -d $(FW_DIR) -t upload --upload-port $(SERIAL_PORT)
|
|
|
|
fw-monitor: ## Ouvre le moniteur série (Ctrl-C pour quitter)
|
|
$(PIO) device monitor -p $(SERIAL_PORT) -b $(BAUD)
|
|
|
|
## ------------------------------------------------------------------ nettoyage
|
|
|
|
clean: ## Supprime les artefacts de compilation et les caches
|
|
rm -rf $(FW_DIR)/.pio
|
|
find . -name '__pycache__' -type d -prune -exec rm -rf {} +
|
|
rm -rf server/*.egg-info .pytest_cache
|