NAME¶
Modem::Vgetty - interface to
vgetty(8)
SYNOPSIS¶
use Modem::Vgetty;
$v = new Modem::Vgetty;
$string = $v->receive;
$v->send($string);
$string = $v->expect($str1, $str2, ...);
$v->waitfor($string);
$rv = $v->chat($expect1, $send1, $expect2, $send2, ...);
$ttyname = $v->getty;
$rv = $v->device($dev_type);
$rv = $v->autostop($bool);
$rv = $v->modem_type; # !!! see the docs below.
$rv = $v->beep($freq, $len);
$rv = $v->dial($number);
$rv = $v->play($filename);
$rv = $v->record($filename);
$rv = $v->wait($seconds);
$rv = $v->play_and_wait($filename);
$v->stop;
$v->add_handler($event, $handler_name, $handler);
$v->del_handler($event, $handler_name);
$v->enable_events;
$v->disable_events;
$number = $v->readnum($message, $tmout, $repeat);
$v->shutdown;
DESCRIPTION¶
"Modem::Vgetty" is an encapsulation object for writing applications
for voice modems using the
vgetty(8) or
vm(8) package. The answering machines and
sofisticated voice applications can be written using this module.
OVERVIEW¶
Voice modem is a special kind of modem, which (besides the normal data
and/or fax mode) can communicate also in voice mode. It means it can record
sounds it hears from the phone line to the file, Play-back recorded files, it
can beep to the line, and it can detect various standard sounds coming from
the line (busy tone, silence, dual tone modulation frequency (DTMF) keypad
tones, etc). An example of the voice modem can be the ZyXEL U1496, US Robotics
Sportster (not Courier), etc.
To use this software with the voice modem you need to have the
vgetty (8) package installed.
Vgetty is
distributed as a part of
mgetty package. In fact,
vgetty is a
mgetty(8) with the voice extensions. Vgetty has
some support for scripting - when it receives an incoming call, it runs a
voice shell (it is program specified in the
voice.conf file) as its
child process, establishes the read and write pipes to it, and tells it the
number of the appropriate descriptors in the environment variables. Voice
shell can now communicate with
vgetty. It can tell
vgetty
"Play this file", or "Record anything you hear to that
file", or "Notify me when user hangs up", etc. Sophisticated
voice systems and answering machines can be build on top of
vgetty.
mgetty (including the
vgetty) is available at the following URL:
ftp://alpha.greenie.net/pub/mgetty/
Originally there was a (Bourne) shell interface to
vgetty only. The
Modem::Vgetty module allows user to write the voice shell in Perl. The
typical use is to write a script and point the
vgetty to it (in
voice.conf file). The script will be run when somebody calls in.
Another use is running voice shell from the
vm(8)
program, which can for example dial somewhere and say something.
QUICK START¶
#!/usr/bin/perl
use Modem::Vgetty;
my $v = new Modem::Vgetty;
$v->add_handler('BUSY_TONE', 'endh', sub { $v->stop; exit(0); });
local $SIG{ALRM} = sub { $v->stop; };
$v->enable_events;
$v->record('/tmp/hello.rmd');
alarm(20);
$v->waitfor('READY');
$v->shutdown;
The above example installs the simple `exit now'-style handler for the
BUSY_TONE event (which is sent by
vgetty when user hangs up) and
then records the
hello.rmd file. Put this text into a file and then
point
vgetty to it in the
voice.conf. After you dial into your
voice modem, you can record a 20-seconds of some message. Verify that
/tmp/hello.rmd exists. Now delete the line contaning the word
"record" and two subsequent lines and insert to the file the
following line instead of them:
$v->play_and_wait('/tmp/hello.rmd');
Now call the voice modem and listen to the sounds you have just recorded.
METHODS¶
Begin and end of communication¶
The
Modem::Vgetty object will initialize the communication pipes to the
vgetty at the creation time - in the constructor. The closing of the
communication is done via the
shutdown method:
$v->shutdown;
The module will call this method itself from the destructor, if you do not call
it explicitly.
Low-level communication¶
Users probably don't want to use these methods directly. Use the higher-level
functions instead.
- receive
- This method returns a string received from the vgetty. It parses
the string for the event types and runs appropriate event handlers. If
event handler is run it waits for another string.
- send($string)
- This method sends the string $string to the
vgetty process.
- expect($string1, $string2, ...)
- Receives a string from vgetty (using the receive method
described above) and returns it iff it is equal to one of the strings in
the argument list. When something different is received, this method
returns undef.
- waitfor($string)
- Waits until the string $sring is received from
vgetty (using the receive method described above). =item
chat($expect1, $sent1, $expect2, $sent2, ...)
A chat-script with vgetty. Arguments are interpreted as the
received-sent string pairs. A received string equals to the empty string
means that no receive method will be called at that place. This can
be used for constructing chat scripts beginning with the sent string
instead of the received one.
Vgetty control methods¶
There are miscellaneous methods for controllig
vgetty and querying its
status.
- getty
- Returns the name of the modem special file (e.g. /dev/ttyC4).
- device($name)
- Sets the port of the voice modem input and output is done to. Possible
values are qw(NO_DEVICE DIALUP_LINE EXTERNAL_MICROPHONE INTERNAL_SPEAKER
LOCAL_HANDSET).
- autostop($bool)
- With autostop on, the voicelib will automatically abort a play in progress
and return READY. This is useful for faster reaction times for voice
menus. Possible arguments are qw(ON OFF). Note: The interface
should probably be changed to accept the Perl boolean arguments (undef,
something else). Returns defined value on success, undef on failure.
- modem_type
- vgetty currently has no way of telling voice shell the type of the
current modem. This method is a proposed interface for determining this
type. Currently returns undef. The appropriate low-level interface
has to be implemented in vgetty first.
Voice commands¶
- beep($freq, $len)
- Sends a beep through the chosen device using given frequency (HZ) and
length (in miliseconds). Returns a defined value on success or undef on
failure. The state of the vgetty changes to "BEEPING" and
vgetty returns "READY" after a beep is finshed. Example:
$v->beep(50,10);
# Possibly do something else
$v->waitfor('READY');
- dial($number)
- Modem tries to dial a given number. The vgetty changes its state to
"DIALING" and returns "READY" after the dialing is
finished.
- play($filename)
- The vgetty tries to play the given file as a raw modem data. See
the "Voice data" section for details on creating the raw modem
data file. It changes the state to "PLAYING" and returns
"READY" after playing the whole file.
- record($filename)
- The vgetty records the voice it can hear on the line to the given
file. It uses the raw modem data format (which can be re-played using the
play subroutine). vgetty changes its state to
"RECORDING" and you need to manually stop the recording using
the stop method after some time (or, you can set autostop
and wait for any event - silence, busy tone, etc).
- wait($seconds)
- The modem waits for a given number of seconds. Changes its state to
"WAITING" and returns "READY" after the wait is
finished. Example:
$v->wait(5);
$v->waitfor('READY');
- stop
- The vgetty stops anything it is currently doing and returns to the
command state. You must use stop when you want to call another
beep, dial, play, record or wait before
the previous one is finished. The vgetty returns "READY"
after the stop is called. So it is possible to interrupt a main
routine waiting for "READY" from the event handler:
my $dtmf;
$v->add_handler('RECEIVED_DTMF', 'readnum',
sub { my $self=shift; $self->stop; $dtmf = $_[2]; });
$v->enable_events;
$v->wait(10);
$v->waitfor('READY');
In the previous example the waitfor method can be finished either by
the 10-second timeout expired, or by the 'READY' generated by the
stop in the event handler. See also the Events section.
- play_and_wait($file)
- It is an abbreviation for the following:
$v->play($file);
$v->waitfor('READY');
It is repeated so much time in the voice applications so I have decided to
make a special routine for it. I may add the similar routines for
dial, record, beep and even wait in the future
releases.
Event handler methods¶
- add_handler($event, $handler_name, $handler)
- Installs a call-back routine $handler for the event type $event. The
call-back routine is called with three arguments. The first one is the
Modem::Vgetty object itself, the second one is the event name and the
third one is optional event argument. The
$handler_name argument can be anything. It is used
when you want to delete this handler for identificating it.
- del_handler($event, $handler_name)
- This method deletes the handler $handler_name for the $event event. The
result of unregistering the handler from the event handler of the same
event is unspecified. It may or may not be called.
- enable_events
- Tells the vgetty that the voice shell is willing to dispatch
events. No events are sent by vgetty until this method is
called.
- disable_events
- Tells the vgetty that the voice shell doesn't want to receive any
events anymore.
The readnum method¶
- readnum($message, $tmout, $repeat)
- The applications often need to read the multi-digit number via the DTMF
tones. This routine plays the $message to the voice
object and then waits for the sequence of the DTMF keys finished by the
`#' key. If no key is pressed for $tmout of seconds,
it re-plays the message again. It returns failure if no key is pressed
after the message is played $repeat-th time. It
returns a string (a sequence of DTMF tones 0-9,A-D and `*') without the
final `#'. When some DTMF tones are received and no terminating `#' or
other tone is received for $tmout seconds, the
routine returns the string it currently has without waiting for the final
'#'. DTMF tones are accepted even at the time the
$message is played. When the DTMF tone is received,
the playing of the $message is (with some latency, of
course) stopped.
NOTE: The interface of this routine can be changed in future
releases, because I am not (yet) decided whether the current interface is
the best one. See also the EXAMPLES section where the source code
of this routine (and its co-routine) is discussed.
EVENTS¶
Introduction¶
Events are asynchronous messages sent by
vgetty to the voice shell. The
Modem::Vgetty module dispatches events itself in the
receive
method. User can register any number of handlers for each event. When an event
arrives, all handlers for that event are called (in no specified order).
Event types¶
At this time, the
Modem::Vgetty module recognizes the following event
types (description is mostly re-typed from the
vgetty documentation):
- BONG_TONE
- The modem detected a bong tone on the line.
- BUSY_TONE
- The modem detected busy tone on the line (when dialing to the busy number
or when caller finished the call).
- CALL_WAITING
- Defined in IS-101 (I think it is when the line receives another call-in
when some call is already in progress. -Yenya).
- DIAL_TONE
- The modem detected dial tone on the line.
- DATA_CALLING_TONE
- The modem detected data calling tone on the line.
- DATA_OR_FAX_DETECTED
- The modem detected data or fax calling tones on the line.
- FAX_CALLING_TONE
- The modem detected fax calling tone on the line.
- HANDSET_ON_HOOK
- Locally connected handset went on hook.
- HANDSET_OFF_HOOK
- Locally connected handset went off hook.
- LOOP_BREAK
- Defined in IS-101.
- LOOP_POLARITY_CHANGE
- Defined in IS-101.
- NO_ANSWER
- After dialing the modem didn't detect answer for the time give in
dial_timeout in voice.conf.
- NO_DIAL_TONE
- The modem didn't detect dial tone (make sure your modem is connected
properly to your telephone company's line, or check the ATX command if
dial tone in your system differs from the standard).
- NO_VOICE_ENERGY
- It means that the modem detected voice energy at the beginning of the
session, but after that there was a period of some time of silence (the
actual time can be set using the rec_silence_len and
rec_silence_treshold parameters in voice.conf).
- RING_DETECTED
- The modem detected an incoming ring.
- RINGBACK_DETECTED
- The modem detected a ringback condition on the line.
- RECEIVE_DTMF
- The modem detected a dtmf code. The actual code value (one of 0-9, *, #,
A-D) is given to the event handler as the third argument.
- SILENCE_DETECTED
- The modem detected that there was no voice energy at the beginning of the
session and after some time of silence (the actual time can be set using
the rec_silence_len and rec_silence_treshold parameters in
voice.conf).
- SIT_TONE
- Defined in IS-101.
- TDD_DETECTED
- Defined in IS-101.
- VOICE_DETECTED
- The modem detected a voice signal on the line. IS-101 does not define, how
the modem makes this decision, so be careful.
- UNKNOWN_EVENT
- None of the above :)
VOICE DATA¶
Voice shell can send the voice data to the modem using the
play method
and record them using the
record method. The ".rmd" extension
(Raw Modem Data) is usually used for these files. The ".rmd" is not
a single format - every modem has its own format (sampling frequency, data bit
depth, etc). There is a
pvftools package for converting the sound files
(it is a set of filters similar to the
netpbm for image files). The
pvftormd(1) filter can be used to create the RMD
files for all known types of modems.
EXAMPLES¶
Answering machine¶
A simple answering machine can look like this:
#!/usr/bin/perl
use Modem::Vgetty;
my $voicemaster = 'root@localhost';
my $tmout = 30;
my $finish = 0;
my $v = new Modem::Vgetty;
$v->add_handler('BUSY_TONE', 'finish',
sub { $v->stop; $finish=1; });
$v->add_handler('SILENCE_DETECTED', 'finish',
sub { $v->stop; $finish=1; });
local $SIG{ALRM} = sub { $v->stop; };
$v->enable_events;
$v->play_and_wait('/path/welcome.rmd');
$v->beep(100,10);
$v->waitfor('READY');
if ($finish == 0) {
my $num = 0;
$num++ while(-r "/path/$num.rmd");
$v->record("/path/$num.rmd");
alarm $tmout;
$v->waitfor('READY');
}
system "echo 'Play with rmdtopvf /path/$num.rmd|pvftoau >/dev/audio'" .
" | mail -s 'New voice message' $voicemaster";
exit 0;
See the
examples/answering_machine.pl in the source distribution, which
contains a more configurable version of the above text. It first sets the
event handlers for the case of busy tone (the caller hangs up) or silence (the
caller doesn't speak at all). The handler stops
vgetty from anything it
is currently doing and sets the $finish variable to 1. Then the reception of
the events is enabled and the welcome message is played. Then the answering
machine beeps and starts to record the message. Note that we need to check the
$finish variable before we start recording to determine if user hanged up the
phone. Now we find the first filename <number>.rmd such that this file
does not exist and we start to record the message to this file. We record
until user hangs up the phone or until the timeout occurs.
Readnum routine¶
An interesting application of the low-level routines is the
Voice::Modem::readnum method. The calling sequence of this method has
been discussed above. The source code for this routine and its co-routine will
be discussed here, so that you can write your own variants of
readnum
(which in fact does not have too general interface). See also the source code
of
Vgetty.pm for the
readnum source.
The
readnum routine needs to have its own event handler for the
RECEIVED_DTMF event and the way the handler can communicate with this
routine. In our solution we use "static" variables:
my $_readnum_number = '';
my $_readnum_timeout = 10;
my $_readnum_in_timeout = 1;
The event handler will add the new character to the end of the
$_readnum_number variable. The
$_readnum_timeout is the number of seconds both
readnum and the event handler should wait for the next keypress, and
the
$_readnum_in_timeout is a flag used by the event
handler for notifying the main
readnum routine that it forced the
vgetty to emit the `READY' message because of the final `#' has been
received.
sub _readnum_event {
my $self = shift;
my $input = shift; # Unused. Should be 'RECEIVED_DTMF'.
my $dtmf = shift;
if ($dtmf eq '#') { # Stop the reading now.
$_readnum_in_timeout = 0;
$self->stop;
$self->{LOG}->print("_readnum_event(): Got #; stopping\n");
return;
}
$_readnum_number .= $dtmf;
$self->stop;
$self->expect('READY');
# Restart the wait again.
$_readnum_in_timeout = 1;
$self->wait($_readnum_timeout);
}
The event handler is installed for the `RECEIVED_DTMF' event only, so it doesn't
need to check for the
$input value. The actual DTMF key
is in the third parameter,
$dtmf. Note that the handler
will be called when
vgetty is PLAYING or WAITING and the
readnum
routine will be waiting for the `READY' message. This allows us to immediately
interrupt waiting by the
$self-stop> (which emits the
`READY' message). So when the `#' DTMF tone is received, we send a
stop
to
vgetty. If something else is received, we
stop the
vgetty too but we enter a new wait using
$self-wait>.
sub readnum {
my $self = shift;
my $message = shift;
my $timeout = shift;
my $times = shift;
$_readnum_number = '';
$_readnum_in_timeout = 1;
$_readnum_timeout = $timeout if $timeout != 0;
$times = 3 if $times == 0;
# Install the handler.
$self->add_handler('RECEIVED_DTMF', 'readnum', \&_readnum_event);
while($_readnum_in_timeout != 0 && $_readnum_number eq ''
&& $times-- > 0) {
$self->play_and_wait($message);
last if $_readnum_in_timeout == 0;
while ($_readnum_in_timeout != 0) {
$self->wait($_readnum_timeout);
$self->expect('READY');
}
}
return undef if $times < 0;
$self->del_handler('RECEIVED_DTMF', 'readnum');
$self->stop;
$self->expect('READY');
$_readnum_number;
}
The
readnum routine just sets up the event handler, then plays the
$message and waits for the input (possibly several
times). The main work is done in the event handler. At the end the handler is
unregistered and the final value is returned.
Callme script¶
In the
examples subdirectory of the source distribution there is a
callme.pl script. This dials the given number and plays the given
message. Use the following command to run it:
vm shell -S /usr/bin/perl callme.pl <number> <message>.rmd
BUGS¶
There may be some, but it will more likely be in the
vgetty itself. On
the other hand, there can be typos in this manual (English is not my native
language) or some parts of the interface that should be redesigned. Feel free
to mail any comments on this module to me.
TODO¶
- Modem type recognition
- The vgetty should be able to tell the voice shell the name of the
current modem type.
- The _wait() routines
- I need to implement the routines similar to play_and_wait for other
vgetty states as well.
- Debugging information
- The module has currently some support for writing a debug logs (use the
$Modem::Vgetty::testing = 1 and watch the /var/log/voicelog file). This
needs to be re-done using (I think) Sys::Syslog. I need to implement some
kind of log-levels, etc.
- Mgetty/Vgetty 1.1.17
- Need to figure out what is new in 1.1.17 (I use 1.1.14 now). I think new
vgetty can play more than one file in the single `PLAY' command, it
(I think) have some support for sending voice data from/to the voice shell
via the pipe, etc.
AUTHOR¶
The
Modem::Vgetty package was written by Jan "Yenya" Kasprzak
<kas@fi.muni.cz>. Feel free to mail me any suggestions etc. on this
module. Module itself is available from CPAN, but be sure to check the
following address, where the development versions can be found:
http://www.fi.muni.cz/~kas/vgetty/
COPYRIGHT¶
Copyright (c) 1998 Jan "Yenya" Kasprzak <kas@fi.muni.cz>. All
rights reserved. This package is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.