NAME
Asterisk::AMI - Perl moduling for interacting with the Asterisk Manager Interface
VERSION
0.1.7
SYNOPSIS
use Asterisk::AMI;
my $astman = Asterisk::AMI->new(PeerAddr => '127.0.0.1',
PeerPort => '5038',
Username => 'admin',
Secret => 'supersecret'
);
die "Unable to connect to asterisk" unless ($astman);
my $action = $astman->({ Action => 'Command',
Command => 'sip show peers'
});
DESCRIPTION
This module provides an interface to the Asterisk Manager Interface. It's goal is to provide a flexible, powerful, and reliable way to interact with Asterisk upon which other applications may be built. It utilizes AnyEvent and therefore can integrate very easily into event-based applications, but it still provides blocking functions for us with standard scripting.
Constructor
new([ARGS])
Creates a new AMI object which takes the arguments as key-value pairs.
Key-Value Pairs accepted:
PeerAddr Remote host address <hostname>
PeerPort Remote host port <service>
Events Enable/Disable Events 'on'|'off'
Username Username to access the AMI
Secret Secret used to connect to AMI
BufferSize Maximum size of buffer, in number of actions
Timeout Default timeout of all actions in seconds
Handlers Hash reference of Handlers for events { 'EVENT' => \&somesub };
Keepalive Interval (in seconds) to periodically sends 'Ping' actions to asterisk
TCP_Keepalive Enables/Disables SO_KEEPALIVE option on the socket 0|1
Blocking Enable/Disable blocking connects 0|1
on_connect A subroutine to run after we connect
on_connect_err A subroutine to call if we have an error while connecting
on_error A subroutine to call when an error occurs on the socket
on_disconnect A subroutine to call when the remote end disconnects
on_timeout A subroutine to call if our Keepalive times out
'PeerAddr' defaults to 127.0.0.1.\n
'PeerPort' defaults to 5038.
'Events' default is 'off'. May be anything that the AMI will accept as a part of the 'Events' parameter for the
login action.
'Username' has no default and must be supplied.
'Secret' has no default and must be supplied.
'BufferSize' has a default of 30000. It also acts as our max actionid before we reset the counter.
'Timeout' has a default of 0, which means no timeout or blocking.
'Handlers' accepts a hash reference setting a callback handler for the specfied event. EVENT should match the what
the contents of the {'Event'} key of the event object will be. The handler should be a subroutine reference that
will be passed the a copy of the AMI object and the event object. The 'default' keyword can be used to set
a default event handler. If handlers are installed we do not buffer events and instead immediatly dispatch them.
If no handler is specified for an event type and a 'default' was not set the event is discarded.
'Keepalive' only works when running with an event loop.
'TCP_Keepalive' default is disabled. Actives the tcp keepalive at the socket layer. This does not require
an eventloop and is lightweight. Useful for applications that use long-lived connections to Asterisk but
do not run an event loop.
'Blocking' has a default of 1 (block on connecting). A value of 0 will cause us to queue our connection
and login for when an event loop is started. If set to non blocking we will always return a valid object.
'on_connect' is a subroutine to call when we have successfully connected and logged into the asterisk manager.
it will be passed our AMI object.
'on_connect_err', 'on_error', 'on_disconnect'
These three specify subroutines to call when errors occur. 'on_connect_err' is specifically for errors that
occur while connecting, as well as failed logins. If 'on_connect_err' or 'on_disconnect' it is not set,
but 'on_error' is, 'on_error' wil be called. 'on_disconnect' is not reliable, as disconnects seem to get lumped
under 'on_error' instead. When the subroutine specified for any of theses is called the first argument is a copy
of our AMI object, and the second is a string containing a message/reason. All three of these are 'fatal', when
they occur we destroy our buffers and our socket connections.
'on_timeout' is called when a keepalive has timed out, not when a normal action has. It is non-'fatal'.
The subroutine will be called with a copy of our AMI object and a message.
Warning - Mixing Eventloops and blocking actions
If you are running an event loop and use blocking methods (anything that accepts it's timeout outside of
the action hash e.g. get_response, check_response, action, connected) the outcome is unspecified. It may work,
it may lock everything up, the action may work but break something else. I have tested it and behavior seems
un-predictable at best and is very circumstantial.
If you are running an eventloop use non-blocking callbacks! It is why they are there!
However if you do play with blocking methods inside of your loops let me know how it goes.
Actions
Construction
No matter which method you use to send an action (send_action(), simple_action(), or action()), they all accept actions in the same format, which is a hash reference. The only exceptions to this rules are when specifying a callback and a callback timeout, which only work with send_action.
To build and send an action you can do the following:
%action = ( Action => 'Command',
Command => 'sip show peers'
);
$astman->send_action(\%action);
Alternatively you can also do the following to the same effect:
$astman->send_action({ Action => 'Command',
Command => 'sip show peers'
});
Additionally the value of the hash may be an array reference. When an array reference is used, every value in the array is append as a different line to the action. For example:
{ Variable => [ 'var1=1', 'var2=2' ] }
Will become:
Variable: var1=1
Variable: var2=2
When the action is sent.
Sending and Retrieving
More detailed information on these individual methods is available below
The send_action() method can be used to send an action to the AMI. It will return a positive integer, which is the ActionID of the action, on success and will return undef in the event it is unable to send the action.
After sending an action you can then get its response in one of two methods.
The method check_response() accepts an actionid and will return 1 if the action was considered successful, 0 if it failed and undef if an error occured or on timeout.
The method get_response() accepts an actionid and will return a Response object (really just a fancy hash) with the contents of the Action Response as well as any associated Events it generated. It will return undef if an error occured or on timeout.
All responses and events are buffered, therefor you can issue several send_action()s and then retrieve/check their responses out of order without losing any information. Infact, if you are issuing many actions in series you can get much better performance sending them all first and then retrieving them later, rather than waiting for responses immediatly after issuing an action.
Alternativley you can also use simple_action() and action(). simple_action() combines send_action() and check_response(), and therefore returns 1 on success and 0 on failure, and undef on error or timeout. action() combines send_action() and get_response(), and therefore returns a Response object or undef.
Examples
Send and retrieve and action:
my $actionid = $astman->send_action({ Action => 'Command',
Command => 'sip show peers'
});
my $response = $astman->get_response($actionid)
This is equivalent to the above:
my $response = $astman->action({ Action => 'Command',
Command => 'sip show peers'
});
The following:
my $actionid1 = $astman->send_action({ Action => 'Command',
Command => 'sip show peers'
});
my $actionid2 = $astman->send_action({ Action => 'Command',
Command => 'sip show peers'
});
my $actionid3 = $astman->send_action({ Action => 'Command',
Command => 'sip show peers'
});
my $response3 = $actan->get_response($actionid3);
my $response1 = $actan->get_response($actionid1);
my $response2 = $actan->get_response($actionid2);
Can be much faster than:
my $response1 = $astman->action({ Action => 'Command',
Command => 'sip show peers'
});
my $response2 = $astman->action({ Action => 'Command',
Command => 'sip show peers'
});
my $response3 = $astman->action({ Action => 'Command',
Command => 'sip show peers'
});
Callbacks
You may also specify a method to callback when using send_action as well as a timeout.
An example of this would be:
send_action({ Action => 'Ping',
CALLBACK => \&somemethod,
TIMEOUT => 7 });
In this example once the action 'Ping' finishes we will call somemethod() and pass it the a copy of our AMI object and the Response Object for the action. If TIMEOUT is not specified it will use the default set. A value of 0 means no timeout. When the timeout is reached somemethod() will be called and passed a reference to the our $astman and the un-completed Response Object, therefore somemethod() should check the state of the object. Checking the key {'GOOD'} is usually a good indication if the object is useable.
Callback Caveats
Callbacks only work if we are processing packets, therefore you must be running an event loop. Alternatively, we run mini-event loops for our blocking calls (e.g. action(), get_action()), so in theory if you set callbacks and then issue a blocking call those callbacks should also get trigged. However this is an unsupported scenario.
Timeouts are done using timers, depending on how your event loop works it may be relative or absolute. Either way they are set as soon as you send the object. Therefore if you send an action with a timeout and then monkey around for a long time before getting back to your event loop (to process input) you can time out before ever even attempting to receive the response.
A very contrived example:
send_action({ Action => 'Ping',
CALLBACK => \&somemethod,
TIMEOUT => 3 });
sleep(4);
#Start some loop
someloop;
#Oh no we never even tried to get the response yet it will still time out
ActionIDs
This module handles ActionIDs internally and if you supply one in an action it will simply be ignored and overwritten.
Responses and Events
Responses
Responses are returned as response objects, which are hash references, structured as follows:
$response->{'Response'} Response to our packet (Success, Failed, Error, Pong, etc).
{'ActionID'} ActionID of this Response.
{'Message'} Message line of the response.
{'EVENTS'} Arrary reference containing Event Objects associated with this actionid.
{'PARSED'} Hash refernce of lines we could parse into key->value pairs.
{'DATA'} Array refernce of lines that we could not parse.
{'CMD'} Contains command output from 'Action: Command's. It is an array reference.
{'COMPLETED'} 1 if completed, 0 if not (timeout)
{'GOOD'} 1 if good, 0 if bad. Good means no errors and COMPLETED.
Events
Events are turned into event objects, these are similiar to response objects, but their keys vary much more
depending on the specific event.
Some common contents are:
$event->{'Event'} The type of Event
{'ActionID'} Only available if this event was caused by an action
Event Handlers
Here is a very simple example of how to use event handlers.
my $astman = Asterisk::AMI->new(PeerAddr => '127.0.0.1',
PeerPort => '5038',
Username => 'admin',
Secret => 'supersecret',
Events => 'on',
Handlers => { default => \&do_event,
Hangup => \&do_hangup };
);
die "Unable to connect to asterisk" unless ($astman);
sub do_event {
my ($asterisk, $event) = @_;
print 'Yeah! Event Type: ' . $event->{'Event'} . "\r\n";
}
sub do_hangup {
my ($asterisk, $event) = @_;
print 'Channel ' . $event->{'Channel'} . ' Hungup because ' . $event->{'Cause-txt'} . "\r\n";
}
#Start some event loop
someloop;
How to use in an event-based application
Getting this module to work with your event based application is really easy so long as you are running an
event-loop that is supported by AnyEvent. Below is a simple example of how to use this module with your
preferred event loop. We will use EV as our event loop in this example. I use subroutine references in this
example, but you could use anonymous subroutines if you want to.
#Use your prefered loop before our module so that AnyEvent will autodetect it
use EV;
use Asterisk::AMI:
#Create your connection
my $astman = Asterisk::AMI->new(PeerAddr => '127.0.0.1',
PeerPort => '5038',
Username => 'admin',
Secret => 'supersecret',
Events => 'on',
Handlers => { default => \&eventhandler }
);
#Alternativly you can set Blocking => 0, and set an on_error sub to catch conneciton errors
die "Unable to connect to asterisk" unless ($astman);
#Define the subroutines for events
sub eventhandler { my ($ami, $event) = @_; print 'Got Event: ',$event->{'Event'},"\r\n"; }
#Define a subroutine for your action callback
sub actioncb { my ($ami, $response) = @_; print 'Got Action Reponse: ',$response->{'Response'},"\r\n"; }
#Send an action
my $action = $astman->({ Action => 'Ping',
CALLBACK => \&actioncb });
#Do all of you other eventy stuff here, or before all this stuff, whichever
#..............
#Start our loop
EV::loop
Thats it, the EV loop will allow us to process input from asterisk. Once the action completes it will
call the callback, and any events will be dispatched to eventhandler(). As you can see it is fairly
straight-forward. Most of the work will be in creating subroutines to be called for various events and
actions that you plan to use.
Methods
send_action ( ACTION )
Sends the action to asterisk. If no errors occured while sending it returns the ActionID for the action,
which is a positive integer above 0. If it encounters an error it will return undef.
check_response( [ ACTIONID ], [ TIMEOUT ] )
Returns 1 if the action was considered successful, 0 if it failed, or undef on timeout or error. If no ACTIONID
is specified the ACTIONID of the last action sent will be used. If no TIMEOUT is given it blocks, reading in
packets until the action completes. This will remove a response from the buffer.
get_response ( [ ACTIONID ], [ TIMEOUT ] )
Returns the response object for the action. Returns undef on error or timeout.
If no ACTIONID is specified the ACTIONID of the last action sent will be used. If no TIMEOUT is given it
blocks, reading in packets until the action completes. This will remove the response from the buffer.
action ( ACTION [, TIMEOUT ] )
Sends the action and returns the response object for the action. Returns undef on error or timeout.
If no ACTIONID is specified the ACTIONID of the last action sent will be used.
If no TIMEOUT is given it blocks, reading in packets until the action completes. This will remove the
response from the buffer.
simple_action ( ACTION [, TIMEOUT ] )
Sends the action and returns 1 if the action was considered successful, 0 if it failed, or undef on error
and timeout. If no ACTIONID is specified the ACTIONID of the last action sent will be used. If no TIMEOUT is
given it blocks, reading in packets until the action completes. This will remove the response from the buffer.
disconnect ()
Logoff and disconnects from the AMI. Returns 1 on success and 0 if any errors were encountered.
get_event ( [ TIMEOUT ] )
This returns the first event object in the buffer, or if no events are in the buffer it reads in packets
waiting for an event. It will return undef if an error occurs.
If no TIMEOUT is given it blocks, reading in packets until an event arrives.
amiver ()
Returns the version of the Asterisk Manager Interface we are connected to. Undef until a the connection is made
(important if you have Blocking => 0).
connected ( [ TIMEOUT ] )
This checks the connection to the AMI to ensure it is still functional. It checks at the socket layer and
also sends a 'PING' to the AMI to ensure it is still responding. If no TIMEOUT is given this will block
waiting for a response.
Returns 1 if the connection is good, 0 if it is not.
error ()
Returns 1 if there are currently errors on the socket, 0 if everything is ok.
destroy ( [ FATAL ] )
Destroys the contents of all buffers and removes any current callbacks that are set. If FATAL is true
it will also destroy our IO handle and its associated watcher. Mostly used internally. Useful if you want to
ensure that our IO handle watcher gets removed.
See Also
Asterisk::AMI::Common, Asterisk::AMI::Common::Dev
AUTHOR
Ryan Bullock (rrb3942@gmail.com)
BUG REPORTING AND FEEBACK
Please report any bugs or errors to our github issue tracker at http://github.com/rrb3942/perl-Asterisk-AMI/issues or the cpan request tracker at https://rt.cpan.org/Public/Bug/Report.html?Queue=perl-Asterisk-AMI
COPYRIGHT
Copyright (C) 2010 by Ryan Bullock (rrb3942@gmail.com)
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.