Getting Started with the PAMI: PHP Asterisk Manager Interface = Easy Asterisk Monitoring
Introduction
PAMI is a PHP 5.3 client for AMI: Asterisk Manager Interface. Asterisk is one of the hot topics in the IT world due to its broad acceptance and use case scenarios.
PAMI will let you use the AMI interface to control and monitor your asterisk box(es) in a pretty easy way. Also, it supports Asynchronous AGI, so its even suitable to create a complete IVR solution.
With PAMI you receive events, and send actions. Each action is a command (for example, issue a dial or hangup an ongoing call). The events, on the other hand, are issued by asterisk whenever something interesting occurs in the system.
You will send the actions through the pami client. This is a synchronous operation, the process will block until the response has arrived. To get the events, just register an event listener (more on this after the installation instructions).
Getting it
First, a couple of useful links:
- http://marcelog.github.com/PAMI/: The homepage.
- http://pear.marcelog.name/: The PEAR channel
- http://packagist.org/packages/marcelog/pami: Packagist home
- https://github.com/marcelog/PAMI/: The GitHub repository
- Full PAMI examples
- http://ci.marcelog.name:8080/job/PAMI/lastSuccessfulBuild/artifact/build/: The CI server, containing the latest successful built artifacts, phar and pear packages.
- http://pear.apache.org/log4php/: The log4php pear channel.
- AMI Protocol article
- http://marcelog.github.com/articles/php_asterisk_listener_example_using_pami_and_ding.html: A more advanced pami application, using ding as DI container to capture events.
A brief parenthesis: Installing log4php (only needed if installing manually or using the phar)
Unless you are installing PAMI via the pear channel, you need to install a dependency first. PAMI uses log4php as its logging system, so let's install log4php via the pear channel:
Please also see the official installation guide of log4php.
$ pear channel-discover pear.apache.org/log4php $ pear install pear.apache.org/log4php/Apache_log4php-2.1.0
Make sure the directory "log4php" inside the log4php distribution is in your include path when using PAMI. If you get this error:
PHP Fatal error: Class 'Logger' not found
You need to include the logphp subdirectory correctly in your include_path.
You can download and install PAMI in a number of ways, let's first examine them and then see how to bootstrap the pami client:
For PHP 5.3.9 and 5.3.10 users
NOTE:Be sure that your PHP version is not 5.3.9 or 5.3.10, beacuse they had a bug in stream_get_line() that will prevent PAMI from working correctly. Please use a different 5.3 or 5.4 version. If you really need a patch, just email me or see this.
Option A: Installing and using the PHAR distribution
You can get the latest phar and pear package from the CI server. At this time, the latest version is 1.70.1, so the available artifact is named "PAMI-1.70.1.phar". Let's fetch it:
$ wget http://ci.marcelog.name:8080/job/PAMI/lastSuccessfulBuild/artifact/build/PAMI-1.70.1.phar
Let's see how to use it:
// Include the phar file.
require_once 'PAMI-1.70.1.phar';
// Set the include path to have pami's phar first
// (just to avoid having another installation in
// the include_path loading first).
ini_set('include_path', implode(PATH_SEPARATOR, array(
'phar://pami.phar', ini_get('include_path')
)));
That's it. You can now bootstrap the pami client and do your thing (see below).
Option B: Installing the PEAR distribution manually
As stated before, you can get the pear package at the CI server. You can download it and install it manually (again, 1.70.1 is the latest version at the time of this writing):
$ wget http://ci.marcelog.name:8080/job/PAMI/lastSuccessfulBuild/artifact/build/PAMI-1.70.1.tgz $ pear install PAMI-1.70.1.tgz
Option C: Installing via the PEAR Channel
A pear channel is available here. If you want to use it, just:
$ pear channel-discover pear.marcelog.name $ pear install marcelog/PAMI
Either way, to use it, make sure log4php is in your include path. PAMI will already be, since it's installed via pear:
/* * If you have a PSR-0 compatible autoloader, you wont * need this, just make sure your autoloader is * bootstrapped. If you don't, PAMI comes with its own * PSR-0 autoloader, this can also be used for your own * benefit since you wont need any other autoloader if * your tree honors PSR-0. */ require_once 'PAMI/Autoloader/Autoloader.php'; PAMI\Autoloader\Autoloader::register();
That's it. You can now instantiate the client.
Option D: Getting the source, installing from github
You can download the ZIP file from github, and also, you can clone the repository:
$ git clone git://github.com/marcelog/PAMI.git
When installed this way, you need to make sure you set the include path into the /src/mg subdirectory inside the root tree, so the PSR-0 autoloader would load from the src/mg/PAMI subdirectory.
Option E: Use composer
Just add the package "marcelog/pami":
{
"require": {
"marcelog/pami": "dev-master"
},
"repositories": [
{
"type": "pear",
"url": "http://pear.apache.org/log4php/"
}]
}
Packagist URL: http://packagist.org/packages/marcelog/pami
Let the fun begin!
In your manager.conf file, create a user with permissions to access ami. In the following example, the user admin, has ALL permissions granted (beware!). This is the username that we are going to use from pami:
[admin] secret = mysecret deny=0.0.0.0/0.0.0.0 permit=127.0.0.1/255.255.255.0 read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan write = system,call,agent,log,verbose,user,config,command,reporting,originate
Connecting to AMI
Working with pami is pretty straightforward. You need to get an instance of an IClient implementation. Currently, the only implementation is ClientImpl. A client will let you do this:
- Open the connection to asterisk AMI
- Close the connection to asterisk AMI
- Register an event listener
- Unregister an event listener
In order to get the client instance and open the connection, you need to provide the connection options through an array:
/* These are (in order) the options we can pass to pami client:
*
* The hostname or ip address where asterisk ami is listening
* The scheme can be tcp:// or tls://
* The port where asterisk ami is listening
* Username configured in manager.conf
* Password configured for the above user
* Connection timeout in milliseconds
* Read timeout in milliseconds
*/
$pamiClientOptions = array(
'host' => '127.0.0.1',
'scheme' => 'tcp://',
'port' => 9999,
'username' => 'admin',
'secret' => 'mysecret',
'connect_timeout' => 10000,
'read_timeout' => 10000
);
use PAMI\Client\Impl\ClientImpl as PamiClient;
$pamiClient = new PamiClient($pamiClientOptions);
// Open the connection
$pamiClient->open();
// Close the connection
$pamiClient->close();
Easy, isn't it?
Listening for events
In order to get events, you need to register an event listener. There are 3 possible ways to register an event listener, all of them using the registerEventListener() method of the IClient implementation, and your listener will receive EventMessage objects.
You can have as many listeners as you want, and mix them however you need.
- Register a Closure
use PAMI\Message\Event\EventMessage;
$pamiClient->registerEventListener(function (EventMessage $event) {
var_dump($event);
});
use PAMI\Message\Event\EventMessage;
class MyListener
{
public function handlerMethod(EventMessage $event)
{
var_dump($event);
}
}
$listener = new MyListener();
$pamiClient->registerEventListener(array(
$listener, 'handlerMethod'
));
use PAMI\Message\Event\EventMessage;
use PAMI\Listener\IEventListener;
class MyOtherListener implements IEventListener
{
public function handle(EventMessage $event)
{
var_dump($event);
}
}
$listener = new MyOtherListener();
$pamiClient->registerEventListener($listener);
In order for pami to receive the events, you need to periodically call the process() method, like:
$running = true;
while($running) {
$pamiClient->process();
usleep(1000);
}
If you dont like having a main loop like that, try using register_tick_function() instead:
register_tick_function(array($pamiClient, 'process'));
This will require to use declare(ticks=1) at the top of your source file.
Putting it all together
use PAMI\Client\Impl\ClientImpl as PamiClient;
use PAMI\Message\Event\EventMessage;
use PAMI\Listener\IEventListener;
$pamiClientOptions = array(
'host' => '127.0.0.1',
'scheme' => 'tcp://',
'port' => 9999,
'username' => 'admin',
'secret' => 'mysecret',
'connect_timeout' => 10000,
'read_timeout' => 10000
);
$pamiClient = new PamiClient($pamiClientOptions);
// Open the connection
$pamiClient->open();
$pamiClient->registerEventListener(function (EventMessage $event) {
var_dump($event);
});
$running = true;
// Main loop
while($running) {
$pamiClient->process();
usleep(1000);
}
// Close the connection
$pamiClient->close();
Filtering incoming events
Up to now, we've only seen how to register event listeners. These listeners will receive ALL the events that get to pami. What happens if we only want some events, or there is some important condition to be met before receiving the events? That's where predicates come to the rescue. When registering an event listener, an optional closure can be passed as argument that will be the predicate. If the predicate returns true, the event will be dispatched. If the predicate returns false, the event wont get to the listener:
use PAMI\Message\Event\DialEvent;
$pamiClient->registerEventListener(
function (EventMessage $event) {
var_dump($event);
},
function (EventMessage $event) {
return
$event instanceof DialEvent
&& $event->getSubEvent() == 'Begin'
;
}
);
In this case, our predicate will only return true if and only if the event received is DialEvent, and the subevent type is a begin (i.e: an agi application has issued a Dial() command). You can only add 1 predicate (per listener, but you can have multiple listeners), but you can make it as complex as as you wish, and as a result, your event listener will be a little cleaner.
By the way, here are all the events available.
More fun: Send actions
You can also send actions to asterisk. An action is just an object of a given type. You can see all the actions available here and the asterisk wiki with their official list of actions. Let's send a Reload action:
use PAMI\Message\Action\ReloadAction;
$response = $pamiClient->send(new ReloadAction());
if ($response->isSuccess()) {
echo "Ok!\n";
} else {
echo "Failed!\n"
}
The result of the action sent, will come as a Response message.
Configuring log4php
If you happen to run the code above, you will notice that log4php is outputing to the console. This is because it's probably not configured. You can configure log4php on your own (see this) or have pami do it on its own, by passing an option to the client with the log4php configuration file you want to use (in this case, an .ini file, but you can use xml):
$options = array(
'log4php.properties' => __DIR__ . '/log4php.properties',
'host' => '127.0.0.1',
// more options here
);
A sample log4php.properties ini file:
log4php.appender.default = LoggerAppenderDailyFile
log4php.appender.default.layout = LoggerLayoutPattern
log4php.appender.default.layout.ConversionPattern = "%d{ISO8601} [%p] %c: %m%n"
log4php.appender.default.file = /tmp/log.log
log4php.rootLogger = DEBUG, default
Conclusion
PAMI is simple and straightforward. I hope you have fun using it, just as I had when writing it! A component like this can open a lot of new possibilities to your cli and webapp applications.