How to find current hold time of an extension?

I am interested in writing a script that can retrieve which extensions are currently on hold and how long they have been on hold for.

I see that from the CLI I can use “core show hints” to actually find which extensions have State:Hold and I can also use “core show channels” to trace what/where the channel/extension is connected.

I cannot however find a current hold time for how long an extension/channel has been on hold. I searched the mysql db but did not find the information (or just didn’t know what I was looking for).

What command and/or where should I be looking to find this information?

I am using FreePBX 2.7.0.5 with Asterisk 1.4.18

Thanks

You need to look at the AMI commands. The Asterisk Manager Interface provides this API for third party apps.

www.asteriskdocs.org

Ok, I see now that there are Dialplans, an AGI and the AMI!

I found these, so will start reading:
http://www.asterisk.org/docs
http://www.voip-info.org/wiki/view/Asterisk+manager+API
http://www.voip-info.org/wiki/view/Asterisk+manager+Examples

Thanks

This is what I came up with so far…

I am not sure what scenario would cause you to use the AGI versus the AMI yet (I suppose they have a different set of commands?) but I was reading on the AMI, and I did the following from the command line on asterisk server:

[code]telnet 127.0.0.1 5038

action: login
username: admin
secret: amp111

action: events
eventmask: on[/code]
It then started showing me all the events related to the phones/extensions. When I press HOLD on the phone, I get this message:

Event: ExtensionStatus Privilege: call,all Exten: 2005 Context: ext-local Status: 16
When you take the phone off of hold or hang up, you get the same message, except Status: changes to something other than 16 (usually 1 or 0).

So I can see that if I watch for this ExtensionStatus with Status: 16 I can start a timer and flag it as on hold, and then when the status changes away from 16, stop the timer and flag it as no longer on hold.

I will post back once I have a working script!

Thanks

Here is a script to show calls in the queue:

http://www.markinthedark.nl/news/scripts/76-show-callers-waiting-in-an-asterisk-queue.html

Thanks for that script… it’s a great example of how to access some statistics from MySQL db… I will probably need it for something in the near future!

Unfortunately I am not using queues, and I am trying to use the AMI to figure out hold time of all devices/extensions, not just queues themselves.

Thanks

Ok, here is what I have so far… I used fsockopen to connect just like doing a Telnet connection, and then I just fread each byte one by one and flush to display on screen. I still have to work out all the parsing and so forth to actually calculate when someone is hold and for how long, but this is a good start! I call this my AMI EVENT READER as it just displays all the event info that it receives back in the browser.

<?php
// AMI EVENT READER v0.1 by Jeff Sherk - May 2011
//
// This script will continuously read all the Asterisk AMI events and output them to your browser
//
// This FREE SCRIPT is not copyrighted and is offered AS IS with no guarantees and no warrantees. Do what you want with it!

/////////////////////////////////////////////////
// NOTE: Required for this script to work, and also required is you want to use PHPAGI
/* MODIFY /etc/asterisk/manager.conf and add this (make sure user is asterisk 0664):
    [myamiclient]
    secret=********
    deny=0.0.0.0/0.0.0.0
    permit=127.0.0.1/255.255.255.0
    read = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
    write = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
/*

/////////////////////////////////////////////////
// NOTE: Only required if you want to use PHPAGI
/* MODIFY /etc/asterisk/phpagi.conf and add this (make sure user is asterisk 0777):
    [asmanager]
    server=127.0.0.1 ; server to connect to
    port=5038 ; default manager port
    username=myamiclient ; username for login
    secret=******** ; password for login
*/

//username and secret need to match the /etc/asterisk/manager.conf file
$username = 'myamiclient';
$secret = '********';

//Script should run forever, so prevent it from timing out
set_time_limit(0);

//Use fsockopen to connect the same way you would with Telnet
$fp = fsockopen("127.0.0.1", 5038, $errno, $errstr, 30);

