Asterisk: Ruby Fun with Adhearsion

Thursday, April 26, 2007, at 11:59AM

By Eric Richardson

We're still waiting on a few pieces to come together before we really roll with the VoIP changeover, so things have been mostly quiet for me on the Asterisk front. Today I took a little time to play with dial plans and thought I'd do a short write-up about the fun I've been having with Adhearsion.

Asterisk has the ability to pass call logic off to external applications via what's called the AGI interface. Adhearsion speaks AGI and allows the call logic to be written in Ruby.

More after the jump...

So far I've got my test setup handling both incoming and outgoing calls using a single user database. That database contains fields for name, extension, etc. To the basic structure I've added additional fields for voicemail box and DNID (inbound number).

Users inside the office are put in the internal context. When the user dials and hits send, Asterisk sees this line:

exten => _X.,1,AGI(agi://127.0.0.1)

That tells it to connect to the AGI app listening on localhost. Inside Adhearsion's extensions.rb file I then run against this code:

# dial local calls by adding 213 on
if ( /^\d{7}$/.match(extension.to_s) )
  dial 'SIP/trunk_1/213' + extension.to_s
end

# dial ten digits or ten digits plus a 1
if ( /^1?\d{10}$/.match(extension.to_s) ) 
  dial 'SIP/trunk_1/' + extension.to_s
end

case extension
  # extensions
  when 300...399
    user = User.find_by_extension extension

    if user
        dial user, :for => 20.seconds
        voicemail user.vm
    else
      play 'nobody-but-chickens'
      play 'sorry-youre-having-problems'  
    end

  when 999 then +check_vm
  when 6000 then +kcrwmoh
end

So if they dialed seven digits I assume it's a local call, add our area code and send it out via the trunk. If they dial ten digits again it's external and out the trunk.

Otherwise I assume they're dialing internally and check a few things.

If the number dialed is in the 300 range that's our internal extensions. We look up the user to make sure the extension is valid. If so, we dial the user and let it ring 20 seconds. If no answer, send it to their voicemail. If the extension is invalid we have some fun (these are internal people, remember): we have Asterisk say -- in a very serious voice -- "Nobody here but us chickens. Sorry you're having problems."

If they've dialed 999, our voicemail check extension, we send the execution off to the check_vm context. That code looks like this:

check_vm {
  # try to parse extension out of the channel
  cext = /^SIP\/(\d+)-/.match(channel)

  if cext
    # skip mailbox and password prompts if they're 
    #calling from their own phone
    check_voicemail cext[1], :args => 's'    
  else
    # prompt for mailbox and password
    check_voicemail
  end
}

We parse the channel (which looks like "SIP/302-asdas9324") to find the calling phone. If we get that we pass them off to their voicemail and skip the password prompt. If we don't we send them into the system, which then prompts for the mailbox number.

Incoming calls get a similarly cool treatment. Asterisk passes them on to Adhearsion in the incoming context. From there they hit this code:

incoming {
  user = User.find_by_dnid(dnid.to_s)

  if ( user )
    # send them to the particular user
    dial user, :for => 20.seconds
    voicemail user.vm   
  else
    # send them to the front desk
    exec :answer
    exec :ringing
    exec :queue, 200, 'tr', '', '', 20
    voicemail 200   
  end
}

The DNID is the number the caller dialed. Each employee has their own number here in the office, and all of those numbers get routed into this routine. We look up that number in the users database and check for a match. If we find one we dial the user's extension. If not (for instance, a call into our main number) we put the call in a queue that rings at a couple desks in the office.

There are still plenty of tweaks I'll want to do before we really put any of this into play, but I think that gives you a little rundown on the kind of fun you can have via Adhearsion.