fhem-speech 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170
  1. #!/usr/bin/perl
  2. ################################################################
  3. #
  4. # $Id: fhem-speech,v 1.1 2009-01-12 10:26:50 rudolfkoenig Exp $
  5. #
  6. # Copyright notice
  7. #
  8. # (c) 2008 Copyright: Martin Fischer (m_fischer at gmx dot de)
  9. # All rights reserved
  10. #
  11. # This script free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # The GNU General Public License can be found at
  17. # http://www.gnu.org/copyleft/gpl.html.
  18. # A copy is found in the textfile GPL.txt and important notices to the license
  19. # from the author is found in LICENSE.txt distributed with these scripts.
  20. #
  21. # This script is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. # GNU General Public License for more details.
  25. #
  26. ################################################################
  27. use strict;
  28. use warnings;
  29. use Getopt::Long qw(:config no_ignore_case);;
  30. use Cwd;
  31. use IO::Socket::INET;
  32. use IO::File;
  33. use Pod::Usage;
  34. use JSON::XS;
  35. use vars qw{$call};
  36. use vars qw{%dev};
  37. use vars qw(%lang);
  38. use vars qw{%sys};
  39. use vars qw($VERSION);
  40. use vars qw($VERSION);
  41. ##################################################
  42. # Variables
  43. ###########################
  44. # FHEM
  45. $sys{fhem}{host} = "192.168.1.100";
  46. $sys{fhem}{port} = "7072";
  47. ###########################
  48. # Mandatory external Files
  49. $sys{file}{mbrola} = "/usr/local/bin/mbrola";
  50. $sys{file}{pipefilt} = "/usr/local/bin/pipefilt";
  51. $sys{file}{play} = "/usr/bin/play";
  52. $sys{file}{preproc} = "/usr/local/bin/preproc";
  53. $sys{file}{preprocRules} = "/usr/share/mbrola/Rules.lst";
  54. $sys{file}{preprocShort} = "/usr/share/mbrola/Hadifix.abk";
  55. $sys{file}{sox} = "/usr/bin/sox";
  56. $sys{file}{recode} = "/usr/bin/recode";
  57. $sys{file}{txt2pho} = "/usr/local/bin/txt2pho";
  58. $sys{file}{voiceFemale} = "/usr/share/mbrola/de3/de3";
  59. $sys{file}{voiceMale} = "/usr/share/mbrola/de2/de2";
  60. ###########################
  61. # mbrola / txt2pho options
  62. $sys{speech}{sex} = "f";
  63. $sys{speech}{male} = "-f0.8 -t0.9 -l 15000";
  64. $sys{speech}{female} = "-f1.2 -t1.0 -l 22050";
  65. $sys{speech}{wav} = "-t au - -r 8000 -c1";
  66. $sys{speech}{gsm} = "-t au - -r 8000 -c1";
  67. ###########################
  68. # FHEM Translation
  69. ###########################
  70. # misc / default
  71. $lang{'comment'} = "Status %s";
  72. $lang{'room'} = "Raum %s";
  73. $lang{'battery'} = "Batterie %s";
  74. $lang{'state'} = "%s";
  75. $lang{'on'} = "an";
  76. $lang{'off'} = "aus";
  77. $lang{'yes'} = "ja";
  78. $lang{'no'} = "nein";
  79. $lang{'comma'} = "Komma";
  80. $lang{'error'} = "Status unbekannt";
  81. $lang{'zirkumflex'} = "Zirkumflex";
  82. $lang{'underline'} = "Unterstrich";
  83. $lang{'apostrophe'} = "Hochkomma";
  84. $lang{'degree'} = "Grad";
  85. $lang{'minus'} = "Minus";
  86. $lang{'plus'} = "Plus";
  87. $lang{'squareopen'} = "eckige Klammer auf";
  88. $lang{'squareclose'} = "eckige Klammer zu";
  89. $lang{'backtick'} = "Rueckwaerts geneigtes Hochkomma";
  90. $lang{'singlequote'} = "einfaches Anfuehrungszeichen";
  91. $lang{'quote'} = "Anfuehrungszeichen oben";
  92. $lang{'backslash'} = "umgekehrter Schraegstrich";
  93. $lang{'squaremm'} = "Quadratmilimeter";
  94. $lang{'squarecm'} = "Quadratzentimeter";
  95. $lang{'squarem'} = "Quadratmeter";
  96. $lang{'cubicmm'} = "Kubikmilimeter";
  97. $lang{'cubiccm'} = "Kubikzentimeter";
  98. $lang{'cubicm'} = "Kubikmeter";
  99. ###########################
  100. # FHT
  101. # keys:
  102. $lang{'actuator'} = "Ventilstellung %s Prozent";
  103. $lang{'day-temp'} = "Temperatur Tag %s Grad";
  104. $lang{'desired-temp'} = "Angeforderte Temperatur %s Grad";
  105. $lang{'measured-temp'} = "Gemessene Temperatur %s Grad";
  106. $lang{'mode'} = "Modus %s";
  107. $lang{'night-temp'} = "Temperatur Nacht %s Grad";
  108. $lang{'windowopen-temp'} = "Temperatur Fenster offen %s Grad";
  109. # values:
  110. $lang{'auto'} = "Automatik";
  111. $lang{'holiday'} = "Urlaub";
  112. $lang{'holiday_short'} = "Kurzurlaub";
  113. $lang{'manual'} = "Manuell";
  114. ###########################
  115. # KS300/KS555
  116. # keys:
  117. $lang{'humidity'} = "Luftfeuchtigkeit %s Prozent";
  118. $lang{'israining'} = "Niederschlag %s";
  119. $lang{'temperature'} = "Aussentemperatur %s Grad";
  120. $lang{'wind'} = "Windgeschwindigkeit %s km/h";
  121. ###########################
  122. # HMS
  123. # keys:
  124. $lang{'smoke_detect'} = "Alarm %s";
  125. # End of Variables
  126. ##################################################
  127. ##################################################
  128. # Forward declaration
  129. sub sayFHEM;
  130. sub queryFHEM($);
  131. sub parseFHEM($$);
  132. sub translateKey;
  133. sub translateValue($$);
  134. sub text2speech($);
  135. sub isNumber;
  136. sub isInteger;
  137. sub isFloat;
  138. sub usage;
  139. sub usageShort;
  140. sub version;
  141. main();
  142. exit;
  143. ##################################################
  144. # Main
  145. sub main {
  146. $call = _call();
  147. _debug($call,"called") if(grep(/debug/, @ARGV));
  148. my $result;
  149. # disable buffering
  150. $|=1;
  151. ###########################
  152. # Variables
  153. $VERSION = sprintf("%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/);
  154. my $requiredOptions = "dft";
  155. ###########################
  156. # do some checks
  157. # check for required files
  158. foreach my $exec (sort keys %{$sys{file}}) {
  159. _missing_file($sys{file}{$exec}) unless(-r $sys{file}{$exec});
  160. _debug($call,"check file: '".$sys{file}{$exec}."'") if(grep(/debug/, @ARGV));
  161. }
  162. # check options
  163. _missing_argv($requiredOptions) if(int(@ARGV) == 0);
  164. ###########################
  165. # get options
  166. my $args = {
  167. asterisk => 0, # default false
  168. cache => "", # default undef
  169. debug => 0, # default false
  170. device => "", # default undef
  171. file => "", # default undef
  172. force => 0, # default false
  173. host => "", # default undef
  174. out => "", # default undef
  175. port => "", # default undef
  176. prefix => "", # default undef
  177. quiet => 0, # default false
  178. set => "", # default undef
  179. sex => "", # default undef
  180. text => "", # default undef
  181. };
  182. eval {
  183. local $SIG{__WARN__} = sub {};
  184. GetOptions(
  185. "asterisk|a" => \$args->{asterisk},
  186. "cache|c=s" => \$args->{cache},
  187. "debug" => \$args->{debug},
  188. "device|d=s" => \$args->{device},
  189. "file|f=s" => \$args->{file},
  190. "force" => \$args->{force},
  191. "host|h=s" => \$args->{host},
  192. "out|o=s" => \$args->{out},
  193. "port|p=i" => \$args->{port},
  194. "prefix=s" => \$args->{prefix},
  195. "quiet|q" => \$args->{quiet},
  196. "set=s" => \$args->{set},
  197. "sex|S=s" => \$args->{sex},
  198. "text|t=s" => \$args->{text},
  199. "H|?" => sub { pod2usage( -exitval => 0, -verbose => 0); },
  200. "help" => sub { pod2usage( -exitval => 0, -verbose => 0); },
  201. "man" => sub { pod2usage( -exitval => 0, -verbose => 2); },
  202. "version|V" => sub { version(0) }
  203. );
  204. } or pod2usage();
  205. # set global options
  206. $sys{asterisk} = $args->{asterisk} if($args->{asterisk});
  207. $sys{debug} = $args->{debug} if($args->{debug});
  208. $sys{force} = $args->{force} if($args->{force});
  209. $sys{prefix} = $args->{prefix} if($args->{prefix});
  210. $sys{quiet} = $args->{quiet} if($args->{quiet});
  211. $sys{speech}{sex} = $args->{sex} if($args->{sex});
  212. $sys{speech}{cache} = $args->{cache} if($args->{cache});
  213. $sys{speech}{out} = $args->{out} if($args->{out});
  214. # check for dependent options
  215. _wrong_set("-d or -f or -t") if($args->{device} && ($args->{file} || $args->{text}));
  216. _wrong_set("-d or -f or -t") if($args->{device} && ($args->{file} && $args->{text}));
  217. _wrong_set("-f [-acoqS]") if($args->{file} && ($args->{host} || $args->{port}));
  218. _wrong_set("-f [-acoqS]") if($args->{file} && ($args->{host} && $args->{port}));
  219. _wrong_set("-t [-acoqS]") if($args->{text} && ($args->{host} || $args->{port}));
  220. _wrong_set("-t [-acoqS]") if($args->{text} && ($args->{host} && $args->{port}));
  221. _wrong_set("-d <devspec> --set <state> [-hp]") if(
  222. $args->{set} && ($args->{astersik} || $args->{cache} ||
  223. $args->{file} || $args->{text} || $args->{force} ||
  224. $args->{out} || $args->{prefix} || $args->{quiet} ||
  225. $args->{sex})
  226. );
  227. _wrong_set("[-d|-f|-t] argument [-acopqS]") if($args->{asterisk} && ($args->{host} || $args->{port}));
  228. # check for dependent options
  229. _missing_required("-d") if(!$args->{device} && ($args->{host} || $args->{port}));
  230. _missing_required("-d") if(!$args->{device} && ($args->{host} && $args->{port}));
  231. _missing_required("-o") if(!$args->{out} && $args->{cache});
  232. _missing_required("-c") if(!$args->{cache} && ($args->{prefix} || $args->{force}));
  233. _missing_required("-c") if(!$args->{cache} && ($args->{prefix} && $args->{force}));
  234. _missing_required("-c") if(!$args->{cache} && $args->{asterisk});
  235. # listen to text
  236. if($args->{text}) {
  237. $sys{speech}{text} = $args->{text} if($args->{text});
  238. _debug($call,"ARGV: \$sys{speech}{text}: '".$sys{speech}{text}."'") if($sys{debug});
  239. $result = sayTEXT();
  240. }
  241. # listen to file
  242. if($args->{file}) {
  243. $sys{speech}{file} = $args->{file} if($args->{file});
  244. _debug($call,"ARGV: \$sys{speech}{file}: '".$sys{speech}{file}."'") if($sys{debug} && $args->{file});
  245. $result = sayFILE();
  246. }
  247. # let FHEM talk :-)
  248. if($args->{device}) {
  249. _debug($call,"ARGV is 'fhem'") if($sys{debug});
  250. $sys{fhem}{device} = $args->{device};
  251. _debug($call,"ARGV: \$sys{fhem}{device}: '".$sys{fhem}{device}."'") if($sys{debug});
  252. $sys{fhem}{set} = $args->{set} if($args->{set});
  253. _debug($call,"ARGV: \$sys{fhem}{set}: '".$sys{fhem}{set}."'") if($sys{debug});
  254. $sys{fhem}{host} = $args->{host} if($args->{host});
  255. _debug($call,"ARGV: \$sys{fhem}{host}: '".$sys{fhem}{host}."'") if($sys{debug});
  256. $sys{fhem}{port} = $args->{port} if($args->{port});
  257. _debug($call,"ARGV: \$sys{fhem}{port}: '".$sys{fhem}{port}."'") if($sys{debug});
  258. $result = sayFHEM() if(!$sys{fhem}{set});
  259. $result = setFHEM() if($sys{fhem}{set});
  260. }
  261. return 0;
  262. }
  263. ##################################################
  264. # Text
  265. ###########################
  266. sub sayTEXT {
  267. $call = _call();
  268. _debug($call,"called") if($sys{debug});
  269. my $result;
  270. my $say;
  271. $say = $sys{speech}{text} if($sys{speech}{text});
  272. $result = text2speech($say) if($say);
  273. }
  274. ##################################################
  275. # File
  276. ###########################
  277. sub sayFILE {
  278. $call = _call();
  279. _debug($call,"called") if($sys{debug});
  280. STDOUT->autoflush(1);
  281. my $content = "";
  282. my $file = $sys{speech}{file};
  283. my $result;
  284. _file_error($file) if(!-e $file || -B $file);
  285. my $fh = new IO::File($file, "r") or _file_error($file);
  286. _debug($call,"read : '".$file."'") if($sys{debug});
  287. while (my $line = $fh->getline()) {
  288. $content = $content.$line;
  289. }
  290. $fh->close();
  291. $result=text2speech($content);
  292. }
  293. ##################################################
  294. # FHEM
  295. ###########################
  296. sub setFHEM {
  297. $call = _call();
  298. _debug($call,"called") if($sys{debug});
  299. my $fhemCmd = "set ".$sys{fhem}{device}." ".$sys{fhem}{set};
  300. my $result;
  301. ###########################
  302. # query FHEM and start working
  303. $result = queryFHEM($fhemCmd);
  304. chomp $result if($result);
  305. _debug($call,"\$result: '".$result."'") if($sys{debug} && $result);
  306. _fhem_error($result) if($result && $result =~ /No.*set/);
  307. }
  308. ###########################
  309. sub sayFHEM {
  310. $call = _call();
  311. _debug($call,"called") if($sys{debug});
  312. my $fhemCmd = "jsonlist ".$sys{fhem}{device};
  313. my $result;
  314. my $fhemRaw;
  315. my $say;
  316. ###########################
  317. # query FHEM and start working
  318. $result = queryFHEM($fhemCmd);
  319. chomp $result;
  320. _debug($call,"\$result: '".$result."'") if($sys{debug});
  321. _fhem_error($result) if(!$result || $result =~ /No.*device/);
  322. $fhemRaw = decode_json $result;
  323. %dev = %{$fhemRaw->{ResultSet}->{Results}};
  324. parseFHEM("room",$dev{ATTRIBUTES}{room}) if($dev{ATTRIBUTES}{room});
  325. parseFHEM("comment",$dev{ATTRIBUTES}{comment}) if($dev{ATTRIBUTES}{comment});
  326. while (my ($key,$value) = each %dev) {
  327. _debug($call,"\$key: '".$key."'") if($sys{debug});
  328. if($key eq "ATTRIBUTES" || $key eq "READINGS") {
  329. while (my ($subKey,$subValue) = each %{$dev{$key}}) {
  330. if($subKey ne "comment" && $subKey ne "room") {
  331. _debug($call,"\$subKey: '".$subKey."'") if($sys{debug});
  332. $say = parseFHEM($subKey,$subValue) if($key eq "ATTRIBUTES");
  333. $say = parseFHEM($subKey,$subValue->{VAL}) if($key eq "READINGS");
  334. }
  335. }
  336. } else {
  337. $say = parseFHEM($key,$value);
  338. }
  339. }
  340. }
  341. ###########################
  342. sub queryFHEM($) {
  343. $call = _call();
  344. _debug($call,"called") if($sys{debug});
  345. my $host = $sys{fhem}{host} . ":" . $sys{fhem}{port};
  346. my $cmd = shift;
  347. my $result;
  348. my $buf = "";
  349. my $server = IO::Socket::INET->new(PeerAddr => $host);
  350. _debug($call, "\$host: '".$sys{fhem}{host}.":".$sys{fhem}{port}."'") if($sys{debug});
  351. die "Can't connect to server " . $sys{fhem}{host} . " port " . $sys{fhem}{port} . "\n" if(!$server);
  352. syswrite($server, "$cmd;quit\n");
  353. _debug($call, "\$cmd: '".$cmd."'") if($sys{debug});
  354. while(sysread($server, $buf, 256) > 0) {
  355. $result .= $buf;
  356. }
  357. close($server);
  358. return $result;
  359. }
  360. ###########################
  361. sub parseFHEM($$) {
  362. $call = _call();
  363. _debug($call,"called") if($sys{debug});
  364. my ($key,$value) = @_;
  365. my $translatedKey;
  366. my $translatedValue;
  367. my @say;
  368. my $result;
  369. $result = translateKey($key);
  370. _debug($call,"\$key: '".$key."' \$result = '<null>'") if($sys{debug} && !$result);
  371. if($result) {
  372. $translatedKey = $result;
  373. _debug($call,"\$key: '".$key."' \$translatedKey: '".$translatedKey."'") if($sys{debug});
  374. if($result ne "\%s") {
  375. @say = split("\%s",$translatedKey);
  376. text2speech($say[0]);
  377. translateValue($key,$value);
  378. text2speech($say[1]) if($say[1] && $say[1] ne ":");
  379. } else {
  380. translateValue($key,$value);
  381. }
  382. }
  383. }
  384. ###########################
  385. sub translateKey {
  386. $call = _call();
  387. _debug($call,"called") if($sys{debug});
  388. return $lang{$_[0]} if(exists $lang{$_[0]});
  389. }
  390. ###########################
  391. sub translateValue($$) {
  392. $call = _call();
  393. _debug($call,"called") if($sys{debug});
  394. my ($key,$value) = @_;
  395. _debug($call,"\$key: '".$key."' \$value: '".$value."'") if($sys{debug});
  396. my $return;
  397. $value = removeUnits($value);
  398. # FS20
  399. if($key eq "state" || $key eq "smoke_detect") {
  400. _debug($call,"\$key: '".$key."' is on/off") if($sys{debug});
  401. $value = $lang{'off'} if($value eq "off");
  402. $value = $lang{'on'} if($value eq "on");
  403. }
  404. # KS300/KS555
  405. if($key eq "israining") {
  406. _debug($call,"\$value: '".$value."'") if($sys{debug});
  407. $value = $lang{'no'} if($value eq "no (yes/no)");
  408. $value = $lang{'yes'} if($value eq "yes (yes/no)");
  409. _debug($call,"\$value converted to: '".$value."'") if($sys{debug});
  410. }
  411. if($value =~ m/^-/) {
  412. text2speech($lang{'minus'});
  413. $value = substr($value,1);
  414. }
  415. if(isInteger($value)) {
  416. text2speech(decodeChar($value));
  417. } elsif(isFloat($value)) {
  418. my ($strLeft,$strRight) = split("\\.",$value);
  419. _debug($call,"\$strLeft: '".$strLeft."' \$strRight '".$strRight."'") if($sys{debug});
  420. text2speech(decodeChar($strLeft));
  421. if($strRight !~ m/0+/) {
  422. text2speech($lang{'comma'});
  423. text2speech(decodeChar($strRight));
  424. }
  425. } else {
  426. text2speech(decodeChar($value));
  427. }
  428. }
  429. ##################################################
  430. # mbrola
  431. ###########################
  432. sub text2speech($) {
  433. $call = _call();
  434. _debug($call,"called") if($sys{debug});
  435. my $str = shift;
  436. my $cmd;
  437. my $voice;
  438. my $options;
  439. my $out;
  440. my $result;
  441. # define voice
  442. if($sys{speech}{sex} eq "f") {
  443. $voice = $sys{file}{voiceFemale};
  444. $options = $sys{speech}{female};
  445. }
  446. if($sys{speech}{sex} eq "m") {
  447. $voice = $sys{file}{voiceMale};
  448. $options = $sys{speech}{male};
  449. }
  450. # define extern commands
  451. my $recode = $sys{file}{recode} . " " . "UTF-8..lat1";
  452. my $pipefilt = $sys{file}{pipefilt};
  453. my $preproc = $sys{file}{preproc} . " " . $sys{file}{preprocRules} . " " . $sys{file}{preprocShort};
  454. my $txt2pho = $sys{file}{txt2pho} . " -" . $sys{speech}{sex};
  455. my $mbrola = $sys{file}{mbrola} . " $options " . $voice . " - -.au";
  456. my $sox = $sys{file}{sox};
  457. # set current dir for output if no directory defined
  458. $sys{speech}{cache} = cwd() if(!$sys{speech}{cache} && $sys{speech}{out});
  459. # sox options
  460. if(defined($sys{speech}{out})) {
  461. if($sys{speech}{out} eq "wav") {
  462. # options for wav
  463. $sys{speech}{soxOptions} = $sys{speech}{wav};
  464. } elsif($sys{speech}{out} eq "gsm") {
  465. # options for gsm
  466. $sys{speech}{soxOptions} = $sys{speech}{gsm};
  467. # } elsif($sys{speech}{out} eq "mp3") {
  468. # # options for mp3
  469. # $sys{speech}{soxOptions} = $sys{speech}{mp3};
  470. } else {
  471. _sox_format_error($sys{speech}{out});
  472. }
  473. }
  474. # remove blanks
  475. _debug($call,"remove bad chars") if($sys{debug});
  476. # remove unwanted chars
  477. $str = decodeChar($str);
  478. print $str . "\n" if(!$sys{quiet} && !$sys{asterisk});
  479. # remove trailing slash
  480. $sys{speech}{cache} =~ s/\/+$// if($sys{speech}{out});
  481. _debug($call,"\$str: '".substr($str,0,40)."'") if($sys{debug});
  482. _debug($call,"\$sys{speech}{sex}: '".$sys{speech}{sex}."'") if($sys{debug});
  483. # build command string
  484. $cmd = "(echo \"$str\" | ";
  485. # pipefilt | preproc | txt2pho | mbrola |
  486. $cmd .= $recode . " | " . $pipefilt . " | " . $preproc . " | " . $txt2pho . " | " . $mbrola . " | ";
  487. # append command string for play only
  488. if(!$sys{speech}{out}) {
  489. $cmd .= $sys{file}{play} . " -q -t au -)";
  490. # let's get ready to rumble
  491. $result = systemCmd($cmd);
  492. }
  493. # append command string for destination file
  494. if($sys{speech}{out}) {
  495. $out = substr($str,0,32);
  496. $out =~ s/[^a-zA-Z0-9]/_/gi;
  497. $out = $sys{prefix}.$out if($sys{prefix});
  498. _debug($call,"\$sys{speech}{out}: '".$sys{speech}{out}."'") if($sys{debug});
  499. _debug($call,"\$sys{speech}{cache}: '".$sys{speech}{cache}."' \$out: '".$out."'") if($sys{debug});
  500. # sox
  501. $cmd .= $sox . " " . $sys{speech}{soxOptions} . " " . $sys{speech}{cache} . "/" . $out . ".wav";
  502. # extend cmd for gsm
  503. if($sys{speech}{out} eq "gsm") {
  504. $cmd .= "; ";
  505. # sox imput from wav
  506. $cmd .= $sox . " " . $sys{speech}{cache} . "/" . $out . ".wav" . " ";
  507. # sox output to gsm
  508. $cmd .= $sys{speech}{cache} . "/" . $out . "." . $sys{speech}{out} . "; ";
  509. # remove temporary wav file
  510. $cmd .= "rm " . $sys{speech}{cache} . "/" . $out . ".wav" . "; ";
  511. }
  512. # close cmd
  513. $cmd .= ")";
  514. # let's get ready to rumble
  515. if($sys{force} || !-r $sys{speech}{cache} . "/" . $out . "." . $sys{speech}{out}) {
  516. $result = systemCmd($cmd);
  517. }
  518. # print string for asterisk
  519. if($sys{asterisk}) {
  520. print "EXEC BACKGROUND ". $sys{speech}{cache} . "/" . $out . " \"\"\n";
  521. $result = <STDIN>;
  522. }
  523. # play the result
  524. if(!$sys{quiet}) {
  525. $cmd = $sys{file}{play} . " -q -t " . $sys{speech}{out} . " ";
  526. $cmd .= $sys{speech}{cache} . "/" . $out . "." . $sys{speech}{out};
  527. $result = systemCmd($cmd);
  528. }
  529. }
  530. }
  531. ##################################################
  532. # misc
  533. #####################################
  534. sub version {
  535. print <<EOT;
  536. \e[1mfhem-speech $VERSION\e[0m
  537. Copyright (C) 2008 Martin Fischer <m_fischer\@gmx.de>
  538. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  539. This is free software: you are free to change and redistribute it.
  540. There is NO WARRANTY, to the extent permitted by law.
  541. Written by Martin Fischer
  542. EOT
  543. exit($_[0]);
  544. }
  545. ##################################################
  546. # helper
  547. #####################################
  548. sub systemCmd {
  549. $call = _call();
  550. _debug($call,"called") if($sys{debug});
  551. _debug($call,"system: '" . $_[0] . "'") if($sys{debug});
  552. return system($_[0]) == 0 || die "failed: $?";
  553. }
  554. #####################################
  555. sub removeUnits {
  556. $call = _call();
  557. _debug($call,"called") if($sys{debug});
  558. $_[0] =~ s/\s\(Celsius\)//gi;
  559. $_[0] =~ s/\s\(%\)//gi;
  560. $_[0] =~ s/\s\(km\/h\)//gi;
  561. $_[0] =~ s/%//gi;
  562. return $_[0];
  563. }
  564. #####################################
  565. sub decodeChar {
  566. $call = _call();
  567. _debug($call,"called") if($sys{debug});
  568. $_[0] =~ s/^[\s\t]+//gi;
  569. $_[0] =~ s/[\s\t]$//gi;
  570. $_[0] =~ s/\^/ $lang{zirkumflex} /gi;
  571. $_[0] =~ s/_/ $lang{underline} /gi;
  572. $_[0] =~ s/'/ $lang{apostrophe} /gi;
  573. $_[0] =~ s/°/ $lang{degree} /gi;
  574. $_[0] =~ s/-/ $lang{minus} /gi;
  575. $_[0] =~ s/\+/ $lang{plus} /gi;
  576. $_[0] =~ s/\[/ $lang{squareopen} /gi;
  577. $_[0] =~ s/\]/ $lang{squareclose} /gi;
  578. $_[0] =~ s/`/ $lang{backtick} /gi;
  579. $_[0] =~ s/\'/ $lang{singlequote} /gi;
  580. $_[0] =~ s/"/ $lang{quote} /gi;
  581. $_[0] =~ s/\\/ $lang{backslash} /gi;
  582. $_[0] =~ s/mm²/$lang{squaremm} /gi;
  583. $_[0] =~ s/cm²/$lang{squarecm} /gi;
  584. $_[0] =~ s/m²/$lang{squarem} /gi;
  585. $_[0] =~ s/mm³/$lang{cubicmm} /gi;
  586. $_[0] =~ s/cm³/$lang{cubiccm} /gi;
  587. $_[0] =~ s/m³/$lang{cubicm} /gi;
  588. $_[0] =~ s/[\s\t]+/ /gi;
  589. $_[0] =~ s/[[:cntrl:]]+//gi;
  590. # convert to lowercase
  591. $_[0] = lc($_[0]);;
  592. return $_[0];
  593. }
  594. #####################################
  595. sub isNumber {
  596. $call = _call();
  597. _debug($call,"called") if($sys{debug});
  598. $_[0] =~ /^\d+$/
  599. }
  600. #####################################
  601. sub isInteger {
  602. $call = _call();
  603. _debug($call,"called") if($sys{debug});
  604. $_[0] =~ /^[+-]?\d+$/
  605. }
  606. #####################################
  607. sub isFloat {
  608. $call = _call();
  609. _debug($call,"called") if($sys{debug});
  610. $_[0] =~ /^[+-]?\d+\.?\d*$/
  611. }
  612. ###########################
  613. sub _debug {
  614. printf("\e[33m== debug:\e[37m \e[32m[%s]\e[37m \e[1m%s\e[0m\n",$_[0],$_[1]);
  615. }
  616. ###########################
  617. sub _call {
  618. (caller(1))[3];
  619. }
  620. #####################################
  621. sub _missing_file {
  622. $call = _call();
  623. _debug($call,"called") if($sys{debug});
  624. warn "fhem-speech: Mandatory file `".$_[0]."` does not exist, or is not readable!\n";
  625. warn "fhem-speech has been stopped. please fix this problem...\n";
  626. exit(1);
  627. }
  628. #####################################
  629. sub _missing_argv {
  630. $call = _call();
  631. _debug($call,"called") if($sys{debug});
  632. warn "fhem-speech: You must specify one of the `-".$_[0]."` options.\n";
  633. warn "Try `fhem-speech --help` for more information.\n";
  634. exit(1);
  635. }
  636. #####################################
  637. sub _wrong_set {
  638. $call = _call();
  639. _debug($call,"called") if($sys{debug});
  640. warn "fhem-speech: Wrong combination! Usage: `".$_[0]."` for options!\n";
  641. warn "Try `fhem-speech --help` for more information.\n";
  642. exit(1);
  643. }
  644. #####################################
  645. sub _missing_required {
  646. $call = _call();
  647. _debug($call,"called") if($sys{debug});
  648. warn "fhem-speech: Missing required option `".$_[0]."`!\n";
  649. warn "Try `fhem-speech --help` for more information.\n";
  650. exit(1);
  651. }
  652. #####################################
  653. sub _fhem_error {
  654. $call = _call();
  655. _debug($call,"called") if($sys{debug});
  656. $_[0] = $lang{'error'} if(!$_[0]);
  657. chomp $_[0];
  658. warn "fhem-speech: FHEM result: `".$_[0]."`\n";
  659. #text2speech($_[0]);
  660. exit(1);
  661. }
  662. #####################################
  663. sub _file_error {
  664. $call = _call();
  665. _debug($call,"called") if($sys{debug});
  666. $_[0] = $lang{'error'} if(!$_[0]);
  667. warn "fhem-speech: File `".$_[0]."` is binary. This filetype is not supported!\n";
  668. exit(1);
  669. }
  670. #####################################
  671. sub _sox_format_error {
  672. $call = _call();
  673. _debug($call,"called") if($sys{debug});
  674. $_[0] = $lang{'error'} if(!$_[0]);
  675. warn "fhem-speech: format `".$_[0]."` not supported!\n";
  676. exit(1);
  677. }
  678. ##################################################
  679. __END__
  680. =head1 NAME
  681. fhem-speech - Synthesized voice (based on MBROLA) extension for FHEM
  682. =head1 SYNOPSIS
  683. B<fhem-speech> B<-d> device [B<-achopqS>]
  684. B<fhem-speech> B<-d> device B<--set> state [B<-hp>]
  685. B<fhem-speech> B<-f> file [B<-acoqS>]
  686. B<fhem-speech> B<-t> "text" [B<-acoqS>]
  687. B<fhem-speech> [B<-HmV?>]
  688. Try `B<fhem-speech> B<--man>` for full manual!
  689. =head1 DESCRIPTION
  690. =head2 fhem-speech
  691. fhem-speech read the status of a FHEM device and talk using the MBROLA
  692. speech synthesizer. Furthermore it can read the content of a given file
  693. or text.
  694. =head2 FHEM
  695. FHEM is used to automate some common tasks in the household like switching
  696. lamps/shutters/heating/etc. and to log events like temperature/humidity/power
  697. consumption. Visit the FHEM's homepage L<http://www.koeniglich.de/fhem/fhem.html>
  698. for more information.
  699. =head2 The MBROLA project
  700. Central to the MBROLA project is MBROLA, a speech synthesizer based on the
  701. concatenation of diphones. It takes a list of phonemes as input, together
  702. with prosodic information, and produces speech samples on 16 bits (linear),
  703. at the sampling frequency of the diphone database used. This synthesizer
  704. is provided for free, for non commercial, non military applications
  705. only. Visit the MBROLA's homepage L<http://tcts.fpms.ac.be/synthesis/> for
  706. more information.
  707. =head2 Asterisk
  708. Optionally fhem-speech supports AGI commands to communicate with Asterisk.
  709. Visit the Asterisk(R) homepage L<http://www.asterisk.org/> for more information.
  710. =head1 OPTIONS
  711. Mandatory arguments to long options are mandatory for short options too.
  712. Ordering Options:
  713. =over
  714. =item B<-d>, B<--device> F<device>
  715. Run in FHEM mode. Specifies the FHEM device to be queried. The given device
  716. must be defined.
  717. =item B<-f>, B<--file> F<file-name>
  718. Run in file mode. fhem-speech will read the given file.
  719. =item B<-t>, B<--text> F<"TEXT">
  720. Run in Speaker's mode. fhem-speech will read the given "TEXT".
  721. =back
  722. Other options:
  723. =over
  724. =item B<-a>, B<--asterisk>
  725. Run in Asterisk mode. fhem-speech print out AGI-commands for direct usage in Asterisk.
  726. =item B<-c>, B<--cache> F<directory>
  727. Specifies the location of where the files should be saved if fhem-speech
  728. started with the -o or --out argument.
  729. Default location: current directory.
  730. =item B<--force>
  731. Overwrites existing files.
  732. =item B<-h>, B<--host> F<host>
  733. Specifies the hostaddress for FHEM.
  734. Default address: "localhost".
  735. =item B<-o>, B<--out> [F<gsm>|F<wav>]
  736. fhem-speech saves the output to a file with the specified output format.
  737. Default address: "localhost".
  738. =item B<-p>, B<--port> F<port>
  739. Communicate with FHEM on defined port.
  740. Default port: "7072".
  741. =item B<--prefix> F<prefix>
  742. Set the given prefix in front of filename.
  743. =item B<-q>, B<--quiet>
  744. Run in quiet mode.
  745. =item B<--set> F<state>
  746. Send <state> to device.
  747. =item B<-S>, B<--sex> [F<f>|F<m>]
  748. Specifies the sex for the voice. It depends on which voices for MBROLA
  749. have been installed.
  750. Default: "de3" for the German female voice and "de2" for the German
  751. male voice.
  752. =item B<-m>, B<--man>
  753. Show the manual page and exits.
  754. =item B<-H>, B<--help>
  755. Show a brief help message and exits.
  756. =item B<-V>, B<--version>
  757. Show fhem-speech's version number and exit.
  758. =back
  759. =head1 EXAMPLES
  760. Get status information for device <EG.wz.HZ> in quiet mode:
  761. `fhem-speech -d EG.wz.HZ -q`
  762. Same as above with a male voice. FHEM runs on IP 192.168.1.100:
  763. `fhem-speech -d EG.wz.HZ -S m -h 192.168.1.100`
  764. Get status information for device <EG.wz.HZ> in Asterisk mode:
  765. `fhem-speech -d EG.wz.HZ -a -q -o gsm -c /var/lib/asterisk/sounds/fhem/`
  766. Read the file <foobar>:
  767. `fhem-speech -f foobar`
  768. Read the given text "Geht nicht gibt's nicht.":
  769. `fhem-speech -t "Geht nicht gibt's nicht."`
  770. Set the state for device <EG.wz.SD.01>:
  771. `fhem-speech -d EG.wz.SD.01 --set on`
  772. =head1 INSTALLATION
  773. =head2 Requirements
  774. =head3 MBROLA
  775. You need MBROLA synthesizer, a synthesis voice, txt2pho and sox. For more
  776. information visit:
  777. o MBROLA project, L<http://tcts.fpms.ac.be/synthesis/>
  778. o hadifix, L<http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/>
  779. =head3 FHEM
  780. For FHEM mode you need FHEM 4.5+ and the command extension "jsonlist". For
  781. more information take a look at:
  782. <fhem_src_path>/F<contrib/JsonList/README.JsonList>
  783. or visit the FHEM's homepage:
  784. L<http://www.koeniglich.de/fhem/fhem.html>
  785. =head3 JSON::XS
  786. The required command extension "jsonlist" send the result as a JSON encoded
  787. string. fhem-speech need the Perl module JSON::XS to decode the information.
  788. There are several ways to install the module:
  789. You can download the last version at:
  790. L<http://search.cpan.org/~mlehmann/JSON-XS-2.231/XS.pm>
  791. Or you can use the package from the L<contrib>-folder which was delivered
  792. with fhem-speech.
  793. You can use the L<cpan> command on bash-prompt.
  794. =head2 Installation
  795. This describes the installation on ubuntu:
  796. Make a temporarily directory for the needed files and change to the new
  797. directory, e.g.:
  798. `mkdir /usr/local/src/mbrola; cd !$`
  799. Download the required files:
  800. `wget http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/txt2pho.zip`
  801. `wget http://tcts.fpms.ac.be/synthesis/mbrola/bin/pclinux/mbrola3.0.1h_i386.deb`
  802. Download at least one synthesis voice (e.g. German female voice):
  803. `wget http://tcts.fpms.ac.be/synthesis/mbrola/dba/de3/de3.zip`
  804. =head2 txt2pho
  805. Install txt2pho:
  806. `unzip txt2pho.zip -d /usr/share/`
  807. `chmod 755 /usr/share/txt2pho/txt2pho`
  808. Edit txt2phorc:
  809. `vi /usr/share/txt2pho/txt2phorc`
  810. and change the path for DATAPATH and INVPATH:
  811. DATAPATH=/usr/share/txt2pho/data/
  812. INVPATH=/usr/share/txt2pho/data/
  813. Copy txt2phorc to /etc/txt2pho:
  814. `cp /usr/share/txt2pho/txt2phorc /etc/txt2pho`
  815. =head2 Synthesis Voice
  816. Install the synthesis voice (e.g. German female voice):
  817. `unzip de7.zip -d /usr/share/mbrola/de7`
  818. fhem-speech use "de2" and "de3" as default voices. You can change this
  819. if you like.
  820. =head2 MBROLA
  821. Install MBROLA:
  822. `dpkg -i mbrola3.0.1h_i386.deb`
  823. =head2 sox
  824. Install sox:
  825. `apt-get install sox libsox-fmt-all`
  826. =head2 Test
  827. Test your installation:
  828. `echo "Test" | /usr/share/txt2pho/txt2pho |\
  829. mbrola /usr/share/mbrola/de7/de7 - -.au | play -q -t au -`
  830. =head2 fhem-speech
  831. Copy the script fhem-speech to a directory of your choice, e.g.:
  832. `cp fhem-speech /usr/local/bin`
  833. and make it executable:
  834. `chmod 775 /usr/local/bin/fhem-speech`
  835. =head2 Perl
  836. If you use the delivered module F<contrib/JSON-XS-2.231.tar.gz>:
  837. `tar xzf JSON-XS-2.231.tar.gz`
  838. `cd JSON-XS-2.231`
  839. `perl Makefile.pl`
  840. `make`
  841. `make test`
  842. and as root:
  843. `make install`
  844. =head1 CONFIGURATION
  845. Open fhem-speech with your prefered editor.
  846. =head2 FHEM host settings
  847. Change the default host, if you like:
  848. ###########################
  849. # FHEM
  850. $sys{fhem}{host} = "localhost";
  851. $sys{fhem}{port} = "7072";
  852. =head2 External commands
  853. Change the paths depending on the installed distribution:
  854. ###########################
  855. # Mandatory external Files
  856. $sys{file}{mbrola} = "/usr/local/bin/mbrola";
  857. $sys{file}{pipefilt} = "/usr/local/bin/pipefilt";
  858. $sys{file}{play} = "/usr/bin/play";
  859. $sys{file}{preproc} = "/usr/local/bin/preproc";
  860. [...]
  861. Change the default settings for synthesis voice:
  862. ###########################
  863. # mbrola / txt2pho options
  864. $sys{speech}{sex} = "f";
  865. $sys{speech}{male} = "-f0.8 -t0.9 -l 15000";
  866. $sys{speech}{female} = "-f1.2 -t1.0 -l 22050";
  867. =head2 Translation
  868. fhem-speech need the $lang{} settings to decide what messages from FHEM
  869. to be spoken. For example take a look at the FHT part:
  870. ###########################
  871. # FHEM Translation
  872. [...]
  873. ###########################
  874. # FHT
  875. # keys:
  876. $lang{'actuator'} = "Ventilstellung: %s Prozent";
  877. $lang{'day-temp'} = "Temperatur Tag: %s Grad";
  878. $lang{'desired-temp'} = "Angeforderte Temperatur: %s Grad";
  879. $lang{'measured-temp'} = "Gemessene Temperatur: %s Grad";
  880. $lang{'mode'} = "Modus: %s";
  881. $lang{'night-temp'} = "Temperatur Nacht: %s Grad";
  882. $lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad";
  883. [...]
  884. On every FHEM response all of the defined $lang{} status information will
  885. be spoken. If you don't like status information for e.g. 'windowopen-temp' then comment this out:
  886. # $lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad";
  887. If you like to know the status for e.g. 'lowtemp-offset' add a line like this:
  888. $lang{'lowtemp-offset'} = "Versatz Temperatur %s Grad";
  889. The '%s' stands as a placeholder for the value.
  890. =head1 OPTIONAL
  891. =head2 Asterisk
  892. fhem-speech support AGI commands for direct output in Asterisk.
  893. =head3 Wrapper
  894. If you like fhem-speech for use in Asterisk, you have to install a wrapper around
  895. fhem-speech. You can use the example from F<contrib/fhem-speech.agi>.
  896. Copy the wrapper to your asterisk-environment, e.g:
  897. `cp contrib/fhem-speech.agi /var/lib/asterisk/agi-bin/`
  898. =head3 extension.conf
  899. Take a look at the example from F<contrib/extension.conf>.
  900. =head1 LEGALESE
  901. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
  902. This is free software: you are free to change and redistribute it. There is
  903. NO WARRANTY, to the extent permitted by law.
  904. =head1 AUTHOR
  905. Copyright (C) 2008 Martin Fischer <m_fischer@gmx.de>
  906. =cut