//Unsuccessful connect
if (!$fp) {
    echo "$errstr ($errno)\n<br>";

//Successful connect
} else {

    //login
    fputs($fp,"Action: login\r\nUsername: ".$username."\r\nSecret: ".$secret."\r\n\r\n");
//TO DO: check if login was successful or not
    
    //LOOP FOREVER - continuously read data 
    $line = '';
    while(1) {
        $read = fread($fp,1); //Read one byte at a time from the socket
        $line .= $read;

        //Check if we are at the end of a line
        if ("\n" == $read) {

            //Determine when we have reached a blank line which 
            // signals the end of a single events info
            $event_separator = false;
            if ("\r\n" == $line) {
                $event_separator = true;
            }
//TO DO: Add code that does something when we have an entire events info

            echo $line.'<br>'; //DEBUG ONLY: For screen display. Remove <br> for db storage
//TO DO: How do we redirect echo statement so we can just save info in db?

            flush($fp); //Flush the stdout to get it to display
            $line = '';
            
        } //end IF -> Check if we are at the end of a line
    } //end WHILE -> LOOP FOREVER
    
    fclose($fp); //Will never get here, but looks good to have it!
} //end ELSE -> Successful connect

?>

So I have modified the code some more, and it is posted below. What I will probably do when I get more time, is use the event reader script, and have it simply record the events in the database, and nothing else (no displaying, no filtering) to make sure it can keep up with events being generated. Then I will create a second script, and it will parse thru the events in the db and determine hold times and send any notifications or do any displaying of information.

<?php
// AMI EVENT READER v0.2 by Jeff Sherk - May 2011
//
// This script will continuously read all the Asterisk AMI events and output them to your browser
//
// This FREE SCRIPT is not copyrighted and is offered AS IS with no guarantees and no warrantees. Do what you want with it!

/////////////////////////////////////////////////
// NOTE: Required for this script to work, and also required is you want to use PHPAGI
/* MODIFY /etc/asterisk/manager.conf and add this (make sure user is asterisk 0664):
    [myamiclient]
    secret=********
    deny=0.0.0.0/0.0.0.0
    permit=127.0.0.1/255.255.255.0
    read = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
    write = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
/*

/////////////////////////////////////////////////
// NOTE: Only required if you want to use PHPAGI
/* MODIFY /etc/asterisk/phpagi.conf and add this (make sure user is asterisk 0777):
    [asmanager]
    server=127.0.0.1 ; server to connect to
    port=5038 ; default manager port
    username=myamiclient ; username for login
    secret=******** ; password for login
*/

//username and secret need to match the /etc/asterisk/manager.conf file
$username = 'myamiclient';
$secret = '********';

//DEBUG - Set to TRUE to display a bunch more stuff on the screen
$debug = false;

//Script should run forever, so prevent it from timing out
set_time_limit(0);

//Use fsockopen to connect the same way you would with Telnet
$fp = fsockopen("127.0.0.1", 5038, $errno, $errstr, 30);

