[Asterisk-Users] agi scripting in perl - dealiing with unexpected disconnects gracefully / spurious DTMF

Tim Petlock tim at petlock.net
Wed Feb 18 10:11:24 MST 2004


Ok - before posting I (think I) read every post on the list that had the
phrase "calling card" in it.  This is probably more of a perl-related
question.

I've slightly modified Brian West's calling card script and I'm stumped
for how to deal with users hanging up on it - it doesn't recover
gracefully.  When I first tested the script out I was doing it from
various payphones near my home.  The phones were a little unpredictable
as far as the length of DTMF tones generated and whether the keys
generated DTMF tones at all so there were a number of call attempts made
as I traveled around the area.  I noticed that it took longer and longer
for * to answer the calls as the day went on and it eventually stopped
responding at all.

If everything is entered correctly and perfectly the script works and
the call goes through.  However, I've found that if I enter only a
partial "calling card number" and then hang up the script will continue
to run with the perl process that it lives inside of taking up lots of
processor time and for each failed call you get one of those
disconnected processor-hungry processes.

In the first while statement I changed the OR to an AND because the
script was never satisfying (length($pin) != 8 || $try < 3)
consistently.  I can see how it could if the user perfectly entered
eight digits each time but in the real world that's not going to happen
100%.  Could be that a DTMF recognition issue happens due to background
noise, user will get distracted and enter the wrong length, or someone
will go on a fishing expedition for valid pins.

Once I changed the || to an && it would gracefully finish after three
variable-length inputs - and if you hang up on it the perl process
finishes too: with the unfortunate side effect of taking * with it.
When the perl process goes away so does asterisk.

Any ideas for keeping asterisk running after the perl script dies off?
It'd be kinda nice to have the perl script go away but have asterisk
stay running.  The complete perl script is at the bottom of this message
in the hope that my next question will more likely be noticed. :)

I'm also getting spurious DTMF when calling people.  I very occasionally
will hear a spurious DTMF tone coming from the other end, must its much
more frequent from the point of view of the person I'm talking to - they
hear a lot more.  What should I look for in my configuration?  I'm using
a cisco ata-186 configured as follows:
[cisco]
type=friend
username=cisco
secret=*****
nat=yes                 ; This phone may be natted
host=dynamic
canreinvite=no                  ; Cisco poops on reinvite sometimes
qualify=200                     ; Qualify peer is no more than 200ms
away
defaultip=*****
context=homesip
dtmfmode=rfc2833
callerid="Test" <4444>
mailbox=1000

The audiomode parameter on my ATA-186 is set to 0x11241124 which I'm
pretty sure matches the dtmfmode parameter in my sip.conf.

I'm using nufone as the link to the PSTN.  The way I see it the links
look like this:
(ata) ---sip g.711 ulaw---> (*) ---iax2 gsm---> (nufone) ---???--->
(PSTN)

I'm going to try to make calls by calling the line connected to my FXO
interface to rule out the first hop.  Is there anything I can do in my
iax.conf file?

Here's the modified perl script I'm playing with...
#!/usr/bin/perl
#
#  PrePaid CallingCard IVR Application for Asterisk PBX
#  Copyright 2003, Brian K. West <brian at bkw.org> 2003-08-20
#
#  All prompts are kludged together.. I would like it to read
#  "Please enter your calling card number"
#  "I'm sorry thats an invalid card"
#  "You have Twenty minutes remaing on this card"
#  "Please enter the number you wish to call"
#
#  We could be evil and set the initial AbsoluteTimeout when the call is
answered
#  So slower users are cut off thus not wasting time :P Like 60 seconds.
#
#  Also to be on the more evil side you can start the AbsoluteTimeout
before we ask for the number
#  So we would be on the users time at that point and they would have
some sort of incentive
#  to enter a number.  As you see I have it set to give them 3 trys for
an X number of digits.
#
#  TODO:
#	Not really sure.... This was more to prove I could do it.
#	Fix the error checking... its far from graceful now. 
#	Write monthly cron to clean db and remove old pins.
#	Do a happy dance?
#
#	mad propz to citats for Asterisk::AGI perl modules. 
#

use Asterisk::AGI;
use DBI;

# Config options
%MYSQL = (
	hostname	=>	"localhost",
	username	=>	"****",
	password	=>	"*******",
	database	=>	"*********"
);

$dbh =
DBI->connect("dbi:mysql:$MYSQL{database}:$MYSQL{hostname}","$MYSQL{usern
ame}","$MYSQL{password}")
	|| die("Couldn't connect to database!\n"); 
#
# We should throw an error down the channel and take care of it
gracefully
#

$AGI = new Asterisk::AGI;

my %input = $AGI->ReadParse();

my $target	= 0;
my $try		= 0;

# Watch out for loops they will make you dizzy.
while(length($pin) != 8 && $try < 3) {
	$pin = $AGI->get_data("plscrdnum", "10000", "8");
# 	debug ----------
	$AGI->exec('SayDigits',$try);
#	debug ----------
	$units = check_pin($pin);
	if($units eq undef) {
		$try++;
#		we could do an invalid pin warning here... :P
	} else {
		# reset try because we want to make sure we try to get a
valid
		# X digit number from the user.. its set at 7 now.. but
you can
		# change that to say 11.
		# 
		$try = 0;
		$timeout = $units * 60;
		# use the pin as the account code so we can track this
		$AGI->exec('SetAccount',$pin);
		$AGI->stream_file('balance');
		$AGI->exec('SayDigits',$units);
		$AGI->stream_file('minutes');
		$AGI->exec('Wait','1');
		while(length($target) != 11) {
			if($try >= 3) {
				$AGI->stream_file('vm-goodbye');
				$AGI->hangup();
				exit(0);
			}
			$target = $AGI->get_data("plsdial1", "10000",
"12");
			$try++;
		} 
		$AGI->exec('AbsoluteTimeout',$timeout);
		# maybe a prompt saying "connecing your call" here.
		# this could be a config option or even dynamic
depending on info associated
		# with the pin.  Many Many options here.
		$provider = 'IAX2/******@NuFone/';
		$dialstring = $provider . $target;
		$AGI->exec('Dial',$dialstring);
		$AGI->hangup();
		exit(0);		
	}
	$try++;
}
# user screwed up so lets just say goodbye and let them try again later.
$AGI->stream_file('vm-goodbye');
$AGI->hangup();
exit(0);

sub check_pin($pin) {
	my $query	= "SELECT units FROM pins WHERE pin='$pin' LIMIT
1";
        my $sth		= $dbh->prepare($query);
        $sth->execute 
		|| die("Couldn't exec sth2!");
	# need to be graceful here also
	my $units = $sth->fetchrow_hashref;

	#
	#  Now we subtract usage from this pin.  
	#  Lets ditch all calls under 6 seconds.. you can change this.
	#  Also when you clean the database you need to be sure that you
remove the pin from 
	#  pins database.  Check TODO list above.
	#

	my $query	= "SELECT SUM(CEILING(billsec/60)) AS used FROM
cdr WHERE accountcode='$pin' and billsec > 6;";
        my $sth		= $dbh->prepare($query);
        $sth->execute 
		|| die("Couldn't exec sth2!");
	# need to be graceful here also
	my $used = $sth->fetchrow_hashref;
	$units->{units} = $units->{units} - $used->{used};
	$sth->finish;
	if($units->{units} > 0) {
		return $units->{units};
	} else {
		return undef;
	}
}

Thanks!
Tim Petlock




More information about the asterisk-users mailing list