O'Reilly    
 Published on O'Reilly (http://oreilly.com/)
 See this if you're having trouble printing code examples


Sophisticated Asterisk Development with Adhearsion

by Jay Phillips
06/19/2007

Editor's Note: Jay Phillips is the creator of Adhearsion and has written a section about the open source Ruby framework for the upcoming second edition of Asterisk: The Future of Telephony. We're proud to present this expanded excerpt of Jay's material here on ETel. The newly updated version of O'Reilly's best-selling Asterisk book is available now as a Rough Cut.

As Asterisk has matured, both in technological innovation and in appealing to the masses, the subject of programming complex, enterprise-grade logic with Asterisk alone remains unsolved. The web interface in version 1.4 constitutes an enormous leap forward for the deployment of Asterisk in small businesses where the usage pattern is predictable. Small businesses need an easily managed PBX that reduces monthly phone costs, and Asterisk fits the ticket well.

Beyond the predictable, however, the GUI and even Asterisk's internal extensions.conf and AEL grammars break down. They commonly do not account for the sophisticated uses to which the rest of the software industry has grown accustomed. Having a relatively limited toolbox of functions and little development pattern optimization, these grammars remind us of the embedded assembler languages beyond which most server software developers have matured.

Common pain-points in Asterisk dial plan authoring include:

Supporting higher-level concepts implies the use of a language both with internal support for these criteria and an extensive existing code base with which applications can be written. Aiming to solve this issue, Adhearsion arose with a new approach.

Asterisk Development with Adhearsion

Adhearsion is an open source (LGPL) framework that, in simplest terms, improves Asterisk development. It rests above an Asterisk system, handling parts or all of the dial plan 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 dial plan or adding a user to manager.conf.

Adhearsion primarily uses the highly dynamic, object-oriented Ruby programming language with optional support for other languages such as C or Java. In the VoIP world, many things simply do exist as conceptual objects, making object-oriented programming here particularly comfortable. Scripting languages per se also improve developer productivity by removing tedium such as recompiling, memory management, strict data types, and other extraneous formalities. Those familiar with Python, Perl, or other scripting languages should have no trouble picking up another similar scripting language like Ruby.

Installing Adhearsion

Ruby software is generally installed through Ruby's package manager, an analog 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.

Installing Ruby/RubyGems on AsteriskNOW

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

Installing Ruby/RubyGems on Linux

Most Linux distributions' package managers host a Ruby package, though some oddly do not 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, it can be easily downloaded and installed from http://rubyforge.org/projects/rubygems.

Installing Ruby/RubyGems on Mac OS X

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.

Ruby/RubyGems on Windows

A fantastic "one click installer" exists for Windows. This 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.

Installing Adhearsion from RubyGems

Once you've followed one of the instructions above for your system to fetch Ruby and RubyGems, install Adhearsion with the following command:

gem install adhearsion

Exploring a New Adhearsion Project

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.

An example 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 accompanied documentation.

Adhearsion Dial Plan Writing

The ability to write dial plans in Adhearsion is often the first feature Adhearsion newcomers use. Because Ruby permits such fine-grained modification of the language itself at runtime, Adhearsion makes aesthetic changes to streamline the process of developing dial plans.

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 dial plan script specially. The dial plan script is 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 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, the following is a more real-world dial plan 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 about Ruby, you can probably infer the following things:

This is of course just a simple example and covers only the absolute basics of Adhearsion's dial plan authoring capabilities.

Database Integration

Though immensely successful in the web development space for serving dynamic content, database integration has always been an underused possibility for driving dynamic voice applications with Asterisk. Most Asterisk applications that do accomplish this 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 this requires.

Adhearsion uses a database integration library developed by the makers of the Ruby on Rails framework called ActiveRecord. 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, the 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 with 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 Customers and Groups. 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, which 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).

Below 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 dial plans becomes much more expressive as well. The following is an example dial plan of a service provider that imposes a time limit on outgoing calls using information from the database.

# 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

    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.

Distributing and Reusing Code

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 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 microbrowsers to adding a trivial new dial plan method which 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 dial plan. 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 which 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 an example Adhearsion helper, which 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. This is an example dial plan 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.

Integrate with Your Desk Phone Using Micromenus

With increasing competition between modern IP-enabled desk phone manufacturers, the microbrowser feature has snuck in relatively unnoticed and underused. 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 uses a very simple Ruby-based "Domain Specific Language" to program logic cleanly and independent of any phone brands.

This 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 that, 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 dial plan functionality within the block. This is a great example of how handling the dial plans and on-screen Microbrowsers within the same framework pays off well.

From this example we can see a few other neat things about Micromenus:

The example above of course assumes 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 renders varying responses over HTTP, a normal web browser can make a request to the Micromenus server too. For these more high-tech endpoints, Micromenus renders an attractive interface with Ajax loading, DHTML effects, and draggable windows.

In the interest of "adhering" people together, Micromenus exists as another option to make your Adhearsion VoIP applications more robust.

Integrating with a Web Application

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 on top of an existing Rails application, sharing data automatically. If you have needs to develop a complex web interface to VoIP functionality, consider this deadly duo.

Using Java

Eyebrows around the world raised when Sun announced itshiring 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 dial plan writing. If your corporate environment requires tight integration with other Java technologies, embedding Adhearsion in a J2EE stack may offer the flexibility needed.

Additional Information

Jay Phillips is an innovator in the spaces where sophisticated VoIP development falls apart and where Ruby rocks. As the creator of Adhearsion and its parent company Codemecca, Jay brings new possibilities to these two technologies through his work on the open-source Adhearsion framework and its parent company Codemecca.


Return to Emerging Telephony.

Copyright © 2009 O'Reilly Media, Inc.