Domotique : synthèse vocale, Pulseaudio en unicast, Zoneminder et xAP, avec un soupçon de Sec…

En voila du titre bien décousu :-)

En fait, j’ai passer quelques semaines a mettre en place différentes solutions dans mon environnement domotique et plutôt que d’en faire plein de petits billets, j’ai décidé de tout regrouper en un, à vous de piocher dedans :D

Pour résumer la situation, je voulais d’abord que mon serveur Domotique « parle » (ça c’est plutôt simple), que lorsque j’allume mon portable au sous-sol, j’entende les messages audio du serveur (la ça se complique, si comme moi, vous ne souhaitez pas faire du multicast dans votre LAN), et enfin, comble du geekissime domotique, je voulais récupérer en temps réel les alarmes Zoneminder dès la détection d’un mouvement dans le champ de la caméra et que mon serveur Domotique me l’annonce de sa douce voix (la, ça se complique beaucoup, vous verrez…)

Bref, après plusieurs semaines, tout cela fonctionne, voyons comment.

  • SYNTHESE VOCALE ou comment faire parler une machine

Pour l’installation d’un synthèse vocale sur un serveur Linux, rien de plus simple.

Plutôt que de refaire un énième billet a ce sujet, je vous renvoie vers le blog de Christophe Nowicki dont je vous avez déjà parler, qui explique très bien la façon de le faire :

http://www.csquad.org/2009/08/27/text-to-speech-avec-espeak-mbrola-et-speech-dispatcher/

Il existe bien sûr d’autre tutos, mais Christophe a beaucoup travailler sur le « son » en Domotique et son blog regorge d’infos intéressantes.

Bilan après installation, un système de synthèse vocale installé sous /opt/mbrola

et bien sûr, les commandes essentielles à la création et gestion des « voix » :

(ps : fr4 est une voix française féminine, bizarrement je préfère :-p )

(NB: a l’attention de Fred, je n’arrive pas a reproduire la voix de Karen Cheryl… Désolé… (ou tant mieux pour nous…)

- Convertir le texte écrit dans le fichier « texte » en un phonème compréhensible par Mbrola

espeak -x -v mb/mb-fr4 -f texte > texte.pho

- Faire parler le serveur en lisant le phonème

mbrola -t 1.0 -e -C « n n2″ /opt/mbrola/fr4/fr4 texte.pho -.au | aplay

- Convertir la phrase lue par mbrola en un fichier Wav

mbrola -e -C « n n2″ /opt/mbrola/fr4/fr4 texte.pho texte.wav

(pour les options mbrola que j’utilise, t = la vitesse de lecture, voir la doc officielle)

Pour lire le wav en ligne de commande, mplayer est votre ami.

  • PULSEAUDIO ou le son en réseau

Voila la partie où j’ai passer le plus de temps.

Je souhaitais récupérer le son émis par mon serveur sur mon portable connecté en Wi-Fi.

Le premier serveur de son qui m’est venu a l’esprit fut bien sur Pulseaudio.

C’est là que les complications sont arrivées…

Pulseaudio peut fonctionner en multicast, comprendre que le son est diffusé dans tout le LAN, il peut même s’intégrer a Avahi (le mDNS type « Bonjour »).

Sauf que moi le multicast, j’aime pas ça (va comprendre Charles…) et qui plus est, j’ai un LAN segmenté par VLAN et centralisé par du Cisco, et la conf multicast sous Cisco, ca me fait mal au crâne :-)

Donc, out le multicast, je veux de l’unicast TCP (bah oui, j’aime bien les trames réseaux bien proprettes :-D )

L’installation de Pulseaudio sous Debian Lenny passe par un apt-get install tout simple (n’oubliez pas pulseaudio-utils)

Côté configuration sur le serveur Domotique :

srvdomotique:/etc/pulse# cat default.pa
#!/usr/bin/pulseaudio -nF
.nofail
load-sample-lazy pulse-hotplug /usr/share/sounds/startup3.wav
.fail
.ifexists module-hal-detect.so
load-module module-hal-detect
.else
load-module module-detect
.endif
### Load several protocols
.ifexists module-esound-protocol-unix.so
load-module module-esound-protocol-unix socket="/tmp/.esd/socket"
.endif
load-module module-native-protocol-unix
load-module module-esound-protocol-tcp auth-anonymous=1
load-module module-native-protocol-tcp listen=192.168.43.100 auth-anonymous=1
load-module module-tunnel-sink server=192.168.8.201 sink_name=copie
load-module  module-combine sink_name=combined master="copie" slaves="alsa_output.pci_10de_3f0_alsa_playback_0"
.ifexists module-gconf.so
.nofail
#load-module module-gconf
.fail
.endif
### Automatically restore the volume of playback streams
load-module module-volume-restore
load-module module-default-device-restore
load-module module-rescue-streams
load-module module-suspend-on-idle
.ifexists module-x11-publish.so
.nofail
load-module module-x11-publish
.fail
.endif
set-default-sink combined

