Post

Emiglio AI assistant locale (Parte 2)

Emiglio AI assistant locale (Parte 2)

In questo articolo spiego come ho costruito un assistente vocale completamente self-hosted basato su Home Assistant, Wyoming Satellite su un Raspberry Pi Zero W2 con microfono ReSpeaker 2-Mic, e un server home assistant con Ollama + llama3-groq-tool-use capace di fare ricerche web in tempo reale tramite SearXNG.

Desktop View Speaker Emiglio collegato a raspberry pi zero W2 + Scheda Respeaker

ATTENZIONE ho seguito esattamente gli step illustrati in questo tutorial youtube di NetworkChuck e posso dirvi che se provate a replicarli con un raspberry pi zero W 2 avrete problemi. In questo articolo vi spiego come risolverli e tutti gli step funzionanti da eseguire ad oggi 2026/05/01. Probabilmente il tutorial di Chuck funziona senza problemi su raspberry pi 3/4/5 ma NON su raspberry pi zero W2.

Architettura generale

Il sistema è composto da due macchine:

  1. Raspberry Pi Zero W2 — satellite vocale con microfono ReSpeaker 2-Mic, gira wyoming-satellite e ascolta la wake word
  2. Server con almeno 8 GB di VRAM — gira Home Assistant, Whisper (STT), Piper (TTS), Ollama e SearXNG, tutti in Docker

Desktop View Immagine generata con Gemini

Parte 1: Server — Docker Compose

Sul server, crea una cartella di progetto e un file docker-compose.yml con questo contenuto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
services:
  # 1. Home Assistant Core
  homeassistant:
    container_name: homeassistant
    image: "ghcr.io/home-assistant/home-assistant:stable"
    volumes:
      - ./config:/config
      - /etc/localtime:/etc/localtime:ro
    restart: unless-stopped
    privileged: true
    network_mode: host

  # 2. Faster Whisper (Speech-to-Text in Italiano)
  whisper:
    container_name: wyoming-whisper
    image: rhasspy/wyoming-whisper
    command: --model base --language it
    volumes:
      - ./whisper_data:/data
    environment:
      - TZ=Europe/Rome
    restart: unless-stopped
    ports:
      - "10300:10300"

  # 3. Piper (Text-to-Speech in Italiano)
  piper:
    container_name: wyoming-piper
    image: rhasspy/wyoming-piper
    command: --voice it_IT-riccardo-x_low
    volumes:
      - ./piper_data:/data
    environment:
      - TZ=Europe/Rome
    restart: unless-stopped
    ports:
      - "10200:10200"

  # 4. SearXNG (motore di ricerca self-hosted)
  searxng:
    image: docker.io/searxng/searxng:latest
    container_name: searxng
    restart: unless-stopped
    ports:
      - "8888:8080"
    volumes:
      - ./config:/etc/searxng
      - ./data:/var/cache/searxng

Avvia tutto con:

1
docker compose up -d

Installare Ollama sul server

Ollama deve girare con la porta aperta sulla rete locale, in modo che Home Assistant possa raggiungerlo:

1
2
3
4
5
6
7
8
# Installa Ollama
curl -fsSL https://ollama.com/install.sh | sh

# Scarica il modello
ollama pull llama3-groq-tool-use

# Apri la porta sul firewall
sudo ufw allow 11434

Assicurati che il server abbia almeno 8 GB di VRAM disponibile per far girare il modello in modo fluido.

SearXNG e configuration.yaml

SearXNG è già incluso nel docker-compose e gira sulla porta 8888. Una volta avviato, aggiungi questa configurazione al file configuration.yaml di Home Assistant per esporre il comando di ricerca:

1
2
3
4
5
rest_command:
  searxng_search:
    url: "http://192.168.1.83:8888/search?q=&format=json"
    method: GET
    response_variable: results

Sostituisci 192.168.1.83 con l’IP del tuo server.

Parte 2: Raspberry Pi Zero W2 — Wyoming Satellite con ReSpeaker 2-Mic

