This script keeps track of calls on Asterisk SIP channels and drops them when account credit has expired. Usually accounts end up in negative balance because of script errors or by the way Asterisk and A2B handle concurrent calls on the same account. This script is meant to prevent negative balances or exceeding credit limit.
This script runs as a cron job that constantly checks on asterisk for active calls and the remaining account credit. It has been tested to work on SIP channels, but may work on any type of channel by removing the SIP restriction. Other types of channels were not tested.
Installation steps Asterisk
> 1.6.x , A2B > 1.9.4, PHP 5.2+Modify files in A2B
1a.
In /usr/local/src/Star2Billing194/common/lib/Class.A2Billing.php in the
run_dial function.
Add these two lines at the top of the function before the dial
Code:
$agi ->set_variable("credit",$this->credit);
$agi ->set_variable("accountnumber",$agi->request['agi_accountcode']?$agi->request['agi_accountcode']:$this->accountcode);
1b.
In /usr/local/src/Star2Billing194/common/lib/Class.A2Billing.php in the
call_2did function.
Add this line near the top of the function under the line "
$selling_rate = $listdestination[0][9];" (ths is added to the channel in asterisk)
Code:
$agi ->set_variable("callrate",$selling_rate);
1c.
In /usr/local/src/Star2Billing194/common/lib/Class.A2Billing.php in the
call_did function.
Add this line near the top of the function under the line "
$res = 0;" (ths is added to the channel in asterisk)
Code:
$agi ->set_variable("callrate",$listdestination[0][9]);
1d.
In /usr/local/src/Star2Billing194/common/lib/Class.RateEngile.php in function
rate_engine_performcallAdd this line
Code:
$agi ->set_variable("callrate",$this -> ratecard_obj[$k][12]);
near the top of the function under the block of code that looks like this:
$prefix = $this -> ratecard_obj[$k][$usetrunk+1];
$tech = $this -> ratecard_obj[$k][$usetrunk+2];
$ipaddress = $this -> ratecard_obj[$k][$usetrunk+3];
$removeprefix = $this -> ratecard_obj[$k][$usetrunk+4];
$timeout = $this -> ratecard_obj[$k]['timeout'];
$musiconhold = $this -> ratecard_obj[$k][39];
$failover_trunk = $this -> ratecard_obj[$k][40+$usetrunk_failover];
$addparameter = $this -> ratecard_obj[$k][42+$usetrunk_failover];
$cidgroupid = $this -> ratecard_obj[$k][44];
$inuse = $this -> ratecard_obj[$k][48+$usetrunk_failover];
$maxuse = $this -> ratecard_obj[$k][50+$usetrunk_failover];
$ifmaxuse = $this -> ratecard_obj[$k][52+$usetrunk_failover];
2. Copy the script below, change the name to .php
and set it as a cron job to run every minute (exactly one minute no more no less for proper operation). Also edit the cofiguration variable in the new .php file to point to the asterisk binary et al.
It can also be ran from the command line, eg;
php /usr/local/src/Star2Billing194/Cronjobs/a2billing_check_simult.php -v
Cron instruction
Code:
*/1 * * * * php /usr/local/src/Star2Billing194/Cronjobs/a2billing_check_simult.php
Code:
<?php
/*
$Id: a2billing_check_simult.php,v 1.05 2013/03/10 00:00:00 vulcan Exp $
Author: vulcan
Designed for Asterisk2Billing
Copyright (c) 2011 free to use, package, distribute, alter.
*/
//*******************************
//*******************************
// C O N F I G U R A T I O N
// V A R I A B L E S
//*******************************
//*******************************
/* Requirements: PHP 5.x , Asterisk2Billing > 1.9.4 , Asterisk > 1.6.x */
/* this program checks active outbound SIP channels in asterisk and */
/* terminates them if cost of calls exceeds credit or credit limit. */
/* Console command line : ~]# php a2billing_check_simult.php [ -v ] */
define('ASTERISK_BINARY', '/usr/sbin/asterisk');
//*******************************
//*******************************
// E N D
// C O N F I G U R A T I O N
// V A R I A B L E S
//*******************************
//*******************************
if ( isset($argc) && $argc > 1) {
array_shift($argv);
$allow = array ('-verbose','-v','verbose','v');
if (!in_array($argv[0],$allow)) {
print "\nCommand Format: php <path to executable> -v\n";
} else {
$option = $argv[0];
}
}
// shell command to get string of active channels - Origination/Destination
$str = shell_exec(ASTERISK_BINARY . ' -Rx "core show channels concise"');
$strArray = explode("\n",$str);
foreach ($strArray as $key => $value) {
if ( (strpos($value,'!') !== false) && (strpos($value,'(Outgoing Line)') !== false) ) {
// Active channels to array
$channel = explode('!',trim($value));
if (strpos($channel[0], 'SIP/') !== false ) {
$outGoingChannels[] = $channel;
}
}
}
// build array of accounts
if (isset( $outGoingChannels ) ) {
foreach ( $outGoingChannels as $key => $value ) {
$bridgedTo = trim($value[12]); // Originating channel name bridged to destination
if (!empty($bridgedTo) && $bridgedTo != '(None)') {
$chandest = trim($value[0]); // Active destination channel name
// shell command to get channel variables string of the Originating channel after bridged with Destination
$str = shell_exec(ASTERISK_BINARY . ' -Rx "dialplan show chanvar ' . $bridgedTo . '"' );
if (!empty($str)) {
$strArray = explode("\n",$str);
foreach ($strArray as $k => $v ) {
$index = strpos($v,'=');
if ($index !== false) {
// channel variables to array
$data[substr($v,0,$index)] = trim(substr($v,($index + 1)));
}
}
}
if (isset($data['accountnumber'])
&& is_numeric($data['accountnumber'])
&& isset($data['credit'])
&& isset($data['callrate'])
&& is_numeric($data['callrate']) ) {
$account = $data['accountnumber']; //accountcode
// Initialize using account number as key
if (!isset($current[$account]['highestbalance'])) {
if (is_numeric($data['credit'])) {
$current[$account]['highestbalance'] = $data['credit'];
} else {
$current[$account]['highestbalance'] = 0;
}
$current[$account]['totallengthofcalls'] = 0;
$current[$account]['totalperminuterate'] = 0;
$current[$account]['channeldata'] = array();
}
if ($current[$account]['highestbalance'] == 0) {
$current[$account]['highestbalance'] = $data['credit'];
} elseif ($data['credit'] > $current[$account]['highestbalance'] ) {
$current[$account]['highestbalance'] = $data['credit'];
}
//Asterisk CDR
$ttime = billsec($chandest);
// store remaining data
$current[$account]['totalperminuterate'] = $current[$account]['totalperminuterate'] + $data['callrate'];
$data['lengthofcall'] = $ttime;
$data['channel'] = $chandest;
$data['marktime'] = time();
array_push( $current[$account]['channeldata'], $data); // Destination channel names et al
}
}
}
if (isset( $current) ) {
$tick = 0;
while ($tick <= 59) {
// loop each account
foreach ($current as $key => $accountdata) {
// calculate current total cost of calls for this account
$cost = 0;
$totallengthofcall = 0;
foreach ($accountdata['channeldata'] as $ikey => $ival ) {
$tick = time() - $ival['marktime'];
$cost = $cost + ((($ival['lengthofcall'] + $tick)/60) * $ival['callrate']);
$totallengthofcall = $totallengthofcall + $ival['lengthofcall'] + $tick;
}
$channelcount = count($accountdata['channeldata']);
$averagelength = ($totallengthofcall/60) / $channelcount;
//one second's worth of call
$onesecworth = $accountdata['totalperminuterate']/60;
if ( $cost >= ( $accountdata['highestbalance'] - ($onesecworth*5) ) ) {
//terminate outbound channels for this account
dropChannel ($accountdata['channeldata'] , $key);
unset($current[$key]);
if (count($current) == 0) exit;
} elseif (isset($option)) {
// print to console
stats($key);
}
}
sleep(1);
}
}
}
/****
Hangup the channel(s)
*/
function dropChannel ($channel = array(), $account = null) {
if (!empty($channel) ) {
foreach ($channel as $k => $v) {
$str = shell_exec(ASTERISK_BINARY . ' -Rx "dialplan show chanvar ' . $v['channel'] . '"' );
$strArray = explode("\n",$str);
foreach ($strArray as $kk => $vv ) {
$index = strpos($vv,'=');
if ($index !== false) {
// channel variables to array
$data[substr($vv,0,$index)] = trim(substr($vv,($index + 1)));
}
}
if (isset($data['BRIDGEPEER']) && !empty($data['BRIDGEPEER']) ){
stats($account);
shell_exec(ASTERISK_BINARY . ' -Rx "channel request hangup ' . trim($v['channel']) . '"' );
print_r('Hung up channel "' . $v['channel'] . '" for account: ' . $account . ' [ Not enough Credit ]' . "\n");
}
}
}
}
/****
Get the current CDR billing for call in seconds
*/
function billsec($channel) {
if (!empty($channel)) {
$reply = shell_exec(ASTERISK_BINARY . ' -Rx "core show channel ' . $channel . '"' );
$channeldata = explode("\n",$reply);
if (!empty($channeldata)) {
foreach($channeldata as $key => $value) {
$index = strpos($value,'billsec=');
if ( $index !== false) {
$seconds = explode('=',$value);
break;
}
}
if (isset($seconds[1]) && is_numeric(trim($seconds[1]))) {
return trim($seconds[1]);
} else {
return 0;
}
} else {
return 0;
}
} else {
return 0;
}
}
/****
Print some statistics
*/
function stats($account) {
global $accountdata,$channelcount,$averagelength,$cost;
print_r("\n");
print_r(" Account Code: " . $account ."\n");
print_r(" Available Credit: " . $accountdata['highestbalance'] ."\n");
print_r(" Active Calls: " . $channelcount . "\n" );
print_r("Average Length of Calls: " . $averagelength * 60 . " Seconds\n");
print_r(" Rate Per Minute: " . $accountdata['totalperminuterate'] . "\n");
print_r(" Current Cost of Calls: " . $cost . "\n\n");
}
?>