Asterisk Manager Interface
Asterisk Manager Interface
There are principally two ways Adhearsion can control Asterisk: the Asterisk Gateway Interface protocol or the Asterisk Manager Interface protocol (henceforth referred to as AGI and AMI respectively). The AGI protocol is modeled after the CGI protocol and is bound to a particular call. When a call comes in, Asterisk itself establishes an AGI connection out to Adhearsion via a TCP socket.
Conversely, the AMI protocol is for other processes to connect into Asterisk following a 3rd Party Call Control model similar, but not compliant with, TSAPI. An AMI connection is therefore not bound to a particular call and, instead, allows the other process to manage global operations and query global state on the Asterisk server, as well as manage and receive events on individual calls. The AMI protocol itself is inconsistently designed, but painstaking effort has been made to make it consistent to Adhearsion users. For more information on the protocol per se, see this page.
With the AMI protocol, users can...
- Arbitrarily initiate calls
- Set channel variables on any channel
- Get the state for all SIP peers registered with the server
- Receive events about things which occur within Asterisk in realtime
- Start recording a particular channel to a sound file
- View all parked calls
- View state of all the queues on the system
- Execute CLI commands
- and so forth...
Here are a few ideas of how you might use AMI:
- Subscribe to the Hangup event and then log it in a database for billing.
- Connect into an Adhearsion process via DRb and spawn a call
- Dynamically change the configuration files of Asterisk
AMI is not configured by default with Asterisk though the steps to setting it up are all simple.
Configure Asterisk
Open the /etc/asterisk/manager.conf file in a text editor. You'll probably see a lot of commented-out lines (a ";" denotes a comment) and a "general" section similar to the following:
[general] enabled = yes port = 5038 bindaddr = 0.0.0.0
You'll also need to define an AMI user for your Adhearsion app. Below is an example.
[ahn_ami] secret = iheartlolcats read = system,call,log,verbose,command,agent,user write = system,call,log,verbose,command,agent,user
Configure Adhearsion
Enabling Adhearsion to use AMI is quite easy. In your Adhearsion application, open up config/startup.rb with a text editor and uncomment the line which looks similar to the following
config.asterisk.enable_ami :host => '127.0.0.1', :events => true,
:username => 'ahn_ami', :password => 'iheartlolcats'
Using AMI events
The Asterisk Manager Interface supports broadcasting Asterisk events as they occur in realtime. Adhearsion exposes these events very cleanly using its own internal events subsystem. For more information on how to make use of AMI events, see this wiki's page on Events.
Controlling Asterisk from Rails with Adhearsion
Adhearsion ships with custom DRb support, which allow us to interact with Adhearsion from another application. DRb allows an object in one Ruby process to invoke methods on an object in another Ruby process on the same or a different machine. Add the line below to config/startup.rb:
config.enable_drb :port => 8888
Next, in your separate Ruby process, add the following code. If you're using Rails, add this to the bottom of your Rails app's config/environment.rb file.
AdhearsionDRb = DRbObject.new_with_uri 'druby://localhost:8888'
AdhearsionDRb.introduce('SIP/first-person', 'ZAP/second-person')
AMI from a standalone Ruby script
Adhearsion's AMI library was designed to be very reusable. The examples/asterisk_manager_interface folder contains a few example Ruby scripts but here is one below.
require 'rubygems' require 'adhearsion' require 'adhearsion/voip/asterisk/manager_interface' include Adhearsion::VoIP::Asterisk::Manager asterisk = ManagerInterface.connect :host => "10.0.1.97", :username => "jicksta", :password => "roflcopter" response = asterisk.send_action "Originate", "Channel" => "SIP/mytrunk", "Application" => "Playback", "Args" => "hello-world" puts response.headers.inspect
Handling Future Resources
A Future Resource allows you to have an object updated on a future event. Adhearsion provides this capability to deal with the asynchronous nature of dealing with AMI requests and responses. An example of using this would be to launch a request for the status of a call channel over AMI. The AMI action 'Status' only returns that the action was accepted by the AMI, but the result of that status request comes back as a response event. If you call the method synchronously:
result = send_action_synchronously 'status', { :channel => 'SIP/teliax-011fc210' }
my_future_resource = send_action_asynchronously 'status', { :channel => 'SIP/teliax-011fc210' }
result = my_future_resource.response
Handling AMI Errors
When making an action request to the AMI using send_action or send_action_asynchronously and the AMI raises an error as the response Adhearsion will trap this error and raise it as an exception. To handle this properly, you will want to use a begin/rescue clause to have access to that exception. This example will log the resulting response to the Status action to the log:
begin
Adhearsion::VoIP::Asterisk.manager_interface.send_action_synchronously 'status', { :channel => 'SIP/teliax-011fc210' }
rescue Adhearsion::VoIP::Asterisk::Manager::ManagerInterfaceError => error
ahn_log.statusrequest.debug error
end
Asterisk disconnects
When you restart the Asterisk process or the Asterisk process crashes for whatever reason, Adhearsion may be in the middle of something important (e.g. something billable). This introduces a race condition in the way that Adhearsion talks to Asterisk. Below is a high-level explanation of how commands are sent and received over AMI with Adhearsion:
- Adhearsion starts
- If the Asterisk module is enabled with AMI (enable_ami in startup.rb), the AsteriskInitializer will attempt a connection to Asterisk.
- If events are enabled, a separate socket will be established with events turned on.
- The actions-only socket always has events turned off (but it still receives "causal events")
- When a connection is made, a Thread is spawned to constantly read data from the socket.
- When an action is sent, an ActionID is generated for it and the ManagerInterfaceAction is stored in a Thread-safe dictionary (Hash).
- The reading Thread is constantly pulling responses (and "causal events") from the actions socket and performs a lookup with the Thread-safe dictionary for a ManagerInterfaceAction object associated with the received ActionID.
- The reader thread then gives the ManagerInterfaceAction the new ManagerInterfaceResponse object
- All other Threads that were waiting on the response are then given access to the response by calling ManagerInterfaceAction#response.
The race condition exists during these scenarios
- If Asterisk goes down while data is being sent
- If Asterisk goes down after data has been sent but before a response has been received
- If Asterisk goes down after a response has been received but not all "causal events" have been received
The way to protect yourself from this is simple: don't kill a production system forcefully! You should route traffic around any Asterisk/Adhearsion instance that needs to be removed from the cluster until Adhearsion and Asterisk are completely idle. When doing a new deployment (say, an upgrade of Asterisk), you should stop Adhearsion and Asterisk, then start Asterisk first and Adhearsion second.
SuperManager
The examples above use Adhearsion's ManagerInterface class. This class was designed specifically to be a robust and consistent abstraction of the AMI protocol, though it requires thorough knowledge of the data which AMI requires and supplies.
To be fully consistent with Adhearsion's philosophy, the AMI protocol should be abstracted in such a way that everything is an object. For example, if you were to ask for the current channels on the Asterisk server, you should get back an Array of Channel objects, each of which having intuitive methods such as hangup() or channel_variables().
The SuperManager class will soon offer these very high-level, very intuitive abstractions of AMI. At the moment it's still under development and not available in the stable master branch of Adhearsion.