Importante: durante il setup del Pi, scegli pi come nome utente. I path nei file di servizio dipendono da /home/pi e usare un nome diverso causa problemi.

2.1 Dipendenze di sistema

1
sudo apt install git python3-venv python3-pip -y

2.2 Clona il repository

1
2
git clone https://github.com/rhasspy/wyoming-satellite.git
cd wyoming-satellite

2.3 Aumenta la swap (necessario su Pi Zero W2)

Il Pi Zero ha poca RAM, serve più swap per compilare i driver:

1
2
sudo apt install dphys-swapfile -y
sudo nano /etc/dphys-swapfile

Imposta:

1
CONF_SWAPSIZE=1024

Poi applica:

1
2
sudo dphys-swapfile setup
sudo dphys-swapfile swapon

2.4 Installa i driver ReSpeaker 2-Mic

Prima installa i kernel headers e le dipendenze manualmente:

1
sudo apt install linux-headers-rpi-v8 dkms i2c-tools libasound2-plugins alsa-utils -y

Poi apri lo script di installazione dei driver e commenta il blocco che tenta di rilevare automaticamente la versione kernel e scaricare i driver, perché su kernel recenti non funziona:

1
nano etc/install-respeaker-drivers.sh

Commenta queste righe (aggiungi # davanti):

1
2
3
4
5
6
7
8
9
#kernel_formatted="$(uname -r | cut -f1,2 -d.)"
#driver_url_status="$(curl -ILs https://github.com/HinTak/seeed-voicecard/archive/refs/heads/v$kernel_formatted.tar.gz | tac | grep -o "^HTTP.*" | cut -f 2 -d' ' | head -1)"
#if  [ ! "$driver_url_status" = 200 ]; then
#echo "Could not find driver for kernel $kernel_formatted"
#exit 1
#fi
#apt-get update
#apt-get install --no-install-recommends --yes \
#    curl raspberrypi-kernel-headers dkms i2c-tools libasound2-plugins alsa-utils

Ora clona il fork aggiornato dei driver e installali:

1
2
3
4
5
cd ~
git clone -b v6.12 https://github.com/HinTak/seeed-voicecard
cd seeed-voicecard
sudo ./install.sh
sudo reboot

2.5 Ambiente Python

1
2
3
4
5
6
7
8
9
10
11
12
sudo apt install python3-numpy python3-zeroconf python3-setuptools python3-pip -y

cd ~/wyoming-satellite
rm -rf .venv
python -m venv .venv --system-site-packages

.venv/bin/pip install --upgrade pip
.venv/bin/pip install wyoming==1.5.4
.venv/bin/pip install "zeroconf>=0.133.0"
.venv/bin/pip install "pyring-buffer>=1,<2"
.venv/bin/pip install --no-deps -e .
.venv/bin/pip install --no-deps pysilero-vad==1.0.0 webrtc-noise-gain==1.2.3

Se il file pyproject.toml contiene zeroconf==0.88.0, cambialo in zeroconf>=0.133.0 prima di installare.

2.6 Verifica microfono e altoparlante

1
2
arecord -L
aplay -L

Dovresti vedere tra i dispositivi:

1
2
3
plughw:CARD=seeed2micvoicec,DEV=0
    seeed-2mic-voicecard, bcm2835-i2s-wm8960-hifi wm8960-hifi-0
    Hardware device with all software conversions

Registra 5 secondi e riascolta per verificare che funzioni:

1
2
arecord -D plughw:CARD=seeed2micvoicec,DEV=0 -r 16000 -c 1 -f S16_LE -t wav -d 5 test.wav
aplay -D plughw:CARD=seeed2micvoicec,DEV=0 test.wav

2.7 Test manuale del satellite

1
2
3
4
5
6
7
cd ~/wyoming-satellite
script/run \
  --debug \
  --name 'my satellite' \
  --uri 'tcp://0.0.0.0:10700' \
  --mic-command 'arecord -D plughw:CARD=seeed2micvoicec,DEV=0 -r 16000 -c 1 -f S16_LE -t raw' \
  --snd-command 'aplay -D plughw:CARD=seeed2micvoicec,DEV=0 -r 22050 -c 1 -f S16_LE -t raw'

2.8 Aggiungi il satellite a Home Assistant

Su Home Assistant vai in Impostazioni → Dispositivi e servizi → Aggiungi integrazione → Wyoming Protocol, inserisci l’IP del Pi e la porta 10700. Home Assistant dovrebbe scoprire e aggiungere il satellite automaticamente.

2.9 Service systemd per avvio automatico

1
sudo systemctl edit --force --full wyoming-satellite.service

Incolla:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Unit]
Description=Wyoming Satellite
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/home/pi/wyoming-satellite/script/run \
  --name 'my satellite' \
  --uri 'tcp://0.0.0.0:10700' \
  --mic-command 'arecord -D plughw:CARD=seeed2micvoicec,DEV=0 -r 16000 -c 1 -f S16_LE -t raw' \
  --snd-command 'aplay -D plughw:CARD=seeed2micvoicec,DEV=0 -r 22050 -c 1 -f S16_LE -t raw' \
  --mic-auto-gain 5 \
  --mic-noise-suppression 2
