Asterisk Development with Adhearsion

Recently, a new bit of technology has come along which has the potential to change the way we create dialplans.[128]

A New Approach to Dialplans

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.

Asterisk Development with Adhearsion

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.

Installing Adhearsion

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.

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 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

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 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.

Installing Adhearsion from RubyGems

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

gem install adhearsion

If any dependencies are found, you will probably need to allow them to install for Adhearsion to work correctly.

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.

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.

Adhearsion dialplan writing

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.

Database integration

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 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 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.

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 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.

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 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.

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 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.

Using Java

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.

More Information

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.