Petite explication.

La ligne : « load-module module-native-protocol-tcp listen=192.168.43.100 auth-anonymous=1″ signifie que vous écoutez sur l’ip 102.168.43.100 et que vous autorisez les connexions anonymes (on va filter au niveau ACL du cisco)

La ligne : « load-module module-tunnel-sink server=192.168.8.201 sink_name=copie »

désigne le « client » qui va écouter les sons émis par le serveur domotique (en fait le client est le serveur domotique qui va émettre le son vers un serveur pulseaudio distant à l’ip 192.168.8.201…)

La ligne : « load-module  module-combine sink_name=combined master= »copie » slaves= »alsa_output.pci_10de_3f0_alsa_playback_0″ »

génère un « sink » (une destination audio si vous préférez), qui comprend ET le pc a l’adresse 192.168.8.201 (alias copie) ET la carte son du serveur Domotique.

Flux par défaut : set-default-sink combined

Ce qui veut dire que tout son émis doit être diffusé simultanément sur la carte son du serveur Domotique (je peux donc l’entendre par les enceintes branchées dessus) et sur les enceintes du PC client.

C’est clair ?

Sur le serveur Domotique, dont le système de son est géré par ALSA, je spécifie que tout les sons doivent être envoyés au démon PulseAudio grace au fichier /etc/asound.conf

srvdomotique:/etc# more asound.conf
pcm.pulse {
type pulse
}
 
ctl.pulse {
type pulse
}
 
pcm.!default {
type pulse
}
 
ctl.!default {
type pulse
}

Reste à lancer pulsaudio (ou par la commande pulseaudio -Cvvv pour voir ce qui se passe ou par la commande pulseaudio -D pour le daemoniser en arrière plan).

Les sons émis par votre serveur doivent être audible sur les enceintes de celui-ci.

Sur le PC client (mon portable en l’occurence), installation de pulseaudio normale, puis un default.pa comme suit :

#!/usr/bin/pulseaudio -nF
.ifexists module-hal-detect.so
load-module module-hal-detect
.else
load-module module-detect
.endif
.ifexists module-esound-protocol-unix.so
.endif
load-module module-native-protocol-unix
load-module module-esound-protocol-tcp auth-anonymous=1
load-module module-native-protocol-tcp listen=192.168.8.201 auth-anonymous=1
.ifexists module-gconf.so
.nofail
.fail
.endif
load-module module-volume-restore
load-module module-default-device-restore
load-module module-rescue-streams
load-module module-suspend-on-idle
.ifexists module-x11-publish.so
.nofail
load-module module-x11-publish
.fail
.endif

Rien d’autre (pas de fichier /etc/asound.conf, pas de données dans client.conf)

Lancer pulseaudio sur le client puis lancer pulseaudio sur le serveur Domotique. Les sons émis par le serveur domotique arrive sur le pc portable (même dans KDE pour ma part…)

Oui, MAIS !!!

Le gros problème est que si le PC portable se connecte APRES le démarrage de PulseAudio sur le serveur Domotique et bien, cela ne fonctionne plus !!!!

J’ai passé des jours a chercher et je n’ai pas réussi a comprendre pourquoi le serveur Domotique ne voyait pas le portable se connecter… J’ai tourner dans tout les sens, impossible de le faire marcher comme cela donc si vous avez une solution, je suis preneur !!!

Mais je n’allais pas en rester la et donc pour réussir ce tour de force, j’ai décider de forcer l’enregistrement du pc portable sur le serveur son Domotique via quelques scripts.

Alors, un peu de compassion pour moi, je ne suis pas développeur et c’est sûrement très crado comme code mais au moins ça marche :-p

- Technique d’enregistrement d’un client PulseAudio via socket en Python.

Sur le serveur Domotique, on va faire tourner un petit serveur codé en Python qui écoute sur le port tcp 12345 :

srvdomotique:~# cat pulseserver.py
#!/usr/bin/python
import socket
import subprocess
import sys
import os
servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 
try:
    servsock.bind(('',12345))
 
except socket.error:
        print "Relance Pulse"
        pidpulse = subprocess.Popen("ps acx | grep pulseaudio | awk '{print $1}'", shell=True, stdout=subprocess.PIPE)
        pid = pidpulse.stdout.read()
        print pid
        os.kill(int(pid), 9)
        pidpulse.stdout.close()
        subprocess.call(["pulseaudio -D"],shell=True)
        servsock.bind(('',12345))
 
