Software

Publié par Richard Dern le 16/09/2019 - Aucun commentaire

Sommaire du dossier
  1. Introduction
  2. Les filtres
  3. Le circuit
  4. Câblage
  5. Software

Sur le Raspberry Pi Zero W

La première chose à faire pour sortir du son via les broches PWM du Raspberry Pi est de modifier le fichier /boot/config.txt, et rajouter à la fin:

dtoverlay=pwm-2chan,pin=18,func=2,pin2=13,func2=4
audio_pwm_mode=2

Ne me demandez pas à quoi ça correspond, j'en ai pas la moindre idée... Je sais juste qu'on assigne les GPIO13 et 18 au PWM sur deux canaux, mais j'ignore le but précis de func et func2. C'est évident qu'ici les valeurs 2 et 4 correspondent à gauche et droite, ou vice-versa, mais je ne sais pas quelles autres valeurs on peut affecter à ces variables.

De même, audio_pwm_mode avec une valeur à 2 est censé améliorer la qualité du son. Mais à l'oreille, je ne perçois aucune différence. Enfin, la valeur de 2 m'est totalement inconnue, et une fois de plus, j'ignore complètement quelles sont les autres valeurs qu'on peut utiliser ni leur effet sur le son.

C'est magique

C'est magique...

On peut avoir un peu plus d'informations dans le firmware du Pi, mais ça reste très évasif. Je n'ai pas trouvé de documentation pour le paramètre audio_pwm_mode.

Bon, maintenant on passe à la partie où on m'envoie des tomates à la gueule. Créons un dossier dans lequel travailler:

mkdir -p /opt/server/files
cd /opt/server

Installons quelques dépendances:

apt update
apt install php-cli supervisor

OMFG ! Du PHP ? T'es trop un looser !

Oui, on pourrait le faire en python. J'avais envie de le faire en PHP. Débrouillez-vous si vous tenez à l'écrire en python. Fuck.

Créons un fichier index.php:

<?php

if(!array_key_exists('text', $_REQUEST)) {
  header('HTTP/1.0 400 Bad Request');
  exit;
}

// Remplacez "homeassistant" par l'adresse IP ou le FQDN de votre HomeAssistant.
$host = 'homeassistant';

// Laissez à 8000 pour le moment
$port = 8000;

// À changer si vous utiliser un GPIO différent
$gpioLed = 17;

$text           = urldecode($_REQUEST['text']);
$md5            = md5($text);
$thisDir        = dirname(__FILE__);
$filePath       = sprintf('%s/files/%s.wav', $thisDir, $md5);

if(empty($text)) {
  header('HTTP/1.0 400 Bad Request');
  exit;
}

// On récupère le fichier son en provenance du serveur TTS s'il n'existe pas
// en local
if(!file_exists($filePath)) {
  $content = file_get_contents(sprintf('http://%s:%d?text=%s', $host, $port, urlencode($text)));

  if($content) {
    file_put_contents($filePath, $content);
  } else {
    exit;
  }
}

// On allume la LED (sur le GPIO17) avant la lecture, et on l'éteint après
exec(sprintf('echo %d > /sys/class/gpio/export'), $gpioLed);
exec(sprintf('echo "out" > /sys/class/gpio/gpio%d/direction'), $gpioLed);
exec(sprintf('echo 1 > /sys/class/gpio/gpio%d/value'), $gpioLed);
exec(sprintf('aplay -q ./files/%s.wav', $md5));
exec(sprintf('echo 0 > /sys/class/gpio/gpio%d/value'), $gpioLed);
exec(sprintf('echo %d > /sys/class/gpio/unexport'), $gpioLed);

Le but est d'avoir un serveur qui attend une requête GET avec une query string text contenant le texte qu'on cherche à écouter. On calcule le md5 de cette chaîne, on cherche un fichier avec ce nom, et si on ne le trouve pas, on va le chercher sur le serveur TTS pour le mettre en cache. J'aurai pu aussi utiliser le slug du texte au lieu de son md5.

Enfin, on allume la LED avant de jouer le fichier et on l'éteint ensuite. L'astuce ici est de contrôler le GPIO correspondant directement via le système de fichiers, plutôt que de faire appel à une librairie externe ou un wrapper. Pour cinq lignes de code, on ne va pas s'encombrer davantage.

