Recently, a new bit of technology has come along which has the potential to change the way we create dialplans.[128]
Asterisk has matured both in technological innovation and in popularity, but as one becomes more and more immersed in this wonderful world, one cannot help but bump into limitations. When you handle a lot of complex, enterprise-grade scripting with Asterisk alone, you will face many obstacles using dialplan logic. As flexible and powerful as the dialplan is, as a programming language it is quite odd, and much less flexible than most modern scripting languages. When one needs to provide advanced logic, the dialplan, the GUI, and even the more advanced AEL can become very frustrating.
As you build more and more complexity into your dialplans, some of the following may cause you some head-scratching:
Conditional looping and branching
Variables
Complex data structures
Database/LDAP integration
Use of third-party libraries
Exchanging and distributing VoIP functionality
Extending the configuration languages
Poor error handling
Poor date and time handling
Pattern matching
Usage consistency
Source code organization
Many people addressed these matters by writing the advanced logic in external programs such as Perl and PHP, and connecting to Asterisk via AMI and AGI. Unfortunately, while the desired power was now available, these interactions did not always simplify things for the developer. To the contrary, they often made development more complex. Using existing technologies in Asterisk, but aiming to deliver power and simplicity, Adhearsion has a new approach.
Adhearsion is an open source (LGPL) framework that is designed to
improve Asterisk solution development. It rests above an Asterisk
system, handling parts or all of the dialplan and, in a few unique ways,
manages access to Asterisk with several improved interfaces. Because it
runs as a separate daemon process and integrates through the
already-present Gateway (AGI) and Manager (AMI) interfaces, configuring
a context to use Adhearsion is as simple as adding a few lines to your
dialplan or adding a user to manager.conf
.
Adhearsion primarily uses the highly dynamic, object-oriented Ruby programming language, but has optional support for other languages such as C or Java. In the VoIP world, many things exist as conceptual objects, which means that object-oriented programming can make a lot of sense. Those familiar with Python, Perl, or other scripting languages should have no trouble picking up Ruby, and for those who don’t, Ruby is an excellent choice for your first scripting language.
Ruby software is generally installed through Ruby’s package manager (similar to Linux package managers, but for the Ruby platform exclusively). Adhearsion exists as a gem in the standard RubyGems trove so, with Ruby and RubyGems installed, Adhearsion is only one install command away.
AsteriskNOW comes standard with Ruby but not RubyGems (for support reasons). Thankfully, RubyGems can be easily installed from the Ruby rPath trove with the following command:
conary update rubygems=ruby.rpath.org@rpl:devel source /etc/profile
Most Linux distributions’ package managers host a Ruby package, though some do not yet have RubyGems. With your respective distro’s preferred software management application, install Ruby 1.8.5 or later and RubyGems if available. If RubyGems is not available in CentOS, you can install Ruby by typing:
yum install ruby
Next, we need RubyGems. You can get that by navigating to /usr/src/, and entering:
wget http://rubyforge.org/frs/download.php/20585/rubygems-0.9.3.tgz tar zxvf rubygems-0.9.3.tgz cd rubygems-0.9.3 ruby setup.rb
Ruby actually ships standard with OS X, but you will need to upgrade it and install RubyGems from MacPorts, an OS X package manager. With MacPorts installed, (available from http://www.macports.org if you do not already have it) you can install Ruby and RubyGems with the following command:
sudo port install ruby rb-rubygems
You may also need to add /opt/local/bin
to your PATH variable in
/etc/profile
.
A fantastic “one-click installer” exists for Windows. This installer will automatically install Ruby, RubyGems, and a few commonly used gems all in a matter of minutes. You can download the installer from http://rubyforge.org/projects/rubyinstaller.
With Adhearsion installed, you can begin creating and initializing
a new Adhearsion project with the newly created ahn
command, a command-line utility that manages nearly everything in
Adhearsion.
A sample command for creating a new Adhearsion project is as follows:
ahn create ~/newproject
This creates a folder at the destination specified containing the directory and file hierarchy Adhearsion needs to operate. Right away, you should be able to execute the newly created application by running:
ahn start ~/newproject
To familiarize yourself with the Adhearsion system, take a look through the application’s folders and read the accompanying documentation.
The ability to write dialplans in Adhearsion is typically the first feature newcomers use. Since Ruby permits such fine-grained modification of the language itself at runtime, one of the things Adhearsion does is make aesthetic changes, which are intended to streamline the process of developing dialplans.
Below is an Adhearsion Hello World application:
my_first_context { play "hello-world" }
Though this is completely valid Ruby syntax, not all Ruby applications look like this. Adhearsion makes the declaration of context names comfortable by interpreting the dialplan script specially. Your scripts will be located in the root folder of your newly created Adhearsion application.
As calls come into Asterisk and subsequently Adhearsion, Adhearsion invokes its own version of the context name from which the AGI request originated. Given this, we should ensure that a context in extensions.conf has this same name and forwards calls properly to Adhearsion.
The syntax for directing calls to Adhearsion is as follows:
[my_first_context] exten => _.,1,AGI(agi://127.0.0.1)
This catches any pattern dialed and goes off to Adhearsion via AGI to handle the call-processing instructions for us. The IP provided here should of course be replaced with the necessary IP to reach your Adhearsion machine.
Now that you have a basic understanding of how Adhearsion and Asterisk interact, here is a more real-world dialplan example in Adhearsion:
internal { case extension when 10..99 dial SIP/extension when 6000..6020, 7000..7030 # Join a MeetMe conference with "join" join extension when _'21XX' if Time.now.hour.between? 2, 10 dial SIP/"berlin-office"/extension[2..4] else speak "The German office is closed" end when US_NUMBER dial SIP/'us-trunk-out'/extension when /^\d{11,}$/ # Perl-like regular expression # Pass any other long numbers straight to our trunk. dial IAX/'intl-trunk-out'/extension else play %w'sorry invalid extension please-try-again' end }
With just this small amount of code we accomplish quite a lot. Even with limited or no knowledge of Ruby, you can probably infer the following things:
We use a switch-like statement on the “extension” variable (which Adhearsion creates for us) and branch depending on that.
Dialing a number between 10 and 99 routes us to the SIP peer with the dialed numerical username.
Any number dialed between 6000 and 6200 or between 7000 and 7030 goes to a MeetMe conference of that same number. This of course requires meetme.conf to have these conference numbers configured.
The _'21XX' option comes straight from Asterisk’s pattern style. Prepending a String with an underscore in Adhearsion secretly invokes a method that returns a Ruby regular expression. In a Ruby “case” statement, regular expressions can be used in a “when” statement to check against a pattern. The end effect should be very familiar to those with extensions.conf writing experience.
Adhearsion’s syntax for representing channels also comes
straight from Asterisk’s traditional format.
SIP/123
can be used verbatim to represent the
SIP peer 123. If a trunk were involved, SIP/trunkname/username
would act as you would expect.
The speak()
method abstracts an underlying text-to-speech engine. This
can be configured to use most of the popular engines.
A full-blown Perl-like regular can be used in a
when
statement to perform more sophisticated
pattern matching if Asterisk’s patterns do not suffice.
Adhearsion defines a few constants that may be useful to
someone writing dialplans. The US_NUMBER
constant here is a regular expression for matching an American
number.
If you find the need to play several files in sequence,
play()
accepts an Array of filenames. By luck, Ruby has a
convenient way of creating an Array of Strings.
This is of course just a simple example and covers only the absolute basics of Adhearsion’s dialplan authoring capabilities.
Though immensely successful in the web development space for serving dynamic content, database integration has always been an underutilized possibility for driving dynamic voice applications with Asterisk. Most Asterisk applications that do accomplish database integration outsource the complexity to a PHP or Perl AGI script because the extensions.conf or AEL grammars are simply impractical for the level of sophistication required.
Adhearsion uses a database integration library, called ActiveRecord, developed by the makers of the Ruby on Rails framework. With ActiveRecord, the end user seldom, if ever, writes SQL statements. Instead, the developer accesses the database just like any Ruby object. Because Ruby allows such flexible dynamism, access to the database looks and feels quite natural. Additionally, ActiveRecord abstracts the differences between database management systems, making your database access implementation agnostic.
Without going too much into the internals of ActiveRecord and more sophisticated uses of it, let us consider the following simple MySQL schema:
CREATE TABLE groups ( `id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `description` varchar(255) DEFAULT NULL, `hourly_rate` decimal DEFAULT NULL ); CREATE TABLE customers ( `id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `name` varchar(255) DEFAULT NULL, `phone_number` varchar(10) DEFAULT NULL, `usage_this_month` int(11) DEFAULT 0, `group_id` int(11) DEFAULT NULL );
In practice we would obviously store much more information about the customer and keep the service usage information in a database-driven call detail record, but this degree of simplicity helps demonstrate ActiveRecord fundamentals more effectively.
To connect Adhearsion to this database, one simply specifies the database access information in a YAML configuration file like so:
adapter: mysql host: localhost database: adhearsion username: root password: pass
This tells Adhearsion how to connect to the database, but how we access information in the tables depends on how we model our ActiveRecord objects. Since an object is an instance of a class, we write a class definition to wrap around each table. We define simple properties and relationships in the class with the superclass’s methods.
Here are two classes we may use with the aforementioned tables:
class Customer < ActiveRecord::Base belongs_to :group validates_presence_of :name, :phone_number validates_uniqueness_of :phone_number validates_associated :group def total_bill self.group.hourly_rate * self.usage_this_month / 1.hour end end class Group < ActiveRecord::Base has_many :customers validates_presence_of :description, :hourly_rate end
From just this small amount of information, ActiveRecord can
make a lot of logical inferences. When these classes interpret,
ActiveRecord assumes the table names to be customers
and groups
respectively by lowercasing the
classes’ names and making them plural. If this convention is not
desired, the author can easily override it. Additionally, at
interpretation time, ActiveRecord actually peeks into the database’s
columns and makes available many new dynamically created
methods.
The belongs_to
and has_many
methods in this example define
relationships between Customer
s and
Group
s. Notice again how
ActiveRecord uses pluralization to make the code more expressive in
the has_many :customers
line. From
this example, we also see several validations—policies that
ActiveRecord will enforce. When creating a new Customer
we must provide a
name
and phone_number
at the
bare minimum. No two phone numbers can conflict. Every Customer
must have a Group
. Every Group
must have a description
and hourly_rate
. These help both the developer
and the database stay on track.
Also, notice the total_bill
method in the Customer
class. On
any Customer
object we extract from
the database, we can call this method, which multiplies the hourly_rate
value of the group to which the
Customer
belongs by the Customer
’s own phone usage this month (in
seconds).
Here are a few examples that may clarify the usefulness of having Ruby objects abstract database logic:
everyone = Customer.find :all jay = Customer.find_by_name "Jay Phillips" jay.phone_number # Performs a SELECT statement jay.total_bill # Performs arithmetic on several SELECT statements jay.group.customers.average :usage_this_month jay.group.destroy jay.group = Group.create :description => "New cool group!", :hourly_rate => 1.23 jay.save
Because the database integration here becomes much more natural, Asterisk dialplans becomes much more expressive as well. Below is a sample dialplan of a service provider that imposes a time limit on outgoing calls using information from the database. To remain simple:
# Let's assume we're offering VoIP service to customers # whom we can identify with their callerid. service { # The line of code below performs an SQL SELECT # statement on our database. The find_by_phone_number() # method was created automatically because ActiveRecord # found a phone_number column in the database. Adhearsion # creates the "callerid" variable for us. caller = Customer.find_by_phone_number callerid usage = caller.usage_this_month if usage >= 100.hours play "sorry-cant-let-you-do-that" else play %w'to-hear-your-account-balance press-1 otherwise wait-moment' choice = wait_for_digit 3.seconds p choice if choice == 1 charge = usage / 60.0 * caller.group.hourly_rate play %W"your-account will-reflect-charge-of $#{charge} this month for #{usage / 60} minutes and #{usage % 60} seconds" end # We can also write back to the "usage_this_month" # property of "caller". When the time method finishes, # the database will be updated for this caller. caller.usage_this_month += time do # Code in this block is timed. dial IAX/'main-trunk'/extension end caller.save end }
Robust database integration like this through Adhearsion brings new ease to developing for and managing a PBX. Centrally persistent information allows Asterisk to integrate with other services cleanly while empowering more valuable services whose needs are beyond that of traditional Asterisk development technologies.
Because an Adhearsion application resides within a single folder, completely copying the VoIP application is as simple as zipping the files. For one of the first times in the Asterisk community, users can easily exchange and build upon one another’s successful application. In fact, open sourcing individual Adhearsion applications is greatly encouraged.
Additionally, on a more localized level, users can reuse Adhearsion framework extensions, called helpers, or roll their own. Helpers range from entire sub-frameworks like the Micromenus framework for integrating with on-phone micro-browsers to adding a trivial new dialplan method that returns a random quote by Oscar Wilde.
Below is a simple Adhearsion helper written in Ruby. It creates a new method that will exist across the entire framework, including the dialplan. For simplicity’s sake, the method downloads an XML document at a specified HTTP URL and converts it to a Ruby Hash object (Ruby’s associative array type):
def remote_parse url Hash.from_xml open(url).read end
Note that these three lines can work as the entire contents of a helper file. When Adhearsion boots, it executes the script in a way that makes any methods or classes defined available anywhere in the system.
For some issues, particularly ones of scaling Adhearsion, it may be necessary to profile out bottlenecks to the king of efficiency: C. Below is a sample Adhearsion helper that returns the factorial of a number given:
int fast_factorial(int input) { int fact = 1, count = 1; while(count <= input) { fact *= count++; } return fact; }
Again, the code here can exist as the entire contents of a helper file. In this case, because it is written in C, it should have the name factorial.alien.c. This tells Adhearsion to invoke its algorithm to read the file, add in the standard C and Ruby language development headers, compile it, cache the shared object, load it into the interpreter, and then wrap the C method in a Ruby equivalent. Below is a sample dialplan that simply speaks back the factorial of six using this C helper:
fast_test { num = fast_factorial 6 play num }
Note that the C method becomes a first-class Ruby method. Ruby number objects passed to the method are converted to C’s “int” primitive, and the return value is converted back to a Ruby number object.
Helpers promise robust albeit simple extensibility to a VoIP engineer’s toolbox, but, best of all, useful helpers can be traded and benefit the entire community.
With increasing competition between modern IP-enabled desk phone manufacturers, the microbrowser feature has snuck in relatively unnoticed and underutilized. The principle is simple: physical desk phones construct interactive menus on a phone by pulling XML over HTTP or the like. Contending interests, however, lead this technology amiss: every vendor’s XML differs, microbrowsers are often quirky, and available features vary vastly.
The Micromenus framework exists as an Adhearsion helper and aims to abstract the differences between vendors’ phones. For this very particular development domain (i.e., creating menus), Micromenus use a very simple Ruby-based “Domain Specific Language” to program logic cleanly and independent of any phone brands.
Below is a simple example Micromenu:
image 'company-logo' item "Call an Employee" do # Creates a list of employees as callable links from a database. Employee.find(:all).each do |someone| # Simply select someone to call that person on your phone. call someone.extension, someone.full_name end end item "Weather Information" do call "Hear the weather report" do play weather_report("Portland, OR") end item "Current: " + weather("Portland, OR")[:current][:temp] end item "System Uptime: " + `uptime`
A list item
displays in two ways. If given only
a text String, Micromenus renders only a text element. If the arguments
contain a do
/end
block of nested
information, that text becomes a link to a sub-page rendering that
nested content.
A call
item also has two uses, each producing a
link which, when selected, initiates a call. When
call
receives no
do
/end
block, it simulates
physically dialing the number given as the first argument. When a
do
/end
block exists and all calls
route through Adhearsion, selecting that item executes the dialplan
functionality within the block. This is a great example of how handling
the dialplans and on-screen microbrowsers within the same framework pays
off well.
From this example we can see a few other neat things about Micromenus:
Micromenus support sending images. If the requesting phone does not support images, the response will have no mention of them.
All Adhearsion helpers work here, too. We use the weather helper in this example.
The Micromenus sub-framework can use Adhearsion’s database integration.
Ruby can execute a command inside backticks and return the result as a String. We report the uptime here with it.
This example of course assumes that you have configured your
application’s database integration properly and have an Employee class
mapping to a table with an extension
and
full_name
column.
Because Micromenus simply render varying responses over HTTP, a normal web browser can make a request to the Micromenus’ server, too. For these more high-tech endpoints, Micromenus render an attractive interface with Ajax loading, DHTML effects, and draggable windows.
In the interest of “adhering” people together, Micromenus exist as another option to make your Adhearsion VoIP applications more robust.
Though Adhearsion by design can integrate with virtually any application, including PHP or Java Servlets, Ruby on Rails makes for a particularly powerful partner. Rails is a web development framework getting a lot of press lately for all the right reasons. Its developers use Ruby to its full potential, showing how meta-programming really does eliminate unnecessary developer work. Rails’ remarkable code clarity and application of the Don’t Repeat Yourself (DRY) principle served as a strong inspiration to the inception of Adhearsion as it exists today.
Starting with Adhearsion version 0.8.0, Adhearsion’s application directory drops in place atop an existing Rails application, sharing data automatically. If you have needs to develop a complex web interface to VoIP functionality, consider this deadly duo.
Eyebrows around the world raised when Sun announced their hiring of the two core developers of the JRuby interpreter project, Charles Nutter and Thomas Enebo, in September 2006. JRuby is a Ruby interpreter written in Java instead of C. Because JRuby can compile parts of a Ruby application to Java bytecode, JRuby is actually outperforming the C implementation of Ruby 1.8 in many different benchmarks and promises to outperform Ruby 1.8 in all cases in the near future.
A Ruby application running in JRuby has the full benefit of not only Ruby libraries but any Java library as well. Running Adhearsion in JRuby brings the dumbfounding assortment of third-party Java libraries to PBX dialplan writing. If your corporate environment requires tight integration with other Java technologies, embedding Adhearsion in a J2EE stack may offer the flexibility needed.
For more information about the fast-moving Adhearsion community, including complete walkthroughs, see Adhearsion’s official web site at http://adhearsion.com, Adhearsion’s official blog at http://blog.adhearsion.com, the web site of Adhearsion’s parent consulting company http://codemecca.com, or for help learning Ruby, see http://jicksta.com.
[128] We would like to thank Jay Phillips for contributing the ideas and code for this section of the book.