diff --git a/fhem/contrib/fhem2speech/ChangeLog b/fhem/contrib/fhem2speech/ChangeLog new file mode 100644 index 000000000..b2d586fcf --- /dev/null +++ b/fhem/contrib/fhem2speech/ChangeLog @@ -0,0 +1,45 @@ +2009-01-11 (1.4) Martin Fischer + + * test for arguments added + * new option --set to set device status added + * documentation updated + +2009-01-07 (1.3) Martin Fischer + + * Perl modul JSON::XS for communication with FHEM included. This requires + jsonlist (fhem/contrib) for FHEM. + * several changes for parsing the JSON result + * buffering disabled + * output as asterisk AGI command added + * new subroutine for removing value units + +2009-01-01 (1.2) Martin Fischer + + * Perl modul Term::ANSIColor removed + * external command 'recode' for converting UTF8 to latin1. mbrola need + this to speek german umlauts + * new output format gsm + * options for outputformat now configurable + * translation for special chars added + * new option --asterisk, -a to support asterisk parsing added + * new option --prefix to flag outfiles with a user prefix added + * new option --force to override existing files added + * support for reading files added + * several changes in subroutine text2speech (code cleanup) + +2008-12-31 (1.1) Martin Fischer + + * command-line options now works with Getopt::Long + * check for external files (mbrola, txt2pho, sox, etc.) included + * include a debug routine. use it with --debug on command-line + * documentation added + * added support for quiet mode. use it with -q, --quiet + * FHEM error trap added + * new option -t "TEXT". read the given text + * new option to support female or male voice. use it with -S, --Sex + on command-line + * replace unwanted chars with text, e.g. - = Minus + * support for generating (cached) wave files + * update the documentation + * added cached files for all modes + diff --git a/fhem/contrib/fhem2speech/JSON-XS-2.231.tar.gz b/fhem/contrib/fhem2speech/JSON-XS-2.231.tar.gz new file mode 100644 index 000000000..18e9a1445 Binary files /dev/null and b/fhem/contrib/fhem2speech/JSON-XS-2.231.tar.gz differ diff --git a/fhem/contrib/fhem2speech/README.fhem-speech b/fhem/contrib/fhem2speech/README.fhem-speech new file mode 100644 index 000000000..55fb5f754 --- /dev/null +++ b/fhem/contrib/fhem2speech/README.fhem-speech @@ -0,0 +1,345 @@ +NAME + fhem-speech - Synthesized voice (based on MBROLA) extension for FHEM + +SYNOPSIS + fhem-speech -d device [-achopqS] + + fhem-speech -d device --set state [-hp] + + fhem-speech -f file [-acoqS] + + fhem-speech -t "text" [-acoqS] + + fhem-speech [-HmV?] + + Try `fhem-speech --man` for full manual! + +DESCRIPTION + fhem-speech + fhem-speech read the status of a FHEM device and talk using the MBROLA + speech synthesizer. Furthermore it can read the content of a given file + or text. + + FHEM + FHEM is used to automate some common tasks in the household like + switching lamps/shutters/heating/etc. and to log events like + temperature/humidity/power consumption. Visit the FHEM's homepage + for more information. + + The MBROLA project + Central to the MBROLA project is MBROLA, a speech synthesizer based on + the concatenation of diphones. It takes a list of phonemes as input, + together with prosodic information, and produces speech samples on 16 + bits (linear), at the sampling frequency of the diphone database used. + This synthesizer is provided for free, for non commercial, non military + applications only. Visit the MBROLA's homepage + for more information. + + Asterisk + Optionally fhem-speech supports AGI commands to communicate with + Asterisk. Visit the Asterisk(R) homepage for + more information. + +OPTIONS + Mandatory arguments to long options are mandatory for short options too. + Ordering Options: + + -d, --device device + Run in FHEM mode. Specifies the FHEM device to be queried. The given + device must be defined. + + -f, --file file-name + Run in file mode. fhem-speech will read the given file. + + -t, --text "TEXT" + Run in Speaker's mode. fhem-speech will read the given "TEXT". + + Other options: + + -a, --asterisk + Run in Asterisk mode. fhem-speech print out AGI-commands for direct + usage in Asterisk. + + -c, --cache directory + Specifies the location of where the files should be saved if + fhem-speech started with the -o or --out argument. + + Default location: current directory. + + --force + Overwrites existing files. + + -h, --host host + Specifies the hostaddress for FHEM. + + Default address: "localhost". + + -o, --out [gsm|wav] + fhem-speech saves the output to a file with the specified output + format. + + Default address: "localhost". + + -p, --port port + Communicate with FHEM on defined port. + + Default port: "7072". + + --prefix prefix + Set the given prefix in front of filename. + + -q, --quiet + Run in quiet mode. + + --set state + Send to device. + + -S, --sex [f|m] + Specifies the sex for the voice. It depends on which voices for + MBROLA have been installed. + + Default: "de3" for the German female voice and "de2" for the German + male voice. + + -m, --man + Show the manual page and exits. + + -H, --help + Show a brief help message and exits. + + -V, --version + Show fhem-speech's version number and exit. + +EXAMPLES + Get status information for device in quiet mode: + + `fhem-speech -d EG.wz.HZ -q` + + Same as above with a male voice. FHEM runs on IP 192.168.1.100: + + `fhem-speech -d EG.wz.HZ -S m -h 192.168.1.100` + + Get status information for device in Asterisk mode: + + `fhem-speech -d EG.wz.HZ -a -q -o gsm -c /var/lib/asterisk/sounds/fhem/` + + Read the file : + + `fhem-speech -f foobar` + + Read the given text "Geht nicht gibt's nicht.": + + `fhem-speech -t "Geht nicht gibt's nicht."` + + Set the state for device : + + `fhem-speech -d EG.wz.SD.01 --set on` + +INSTALLATION + Requirements + MBROLA + You need MBROLA synthesizer, a synthesis voice, txt2pho and sox. For + more information visit: + + o MBROLA project, + + o hadifix, + + FHEM + For FHEM mode you need FHEM 4.5+ and the command extension "jsonlist". + For more information take a look at: + + /contrib/JsonList/README.JsonList + + or visit the FHEM's homepage: + + + + JSON::XS + The required command extension "jsonlist" send the result as a JSON + encoded string. fhem-speech need the Perl module JSON::XS to decode the + information. + + There are several ways to install the module: + + You can download the last version at: + + + + Or you can use the package from the contrib-folder which was delivered + with fhem-speech. + + You can use the cpan command on bash-prompt. + + Installation + This describes the installation on ubuntu: + + Make a temporarily directory for the needed files and change to the new + directory, e.g.: + + `mkdir /usr/local/src/mbrola; cd !$` + + Download the required files: + + `wget http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/txt2pho.zip` + `wget http://tcts.fpms.ac.be/synthesis/mbrola/bin/pclinux/mbrola3.0.1h_i386.deb` + + Download at least one synthesis voice (e.g. German female voice): + + `wget http://tcts.fpms.ac.be/synthesis/mbrola/dba/de3/de3.zip` + + txt2pho + Install txt2pho: + + `unzip txt2pho.zip -d /usr/share/` + `chmod 755 /usr/share/txt2pho/txt2pho` + + Edit txt2phorc: + + `vi /usr/share/txt2pho/txt2phorc` + + and change the path for DATAPATH and INVPATH: + + DATAPATH=/usr/share/txt2pho/data/ + INVPATH=/usr/share/txt2pho/data/ + + Copy txt2phorc to /etc/txt2pho: + + `cp /usr/share/txt2pho/txt2phorc /etc/txt2pho` + + Synthesis Voice + Install the synthesis voice (e.g. German female voice): + + `unzip de7.zip -d /usr/share/mbrola/de7` + + fhem-speech use "de2" and "de3" as default voices. You can change this + if you like. + + MBROLA + Install MBROLA: + + `dpkg -i mbrola3.0.1h_i386.deb` + + sox + Install sox: + + `apt-get install sox libsox-fmt-all` + + Test + Test your installation: + + `echo "Test" | /usr/share/txt2pho/txt2pho |\ + mbrola /usr/share/mbrola/de7/de7 - -.au | play -q -t au -` + + fhem-speech + Copy the script fhem-speech to a directory of your choice, e.g.: + + `cp fhem-speech /usr/local/bin` + + and make it executable: + + `chmod 775 /usr/local/bin/fhem-speech` + + Perl + If you use the delivered module contrib/JSON-XS-2.231.tar.gz: + + `tar xzf JSON-XS-2.231.tar.gz` + `cd JSON-XS-2.231` + `perl Makefile.pl` + `make` + `make test` + + and as root: + + `make install` + +CONFIGURATION + Open fhem-speech with your prefered editor. + + FHEM host settings + Change the default host, if you like: + + ########################### + # FHEM + $sys{fhem}{host} = "localhost"; + $sys{fhem}{port} = "7072"; + + External commands + Change the paths depending on the installed distribution: + + ########################### + # Mandatory external Files + $sys{file}{mbrola} = "/usr/local/bin/mbrola"; + $sys{file}{pipefilt} = "/usr/local/bin/pipefilt"; + $sys{file}{play} = "/usr/bin/play"; + $sys{file}{preproc} = "/usr/local/bin/preproc"; + [...] + + Change the default settings for synthesis voice: + + ########################### + # mbrola / txt2pho options + $sys{speech}{sex} = "f"; + $sys{speech}{male} = "-f0.8 -t0.9 -l 15000"; + $sys{speech}{female} = "-f1.2 -t1.0 -l 22050"; + + Translation + fhem-speech need the $lang{} settings to decide what messages from FHEM + to be spoken. For example take a look at the FHT part: + + ########################### + # FHEM Translation + + [...] + + ########################### + # FHT + # keys: + $lang{'actuator'} = "Ventilstellung: %s Prozent"; + $lang{'day-temp'} = "Temperatur Tag: %s Grad"; + $lang{'desired-temp'} = "Angeforderte Temperatur: %s Grad"; + $lang{'measured-temp'} = "Gemessene Temperatur: %s Grad"; + $lang{'mode'} = "Modus: %s"; + $lang{'night-temp'} = "Temperatur Nacht: %s Grad"; + $lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad"; + [...] + + On every FHEM response all of the defined $lang{} status information + will be spoken. If you don't like status information for e.g. + 'windowopen-temp' then comment this out: + + # $lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad"; + + If you like to know the status for e.g. 'lowtemp-offset' add a line like + this: + + $lang{'lowtemp-offset'} = "Versatz Temperatur %s Grad"; + + The '%s' stands as a placeholder for the value. + +OPTIONAL + Asterisk + fhem-speech support AGI commands for direct output in Asterisk. + + Wrapper + If you like fhem-speech for use in Asterisk, you have to install a + wrapper around fhem-speech. You can use the example from + contrib/fhem-speech.agi. + + Copy the wrapper to your asterisk-environment, e.g: + + `cp contrib/fhem-speech.agi /var/lib/asterisk/agi-bin/` + + extension.conf + Take a look at the example from contrib/extension.conf. + +LEGALESE + License GPLv3+: GNU GPL version 3 or later + . + + This is free software: you are free to change and redistribute it. There + is NO WARRANTY, to the extent permitted by law. + +AUTHOR + Copyright (C) 2008 Martin Fischer + diff --git a/fhem/contrib/fhem2speech/README.fhem2speech b/fhem/contrib/fhem2speech/README.fhem2speech deleted file mode 100644 index ffd072576..000000000 --- a/fhem/contrib/fhem2speech/README.fhem2speech +++ /dev/null @@ -1,63 +0,0 @@ -Copyright (c)2008 Martin Fischer - -Description: - The script fhem2speech.sh let FHEM talk. - -Requirements: - You need MBROLA synthesizer, a synthesis voice, txt2pho and bplay. - For more information visit: - o MBROLA Project, http://tcts.fpms.ac.be/synthesis/ - o hadifix, http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/ - -Installation: - This describes the installation on ubuntu: - - Make a temporarily directory for the needed files and change to - the new directory, e.g.: - 'mkdir /usr/local/src/mbrola; cd !$' - - Download the required files: - 'wget http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/txt2pho.zip' - 'wget http://tcts.fpms.ac.be/synthesis/mbrola/bin/pclinux/mbrola3.0.1h_i386.deb' - - Download at least one synthesis voice (e.g. german female voice): - 'wget http://tcts.fpms.ac.be/synthesis/mbrola/dba/de7/de7.zip' - - Install txt2pho: - 'unzip txt2pho.zip -d /usr/share/' - 'chmod 755 /usr/share/txt2pho/txt2pho' - - Edit txt2phorc: - 'vi /usr/share/txt2pho/txt2phorc' - and change the path for DATAPATH and INVPATH: - DATAPATH=/usr/share/txt2pho/data/ - INVPATH=/usr/share/txt2pho/data/ - - Copy txt2phorc to /etc/txt2pho: - 'cp /usr/share/txt2pho/txt2phorc /etc/txt2pho' - - Install the synthesis voice (e.g. german female voice): - 'unzip de7.zip -d /usr/share/mbrola/de7' - - Install mbrola: - 'dpkg -i mbrola3.0.1h_i386.deb' - - Install bplay: - 'apt-get install bplay' - - Test your installation: - 'echo "Test" | /usr/share/txt2pho/txt2pho | mbrola /usr/share/mbrola/de7/de7 - - | bplay -s22050 - -b 16' - - Copy the script fhem2speech.sh to a directory of your choice, - e.g.: - 'cp fhem2speech.sh /usr/local/bin' - and make it executable: - 'chmod 775 /usr/local/bin/fhem2speech.sh' - -Usage: - Edit your FHEM configuration file and add a line like this: - define speechBattery notify .*warnings.*Battery.*low.* "/usr/local/bin/fhem2speech.sh -s "Achtung, Batterie schwach: @"" - - At last restart FHEM or do a rereadcfg and listen to your speaker. - diff --git a/fhem/contrib/fhem2speech/extensions.conf b/fhem/contrib/fhem2speech/extensions.conf new file mode 100644 index 000000000..ea6bd07ce --- /dev/null +++ b/fhem/contrib/fhem2speech/extensions.conf @@ -0,0 +1,163 @@ +; extensions.conf - the Asterisk dial plan +; + +[myHCE] +; houseautomation +exten => 5000,1(myhce),Answer() +exten => 5000,n,Set(TIMEOUT(digit)=5) +exten => 5000,n,Set(TIMEOUT(response)=10) +; skip authentication for known numbers +exten => 5000,n,GotoIf($["${CALLERID(num)}" = "01601234567"]?5000,main) +exten => 5000,n,GotoIf($["${CALLERID(num)}" = "01701234567"]?5000,main) +; authentication +exten => 5000,n,Authenticate(1137) +exten => 5000,n,Wait(1) +; main menu +exten => 5000,n(main),NoOp(Main Menu) +exten => 5000,n,Set(GLOBAL(myHCE_ext)=${EXTEN}) +exten => 5000,n,Set(GLOBAL(myHCE_pExt)=5000) +exten => 5000,n,Set(GLOBAL(myHCE_pCon)=myHCE) +include => myHCE-default +exten => 5000,n(menu),AGI(fhem-speech.agi,t,"Hauptmenü") +exten => 5000,n(choice),AGI(fhem-speech.agi,t,"Bitte wählen Sie") +exten => 5000,n,AGI(fhem-speech.agi,t,"1 für Statusabfrage") +exten => 5000,n,AGI(fhem-speech.agi,t,"2 für Steuerung") +exten => 5000,n,AGI(fhem-speech.agi,t,"5 für Hilfe") +exten => 5000,n,Background(silence/3) +exten => 5000,n,Goto(choice) +; help +exten => 5000,n(help),AGI(fhem-speech.agi,t,"Menüsteuerung für alle Menüs") +exten => 5000,n,AGI(fhem-speech.agi,t,"8 zurück zum letzten Menü") +exten => 5000,n,AGI(fhem-speech.agi,t,"9 zurück zum Hauptmenü") +exten => 5000,n,AGI(fhem-speech.agi,t,"0 zum Beenden") +exten => 5000,n,Background(silence/3) +exten => 5000,n,Goto(menu) +; selection +exten => 1,1,Goto(myHCE-status,5100,status) +exten => 2,1,Goto(myHCE-control,5200,control) +exten => 5,1,Goto(5000,help) + +[myHCE-default] +; global menu navigation +exten => 8,1,Goto(${myHCE_pCon},${myHCE_pExt},menu) +exten => 9,1,Goto(myHCE,5000,main) +exten => 0,1,Goto(myHCE-exit,5099,exit) +; wrong input +exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.") +exten => i,2,Goto(${myHCE_ext},menu) + +[myHCE-exit] +; exit +exten => 5099,n(exit),AGI(fhem-speech.agi,t,"Verbindung wird getrennt. Vielen Dank!") +exten => 5099,n,Hangup() + +[myHCE-status] +exten => 5100,1(status),NoOp(Status Menu) +exten => 5100,n,Set(GLOBAL(myHCE_ext)=${EXTEN}) +exten => 5100,n,Set(GLOBAL(myHCE_pExt)=5000) +exten => 5100,n,Set(GLOBAL(myHCE_pCon)=myHCE) +include => myHCE-default +; submenu device status +exten => 5100,n(menu),AGI(fhem-speech.agi,t,"Menü Statusabfrage") +exten => 5100,n(choice),AGI(fhem-speech.agi,t,"Bitte wählen Sie") +exten => 5100,n,AGI(fhem-speech.agi,t,"1 für Wetterstation") +exten => 5100,n,AGI(fhem-speech.agi,t,"2 für Rauchmelder") +exten => 5100,n,AGI(fhem-speech.agi,t,"5 für Raumthermostate") +exten => 5100,n,Background(silence/3) +exten => 5100,n,Goto(choice) +; selection +exten => 1,1,Playback(beep) +exten => 1,n,AGI(fhem-speech.agi,d,GH.ga.WE.01) +exten => 1,n,Playback(beep) +exten => 1,n,Goto(5100,status) +exten => 2,1,Playback(beep) +exten => 2,n,AGI(fhem-speech.agi,d,NN.xx.RM.01) +exten => 2,n,Playback(beep) +exten => 2,n,Goto(5100,status) +exten => 5,1,Goto(myHCE-status_fht,5110,menu) + +[myHCE-status_fht] +exten => 5110,1(status),NoOp(Status Menu) +exten => 5110,n,Set(GLOBAL(myHCE_ext)=${EXTEN}) +exten => 5110,n,Set(GLOBAL(myHCE_pExt)=5100) +exten => 5110,n,Set(GLOBAL(myHCE_pCon)=myHCE-status) +include => myHCE-default +; submenu fht devices +exten => 5110,n(menu),AGI(fhem-speech.agi,t,"Menü Raumthermostate") +exten => 5110,n(choice),AGI(fhem-speech.agi,t,"Bitte wählen Sie") +exten => 5110,n,AGI(fhem-speech.agi,t,"1 für Wohnzimmer") +exten => 5110,n,AGI(fhem-speech.agi,t,"2 für Schlafzimmer") +exten => 5110,n,AGI(fhem-speech.agi,t,"3 für Büro") +exten => 5110,n,AGI(fhem-speech.agi,t,"4 für Badezimmer") +exten => 5110,n,Background(silence/3) +exten => 5110,n,Goto(choice) +; selection +exten => 1,1,Playback(beep) +exten => 1,n,AGI(fhem-speech.agi,d,EG.wz.HZ) +exten => 1,n,Playback(beep) +exten => 1,n,Goto(5110,status) +exten => 2,1,Playback(beep) +exten => 2,n,AGI(fhem-speech.agi,d,EG.sz.HZ) +exten => 2,n,Playback(beep) +exten => 2,n,Goto(5110,status) +exten => 3,1,Playback(beep) +exten => 3,n,AGI(fhem-speech.agi,d,EG.bu.HZ) +exten => 3,n,Playback(beep) +exten => 3,n,Goto(5110,status) +exten => 4,1,Playback(beep) +exten => 4,n,AGI(fhem-speech.agi,d,EG.bz.HZ) +exten => 4,n,Playback(beep) +exten => 4,n,Goto(5110,status) + +[myHCE-control] +include => myHCE-default +exten => 5200,1(control),AGI(fhem-speech.agi,t,"Menü Steuerung") +exten => 5200,n(menu),AGI(fhem-speech.agi,t,"Bitte wählen Sie") +exten => 5200,n,AGI(fhem-speech.agi,t,"1 für Wohnzimmer") +exten => 5200,n,AGI(fhem-speech.agi,t,"2 für Schlafzimmer") +exten => 5200,n,AGI(fhem-speech.agi,t,"3 für Büro") +exten => 5200,n,AGI(fhem-speech.agi,t,"4 für Badezimmer") +exten => 5200,n,Background(silence/3) +exten => 5200,n,Goto(menu) + +exten => 1,1,Goto(myHCE-control_wohnen,5210,menu) + +exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.") +exten => i,2,Goto(5200,menu) + +[myHCE-control_wohnen] +include => myHCE-default +exten => 5210,1(control),AGI(fhem-speech.agi,t,"Menü Steuerung") +exten => 5210,n,AGI(fhem-speech.agi,t,"Wohnzimmer") +exten => 5210,n(menu),AGI(fhem-speech.agi,t,"Bitte wählen Sie") +exten => 5210,n,AGI(fhem-speech.agi,t,"1 für Lampen") +exten => 5210,n,Background(silence/3) +exten => 5210,n,Goto(menu) + +exten => 1,1,Goto(myHCE-control_wohnen-lampen,5211,set) + +exten => 8,1,Goto(myHCE-control,5200,menu) + +exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.") +exten => i,2,Goto(5200,menu) + +[myHCE-control_wohnen-lampen] +include => myHCE-default +exten => 5211,1(set),AGI(fhem-speech.agi,t,"Steuerung Lampen") +exten => 5211,n,AGI(fhem-speech.agi,d,EG.wz.SD.Licht.grp) +exten => 5211,n(menu),AGI(fhem-speech.agi,t,"1 für an") +exten => 5211,n,AGI(fhem-speech.agi,t,"2 für aus") +exten => 5211,n,Background(silence/3) +exten => 5211,n,Goto(menu) + +exten => 1,1,AGI(fhem-speech.agi,s,EG.wz.SD.Licht.grp,on) +exten => 1,n,Goto(5211,set) + +exten => 2,1,AGI(fhem-speech.agi,s,EG.wz.SD.Licht.grp,off) +exten => 2,n,Goto(5211,set) + +exten => 8,1,Goto(myHCE-control_wohnen,5210,menu) + +exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.") +exten => i,2,Goto(5211,menu) + diff --git a/fhem/contrib/fhem2speech/fhem-speech b/fhem/contrib/fhem2speech/fhem-speech new file mode 100755 index 000000000..13f1a73f3 --- /dev/null +++ b/fhem/contrib/fhem2speech/fhem-speech @@ -0,0 +1,1169 @@ +#!/usr/bin/perl +################################################################ +# +# $Id: fhem-speech,v 1.1 2009-01-12 10:26:50 rudolfkoenig Exp $ +# +# Copyright notice +# +# (c) 2008 Copyright: Martin Fischer (m_fischer at gmx dot de) +# All rights reserved +# +# This script free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +################################################################ + +use strict; +use warnings; + +use Getopt::Long qw(:config no_ignore_case);; +use Cwd; +use IO::Socket::INET; +use IO::File; +use Pod::Usage; +use JSON::XS; + +use vars qw{$call}; +use vars qw{%dev}; +use vars qw(%lang); +use vars qw{%sys}; +use vars qw($VERSION); +use vars qw($VERSION); + +################################################## +# Variables + +########################### +# FHEM +$sys{fhem}{host} = "192.168.1.100"; +$sys{fhem}{port} = "7072"; + +########################### +# Mandatory external Files +$sys{file}{mbrola} = "/usr/local/bin/mbrola"; +$sys{file}{pipefilt} = "/usr/local/bin/pipefilt"; +$sys{file}{play} = "/usr/bin/play"; +$sys{file}{preproc} = "/usr/local/bin/preproc"; +$sys{file}{preprocRules} = "/usr/share/mbrola/Rules.lst"; +$sys{file}{preprocShort} = "/usr/share/mbrola/Hadifix.abk"; +$sys{file}{sox} = "/usr/bin/sox"; +$sys{file}{recode} = "/usr/bin/recode"; +$sys{file}{txt2pho} = "/usr/local/bin/txt2pho"; +$sys{file}{voiceFemale} = "/usr/share/mbrola/de3/de3"; +$sys{file}{voiceMale} = "/usr/share/mbrola/de2/de2"; + +########################### +# mbrola / txt2pho options +$sys{speech}{sex} = "f"; +$sys{speech}{male} = "-f0.8 -t0.9 -l 15000"; +$sys{speech}{female} = "-f1.2 -t1.0 -l 22050"; +$sys{speech}{wav} = "-t au - -r 8000 -c1"; +$sys{speech}{gsm} = "-t au - -r 8000 -c1"; + +########################### +# FHEM Translation + +########################### +# misc / default +$lang{'comment'} = "Status %s"; +$lang{'room'} = "Raum %s"; +$lang{'battery'} = "Batterie %s"; +$lang{'state'} = "%s"; +$lang{'on'} = "an"; +$lang{'off'} = "aus"; +$lang{'yes'} = "ja"; +$lang{'no'} = "nein"; +$lang{'comma'} = "Komma"; +$lang{'error'} = "Status unbekannt"; +$lang{'zirkumflex'} = "Zirkumflex"; +$lang{'underline'} = "Unterstrich"; +$lang{'apostrophe'} = "Hochkomma"; +$lang{'degree'} = "Grad"; +$lang{'minus'} = "Minus"; +$lang{'plus'} = "Plus"; +$lang{'squareopen'} = "eckige Klammer auf"; +$lang{'squareclose'} = "eckige Klammer zu"; +$lang{'backtick'} = "Rueckwaerts geneigtes Hochkomma"; +$lang{'singlequote'} = "einfaches Anfuehrungszeichen"; +$lang{'quote'} = "Anfuehrungszeichen oben"; +$lang{'backslash'} = "umgekehrter Schraegstrich"; +$lang{'squaremm'} = "Quadratmilimeter"; +$lang{'squarecm'} = "Quadratzentimeter"; +$lang{'squarem'} = "Quadratmeter"; +$lang{'cubicmm'} = "Kubikmilimeter"; +$lang{'cubiccm'} = "Kubikzentimeter"; +$lang{'cubicm'} = "Kubikmeter"; + +########################### +# FHT +# keys: +$lang{'actuator'} = "Ventilstellung %s Prozent"; +$lang{'day-temp'} = "Temperatur Tag %s Grad"; +$lang{'desired-temp'} = "Angeforderte Temperatur %s Grad"; +$lang{'measured-temp'} = "Gemessene Temperatur %s Grad"; +$lang{'mode'} = "Modus %s"; +$lang{'night-temp'} = "Temperatur Nacht %s Grad"; +$lang{'windowopen-temp'} = "Temperatur Fenster offen %s Grad"; +# values: +$lang{'auto'} = "Automatik"; +$lang{'holiday'} = "Urlaub"; +$lang{'holiday_short'} = "Kurzurlaub"; +$lang{'manual'} = "Manuell"; +########################### +# KS300/KS555 +# keys: +$lang{'humidity'} = "Luftfeuchtigkeit %s Prozent"; +$lang{'israining'} = "Niederschlag %s"; +$lang{'temperature'} = "Aussentemperatur %s Grad"; +$lang{'wind'} = "Windgeschwindigkeit %s km/h"; +########################### +# HMS +# keys: +$lang{'smoke_detect'} = "Alarm %s"; + +# End of Variables +################################################## + +################################################## +# Forward declaration +sub sayFHEM; +sub queryFHEM($); +sub parseFHEM($$); +sub translateKey; +sub translateValue($$); +sub text2speech($); +sub isNumber; +sub isInteger; +sub isFloat; +sub usage; +sub usageShort; +sub version; + +main(); +exit; + +################################################## +# Main +sub main { + $call = _call(); + _debug($call,"called") if(grep(/debug/, @ARGV)); + my $result; + # disable buffering + $|=1; + + ########################### + # Variables + $VERSION = sprintf("%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/); + my $requiredOptions = "dft"; + + ########################### + # do some checks + # check for required files + foreach my $exec (sort keys %{$sys{file}}) { + _missing_file($sys{file}{$exec}) unless(-r $sys{file}{$exec}); + _debug($call,"check file: '".$sys{file}{$exec}."'") if(grep(/debug/, @ARGV)); + } + # check options + _missing_argv($requiredOptions) if(int(@ARGV) == 0); + + ########################### + # get options + my $args = { + asterisk => 0, # default false + cache => "", # default undef + debug => 0, # default false + device => "", # default undef + file => "", # default undef + force => 0, # default false + host => "", # default undef + out => "", # default undef + port => "", # default undef + prefix => "", # default undef + quiet => 0, # default false + set => "", # default undef + sex => "", # default undef + text => "", # default undef + }; + + eval { + local $SIG{__WARN__} = sub {}; + GetOptions( + "asterisk|a" => \$args->{asterisk}, + "cache|c=s" => \$args->{cache}, + "debug" => \$args->{debug}, + "device|d=s" => \$args->{device}, + "file|f=s" => \$args->{file}, + "force" => \$args->{force}, + "host|h=s" => \$args->{host}, + "out|o=s" => \$args->{out}, + "port|p=i" => \$args->{port}, + "prefix=s" => \$args->{prefix}, + "quiet|q" => \$args->{quiet}, + "set=s" => \$args->{set}, + "sex|S=s" => \$args->{sex}, + "text|t=s" => \$args->{text}, + "H|?" => sub { pod2usage( -exitval => 0, -verbose => 0); }, + "help" => sub { pod2usage( -exitval => 0, -verbose => 0); }, + "man" => sub { pod2usage( -exitval => 0, -verbose => 2); }, + "version|V" => sub { version(0) } + ); + } or pod2usage(); + + # set global options + $sys{asterisk} = $args->{asterisk} if($args->{asterisk}); + $sys{debug} = $args->{debug} if($args->{debug}); + $sys{force} = $args->{force} if($args->{force}); + $sys{prefix} = $args->{prefix} if($args->{prefix}); + $sys{quiet} = $args->{quiet} if($args->{quiet}); + $sys{speech}{sex} = $args->{sex} if($args->{sex}); + $sys{speech}{cache} = $args->{cache} if($args->{cache}); + $sys{speech}{out} = $args->{out} if($args->{out}); + # check for dependent options + _wrong_set("-d or -f or -t") if($args->{device} && ($args->{file} || $args->{text})); + _wrong_set("-d or -f or -t") if($args->{device} && ($args->{file} && $args->{text})); + _wrong_set("-f [-acoqS]") if($args->{file} && ($args->{host} || $args->{port})); + _wrong_set("-f [-acoqS]") if($args->{file} && ($args->{host} && $args->{port})); + _wrong_set("-t [-acoqS]") if($args->{text} && ($args->{host} || $args->{port})); + _wrong_set("-t [-acoqS]") if($args->{text} && ($args->{host} && $args->{port})); + _wrong_set("-d --set [-hp]") if( + $args->{set} && ($args->{astersik} || $args->{cache} || + $args->{file} || $args->{text} || $args->{force} || + $args->{out} || $args->{prefix} || $args->{quiet} || + $args->{sex}) + ); + _wrong_set("[-d|-f|-t] argument [-acopqS]") if($args->{asterisk} && ($args->{host} || $args->{port})); + # check for dependent options + _missing_required("-d") if(!$args->{device} && ($args->{host} || $args->{port})); + _missing_required("-d") if(!$args->{device} && ($args->{host} && $args->{port})); + _missing_required("-o") if(!$args->{out} && $args->{cache}); + _missing_required("-c") if(!$args->{cache} && ($args->{prefix} || $args->{force})); + _missing_required("-c") if(!$args->{cache} && ($args->{prefix} && $args->{force})); + _missing_required("-c") if(!$args->{cache} && $args->{asterisk}); + + # listen to text + if($args->{text}) { + $sys{speech}{text} = $args->{text} if($args->{text}); + _debug($call,"ARGV: \$sys{speech}{text}: '".$sys{speech}{text}."'") if($sys{debug}); + $result = sayTEXT(); + } + + # listen to file + if($args->{file}) { + $sys{speech}{file} = $args->{file} if($args->{file}); + _debug($call,"ARGV: \$sys{speech}{file}: '".$sys{speech}{file}."'") if($sys{debug} && $args->{file}); + $result = sayFILE(); + } + + # let FHEM talk :-) + if($args->{device}) { + _debug($call,"ARGV is 'fhem'") if($sys{debug}); + $sys{fhem}{device} = $args->{device}; + _debug($call,"ARGV: \$sys{fhem}{device}: '".$sys{fhem}{device}."'") if($sys{debug}); + $sys{fhem}{set} = $args->{set} if($args->{set}); + _debug($call,"ARGV: \$sys{fhem}{set}: '".$sys{fhem}{set}."'") if($sys{debug}); + $sys{fhem}{host} = $args->{host} if($args->{host}); + _debug($call,"ARGV: \$sys{fhem}{host}: '".$sys{fhem}{host}."'") if($sys{debug}); + $sys{fhem}{port} = $args->{port} if($args->{port}); + _debug($call,"ARGV: \$sys{fhem}{port}: '".$sys{fhem}{port}."'") if($sys{debug}); + + $result = sayFHEM() if(!$sys{fhem}{set}); + $result = setFHEM() if($sys{fhem}{set}); + } + return 0; +} + + +################################################## +# Text + +########################### +sub sayTEXT { + $call = _call(); + _debug($call,"called") if($sys{debug}); + my $result; + my $say; + + $say = $sys{speech}{text} if($sys{speech}{text}); + $result = text2speech($say) if($say); +} + +################################################## +# File + +########################### +sub sayFILE { + $call = _call(); + _debug($call,"called") if($sys{debug}); + STDOUT->autoflush(1); + my $content = ""; + my $file = $sys{speech}{file}; + my $result; + + _file_error($file) if(!-e $file || -B $file); + + my $fh = new IO::File($file, "r") or _file_error($file); + _debug($call,"read : '".$file."'") if($sys{debug}); + while (my $line = $fh->getline()) { + $content = $content.$line; + } + $fh->close(); + + $result=text2speech($content); + +} + +################################################## +# FHEM + +########################### +sub setFHEM { + $call = _call(); + _debug($call,"called") if($sys{debug}); + my $fhemCmd = "set ".$sys{fhem}{device}." ".$sys{fhem}{set}; + my $result; + + ########################### + # query FHEM and start working + $result = queryFHEM($fhemCmd); + chomp $result if($result); + _debug($call,"\$result: '".$result."'") if($sys{debug} && $result); + _fhem_error($result) if($result && $result =~ /No.*set/); +} + +########################### +sub sayFHEM { + $call = _call(); + _debug($call,"called") if($sys{debug}); + my $fhemCmd = "jsonlist ".$sys{fhem}{device}; + my $result; + my $fhemRaw; + my $say; + + ########################### + # query FHEM and start working + $result = queryFHEM($fhemCmd); + chomp $result; + _debug($call,"\$result: '".$result."'") if($sys{debug}); + _fhem_error($result) if(!$result || $result =~ /No.*device/); + + $fhemRaw = decode_json $result; + %dev = %{$fhemRaw->{ResultSet}->{Results}}; + + parseFHEM("room",$dev{ATTRIBUTES}{room}) if($dev{ATTRIBUTES}{room}); + parseFHEM("comment",$dev{ATTRIBUTES}{comment}) if($dev{ATTRIBUTES}{comment}); + + while (my ($key,$value) = each %dev) { + _debug($call,"\$key: '".$key."'") if($sys{debug}); + if($key eq "ATTRIBUTES" || $key eq "READINGS") { + while (my ($subKey,$subValue) = each %{$dev{$key}}) { + if($subKey ne "comment" && $subKey ne "room") { + _debug($call,"\$subKey: '".$subKey."'") if($sys{debug}); + $say = parseFHEM($subKey,$subValue) if($key eq "ATTRIBUTES"); + $say = parseFHEM($subKey,$subValue->{VAL}) if($key eq "READINGS"); + } + } + } else { + $say = parseFHEM($key,$value); + } + } +} + +########################### +sub queryFHEM($) { + $call = _call(); + _debug($call,"called") if($sys{debug}); + my $host = $sys{fhem}{host} . ":" . $sys{fhem}{port}; + my $cmd = shift; + my $result; + my $buf = ""; + + my $server = IO::Socket::INET->new(PeerAddr => $host); + _debug($call, "\$host: '".$sys{fhem}{host}.":".$sys{fhem}{port}."'") if($sys{debug}); + die "Can't connect to server " . $sys{fhem}{host} . " port " . $sys{fhem}{port} . "\n" if(!$server); + syswrite($server, "$cmd;quit\n"); + _debug($call, "\$cmd: '".$cmd."'") if($sys{debug}); + while(sysread($server, $buf, 256) > 0) { + $result .= $buf; + } + close($server); + return $result; +} + +########################### +sub parseFHEM($$) { + $call = _call(); + _debug($call,"called") if($sys{debug}); + my ($key,$value) = @_; + my $translatedKey; + my $translatedValue; + my @say; + my $result; + + $result = translateKey($key); + _debug($call,"\$key: '".$key."' \$result = ''") if($sys{debug} && !$result); + + if($result) { + $translatedKey = $result; + _debug($call,"\$key: '".$key."' \$translatedKey: '".$translatedKey."'") if($sys{debug}); + + if($result ne "\%s") { + @say = split("\%s",$translatedKey); + text2speech($say[0]); + translateValue($key,$value); + text2speech($say[1]) if($say[1] && $say[1] ne ":"); + } else { + translateValue($key,$value); + } + } +} + +########################### +sub translateKey { + $call = _call(); + _debug($call,"called") if($sys{debug}); + + return $lang{$_[0]} if(exists $lang{$_[0]}); +} + +########################### +sub translateValue($$) { + $call = _call(); + _debug($call,"called") if($sys{debug}); + my ($key,$value) = @_; + _debug($call,"\$key: '".$key."' \$value: '".$value."'") if($sys{debug}); + my $return; + + $value = removeUnits($value); + + # FS20 + if($key eq "state" || $key eq "smoke_detect") { + _debug($call,"\$key: '".$key."' is on/off") if($sys{debug}); + $value = $lang{'off'} if($value eq "off"); + $value = $lang{'on'} if($value eq "on"); + } + # KS300/KS555 + if($key eq "israining") { + _debug($call,"\$value: '".$value."'") if($sys{debug}); + $value = $lang{'no'} if($value eq "no (yes/no)"); + $value = $lang{'yes'} if($value eq "yes (yes/no)"); + _debug($call,"\$value converted to: '".$value."'") if($sys{debug}); + } + + if($value =~ m/^-/) { + text2speech($lang{'minus'}); + $value = substr($value,1); + } + if(isInteger($value)) { + text2speech(decodeChar($value)); + } elsif(isFloat($value)) { + my ($strLeft,$strRight) = split("\\.",$value); + _debug($call,"\$strLeft: '".$strLeft."' \$strRight '".$strRight."'") if($sys{debug}); + text2speech(decodeChar($strLeft)); + if($strRight !~ m/0+/) { + text2speech($lang{'comma'}); + text2speech(decodeChar($strRight)); + } + } else { + text2speech(decodeChar($value)); + } + +} + +################################################## +# mbrola + +########################### +sub text2speech($) { + $call = _call(); + _debug($call,"called") if($sys{debug}); + + my $str = shift; + my $cmd; + my $voice; + my $options; + my $out; + my $result; + + # define voice + if($sys{speech}{sex} eq "f") { + $voice = $sys{file}{voiceFemale}; + $options = $sys{speech}{female}; + } + if($sys{speech}{sex} eq "m") { + $voice = $sys{file}{voiceMale}; + $options = $sys{speech}{male}; + } + + # define extern commands + my $recode = $sys{file}{recode} . " " . "UTF-8..lat1"; + my $pipefilt = $sys{file}{pipefilt}; + my $preproc = $sys{file}{preproc} . " " . $sys{file}{preprocRules} . " " . $sys{file}{preprocShort}; + my $txt2pho = $sys{file}{txt2pho} . " -" . $sys{speech}{sex}; + my $mbrola = $sys{file}{mbrola} . " $options " . $voice . " - -.au"; + my $sox = $sys{file}{sox}; + + # set current dir for output if no directory defined + $sys{speech}{cache} = cwd() if(!$sys{speech}{cache} && $sys{speech}{out}); + + # sox options + if(defined($sys{speech}{out})) { + if($sys{speech}{out} eq "wav") { + # options for wav + $sys{speech}{soxOptions} = $sys{speech}{wav}; + } elsif($sys{speech}{out} eq "gsm") { + # options for gsm + $sys{speech}{soxOptions} = $sys{speech}{gsm}; +# } elsif($sys{speech}{out} eq "mp3") { +# # options for mp3 +# $sys{speech}{soxOptions} = $sys{speech}{mp3}; + } else { + _sox_format_error($sys{speech}{out}); + } + } + + # remove blanks + _debug($call,"remove bad chars") if($sys{debug}); + + # remove unwanted chars + $str = decodeChar($str); + + print $str . "\n" if(!$sys{quiet} && !$sys{asterisk}); + + # remove trailing slash + $sys{speech}{cache} =~ s/\/+$// if($sys{speech}{out}); + + _debug($call,"\$str: '".substr($str,0,40)."'") if($sys{debug}); + _debug($call,"\$sys{speech}{sex}: '".$sys{speech}{sex}."'") if($sys{debug}); + + # build command string + $cmd = "(echo \"$str\" | "; + # pipefilt | preproc | txt2pho | mbrola | + $cmd .= $recode . " | " . $pipefilt . " | " . $preproc . " | " . $txt2pho . " | " . $mbrola . " | "; + + # append command string for play only + if(!$sys{speech}{out}) { + $cmd .= $sys{file}{play} . " -q -t au -)"; + # let's get ready to rumble + $result = systemCmd($cmd); + } + + # append command string for destination file + if($sys{speech}{out}) { + $out = substr($str,0,32); + $out =~ s/[^a-zA-Z0-9]/_/gi; + $out = $sys{prefix}.$out if($sys{prefix}); + _debug($call,"\$sys{speech}{out}: '".$sys{speech}{out}."'") if($sys{debug}); + _debug($call,"\$sys{speech}{cache}: '".$sys{speech}{cache}."' \$out: '".$out."'") if($sys{debug}); + # sox + $cmd .= $sox . " " . $sys{speech}{soxOptions} . " " . $sys{speech}{cache} . "/" . $out . ".wav"; + # extend cmd for gsm + if($sys{speech}{out} eq "gsm") { + $cmd .= "; "; + # sox imput from wav + $cmd .= $sox . " " . $sys{speech}{cache} . "/" . $out . ".wav" . " "; + # sox output to gsm + $cmd .= $sys{speech}{cache} . "/" . $out . "." . $sys{speech}{out} . "; "; + # remove temporary wav file + $cmd .= "rm " . $sys{speech}{cache} . "/" . $out . ".wav" . "; "; + } + # close cmd + $cmd .= ")"; + # let's get ready to rumble + if($sys{force} || !-r $sys{speech}{cache} . "/" . $out . "." . $sys{speech}{out}) { + $result = systemCmd($cmd); + } + # print string for asterisk + if($sys{asterisk}) { + print "EXEC BACKGROUND ". $sys{speech}{cache} . "/" . $out . " \"\"\n"; + $result = ; + } + # play the result + if(!$sys{quiet}) { + $cmd = $sys{file}{play} . " -q -t " . $sys{speech}{out} . " "; + $cmd .= $sys{speech}{cache} . "/" . $out . "." . $sys{speech}{out}; + $result = systemCmd($cmd); + } + } +} + +################################################## +# misc + +##################################### +sub version { + print < +License GPLv3+: GNU GPL version 3 or later +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Written by Martin Fischer +EOT + exit($_[0]); +} + +################################################## +# helper + +##################################### +sub systemCmd { + $call = _call(); + _debug($call,"called") if($sys{debug}); + _debug($call,"system: '" . $_[0] . "'") if($sys{debug}); + return system($_[0]) == 0 || die "failed: $?"; +} + +##################################### +sub removeUnits { + $call = _call(); + _debug($call,"called") if($sys{debug}); + + $_[0] =~ s/\s\(Celsius\)//gi; + $_[0] =~ s/\s\(%\)//gi; + $_[0] =~ s/\s\(km\/h\)//gi; + $_[0] =~ s/%//gi; + + return $_[0]; +} + +##################################### +sub decodeChar { + $call = _call(); + _debug($call,"called") if($sys{debug}); + + $_[0] =~ s/^[\s\t]+//gi; + $_[0] =~ s/[\s\t]$//gi; + $_[0] =~ s/\^/ $lang{zirkumflex} /gi; + $_[0] =~ s/_/ $lang{underline} /gi; + $_[0] =~ s/'/ $lang{apostrophe} /gi; + $_[0] =~ s/°/ $lang{degree} /gi; + $_[0] =~ s/-/ $lang{minus} /gi; + $_[0] =~ s/\+/ $lang{plus} /gi; + $_[0] =~ s/\[/ $lang{squareopen} /gi; + $_[0] =~ s/\]/ $lang{squareclose} /gi; + $_[0] =~ s/`/ $lang{backtick} /gi; + $_[0] =~ s/\'/ $lang{singlequote} /gi; + $_[0] =~ s/"/ $lang{quote} /gi; + $_[0] =~ s/\\/ $lang{backslash} /gi; + $_[0] =~ s/mm²/$lang{squaremm} /gi; + $_[0] =~ s/cm²/$lang{squarecm} /gi; + $_[0] =~ s/m²/$lang{squarem} /gi; + $_[0] =~ s/mm³/$lang{cubicmm} /gi; + $_[0] =~ s/cm³/$lang{cubiccm} /gi; + $_[0] =~ s/m³/$lang{cubicm} /gi; + $_[0] =~ s/[\s\t]+/ /gi; + $_[0] =~ s/[[:cntrl:]]+//gi; + # convert to lowercase + $_[0] = lc($_[0]);; + + return $_[0]; +} + +##################################### +sub isNumber { + $call = _call(); + _debug($call,"called") if($sys{debug}); + $_[0] =~ /^\d+$/ +} + +##################################### +sub isInteger { + $call = _call(); + _debug($call,"called") if($sys{debug}); + $_[0] =~ /^[+-]?\d+$/ +} + +##################################### +sub isFloat { + $call = _call(); + _debug($call,"called") if($sys{debug}); + $_[0] =~ /^[+-]?\d+\.?\d*$/ +} + +########################### +sub _debug { + printf("\e[33m== debug:\e[37m \e[32m[%s]\e[37m \e[1m%s\e[0m\n",$_[0],$_[1]); +} + +########################### +sub _call { + (caller(1))[3]; +} + +##################################### +sub _missing_file { + $call = _call(); + _debug($call,"called") if($sys{debug}); + warn "fhem-speech: Mandatory file `".$_[0]."` does not exist, or is not readable!\n"; + warn "fhem-speech has been stopped. please fix this problem...\n"; + exit(1); +} + +##################################### +sub _missing_argv { + $call = _call(); + _debug($call,"called") if($sys{debug}); + warn "fhem-speech: You must specify one of the `-".$_[0]."` options.\n"; + warn "Try `fhem-speech --help` for more information.\n"; + exit(1); +} + +##################################### +sub _wrong_set { + $call = _call(); + _debug($call,"called") if($sys{debug}); + warn "fhem-speech: Wrong combination! Usage: `".$_[0]."` for options!\n"; + warn "Try `fhem-speech --help` for more information.\n"; + exit(1); +} + +##################################### +sub _missing_required { + $call = _call(); + _debug($call,"called") if($sys{debug}); + warn "fhem-speech: Missing required option `".$_[0]."`!\n"; + warn "Try `fhem-speech --help` for more information.\n"; + exit(1); +} + +##################################### +sub _fhem_error { + $call = _call(); + _debug($call,"called") if($sys{debug}); + $_[0] = $lang{'error'} if(!$_[0]); + chomp $_[0]; + warn "fhem-speech: FHEM result: `".$_[0]."`\n"; + #text2speech($_[0]); + exit(1); +} + +##################################### +sub _file_error { + $call = _call(); + _debug($call,"called") if($sys{debug}); + $_[0] = $lang{'error'} if(!$_[0]); + warn "fhem-speech: File `".$_[0]."` is binary. This filetype is not supported!\n"; + exit(1); +} + +##################################### +sub _sox_format_error { + $call = _call(); + _debug($call,"called") if($sys{debug}); + $_[0] = $lang{'error'} if(!$_[0]); + warn "fhem-speech: format `".$_[0]."` not supported!\n"; + exit(1); +} + + +################################################## +__END__ + +=head1 NAME + +fhem-speech - Synthesized voice (based on MBROLA) extension for FHEM + +=head1 SYNOPSIS + +B B<-d> device [B<-achopqS>] + +B B<-d> device B<--set> state [B<-hp>] + +B B<-f> file [B<-acoqS>] + +B B<-t> "text" [B<-acoqS>] + +B [B<-HmV?>] + +Try `B B<--man>` for full manual! + +=head1 DESCRIPTION + +=head2 fhem-speech + +fhem-speech read the status of a FHEM device and talk using the MBROLA +speech synthesizer. Furthermore it can read the content of a given file +or text. + +=head2 FHEM + +FHEM is used to automate some common tasks in the household like switching +lamps/shutters/heating/etc. and to log events like temperature/humidity/power +consumption. Visit the FHEM's homepage L +for more information. + +=head2 The MBROLA project + +Central to the MBROLA project is MBROLA, a speech synthesizer based on the +concatenation of diphones. It takes a list of phonemes as input, together +with prosodic information, and produces speech samples on 16 bits (linear), +at the sampling frequency of the diphone database used. This synthesizer +is provided for free, for non commercial, non military applications +only. Visit the MBROLA's homepage L for +more information. + +=head2 Asterisk + +Optionally fhem-speech supports AGI commands to communicate with Asterisk. +Visit the Asterisk(R) homepage L for more information. + +=head1 OPTIONS + +Mandatory arguments to long options are mandatory for short options too. +Ordering Options: + +=over + +=item B<-d>, B<--device> F + +Run in FHEM mode. Specifies the FHEM device to be queried. The given device +must be defined. + +=item B<-f>, B<--file> F + +Run in file mode. fhem-speech will read the given file. + +=item B<-t>, B<--text> F<"TEXT"> + +Run in Speaker's mode. fhem-speech will read the given "TEXT". + +=back + +Other options: + +=over + +=item B<-a>, B<--asterisk> + +Run in Asterisk mode. fhem-speech print out AGI-commands for direct usage in Asterisk. + +=item B<-c>, B<--cache> F + +Specifies the location of where the files should be saved if fhem-speech +started with the -o or --out argument. + +Default location: current directory. + +=item B<--force> + +Overwrites existing files. + +=item B<-h>, B<--host> F + +Specifies the hostaddress for FHEM. + +Default address: "localhost". + +=item B<-o>, B<--out> [F|F] + +fhem-speech saves the output to a file with the specified output format. + +Default address: "localhost". + +=item B<-p>, B<--port> F + +Communicate with FHEM on defined port. + +Default port: "7072". + +=item B<--prefix> F + +Set the given prefix in front of filename. + +=item B<-q>, B<--quiet> + +Run in quiet mode. + +=item B<--set> F + +Send to device. + +=item B<-S>, B<--sex> [F|F] + +Specifies the sex for the voice. It depends on which voices for MBROLA +have been installed. + +Default: "de3" for the German female voice and "de2" for the German +male voice. + +=item B<-m>, B<--man> + +Show the manual page and exits. + +=item B<-H>, B<--help> + +Show a brief help message and exits. + +=item B<-V>, B<--version> + +Show fhem-speech's version number and exit. + +=back + +=head1 EXAMPLES + +Get status information for device in quiet mode: + + `fhem-speech -d EG.wz.HZ -q` + +Same as above with a male voice. FHEM runs on IP 192.168.1.100: + + `fhem-speech -d EG.wz.HZ -S m -h 192.168.1.100` + +Get status information for device in Asterisk mode: + + `fhem-speech -d EG.wz.HZ -a -q -o gsm -c /var/lib/asterisk/sounds/fhem/` + +Read the file : + + `fhem-speech -f foobar` + +Read the given text "Geht nicht gibt's nicht.": + + `fhem-speech -t "Geht nicht gibt's nicht."` + +Set the state for device : + + `fhem-speech -d EG.wz.SD.01 --set on` + +=head1 INSTALLATION + +=head2 Requirements + +=head3 MBROLA + +You need MBROLA synthesizer, a synthesis voice, txt2pho and sox. For more +information visit: + +o MBROLA project, L + +o hadifix, L + +=head3 FHEM + +For FHEM mode you need FHEM 4.5+ and the command extension "jsonlist". For +more information take a look at: + +/F + +or visit the FHEM's homepage: + +L + +=head3 JSON::XS + +The required command extension "jsonlist" send the result as a JSON encoded +string. fhem-speech need the Perl module JSON::XS to decode the information. + +There are several ways to install the module: + +You can download the last version at: + +L + +Or you can use the package from the L-folder which was delivered +with fhem-speech. + +You can use the L command on bash-prompt. + +=head2 Installation + +This describes the installation on ubuntu: + +Make a temporarily directory for the needed files and change to the new +directory, e.g.: + + `mkdir /usr/local/src/mbrola; cd !$` + +Download the required files: + + `wget http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/txt2pho.zip` + `wget http://tcts.fpms.ac.be/synthesis/mbrola/bin/pclinux/mbrola3.0.1h_i386.deb` + +Download at least one synthesis voice (e.g. German female voice): + + `wget http://tcts.fpms.ac.be/synthesis/mbrola/dba/de3/de3.zip` + +=head2 txt2pho + +Install txt2pho: + + `unzip txt2pho.zip -d /usr/share/` + `chmod 755 /usr/share/txt2pho/txt2pho` + +Edit txt2phorc: + + `vi /usr/share/txt2pho/txt2phorc` + +and change the path for DATAPATH and INVPATH: + + DATAPATH=/usr/share/txt2pho/data/ + INVPATH=/usr/share/txt2pho/data/ + +Copy txt2phorc to /etc/txt2pho: + + `cp /usr/share/txt2pho/txt2phorc /etc/txt2pho` + +=head2 Synthesis Voice + +Install the synthesis voice (e.g. German female voice): + + `unzip de7.zip -d /usr/share/mbrola/de7` + +fhem-speech use "de2" and "de3" as default voices. You can change this +if you like. + +=head2 MBROLA + +Install MBROLA: + + `dpkg -i mbrola3.0.1h_i386.deb` + +=head2 sox + +Install sox: + + `apt-get install sox libsox-fmt-all` + +=head2 Test + +Test your installation: + + `echo "Test" | /usr/share/txt2pho/txt2pho |\ + mbrola /usr/share/mbrola/de7/de7 - -.au | play -q -t au -` + +=head2 fhem-speech + +Copy the script fhem-speech to a directory of your choice, e.g.: + + `cp fhem-speech /usr/local/bin` + +and make it executable: + + `chmod 775 /usr/local/bin/fhem-speech` + +=head2 Perl + +If you use the delivered module F: + + `tar xzf JSON-XS-2.231.tar.gz` + `cd JSON-XS-2.231` + `perl Makefile.pl` + `make` + `make test` + +and as root: + + `make install` + +=head1 CONFIGURATION + +Open fhem-speech with your prefered editor. + +=head2 FHEM host settings + +Change the default host, if you like: + + ########################### + # FHEM + $sys{fhem}{host} = "localhost"; + $sys{fhem}{port} = "7072"; + +=head2 External commands + +Change the paths depending on the installed distribution: + + ########################### + # Mandatory external Files + $sys{file}{mbrola} = "/usr/local/bin/mbrola"; + $sys{file}{pipefilt} = "/usr/local/bin/pipefilt"; + $sys{file}{play} = "/usr/bin/play"; + $sys{file}{preproc} = "/usr/local/bin/preproc"; + [...] + +Change the default settings for synthesis voice: + + ########################### + # mbrola / txt2pho options + $sys{speech}{sex} = "f"; + $sys{speech}{male} = "-f0.8 -t0.9 -l 15000"; + $sys{speech}{female} = "-f1.2 -t1.0 -l 22050"; + +=head2 Translation + +fhem-speech need the $lang{} settings to decide what messages from FHEM +to be spoken. For example take a look at the FHT part: + + ########################### + # FHEM Translation + + [...] + + ########################### + # FHT + # keys: + $lang{'actuator'} = "Ventilstellung: %s Prozent"; + $lang{'day-temp'} = "Temperatur Tag: %s Grad"; + $lang{'desired-temp'} = "Angeforderte Temperatur: %s Grad"; + $lang{'measured-temp'} = "Gemessene Temperatur: %s Grad"; + $lang{'mode'} = "Modus: %s"; + $lang{'night-temp'} = "Temperatur Nacht: %s Grad"; + $lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad"; + [...] + +On every FHEM response all of the defined $lang{} status information will +be spoken. If you don't like status information for e.g. 'windowopen-temp' then comment this out: + + # $lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad"; + +If you like to know the status for e.g. 'lowtemp-offset' add a line like this: + + $lang{'lowtemp-offset'} = "Versatz Temperatur %s Grad"; + +The '%s' stands as a placeholder for the value. + +=head1 OPTIONAL + +=head2 Asterisk + +fhem-speech support AGI commands for direct output in Asterisk. + +=head3 Wrapper + +If you like fhem-speech for use in Asterisk, you have to install a wrapper around +fhem-speech. You can use the example from F. + +Copy the wrapper to your asterisk-environment, e.g: + + `cp contrib/fhem-speech.agi /var/lib/asterisk/agi-bin/` + +=head3 extension.conf + +Take a look at the example from F. + +=head1 LEGALESE + +License GPLv3+: GNU GPL version 3 or later . + +This is free software: you are free to change and redistribute it. There is +NO WARRANTY, to the extent permitted by law. + +=head1 AUTHOR + +Copyright (C) 2008 Martin Fischer + +=cut + diff --git a/fhem/contrib/fhem2speech/fhem-speech.agi b/fhem/contrib/fhem2speech/fhem-speech.agi new file mode 100755 index 000000000..9b0ad4169 --- /dev/null +++ b/fhem/contrib/fhem2speech/fhem-speech.agi @@ -0,0 +1,55 @@ +#!/usr/bin/perl +################################################################ +# +# $Id: fhem-speech.agi,v 1.1 2009-01-12 10:26:50 rudolfkoenig Exp $ +# + +use strict; + +$|=1; + +# Setup some variables +my $sounds = "/var/lib/asterisk/sounds/fhem/"; + +my %AGI; +my $tests = 0; +my $fail = 0; +my $pass = 0; + +while() { + chomp; + last unless length($_); + if (/^agi_(\w+)\:\s+(.*)$/) { + $AGI{$1} = $2; + } +} + +print STDERR "AGI Environment Dump:\n"; +foreach my $i (sort keys %AGI) { + print STDERR " -- $i = $AGI{$i}\n"; +} + +sub checkresult { + my ($res) = @_; + my $retval; + $tests++; + chomp $res; + if ($res =~ /^200/) { + $res =~ /result=(-?\d+)/; + if (!length($1)) { + print STDERR "FAIL ($res)\n"; + $fail++; + } else { + print STDERR "PASS ($1)\n"; + $pass++; + } + } else { + print STDERR "FAIL (unexpected result '$res')\n"; + $fail++; + } +} + +system("fhem-speech -d $ARGV[1] -a -q -o gsm -c $sounds") if ($ARGV[0] eq "d"); +system("fhem-speech -t $ARGV[1] -a -q -o gsm -c $sounds") if ($ARGV[0] eq "t"); +system("fhem-speech -d $ARGV[1] --set $ARGV[2]") if ($ARGV[0] eq "s"); + diff --git a/fhem/contrib/fhem2speech/fhem2speech.sh b/fhem/contrib/fhem2speech/fhem2speech.sh deleted file mode 100755 index 1a20c5e0b..000000000 --- a/fhem/contrib/fhem2speech/fhem2speech.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -if [ $# = 0 ]; then - echo "Usage: `basename $0` -f [filename]" - echo " `basename $0` -s \"Text\"" - exit 1; -elif [ $1 = -f ]; then - /usr/share/txt2pho/txt2pho -i $2 -f | /usr/bin/mbrola /usr/share/mbrola/de7/de7 - - \ - | /usr/bin/bplay -s 22050 -b 16 -q -elif [ $1 = -s ]; then - echo $2 | /usr/share/txt2pho/txt2pho -f | /usr/bin/mbrola /usr/share/mbrola/de7/de7 - - \ - | /usr/bin/bplay -s 22050 -b 16 -q -fi