Skip to content


Simple Channel Monitoring via Shift8

Last week, I received a request to provide some samples on the Shift8 PHP Library. I managed to get some time and I wrapped something up just as a proof of concept. So here it is.

It is a simple AJAX application that will display the active channels and update them on hangup. (Something like Flash Operator Panel). First off here is how it looks:

Shift8 Demo

So let’s see how this application got crafted. Such kind of applications require two parts. The Poller section that retrieves the information from the remote asterisk and the Javascript equivalent to display the information in the user’s browser.

Here is the poller:

<!--?php
session_start();
 
//
// Dummy configuration
//
$config['asterisk'] = 'http://localhost:8088/mxml';
$config['manager']  = 'manager';
$config['secret']   = 'secret';
 
require_once 'Shift8-0.1.3/library/Shift8.php';
require_once 'Shift8-0.1.3/library/Debug/Listener/Html.php';
 
//
// Create a poller to retrieve the events from Shift8
//
class Poller extends Shift8_Event_Listener {
        private $events;
        private $shift8;
 
        public function __construct() {
                global $config;
 
                $this->events = array();
 
                // Construct the shift8 object and setup the event listener
                $this->shift8 = new Shift8($config['asterisk'], $config['manager'], $config['secret'], false, false);
                //Replace the above line with the next to enable debugging. (Note: this will stop the javascript from working)
                //$this->shift8 = new Shift8($config['asterisk'], $config['manager'], $config['secret'], false, new Shift8_Debug_Listener_Html());
 
                // Add the event listener
                $this->shift8->addEventListener('Poller', $this, new Poller_Filter());
 
                //
                // AJAM uses a cookie value to identify a session. In order to keep the session open
                // we pass down the cookie value in the session with the user browser
                //
                if( !isset($_SESSION['acookie']) ) {
                        // No session cookie available. Let's login to the server
                        if( !$this->shift8->login() ) {
                                throw new Exception("Unable to connect to the remote asterisk");
                        }
 
                        $_SESSION['acookie'] = $this->shift8->getCookie();
                }
                else {
                        // Session cookie already exists. Let's use this
                        $this->shift8->setCookie($_SESSION['acookie']);
                }
        }
 
        public function notify( $event ) {
                $this->events[] = $event;
        }
 
        public function getEvents() {
                if( !$this->shift8->waitEvent() ) {
                        //
                        // No events received. This could mean that our session has expired. Let's reset the
                        // cookie and on next call we will relogin on the remote asterisk
                        //
                        unset($_SESSION['acookie']);
                        return null;
                }
 
                return $this->events;
        }
}
 
//
// Create a filter to only get the events that we are interested of. In this case 'Newchannel' and 'Hangup'
//
class Poller_Filter extends Shift8_Event_Filter {
        public function filter( $event ) {
                if( $event == 'Newchannel' || $event == 'Hangup' )
                        return true;
 
                return false;
        }
}
 
//
// Ok let's start our poller here. The rest is history
//
 
try {
        $poller = new Poller();
} catch( Exception $e ) {
        echo json_encode(
                array(
                        'type'    => 'error',
                        'message' => $e->getMessage()
                )
        );
 
        return;
}
 
echo json_encode(
        array(
                'type'   => 'success',
                'events' => $poller->getEvents()
        )
);

As you can see from the comments we create a Shift8_Event_Listener class and we pass it to the Shift8 library so that we can easily intercept all the events that we receive from the remote asterisk server. We also create a Shift8_Event_Filter object to filter out the events that are not needed and only keep the events that are of interest to us. (In our case, ‘Newchannel’ for new channel creation and ‘Hangup’ for channel termination).

One interesting note is the use of the getCookie()/setCookie() functions of the Shift8 PHP library. AJAM uses a cookie to keep the session open between the client and the manager. In order to be able to maintain an open session in between AJAX calls on first login(), we get the cookie value via getCookie() and store it in the user’s $_SESSION. If the value exists from that point on we keep using it for every subsequent request to AJAM. If method waitEvent() returns null we presume that the session has expired, we unset the $_SESSION variable thus forcing the poller to re-login on the next request.

Please note that there is one more thing not handled by this poller. If you were to do multiple requests using AJAX please remember that PHP Sessions are locking. Thus you can’t have an open connection to the poller waiting for events from the remote asterisk and perform another command on the Asterisk at the same time. (PHP will wait for the poller to return before unlocking the session and giving access to the next request). (This is a common scenario which you will face if you need to terminate or spy an active channel – See here on how to spy a channel via Shift8).

In order to avoid this you must use session_write_close() before calling WaitEvent() so that the session is released before polling the remote server. However if you have closed the session before the WaitEvent() you cannot unset the $_SESSION['acookie'] on null return. (You should have another AJAX request pinging the remote asterisk (See ping()) and if the request fails re-login)

Finally we fire up the poller and return the events trapped via a JSON reply back to the Javascript side. Here is a dummy Javascript file: (I am using jQuery)