WorkingDirectory=/home/pi/wyoming-satellite
Restart=always
RestartSec=1

[Install]
WantedBy=default.target
1
sudo systemctl enable --now wyoming-satellite.service

I parametri --mic-auto-gain 5 e --mic-noise-suppression 2 sono necessari per evitare che il satellite rimanga bloccato in ascolto infinito dopo la wake word. Dall’interfaccia di Home Assistant imposta anche: mic audio = 5, noise suppression = medio, terminazione rilevamento vocale = aggressivo.

2.10 Wake word con OpenWakeWord

1
2
3
4
5
sudo apt-get install --no-install-recommends libopenblas-dev -y
cd ~
git clone https://github.com/rhasspy/wyoming-openwakeword.git
cd wyoming-openwakeword
script/setup

OpenWakeWord include già alcuni modelli pronti all’uso. Le principali wake word disponibili di default sono ok_nabu, hey_jarvis e alexa. Per usarne una, modifica il service di openwakeword aggiungendo --preload-model 'hey_jarvis', e il service del satellite aggiungendo --wake-word-name 'hey_jarvis'. Poi:

1
2
sudo systemctl daemon-reload
sudo systemctl restart wyoming-satellite.service

La prima volta l’avvio è lento perché il modello viene caricato in memoria.

Wake word personalizzata

Se nessuna delle parole incluse ti soddisfa, puoi usare un modello .tflite custom da questo repository: home-assistant-wakewords-collection.

Il satellite da solo non sa leggere i file .tflite: è openWakeWord che li gestisce, quindi il file va dato in pasto a lui.

1. Scarica il modello

1
2
3
4
5
mkdir -p ~/wyoming-satellite/custom_models
cd ~/wyoming-satellite/custom_models

# Esempio con il modello "jarvis"
wget https://github.com/fwartner/home-assistant-wakewords-collection/raw/main/trained_models/jarvis.tflite

2. Configura openWakeWord per leggere la cartella custom

Modifica il service di openwakeword e aggiungi --custom-model-dir:

1
sudo systemctl edit --force --full wyoming-openwakeword.service

Nella riga ExecStart aggiungi il parametro:

1
2
3
4
ExecStart=/home/pi/wyoming-openwakeword/script/run \
  --uri 'tcp://127.0.0.1:10400' \
  --custom-model-dir /home/pi/wyoming-satellite/custom_models \
  --preload-model 'jarvis'
1
2
sudo systemctl daemon-reload
sudo systemctl restart wyoming-openwakeword.service

3. Aggiorna il service del satellite

Il nome da usare in --wake-word-name è il nome del file .tflite senza estensione:

