/AsteriskRuby

A full featured Asterisk AGI Framework on Ruby. Minimal and powerful

Primary LanguageRuby

Author: Michael Komitee <mkomitee@vonage.com>

License: BSD License

  Copyright (c) 2007, Vonage Holdings

  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

      * Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
      * Neither the name of Vonage Holdings nor the names of its       
  contributors may be used to endorse or promote products derived from this
  software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE.

= Asterisk Ruby Framework -- AGI for Ruby

AGI, AGIMenu, AGISelection, and AGIServer work together with their supporting libraries to complete an Asterisk AGI Framework for applications.
  * AGI: handles interaction between ruby and Asterisk. Asterisk connects to the AGI via either pipes or sockets
  * AGIMenu: implements more complex logic dealing with menus and prompts
  * AGISelection: implements more complex logic dealing with audio playback and digit strings
  * AGIServer: implements a TCPServer via a block or rails routes, which instantiates AGI objects for Asterisk interaction

== Download

The latest version of asterisk-ruby can be found at

  * http://rubyforge.org/project/

== Installation

=== Normal Installation

  Setup based installation is currently unsupported

=== GEM Installation

Download and install asterisk-ruby with the following.

  % gem install asterisk-ruby

== Usage, ...

=== AGI

  The AGI object can be used to interact with an Asterisk. It can be used within the AGIServer framework, or independantly. Simply instantiate an AGI object and pass it input and output IO objects. Asterisk extensions can exec an agi script (in asterisk parlance, agi or deadagi), in which case you'll want to use stdin and stdout, or asterisk extensions can connect to a daemonized agi server (in asterisk parlance, fagi) and you'd want to use a tcp socket.

    agi = AGI.new(:input => STDIN, :output => STDOUT)
    agi.init
    agi.answer
    agi.hangup

  Note, all agi instances will be uninitialized. The initialization process of an agi channel must be performed before any other interaction with the channel can be accomplished.

=== AGIMenu

Sometimes when dealing with Asterisk AGI, a generic AGIMenu object can come in handy. Thats where the aptly named AGIMenu object comes into play. With AGIMenu, you can configure a menu of options based on a yaml configuration or a hash. For example:

  AGIMenu.sounds_dir = 'agimenu-test/sounds/'
  hash = {:introduction=>["welcome", "instructions"],
    :conclusion=>"what-is-your-choice",
    :timeout=>17,
    :choices=>
    [{:dtmf=>"*", :audio=>["to-go-back", "press", "digits/star"]},
      {:dtmf=>1, :audio=>["press", "digits/1", "for-option-1"]},
      {:dtmf=>2, :audio=>["press", "digits/2", "for-option-2"]},
      {:dtmf=>"#", :audio=>["or", "press", "digits/pound", "to-repeat"]}]}

  menu = AGIMenu.new(hash)
  menu.play(:agi => AGI.new())
  
This results in Asterisk using sounds in it's agimenu-test/sounds directory to play a menu which accepts the DTMF *, 1, 2, or #. It will first play the introduction sound files 'welcome' and 'instructions', then play the menu sound files 'to-go-back' 'press' 'digits/star' 'press' 'digits/1' 'for-option-1' 'press 'digits/2' 'for-option-2' 'or' 'press' 'digits/pound' 'to-repeat', and then play the conslusion sound file 'what-is-your-choice' and then wait 17 seconds.

You could hand AGIMenu.new() a filename, a file, a yaml oject, a hash, or nothing at all.  You can then modify the menu with the classes various methods.

=== AGISelection

AGISelection can be used to get a string of digits from an asterisk channel. You configure it similarly to an AGIMenu, but it takes a max_digits paramater and only accepts a single audio file. AGIMenu only accepts a single dtmf digit, whereas AGISelection can accept multiple digits. It would be used to have a user input a PIN or a telephone number or something similar.

  agi = AGI.new()
  yaml_hash = {:audio => 'tt-monkeys', :max_digits => 4}
  foo = AGISelection.new(yaml_hash)
  foo.read(:agi => AGI.new())

=== AGIServer

There are several ways to use the AGIServer module. All of them have a few things in common. In general, since we're creating a server, we need a way to cleanly kill it. So we setup sigint anf sigterm handlers to shutdown all instances of AGIServer Objects

  trap('INT')   { AGIServer.shutdown }
  trap('TERM')   { AGIServer.shutdown }

We also tend to use a logger because this should be daemonized. While developing, I reccomend you log to STDERR

  logger = Logger.new(STDERR)
  logger.level = Logger::DEBUG

I use YAML for configuration options. This just sets up the bind port, address, and some threading configuration options.

  config = YAML.load_file('config/example-config.yaml')
  config[:logger] = logger
  config[:params] = {:custom1 => 'data1'}

And then we generate our server

  begin
    MyAgiServer = AGIServer.new(config)
  rescue Errno::EADDRINUSE
    error = "Cannot start MyAgiServer, Address already in use."
    logger.fatal(error)
    print "#{error}\n"
    exit
  else
    print "#{$$}"
  end

In this example, I'll show you the rails-routing means of working with the AGIServer. Define a Route class along with a few routes, and start the server.

  class TestRoutes < AGIRoute
    def sample
      agi.answer
      print  "CUSTOM1 = [#{params[:custom1]}]\n"
      print  "URI     = [#{request[:uri]}]\n"
      print  "ID      = [#{request[:id]}]\n"
      print  "METHOD  = [#{request[:method]}]\n"
      print  "OPTIONS = #{request[:options].pretty_inspect}"
      print  "FOO     = [#{request[:options]['foo']}]\n"
      print '-' * 10 + "\n"
      helper_method
      agi.hangup
    end
    private
    def helper_method
      print "I'm private which means I'm not accessible as a route!\n"
    end
  end
  MyAgiServer.start
  MyAgiServer.finish

Pointing an asterisk extension at agi://localhost:4573/TestRoutes/sample/1/?foo=bar will execute the sample method in the TestRoutes class.

In this example, I'll show you how to use a block to define the AGI logic. Simply start the server and pass it a block expecting an agi object:

  MyAgiServer.start do |agi|
    agi.answer
    puts "I'm Alive!"
    agi.hangup
  end
  MyAgiServer.finish

In this example, I'll show you another way to use a block to define the AGI logic. This block makes configuration parameters available during the call:

  MyAgiServer.start do |agi,params|
    agi.answer
    print  "PARAMS = #{params.pretty_inspect}"
    agi.hangup
  end
  MyAgiServer.finish