//Unsuccessful connect
if (!$fp) {
    echo "$errstr ($errno)\n<br>";

//Successful connect
} else {

    //Login and make sure events are turned off (we will turn them on later).
    fputs($fp,"Action: Login\r\nUsername: ".$username."\r\nSecret: ".$secret."\r\n\r\n");
    fputs($fp,"Action: Events\r\nEventMask: off\r\n\r\n");

    //Check if authentication was successful or not. This duplicates the code from below, but will
    // help code below run a little faster because it will not be checking for this every loop.
    $authentication_failed = true;
    $authentication_loop = true;
    $authentication_time = time();
    $line = '';
    while($authentication_loop) { 
        $read = fread($fp,1); //Read one byte at a time from the socket
        $line .= $read;
        if ("\n" == $read) {
            if ($debug) {
                echo '<br>'.$line;
            }
            flush($fp);
            if ("\r\n" == $line) {
                $authentication_loop = false; //Loop until first blank line is returned.
            }
            if ('Response' == substr($line,0,8) ) {
                if ('Success' == substr($line,10,7) ) {
                    $authentication_failed = false;
                }
            }
            $line = '';        
        }
    }
    if ($authentication_failed) {
        die(' ERROR: Authentication failed!');
    } else {
        if ($debug) {
            echo " --Authentication SUCCESS--\n<br>";
        }
        flush($fp);
    }

    //Get list of current extensions and their current state
    fputs($fp,"Action: Command\r\nCommand: core show hints\r\n\r\n");
    $ext_loop = true;
    $ext_parse = false;
    $line = '';
    $ext_loop_start = 'Registered Asterisk Dial Plan Hints'; //Used to identify where we start parsing
    $ext_loop_end = '-----'; //Used to identify where we stop parsing
    $hold_array = array();
    while($ext_loop) { 
        $read = fread($fp,1); //Read one byte at a time from the socket
        $line .= $read;
        if ("\n" == $read) {
            //Are we at the end of the list? If so, stop parsing and exit WHILE loop
            if ($ext_loop_end == substr($line,0,5) ) {
                $ext_parse = false;
                $ext_loop = false;
            }
            //Parse for EXTEN number, CONTEXT and current STATUS
            if ($ext_parse) {
                $exten = explode('@',$line,2);
                $exten[0] = trim($exten[0]); //Extension number
                $exten[1] = trim($exten[1]);
                $context = explode(' ',$exten[1],5);
                $context[0] = trim($context[0]); //Context
                $state = explode('State:',$exten[1],2);
                $pos = strpos($state[1], ' ');
                $state[1] = substr($state[1],0,$pos); //Current State
                $hold_array[$exten[0]] = array('Status'=>$state[1], 'Context'=>$context[0], 'HoldStart'=>'');
            }
            //Are we at the beginning of list? If so, start parsing on next pass.
            if (strpos($line, $ext_loop_start) ) {
                $ext_parse = true;
            }
            $line = '';
        }
    }
    do_something_with_array($hold_array);

    //Turn Events back ON
    // Use eventmask ON for all events or CALL for call related events only.
    // on; off; system,call,log,verbose,command,agent,user
    fputs($fp,"Action: Events\r\nEventMask: call\r\n\r\n");

//TO DO: Do we need to loop thru entire list of extensions and do an Action: ExtensionState for each??

    //LOOP FOREVER - continuously read data 
    $line = '';
    $event_array = array();
    while(1) {
        $read = fread($fp,1); //Read one byte at a time from the socket
        $line .= $read;

        //Check if we are at the end of a line
        if ("\n" == $read) {

            //Determine when we have reached a blank line which 
            // signals the end of the events info
            if ("\r\n" == $line) {
               
                //Filter for data related to extensionstatus, linking, unlinking and hangup
                // Do we filter here, or should we just offload all data to another script and let it do the filtering??
                if ('ExtensionStatus'==$event_array['Event'] || 'Link'==$event_array['Event'] || 'Unlink'==$event_array['Event'] || 'Hangup'==$event_array['Event']) {

                    if ($debug) {
                        echo '<pre>';
                        print_r($event_array);
                        echo '</pre>';
                    }
                    flush($fp);
                    
                    //Keep track of each extensions current status
                    // -1 = ExtensionNotFound
                    //  0 = Idle
                    //  1 = InUse
                    //  2 = Busy
                    //  4 = Unavailable
                    //  8 = Ringing
                    // 16 = On Hold 
                    if ('ExtensionStatus'==$event_array['Event']) {
                        $hold_start = '';
                        $status = $event_array['Status'];
                        switch ($event_array['Status']) {
                            case -1:
                                $status = 'ExtensionNotFound';
                                break;
                            case 0:
                                $status = 'Idle';
                                break;
                            case 1:
                                $status = 'InUse';
                                break;
                            case 2:
                                $status = 'Busy';
                                break;
                            case 4:
                                $status = 'Unavailable';
                                break;
                            case 8:
                                $status = 'Ringing';
                                break;
                            case 16:
                                $status = 'Hold';
                                $hold_start = time();
                                break;
                        }
                        
                        $hold_array[$event_array['Exten']] = array('Status'=>$status, 'Context'=>$event_array['Context'], 'HoldStart'=>$hold_start);
                        if ($debug) {
                            echo '<pre>HOLD ';
                            print_r($hold_array);
                            echo '</pre>';
                        }
                        
                        do_something_with_array($hold_array);
                    }
                }
                unset($event_array);

            } else {
                $line_expl = explode(": ", $line, 2);
                $event_array[$line_expl[0]] = trim($line_expl[1]);
            }
            $line = '';
            
        } //end IF -> Check if we are at the end of a line
    } //end WHILE -> LOOP FOREVER
    
    fclose($fp); //Will never get here, but looks good to have it!
} //end ELSE -> Successful connect

function do_something_with_array($hold_array) {
    //Display on screen
    //TO DO: you could pass this info on to another script or save in db instead of displaying on screen
    echo '<br><b><u>EXTENSION STATUS</u></b><br>';
    foreach ($hold_array as $key=>$value) {
        echo $key.' '.$value['Status'].' '.$value['HoldStart'].'<br>';
    }
    flush($fp);
}

?>