Il reste à s'assurer que ce script soit toujours en état de fonctionner, d'où l'intérêt de supervisor. De plus, on va faire appel au serveur intégré à PHP afin de limiter les dépendances (encore une fois, je sais, on peut faire la même chose en python sans installer PHP...).

On va donc créer le fichier /etc/supervisor/conf.d/tts.conf:

[program:tts]
command=php -S 0.0.0.0:8000 -t /opt/server/
autostart=true
autorestart=true
user=tts
numprocs=1

Le serveur PHP va écouter sur le port 8000 sur toutes les interfaces disponibles avec l'utilisateur tts ; il faut donc créer ce dernier:

adduser --system --no-create-home --home /opt/server --disabled-password --disabled-login tts

Et on relance supervisor:

supervisorctl reread
supervisorctl update
supervisorctl start tts

Le Raspberry Pi est prêt, on passe au serveur TTS (qui n'a pas besoin d'être HomeAssistant d'ailleurs).

Sur le serveur TTS

On va installer les mêmes dépendances, ainsi que picotts.

apt update
apt install php-cli supervisor libttspico-utils

On se créé notre dossier de travail:

mkdir -p /opt/server/files
cd /opt/server

Et on créé notre serveur:

<?php

if(!array_key_exists('text', $_REQUEST)) {
  header('HTTP/1.0 400 Bad Request');
  exit;
}

$text           = urldecode($_REQUEST['text']);
$md5            = md5($text);
$thisDir        = dirname(__FILE__);
$filePath       = sprintf('%s/files/%s.wav', $thisDir, $md5);

if(empty($text)) {
  header('HTTP/1.0 400 Bad Request');
  exit;
}

if(!file_exists($filePath)) {
  exec(sprintf('pico2wave -l "fr-FR" -w %s "%s"', $filePath, $text));
}

header('HTTP/1.0 200 OK');
header('Content-Type: audio/wav');

print file_get_contents($filePath);

Comme précédemment, on attend une requête contenant le texte à dicter. Si le fichier correspondant n'existe pas, on exécute picotts (pauvre picotts...). Enfin, on retourne le fichier son.

Comme précédemment, également, on veut exécuter ce script en continu. On va donc créer le fichier /etc/supervisor/conf.d/tts.conf:

[program:tts]
command=php -S 0.0.0.0:8000 -t /opt/server/
autostart=true
autorestart=true
user=tts
numprocs=1

Exactement le même que sur le Pi. Donc, rebelotte, on créé l'utilisateur qui va bien:

adduser --system --no-create-home --home /opt/server --disabled-password --disabled-login tts

Et on relance supervisor:

supervisorctl reread
supervisorctl update
supervisorctl start tts

HomeAssistant

On va créer un script qui va être utilisé comme un notifier en ligne de commande:

#!/bin/bash

text=$(cat)
host="raspberry_pi"
port=8000

curl --data-urlencode "text=$text" -G "http://$host:$port" >> /dev/null

Pensez à changer "raspberry_pi" par l'IP ou le FQDN de votre Raspberry Pi et le port d'écoute du serveur. On intègre ce script à HomeAssistant:

#[...]
notifier:
  - name: Pipelette
    platform: command_line
    command: "sh tts-notifier.sh"
#[...]

Oui, j'ai appelé le Raspberry Pi de notifications vocales "Pipelette".

On peut maintenant créer des automatisations pour les notifications vocales:

- alias: "Garage - Mouvement détecté"
  trigger:
    - platform: state
      to: "on"
      entity_id: binary_sensor.detecteur_de_mouvement
  action:
    - service: notify.pipelette
      data:
        message: "Du mouvement a été détecté dans le garage !"

Fonctionnement

Dans le meilleurs des cas, il n'y a donc qu'une seule requête/réponse entre HomeAssistant et le Raspberry Pi: gain de temps et de bande passante sur le réseau.

Dans le pire des cas, HomeAssistant envoie une requête au Raspberry Pi, patiente pendant que le Raspberry Pi envoie une requête au serveur TTS, et récupère le fichier son généré. Personnellement, je trouve le délais tout à fait satisfaisant.

Commentaires

Vous pouvez utiliser markdown dans votre commentaire. Si vous indiquez une adresse email, elle sera utilisée pour vous notifier des commentaires publiés sur cette page. Elle ne sera utilisée qu'à cette fin et ne sera jamais transmise à un tiers.