NAME
Asterisk::AMI - Perl module for interacting with the Asterisk Manager Interface
VERSION
0.2.8
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.
SSL SUPPORT INFORMATION
For SSL support you will also need the module that AnyEvent::Handle uses for SSL support, which is not a required dependency. Currently that module is 'Net::SSLeay' (AnyEvent:Handle version 5.251) but it may change in the future.
CentOS/Redhat
If the version of Net:SSLeay included in CentOS/Redhat does not work try installing an updated version from CPAN.
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
AuthType Authentication type to
use
for
login
'plaintext'
|
'MD5'
UseSSL Enables/Disables SSL
for
the connection 0|1
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
send
'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
OriginateHack Changes settings to allow Async Originates to work 0|1
'PeerAddr'
defaults to 127.0.0.1.
'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.
'AuthType'
sets the authentication type to
use
for
login. Default is
'plaintext'
. Use
'MD5'
for
MD5 challenge
authentication.
'UseSSL'
defaults to 0 (
no
ssl). When SSL is enabled the
default
PeerPort changes to 5039.
'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 on blocking.
'Handlers'
accepts a hash reference setting a callback handler
for
the specified event. EVENT should match
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 immediately 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. Used
with
on_timeout, this can be used to detect
if
asterisk
has
become un-responsive.
'TCP_Keepalive'
default
is disabled. Activates the tcp keep-alive at the
socket
layer. This does not
require
an event-loop 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'
will 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 keep-alive
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.
'OriginateHack'
defaults to 0 (off). This essentially enables
'call'
events and says 'discard all events
unless
the user
has
explicitly enabled events' (prevents a memory leak). It does its best not to mess up
anything you have already set. Without this,
if
you
use
'Async'
with
an
'Originate'
the action will timeout
or never callback. You don
't need this if you are already doing work with events, simply add '
call' events
to your eventmask.
Disabling Warnings
If you have warnings enabled this module will emit a number of them on connection errors, deprecated features, etc.
To disable this but still have all other warnings in perl enabled you can
do
the following:
no
warnings
qw(Asterisk::AMI)
;
That will enable warnings but disable any warnings from this module.
Warning - Mixing Event-loops and blocking actions
For an intro to Event-Based programming please check out the documentation in AnyEvent::Intro.
If you are running an event loop and
use
blocking methods (e.g. get_response, check_response, action,
simple_action, connected, or a blocking
connect
) 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 unpredictable at best and is very
circumstantial.
However
if
you
do
play
with
blocking methods inside of your loops let me know how it goes.
Actions
ActionIDs
This module handles ActionIDs internally and if you supply one in an action it will simply be ignored and overwritten.
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 occurred 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 occurred 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. In-fact, 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 immediately after issuing an action.
Alternatively 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
=
$astman
->get_response(
$actionid3
);
my
$response1
=
$astman
->get_response(
$actionid1
);
my
$response2
=
$astman
->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'
});
Originate Examples
These don
't include non-blocking examples, please read the section on '
Callbacks' below
for
information
on using non-blocking callbacks and events.
NOTE: Please
read
about the
'OriginateHack'
option
for
the constructor above
if
you plan on using the
'Async'
option in your Originate command, as it may be required to properly retrieve the response.
In these examples we are dialing extension
'12345'
at a sip peer named
'peer'
and
when
the call connects
we drop the channel into
'some_context'
at priority 1
for
extension 100.
Example 1 - A simple non-ASYNC Originate
my
$response
=
$astman
->action({
Action
=>
'Originate'
,
Channel
=>
'SIP/peer/12345'
,
Context
=>
'some_context'
,
Exten
=> 100,
Priority
=> 1});
And the contents of respone will look similiar to the following:
{
'Message'
=>
'Originate successfully queued'
,
'ActionID'
=>
'3'
,
'GOOD'
=> 1,
'COMPLETED'
=> 1,
'Response'
=>
'Success'
};
Example 2 - Originate
with
multiple variables
This will set the channel variables
'var1'
and
'var2'
to 1 and 2, respectfully.
The value
for
the
'Variable'
key should be an array reference or an anonymous array in order
to set multiple variables.
my
$response
=
$astman
->action({
Action
=>
'Originate'
,
Channel
=>
'SIP/peer/12345'
,
Context
=>
'some_context'
,
Exten
=> 100,
Priority
=> 1,
Variable = [
'var1=1'
,
'var2=2'
]});
Example 3 - An Async Originate
If your Async Originate never returns please
read
about the
'OriginateHack'
option
for
the constructor.
my
$response
=
$astman
->action({
Action
=>
'Originate'
,
Channel
=>
'SIP/peer/12345'
,
Context
=>
'some_context'
,
Exten
=> 100,
Priority
=> 1,
Async
=> 1});
And the contents of response will look similiar to the following:
{
'Message'
=>
'Originate successfully queued'
,
'EVENTS'
=> [
{
'Exten'
=>
'100'
,
'CallerID'
=>
'<unknown>'
,
'Event'
=>
'OriginateResponse'
,
'Privilege'
=>
'call,all'
,
'Channel'
=>
'SIP/peer-009c5510'
,
'Context'
=>
'some_context'
,
'Response'
=>
'Success'
,
'Reason'
=>
'4'
,
'CallerIDName'
=>
'<unknown>'
,
'Uniqueid'
=>
'1276543236.82'
,
'ActionID'
=>
'3'
,
'CallerIDNum'
=>
'<unknown>'
}
],
'ActionID'
=>
'3'
,
'GOOD'
=> 1,
'COMPLETED'
=> 1,
'Response'
=>
'Success'
};
More Info:
Check out the voip-info.org page
for
more information on the Originate action.
http://www.voip-info.org/wiki/view/Asterisk+Manager+API+Action+Originate
Callbacks
You may also specify a subroutine to callback
when
using send_action as well as a timeout.
An example of this would be:
$astman
->send_action({
Action
=>
'Ping'
}, \
&somemethod
, 7,
$somevar
);
In this example once the action 'Ping' finishes we will call somemethod() and pass it the a copy of our AMI object, the Response Object for the action, and an optional variable $somevar. If a 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 our $astman and the uncompleted Response Object, therefore somemethod() should check the state of the object. Checking the key {'GOOD'} is usually a good indication if the response is useable.
Anonymous subroutines are also acceptable as demostrated in the examples below:
my
$callback
=
sub
{
return
};
$astman
->send_action({
Action
=>
'Ping'
},
$callback
, 7);
Or
$astman
->send_action({
Action
=>
'Ping'
},
sub
{
return
}, 7);
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 triggered. However this is an unsupported scenario.
Timeouts are done using timers and 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:
$astman
->send_action({
Action
=>
'Ping'
}, \
&somemethod
, 3);
sleep
(4);
#Start loop
$astman
->loop;
#Oh no we never even tried to get the response yet it will still time out
Passing Variables in an Action Response
Sometimes, when working in an event framework, you want a way to associate/map the response to an action with another identifier used in your application. Normally you would have to maintain some sort of separate mapping involving the ActionID to accomplish this. This modules provides a generic way to pass any perl scalar (this includes references) with your action which is then passed to the callback with the response.
Passing
The variable to be passed to the callback should be passed as the fourth argument to the send_action() method.
For example to pass a simple scalar value:
my
$vartostore
=
"Stored"
;
$astman
->send_action({
Action
=>
'Ping'
}, \
&somemethod
,
undef
,
$vartostore
});
And to pass a reference:
my
@vartostore
= (
"One"
,
"Two"
);
$astman
->send_action({
Action
=>
'Ping'
}, \
&somemethod
,
undef
, \
@vartostore
});
Retrieving
The passed variable will be available as the third argument to the callback.
To retrieve in a callback:
my
(
$astman
,
$resp
,
$store
) =
@_
;
$store
.
" was stored\n"
;
Responses and Events
NOTE: Empty fields sent by Asterisk (e.g.
'Account: '
with
no
value in an event) are represented by the hash
value of null string, not
undef
. This means you need to test
for
''
(e.g.
if
(
$response
->{
'Account'
} ne
''
)) )
for
any
values
that might be possibly be empty.
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'
} Array reference containing Event Objects associated
with
this actionid.
{
'PARSED'
} Hash reference of lines we could parse into key->value pairs.
{
'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 similar 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. Please note that the key
for
the event handler
is matched against the event type that asterisk sends. For example
if
asterisk sends
'Event: Hangup'
you
use
a
key of
'Hangup'
to match it. This works
for
any event type that asterisk sends.
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
) =
@_
;
'Yeah! Event Type: '
.
$event
->{
'Event'
} .
"\r\n"
;
}
sub
do_hangup {
my
(
$asterisk
,
$event
) =
@_
;
'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
#Use your preferred loop before our module so that AnyEvent will auto-detect 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
}
);
#Alternatively you can set Blocking => 0, and set an on_error sub to catch connection errors
die
"Unable to connect to asterisk"
unless
(
$astman
);
#Define the subroutines for events
sub
eventhandler {
my
(
$ami
,
$event
) =
@_
;
'Got Event: '
,
$event
->{
'Event'
},
"\r\n"
; }
#Define a subroutine for your action callback
sub
actioncb {
my
(
$ami
,
$response
) =
@_
;
'Got Action Reponse: '
,
$response
->{
'Response'
},
"\r\n"
; }
#Send an action
my
$action
=
$astman
->({
Action
=>
'Ping'
}, \
&actioncb
);
#Do all of you other eventy stuff here, or before all this stuff, whichever ..............
#Start our loop
EV::loop
That's 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, [ [ CALLBACK ], [ TIMEOUT ], [ USERDATA ] ] )
Sends the action to asterisk, where ACTION is a hash reference. If
no
errors occurred
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
.
CALLBACK is optional and should be a subroutine reference or any anonymous subroutine. TIMEOUT is optional and
only
has
an affect
if
a CALLBACK is specified. USERDATA is optional and is a perl variable that will be passed to
the CALLBACK in addition to the response.
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
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 ()
Destroys the contents of all buffers and removes any current callbacks that are set.
Mostly used internally. Useful
if
you want to ensure that
our
IO handle watcher gets removed.
Gets called automatically
when
our
object goes out of scope.
loop ()
Starts an event loop via AnyEvent.
break ()
Breaks/exits the current event loop. The program will
continue
from where the event loop was invoked.
See Also
AnyEvent, Asterisk::AMI::Common, Asterisk::AMI::Common::Dev
AUTHOR
Ryan Bullock (rrb3942@gmail.com)
BUG REPORTING AND FEEDBACK
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
LICENSE
Copyright (C) 2011 by Ryan Bullock (rrb3942@gmail.com)
This module is free software. You can redistribute it and/or modify it under the terms of the Artistic License 2.0.
This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.