1
2
3
4
5
6
7
8
9
ExecStart=/home/pi/wyoming-satellite/script/run \
  --name 'my satellite' \
  --uri 'tcp://0.0.0.0:10700' \
  --mic-command 'arecord -D plughw:CARD=seeed2micvoicec,DEV=0 -r 16000 -c 1 -f S16_LE -t raw' \
  --snd-command 'aplay -D plughw:CARD=seeed2micvoicec,DEV=0 -r 22050 -c 1 -f S16_LE -t raw' \
  --mic-auto-gain 5 \
  --mic-noise-suppression 2 \
  --wake-uri 'tcp://127.0.0.1:10400' \
  --wake-word-name 'jarvis'
1
2
sudo systemctl daemon-reload
sudo systemctl restart wyoming-satellite.service

2.11 LED ReSpeaker 2-Mic

1
2
3
cd ~/wyoming-satellite
.venv/bin/pip install 'pixel-ring'
sudo apt-get install python3-spidev python3-gpiozero -y

Crea il service per i LED:

1
sudo systemctl edit --force --full 2mic-leds.service
1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=2Mic LEDs

[Service]
Type=simple
ExecStart=/home/pi/wyoming-satellite/.venv/bin/python /home/pi/wyoming-satellite/examples/2mic_service.py --uri 'tcp://127.0.0.1:10500'
WorkingDirectory=/home/pi/wyoming-satellite/examples
Restart=always
RestartSec=1

[Install]
WantedBy=default.target
1
sudo systemctl enable --now 2mic-leds.service

2.12 Pulsante di spegnimento sicuro

Non spegnere mai il Pi togliendo la corrente brutalmente: corrompe la SD card.

Aggiungi un pulsante hardware sul GPIO 17 configurando /boot/firmware/config.txt:

1
sudo nano /etc/firmware/config.txt

Aggiungi in fondo:

1
2
3
4
5
[all]
enable_uart=1
dtoverlay=i2s-mmap
dtparam=i2s=on
dtoverlay=gpio-shutdown,gpio_pin=17,active_low=1,gpio_pull=up

In alternativa, spegni sempre in modo sicuro via SSH:

1
2
3
ssh pi@<hostname>.local
sudo halt
# aspetta che i LED si spengano, poi togli la corrente

Parte 3: Agente AI con Extended OpenAI Conversation e SearXNG

Questa è la parte che trasforma il satellite da semplice comando vocale a un vero assistente AI con ricerca web in tempo reale.

3.1 Installa HACS e Extended OpenAI Conversation

Installa prima HACS su Home Assistant, poi tramite HACS installa l’integrazione Extended OpenAI Conversation.

Questa integrazione permette di collegare Home Assistant a qualsiasi endpoint compatibile OpenAI — incluso Ollama — e di definire function calling personalizzato, ad esempio per chiamare SearXNG.

3.2 Configurazione dell’agente

Il modello usato è llama3-groq-tool-use tramite Ollama, scelto perché supporta nativamente il function calling.

ParametroValore
Modellollama3-groq-tool-use
Max token1200
Top P1
Temperature0.05
Max tool call per conversazione1
Soglia contesto13000

La temperature bassa (0.05) serve a rendere le risposte più deterministiche e ad evitare che il modello improvvisi invece di chiamare il tool di ricerca quando dovrebbe.

System prompt:

1
2
3
4
5
6
7
8
9
10
11
If the user asks for facts, news, real-world information, or current events, YOU MUST CALL 'web_search'.
DO NOT write sentences. DO NOT say "I will search".
OUTPUT ONLY THE JSON:
{
  "function_call": {
    "name": "web_search",
    "arguments": "{\"query\": \"DOMANDA_QUI\"}"
  }
}
Play along with the user, be also sarcastic. NEVER say "I am an AI", "I don't have opinions", or "I cannot form subjective thoughts".
ANSWER ONLY IN ITALIAN.

Nella configurazione di Extended OpenAI Conversation, definisci la funzione web_search, copiate lo snippet di codice da questo gist link

Quando l’agente non conosce la risposta o viene interrogato su eventi recenti, richiama automaticamente SearXNG, ottiene i risultati e li include nella risposta vocale.

Conclusione

Ora possiamo interrogare vocalmente Emiglio. STAY TUNED per altri tutorial

This post is licensed under CC BY 4.0 by the author.