Articles

Making an Asterisk Manager Interface monitor using PHP, PAMI, and Ding


Introduction

NOTE: If you are just looking for an introduction to ami and PAMI, take a look at: this.

Here, I'll write about AsterTrace (https://github.com/marcelog/AsterTrace), a simple project that will help you get started using the asterisk manager interface in extremely few lines of code :)

The goal for AsterTrace is to log into ami and capture every event sent by asterisk, process some (or not) and maybe log every one of them to some database.

AsterTrace uses Ding and PAMI. The first one as the container, and latter to communicate with AMI, so I'll talk about how AsterTrace uses them both, and how this will help you here and in other applications to get a more decoupled code, very easy to mantain and scale.

A similar article with Ding and Doctrine2 as ORM is available here.

Installing AsterTrace

You can get AsterTrace directly from github:

  • ZIP
  • TAR
  • SVN: http://svn.github.com/marcelog/AsterTrace.git
  • GIT: git://github.com/marcelog/AsterTrace.git

That's it :) Now read on to see how it works.

What is what and how it works

To sum up, this is the general idea:

  • Use Ding as Inversion of Control and Dependency Injection container. All we're going to do are just beans, plain old php objects.
  • Use PAMI as the framework to handle the connection to the Asterisk Manager Interface.
  • Use Ding's PamiHelper so we dont need to deal with pami use details. Our main program will be just a bean.
  • Use Ding's PropertiesHolder so we can configure mysql, ami, php, etc, from properties file (aka php INI files).
  • Use Ding's TCPServerHelper to open a tcp server where you can connect via telnet or via the language of your choice, to listen for events serialized using json. You can also send commands and get the responses in json format.
  • Our main program (called from PamiHelper) will use Ding's events to dispatch events coming in from AMI, to our "event listeners".
  • A REST interface is also provided, so this software can be used from a web environment.
  • Every event listener will be just a bean, that will get called by the container whenever an event is dispatched from our main program.
  • We are going to log events to a database. I chose to use mysql, via pdo. This PDO object is a bean.
  • To actually write to the database, we'll use prepared statements.
  • Each prepared statement will be a "bean", and will get injected to our event listeners. These beans are instantiated by ding using the factory-method and factory-bean options.
  • We need to log to files the normal application stuff, like debug info, errors, etc. We'll use ding's ILoggerAware interface in our beans, giving them direct access to the logger of the container (this needs log4php, so may want to install it before proceeding).
  • Handle errors through Ding error handler helper
  • Handle signals through Ding signal handler helper
  • Handle shutdown through Ding shutdown handler helper

Application tree layout overview. What's where.

src/mg/AsterTrace         ==> Main source directory
  ServerHandlers          ==> Server Handlers reside here. These will listen for events triggered from the tcp server.
    ServerHandler.php     ==> A base class for all tcp command listeners.
    ServerCommandDTO.php  ==> DTO to be used when dispatching events from the TCP server. The handlers will get this as the argument of the event.
    CoreShowChannels.php  ==> Event handler when a tcp client issues CoreShowChannels
  EventHandlers           ==> Event Handlers reside here. These are our listening beans.
    DialListener.php
    DtmfListener.php
    EventListener.php
    PDOListener.php
    VarSetListener.php
    NewChannelListener.php
    NewStateListener.php
    NewExtenListener.php
  Handlers
    ErrorHandler.php      ==> Application error handler.
    PamiHandler.php       ==> Our pami handler (will receive all events issued by the PamiHelper).
    ShutdownHandler.php   ==> Application shutdown handler.
    SignalHandler.php     ==> Application signal handler (so you may shutdown everything with kill or ^C).
    TCPServerHandler.php  ==> The TCP Server. This will get called from the TCPServerHelper from ding. In turn, this will dispatch events according to what each client sent as a command.
  bin
    astertrace.php        ==> Main entry point for the application (see below).
    bootstrap.php         ==> Bootstrap code (see below).
    rest.php              ==> REST interface. Use it as a webservice (see below).