servsock.listen(1)
while True:
sock,addr = servsock.accept()
echo = sock.recv(1024)
subprocess.call(["/home/scripts/enregistrement_station.sh"],shell=True)
sock.send(echo)
sock.close()

Quand le client se connecte, le serveur domotique lance le script enregistrement_station.sh :

#!/bin/bash
PIDPULSE=` ps acx | grep pulseaudio | awk '{print $1}'`
kill -9 $PIDPULSE
pulseaudio -D
sleep 2
mplayer /opt/sound/notify.wav
mbrola -t 1.1 -e -C "n n2" /opt/mbrola/fr4/fr4 /opt/mbrola/enregistrement_station.pho -.au | aplay

Dans /etc/rc.local, vous n’avez plus qu’a rajouter la ligne :
/cheminduscript/pulseserver.py &
pour le lancer au boot du serveur.

Côté PC portable, le script de connexion.sh :

#!/bin/bash
PIDPULSE=` ps acx | grep pulseaudio | awk '{print $1}'`
# On allume le haut-parleur a 80%
amixer -q set Master 80%,80%  unmute
if  test -n "$PIDPULSE"
then
echo "pulseaudio actif"
else
pulseaudio -D
fi
sleep 1
python /home/guiguiabloc/pulseclient.py
exit 0

Et le client en python (pulseclient.py)

#!/usr/bin/python
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host = 'srvdomotique'
port = 12345
print 'Connexion sur ', host, port
client.connect((host, port))
client.close

Je vous le conçois, c’est bourrin…
Quand le portable lance le script « connexion.sh », il lance pulseaudio en local, se connecte au serveur domotique sur le port 12345 qui kill son pulseaudio et le relance, puis fait dire au serveur domotique une phrase annonçant l’enregistrement de la station.
Mais au moins ça marche…

Donc si vous avez d’autres solutions, je suis à votre écoute :D

  • ZONEMINDER et xAP (avec un soupçon de SEC)

Dernière étape de mon travail, mon zoneminder.

ZoneMinder, c’est « vachement » bien, ça fait plein de truc par défaut mais il me manquait une fonction essentielle.

Quand l’on positionne ses caméras en mode « Modect », il détecte les mouvements dans la zone que vous avez définie et enregistre la séquence. Une fois achevée, il vous envoie un email pour vous prévenir d’un mouvement et vous donne le lien vers le « film ».

Super, mais entre le moment où ZoneMinder déclenche l’enregistrement, l’achève après un temps pré-défini (j’utlilise un buffer de 10 secondes avant et 30 secondes après), et vous envoie le mail, bah il se passe plusieurs secondes, voir minutes.

Ce que je voulais c’est que dès qu’un mouvement était détecté par la caméra, ZoneMinder me prévienne d’une manière ou d’une autre. Dans mon cas, en me l’annonçant de sa douce voix (via mbrola).

Ce n’est pas natif dans les fonctions de ZoneMinder et mon bonheur je l’ai trouvé (après moults recherches je vous l’avoue), chez xAP.

Pour résumer, xAP est un protocole open dédié a l’automatisation. C’est la première fois que j’en entendais parler et les fonctionnalités sont nombreuses et puissantes.

Et l’une de ses nombreuses fonctionnalités justement, c’est sa possibilité d’intégration avec ZoneMinder, via un script perl nommé ZMXAP.

En fait, au fin fond du WiKi Zoneminder, on trouve un article dessus :

http://www.zoneminder.com/wiki/index.php/Zmxap

Ni une ni deux, on se télécharge le bazar ici :

http://www.limings.net/xap/zmxap/zmxap-v0.9.tar.gz

La conf est trivial, juste a spécifier nohub=1 dans le zmxap.conf et surtout, pour le besoin que j’avais, générer un fichier de log avec la variable log_file = /tmp/zmxap.log

Car  zmxap est capable d’intéragir directement avec Zoneminder et donc de recevoir en temps réel les informations qui en découlent.

Petit test pour le vérifier :

On lance Zmxap.pl avec la variable du path :

/opt/scripts/zmxap/zmxap.pl -path=/opt/scripts/zmxap &

On passe la caméra en mode « Modect », on passe devant elle et on regarde ce qui se passe dans le fichier /tmp/zmxap.log

24.08.2010 19:32:08 [INFO] (main::loadMonitors): Loading monitors
######## in sendAlarmEvent
####### state=2 and cause=Motion
###### starting fetch of event_data
###### completed fetch of event_data
######## completed sendAlarmEvent
24.08.2010 19:36:03 [INFO] (checkState) Sous_Sol - initiating alarm state
######### completed sendTrackingEvent
24.08.2010 19:36:18 [INFO] (checkState) Sous_Sol - resuming idle state
######## in sendAlarmEvent
####### state=0 and cause=Motion
###### starting fetch of event_data
###### completed fetch of event_data