Poller = function( container ) {
        var me = this;
        var _channels = [];
        var _stopPoll = false;
 
        this.poll = function() {
                if( _stopPoll ) {
                        return;
                }
 
                $.ajax({
                        'url': 'poller.php?randomId=' + Math.random(),
                        'dataType': 'json',
                        'success': function(data, textStatus, XMLHttpRequest) {
                                _process(data);
 
                                me.poll();
                        }
                });
        };
 
        _process = function( events ) {
                if( events.type != 'success' ) {
                        alert("Unable to communicate with the remote asterisk");
                        _stopPoll = true;                       
                }
 
                for( var i in events.events ) {
                        var event = events.events[i];
 
                        if( event['variables']['event'] == 'Newchannel' ) {
                                _newCall(event['variables']['uniqueid'], event['variables']['calleridname'] + " <" + event['variables']['calleridnum'] + ">", event['variables']['channel']);
                        }
                        else {
                                _terminateCall(event['variables']['uniqueid']);
                        }
                }
        };
 
        _newCall = function( id, caller, channel ) {
                console.log("New call (" + id + ")");
 
                _channels[id] = {'caller': caller, 'channel': channel};
                container.append("<div class=\"call\" id=\"call_" + id.replace(".", "-") + "\"><h1>" + caller + "</h1><br/><br/><div class=\"ext\">" + channel + "</div></div>");
        };
 
        _terminateCall = function( id ) {
                console.log("Call (" + id + ") terminated");
 
                $("#call_" + id.replace(".", "-")).fadeOut('slow', function() {
                        delete _channels[id];
                });
        };
};
 
$(document).ready(function() {
        var poller = new Poller($("#calls"));
        poller.poll();
});

Here things are simple. We perform an AJAX request to poller.php (using a random number to avoid browser caching), get the JSON result and append/delete the call div to the container object ($(“#calls”)).

While writing this demo, I stumbled upon two bugs of the Shift8 library so I’ve released a new version 0.1.3 found here. Also please note that the way I’ve build this little demo will only display new calls in the browser and not already established calls.

Ideas/Improvements

Well sky is the limit but here are a few I though off while writing this:

  • Channel Spy/Whisper
  • Call termination
  • Channel status
  • New call from web interface
  • ….

Demo download

Here is a tarball containing all the requested files to try out the demo yourself:

Please share thoughts, ideas on the matter. I’d love to hear how you use Shift8.

Cheers!

Posted in PHP, Telephony.

Tagged with , , , , .


10 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Shri Ram says

    Hi

    I know..this might be a ridiculous question from me

    Actually I am new to Asterisk system.

    After reading about Asterisk Account Manager Api
    I get to know that we can track our events. and I am still not able to understood what exactly we use this for and
    how can we use this, where and how?

  2. Jini says

    What is mxml over here in Asterisk config?

  3. mobius says

    @Jini: By specifying mxml you request from the AJAM interface to return the output in XML instead of RAW. Shift8 intercepts XML only. If you were to do a http show status on the asterisk cli you would get something along these lines:

    http show status
    HTTP Server Status:
    Prefix: CLI>
    Server Enabled and Bound to 127.0.0.1:8088

    Enabled URI’s:
    /httpstatus => Asterisk HTTP General Status
    /phoneprov/… => Asterisk HTTP Phone Provisioning Tool
    /manager => HTML Manager Event Interface
    /rawman => Raw HTTP Manager Event Interface
    /static/… => Asterisk HTTP Static Delivery
    /mxml => XML Manager Event Interface

    Cheers!

  4. mobius says

    @Shri Ram,

    I am not sure I understood your question exactly. You mean what are you support to do with the asterisk events? Asterisk fires events through the manager API constantly informing the manager for all changes in state. By monitoring the events that actually take place, and probably filtering some of them out, you can get valuable information to create dynamic applications. For example the post above, filters the NewChannel and Hangup events, so that the application knows pretty much when a new call has been established and when an old call has hangup. If you add each new call in an array per unique.id on NewChannel, and remove the call on Hangup, you eventually end up with an array that contains the current running calls in that asterisk box.

    I hope that helped a bit :)

  5. Sebastian says

    Hi, i have question.

    I have a problem with your demo, i get only the calleridnum when the call is made from one extension to an other. What can I do to print the number from an inbound call?

    thanks,
    Sebastian

  6. mobius says

    @Sebastian,

    You mean you get an inbound call and no event is fired or it doesn’t contain the calleridnum?

    If it is the first case, what technology are you using for the inbound call? SIP/IAX/Dahdi? Maybe asterisk is raising another event than NewChannel that you need to add to the Filter.

    If it does raise an event but does not contain the calleridnum maybe it is something to do with the call itself? Does the call arrive to a UA with caller-id num properly? Could you show me the event raised? You can use the Shift8_Debug_Listener_Syslog to catch the events and log them for debugging purposes.

    Cheers!

  7. Sebastian says

    @mobius

    Thanks for the tips, all works well now.
    But one more question, what event should i listen for if i want to print out the yellow boxes, only when the call is answererd by a softphone.

    Thanks,

  8. Alessandro says

    Hi i’m using asterisk 1.8 but i receive “Unable to communicate with the remote Asterisk”

    could you please help me?

    Alessandro

  9. Deivis says

    I’m using asterisk 1.8, but it doesn’t work, coul you help us? please.

  10. mobius says

    Give me some information how you have set it up and any errors it might give out so that i can help



Some HTML is OK

or, reply to this post via trackback.