conf/
    astertrace.properties.example ==> AMI connection information. Rename to astertrace.properties and edit.
    log4php.properties.example    ==> log4php configuration to log errors, debug, etc. Rename to log4php.properties (and log4php-rest.properties to use it with the rest interface)and edit.
    mysql.properties.example      ==> mysql configuration (host, tables, database, etc). Rename to mysql.properties and edit.
    php.properties.example        ==> php properties, like display_errors, error_reporting, etc. Rename to php.properties and edit.
    server.properties.example     ==> TCP Server properties. Rename to server.properties and edit.
    support                       ==> Bean definitions are here.
      pdo-mysql.xml               ==> PDO related beans.
      cli.xml                     ==> Main bean definitions for the CLI interface.
      handlers.xml                ==> Beans for error handler, signal handler, shutdown handler, pami, etc.
      server-handlers.xml         ==> Each bean defined here will handle a command (action) from a TCP Client.
      rest.xml                    ==> Main bean definitions for the REST interface.
      event-handlers.xml          ==> Where to include the actual listeners to be run (CLI).
      event-handlers-rest.xml     ==> Where to include the actual listeners to be run (REST).
      event-handlers
        dial-pdo-mysql-statements.xml    ==> Prepared statements for dial listener.
        dial.xml                         ==> dial listener.
        dtmf-pdo-mysql-statements.xml    ==> Prepared statements for dtmf listener.
        dtmf.xml                         ==> dtmf listener.
        event-pdo-mysql-statements.xml   ==> Prepared statements for generic events listener.
        event.xml                        ==> Generic events listener.
        rest-event.xml                   ==> Generic events listener (REST).
        varset-pdo-mysql-statements.xml  ==> Prepared statements for varset listener.
        varset.xml                       ==> Varset listener.
        newchannel-pdo-mysql-statements.xml  ==> Prepared statements for newchannel listener.
        newchannel.xml                       ==> Newchannel listener.
        newstate-pdo-mysql-statements.xml  ==> Prepared statements for newstate listener.
        newstate.xml                       ==> Newstate listener.
        newexten-pdo-mysql-statements.xml  ==> Prepared statements for newexten listener.
        newexten.xml                       ==> Newexten listener.
      server-handlers                      ==> Beans that handle TCPClient actions.
        coreShowChannels.xml               ==> Handler for a TCP Client issuing a CoreShowChannels.
      handlers                             ==> Several "handlers" beans (error, signal, etc).
        error-handler.xml
        pami-handler.xml
        shutdown-handler.xml
        signal-handler.xml
        server.xml

The key here, are the xml files, which are the container configuration. This is where all the beans are defined and related to each other. Because of this configuration, our beans will be injected with everything they need to work (even a logger :))

Main entry point

The main entry point for AsterTrace is astertrace.php. So you can start it by running it:

php src/mg/AsterTrace/bin/astertrace.php ./conf

This script will call the bootstrap.php file that resides in the same directory. Boostrap.php will:

  • Setup the include path needed
  • Check the invoking arguments (remember, if this is a command line application, you need one argument which is the config directory where all configuration files reside, typically, ./conf)
  • If on a web environment, check the environment variable "CONFIG_DIR" to get the application's config directory.
  • Setup the ding's container configuration

After this, astertrace.php will continue execution:

  • The ding container will be instantiated using the configuration that bootstrap.php prepared.
  • The container will use the cli.xml file as its configuration (if cli environment), or the rest.xml (if on web environment).
  • Either beans file uses the PropertiesHolder to make ding load properties from php.properties, mysql.properties, astertrace.properties. So the correct values are injected in any beans that need them.
  • Either beans file includes other bean configurations, for PDO, event listeners, error handler, signal handler, and shutdown handler, so all of them are available in the application.
  • The bean pamiHelper is requested to the container. This bean is actually the PamiHelper that comes from ding. The container will also create the bean pami which is our own event handler.
  • An infinite loop is started, calling the process() method of the PamiHelper. This method will read messages incoming from AMI.

And that is pretty much all we have to do :) the container takes care of the rest:

  • Whenever an event is received by PAMI, it will call the PamiHelper.
  • The PamiHelper will call our own handler.
  • Our handler will dispatch an event through the container, named "anyEvent", so any beans listening for this event will execute.
  • Based on the name of the event, the handler dispatches another event through the container. For example, if the event was Dial, then the "dial" event is dispatched, and the method onDial() of every listening bean (for that event) is executed.
  • Make a telnet connection to the address:port configured in server.properties. You should see the events coming in from ami in json format :)
  • Setup your web server so you can access rest.php. Remember to setup the environment variable CONFIG_DIR to point to the application environment. When requesting rest.php, you should start seeing events in json format coming in from ami.

Currently available listeners

Event Listener

VarSet Listener

Dial Listener

DTMF Listener

Newchannel Listener

Newstate Listener

Newexten Listener

Extending the application

We can add more beans to extend this functionality. Adding beans is trivial, we could use annotations, yaml, or xml configurations.

Whenever we want to get a specific event, we just need to define a listens-on directive in the bean configuration, and the container will automagically call the method onEventName() with the event received :) So we can propagate events via radius, or activemq, or rabbitmq, etc.

Conclusions

As you see, having Ding and PAMI work together is a pretty smooth task, and will benefit your code by making it cleaner and less coupled. You will be able to focus on the task you really want to do. Ding will take care of the inversion of control and dependeny injection, while PAMI will take care of the managing the AMI protocol. In this way, your application is truly event driven, and everything will get called without you having to worry about how or when. Even configuring the application is trivial due to the use of the PropertiesHolder.

Everything is a bean. The PDO object, the PDO statements, and error/shutdown/signal handlers, our own event handlers, etc. This allows the application to be easily extended (just write new beans that "listens-on" different events, or event the same). Everything can be done within extremely few lines of code :)