Magique !!!

ZMXAP a détecté immédiatement le passage (initiating alarm state) puis on voit l’enregistrement se faire.

Ne reste plus qu’a analyser ce fichier de log en temps réel pour remonter l’info.

Et qui a-t-il de mieux pour analyser un fichier de log temps réel ? hein ? Pourtant je vous en avez déjà parler :

http://blog.guiguiabloc.fr/index.php/2009/03/18/interception-des-erreurs-applicatives-dans-nagios-avec-sec-et-prelude-lml/

SEC bien sûr !!!

Allez hop, on se configure notre sec.conf :

type=single
ptype=RegExp
pattern=Sous_Sol(.+)alarm
desc=$0
action=shellcmd  /opt/scripts/AUDIO_alarme_soussol.sh

le script AUDIO_alarme_soussol.sh ne fait que lire une phrase disant « attention y’a un méchant monsieur au sous-sol » (ou autre chose hein…)

Ne reste qu’a lancer tout cela ensemble :

#!/bin/bash
/opt/scripts/zmxap/zmxap.pl -path=/opt/scripts/zmxap &
/usr/local/bin/sec/sec.pl -conf=/usr/local/bin/sec/etc/sec.conf -input=/tmp/zmxap.log -log /var/log/sec.log &

Et voilà, désormais, en plus d’enregistrer les mouvements détectés, ZoneMiner vous prévient immédiatement dès qu’il détecte un mouvement !

C’est pas beau ça ?

Voila un tour de ce que j’ai mis en oeuvre ses dernières semaines, j’avoue que pulseaudio m’a beaucoup coûter en temps, essayant de lui faire recharger sa configuration quand il voyait le portable mais sans succès.

En tout cas, désormais, tout fonctionne à merveille et après tout, c’est tout ce que l’on en attend.

Amusez vous bien :D

Ce billet a été posté dans domotique, geekerie, linux et taggé , , , , . Bookmark ce permalink.

9 commentaires sur “Domotique : synthèse vocale, Pulseaudio en unicast, Zoneminder et xAP, avec un soupçon de Sec…

  1. Bonjour j’ai suivi avec intérêt ta démonstration,
    chez moi ça ‘bugue’ au niveau de la conversion du .pho en .wav
    à quoi correspondent les paramètres n et n2?
    j’ai remplacé la voix fr4 par fr1 (en suivant le tuto de Christophe)

    mbrola -e -C « n n2? /opt/mbrola/fr1/fr1 texte.pho texte.wav

    par quoi puis-je remplacer n et n2?
    merci,
    Michel

  2. L’option n2 en fait une omission du phonème « on a » qui est mal interprété par mbrola. C’est un bug connu qu’on outrepasse on demandant a mbrola de considerer n2 comme un phonème et non une variable. (-C -e = IGNORE fatal errors on unkown diphone )

    Je pense plutot à une librairie manquante sur le système. Quel est le message d’erreur ?

  3. wow, merci pour la rapidité de la réponse!
    j’ai regardé sur le forum ubuntu et testé avec la commande
    mbrola /usr/share/mbrola/fr1/fr1 texte.pho texte.wav

    apparemment ça me renvoie une erreur dans le fichier phonème?

    Fatal error in line: bO~Z’ur

  4. C’est exactement cela. tu as un phonème qui pose problème. Essaye avec une phrase simple type « bonjour » et pas « guiguiabloc est génial c’est le plus beau le plus fort » (nan j’déconne). Il semble que c’est un des mots qui soit mal reconnu, mbrola n’est pas encore totalement a l’aise avec l’ensemble de la langue française.

  5. oki, je vais voir si je peux réinstaller mbrola un peu plus proprement , merci et joyeux Noël!

  6. I have been following your blog and its just excellent. My question is how do I integrate zoneminder with mythtv? Do you have any write up on it. I have mythbuntu and zoneminder and would like to integrate them.Thanks

    Jon

  7. Bonjour

    Je n’arrive pas à configurer Zmxap, il me dit dans le fichier zmxap.log :

    [WARN] (checkState) Can’t read from shared memory: Ioctl() inappropré pour un périphérique

    si je pouvais avoir un exemple de fichier zmxap.conf, ça m’aiderais peut-être à comprendre ce qui ne vas pas.

    Merci de votre aide.

    Régis

  8. Bonjour, rien de spécial, ci dessous mon fichier zmxap.conf:
    [general]

    log_file = /tmp/zmxap.log
    log_level = 3
    monitor_check_interval = 0.25

    [xap]
    instance_name = house
    nohub = 1
    enable_control = 1
    [message.display]
    [message.display.1]
    line2 = $alarm_notes
    duration = 30
    priority = 80
    [message.display.6]
    line2 = $alarm_notes
    duration = 30
    priority = 80