Module: Adhearsion::VoIP::Asterisk::Commands

Defined in:
lib/adhearsion/voip/asterisk/commands.rb

Defined Under Namespace

Modules: MenuDigitResponse, SpeechEngines Classes: QueueProxy

Constant Summary collapse

RESPONSE_PREFIX =
"200 result="
DIAL_STATUSES =

These are the status messages that asterisk will issue after a dial command is executed.

Here is a current list of dial status messages which are not all necessarily supported by adhearsion:

ANSWER: Call is answered. A successful dial. The caller reached the callee. BUSY: Busy signal. The dial command reached its number but the number is busy. NOANSWER: No answer. The dial command reached its number, the number rang for too long, then the dial timed out. CANCEL: Call is cancelled. The dial command reached its number but the caller hung up before the callee picked up. CONGESTION: Congestion. This status is usually a sign that the dialled number is not recognised. CHANUNAVAIL: Channel unavailable. On SIP, peer may not be registered. DONTCALL: Privacy mode, callee rejected the call TORTURE: Privacy mode, callee chose to send caller to torture menu INVALIDARGS: Error parsing Dial command arguments (added for Asterisk 1.4.1, SVN r53135-53136)

Hash.new(:unknown).merge(:answer      => :answered, #:doc:
:congestion  => :congested,
:busy        => :busy,
:cancel      => :cancelled,
:noanswer    => :unanswered,
:cancelled   => :cancelled,
:chanunavail => :channel_unavailable)
DYNAMIC_FEATURE_EXTENSIONS =
{
  :attended_transfer => lambda do |options|
    variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context)
    extend_dynamic_features_with "atxfer"
  end,
  :blind_transfer => lambda do
    variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context)
    extend_dynamic_features_with 'blindxfer'
  end
}

Instance Method Summary collapse

Instance Method Details

#answerObject

This must be called first before any other commands can be issued. In typical Adhearsion applications this is called by default as soon as a call is transfered to a valid context in dialplan.rb. If you do not want your Adhearsion application to automatically issue an answer command, then you must edit your startup.rb file and configure this setting. Keep in mind that you should not need to issue another answer command after one has already been issued either explicitly by your code or implicitly by the standard adhearsion configuration.



123
124
125
126
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 123

def answer
  response "ANSWER"
  true
end

#check_voicemailObject



805
806
807
808
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 805

def check_voicemail
  ahn_log.agi.warn "THE check_voicemail() DIALPLAN METHOD WILL SOON BE DEPRECATED! CHANGE THIS TO voicemail_main() INSTEAD"
  voicemail_main
end

#dial(number, options = {}) ⇒ Object

Dial an extension or “phone number” in asterisk. Maps to the Asterisk DIAL command in the asterisk dialplan.

Be careful to not just specify a number like 5001, 9095551001 You must specify a properly formatted string as Asterisk would expect to use in order to understand whether the call should be dialed using SIP, IAX, or some other means.

:caller_id - the caller id number to be used when the call is placed. It is advised you properly adhere to the policy of VoIP termination providers with respect to caller id values.

:name - this is the name which should be passed with the caller ID information if :name=>“John Doe” and :caller_id => “444-333-1000” then the compelete CID and name would be “John Doe” <4443331000> support for caller id information varies from country to country and from one VoIP termination provider to another.

:for - this option can be thought of best as a timeout. i.e. timeout after :for if no one answers the call For example, dial(“SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2”, :for => 15.seconds, :caller_id => callerid) this call will timeout after 15 seconds if 1 of the 3 extensions being dialed do not pick prior to the 15 second time limit

:options - This is a string of options like “Tr” which are supported by the asterisk DIAL application. for a complete list of these options and their usage please check the link below.

:confirm - ?

for this call specified by the variable my_callerid

dial "SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => my_callerid

Examples:

Make a call to the PSTN using my SIP provider for VoIP termination

dial("SIP/[email protected]")

Make 3 Simulataneous calls to the SIP extensions separated by & symbols, try for 15 seconds and use the callerid

Make a call using the IAX provider to the PSTN

dial("IAX2/my.id@voipjet/19095551234", :name=>"John Doe", :caller_id=>"9095551234")

Parameters:

  • number (String)

    represents the extension or “number” that asterisk should dial.

  • options (Hash) (defaults to: {})

Raises:

  • (ArgumentError)

See Also:



847
848
849
850
851
852
853
854
855
856
857
858
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 847

def dial(number, options={})
  *recognized_options = :caller_id, :name, :for, :options, :confirm

  unrecognized_options = options.keys - recognized_options
  raise ArgumentError, "Unknown dial options: #{unrecognized_options.to_sentence}" if unrecognized_options.any?
  set_caller_id_name options[:name]
  set_caller_id_number options[:caller_id]
  confirm_option = dial_macro_option_compiler options[:confirm]
  all_options = options[:options]
  all_options = all_options ? all_options + confirm_option : confirm_option
  execute "Dial", number, options[:for], all_options
end

#disable_feature(feature_name) ⇒ Object

Disables a feature name specified in features.conf. If you’re disabling it, it was probably set by enable_feature().

Parameters:



621
622
623
624
625
626
627
628
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 621

def disable_feature(feature_name)
  enabled_features_variable = variable 'DYNAMIC_FEATURES'
  enabled_features = enabled_features_variable.split('#')
  if enabled_features.include? feature_name
    enabled_features.delete feature_name
    variable 'DYNAMIC_FEATURES' => enabled_features.join('#')
  end
end

#dtmf(digits) ⇒ Object

Simulates pressing the specified digits over the current channel. Can be used to traverse a phone menu.



256
257
258
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 256

def dtmf(digits)
  execute "SendDTMF", digits.to_s
end

#duration_ofFloat

Get the number of seconds the given block takes to execute. This is particularly useful in dialplans for tracking billable time. Note that if the call is hung up during the block, you will need to rescue the exception if you have some mission-critical logic after it with which you’re recording this return-value.

Returns:

  • (Float)

    number of seconds taken for block to execute



895
896
897
898
899
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 895

def duration_of
  start_time = Time.now
  yield
  Time.now - start_time
end

#enable_feature(feature_name, optional_options = nil) ⇒ Object

A high-level way of enabling features you create/uncomment from features.conf.

Certain Symbol features you enable (as defined in DYNAMIC_FEATURE_EXTENSIONS) have optional arguments that you can also specify here. The usage examples show how to do this.

Usage examples:

enable_feature :attended_transfer                        # Enables "atxfer"

enable_feature :attended_transfer, :context => "my_dial" # Enables "atxfer" and then
                                                         # sets "TRANSFER_CONTEXT" to :context's value

enable_feature :blind_transfer, :context => 'my_dial'    # Enables 'blindxfer' and sets TRANSFER_CONTEXT

enable_feature "foobar"                                  # Enables "foobar"

enable_feature("dup"); enable_feature("dup")             # Enables "dup" only once.


607
608
609
610
611
612
613
614
615
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 607

def enable_feature(feature_name, optional_options=nil)
  if DYNAMIC_FEATURE_EXTENSIONS.has_key? feature_name
    instance_exec(optional_options, &DYNAMIC_FEATURE_EXTENSIONS[feature_name])
  else
    raise ArgumentError, "You cannot supply optional options when the feature name is " +
                         "not internally recognized!" if optional_options
    extend_dynamic_features_with feature_name
  end
end

#execute(application, *arguments) ⇒ Object

This asterisk dialplan command allows you to instruct Asterisk to start applications which are typically run from extensions.conf.

The most common commands are already made available through the FAGI interface provided by this code base. For commands that do not fall into this category, then exec is what you should use.

For example, if there are specific asterisk modules you have loaded that will not be available through the standard commands provided through FAGI - then you can used EXEC.

Examples:

Using execute in this way will add a header to an existing SIP call.

execute 'SIPAddHeader', '"Call-Info: answer-after=0"

See Also:



142
143
144
145
146
147
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 142

def execute(application, *arguments)
  result = raw_response(%{EXEC %s "%s"} % [ application,
    arguments.join(%{"#{AHN_CONFIG.asterisk.argument_delimiter}"}) ])
  return false if error?(result)
  result
end

#get_variable(variable_name) ⇒ Object

Issue this command to access a channel variable that exists in the asterisk dialplan (i.e. extensions.conf) Use get_variable to pass information from other modules or high level configurations from the asterisk dialplan to the adhearsion dialplan.

@see: www.voip-info.org/wiki/view/get+variable Asterisk Get Variable

Parameters:



660
661
662
663
664
665
666
667
668
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 660

def get_variable(variable_name)
  result = response("GET VARIABLE", variable_name)
  case result
    when "200 result=0"
      return nil
  when /^200 result=1 \((.*)\)$/
    return $LAST_PAREN_MATCH
  end
end

#hangupObject

Hangs up the current channel. After this command is issued, you will not be able to send any more AGI commands but the dialplan Thread will still continue, allowing you to do any post-call work.



170
171
172
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 170

def hangup
  response 'HANGUP'
end

#input(*args) ⇒ Object

obviously override this by passing in a new key with :accept_key.



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 471

def input(*args)
   options = args.last.kind_of?(Hash) ? args.pop : {}
   number_of_digits = args.shift

   sound_files     = Array options.delete(:play)
   timeout         = options.delete(:timeout)
   terminating_key = options.delete(:accept_key)
   terminating_key = if terminating_key
     terminating_key.to_s
   elsif number_of_digits.nil? && !terminating_key.equal?(false)
     '#'
   end

   if number_of_digits && number_of_digits < 0
     ahn_log.agi.warn "Giving -1 to input() is now deprecated. Don't specify a first " +
                      "argument to simulate unlimited digits." if number_of_digits == -1
     raise ArgumentError, "The number of digits must be positive!"
   end

   buffer = ''
   key = sound_files.any? ? interruptible_play(*sound_files) || '' : wait_for_digit(timeout || -1)
   loop do
     return buffer if key.nil?
     if terminating_key
       if key == terminating_key
         return buffer
       else
         buffer << key
         return buffer if number_of_digits && number_of_digits == buffer.length
       end
     else
       buffer << key
       return buffer if number_of_digits && number_of_digits == buffer.length
     end
     key = wait_for_digit(timeout || -1)
   end
end

#interruptible_play(*files) ⇒ String?

Play a sequence of files, stopping the playback if a digit is pressed.

Returns:

  • (String, nil)

    digit pressed, or nil if none



906
907
908
909
910
911
912
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 906

def interruptible_play(*files)
  files.flatten.each do |file|
    result = result_digit_from response("STREAM FILE", file, "1234567890*#")
    return result if result != 0.chr
  end
  nil
end

#join(conference_id, options = {}) ⇒ Object

Used to join a particular conference with the MeetMe application. To use MeetMe, be sure you have a proper timing device configured on your Asterisk box. MeetMe is Asterisk’s built-in conferencing program.

Parameters:

  • conference_id (String)
  • options (Hash) (defaults to: {})

Raises:

  • (ArgumentError)

See Also:



638
639
640
641
642
643
644
645
646
647
648
649
650
651
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 638

def join(conference_id, options={})
  conference_id = conference_id.to_s.scan(/\w/).join
  command_flags = options[:options].to_s # This is a passthrough string straight to Asterisk
  pin = options[:pin]
  raise ArgumentError, "A conference PIN number must be numerical!" if pin && pin.to_s !~ /^\d+$/

  # To disable dynamic conference creation set :use_static_conf => true
  use_static_conf = options.has_key?(:use_static_conf) ? options[:use_static_conf] : false

  # The 'd' option of MeetMe creates conferences dynamically.
  command_flags += 'd' unless (command_flags.include?('d') or use_static_conf)

  execute "MeetMe", conference_id, command_flags, options[:pin]
end

#jump_to(context, overrides = {}) ⇒ Object

Jumps to a context. An alternative to DialplanContextProc#+@. When jumping to a context, it will not resume executing the former context when the jumped-to context has finished executing. Make sure you don’t have any ensure closures which you expect to execute when the call has finished, as they will run when this method is called.

You can optionally override certain dialplan variables when jumping to the context. A popular use of this is to redefine extension (which this method automatically boxes with a PhoneNumber object) so you can effectively “restart” a call (from the perspective of the jumped-to context). When you override variables here, you’re effectively blowing away the old variables. If you need them for some reason, you should assign the important ones to an instance variable first before calling this method.



519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 519

def jump_to(context, overrides={})
  context = lookup_context_with_name(context) if context.kind_of?(Symbol) || (context.kind_of?(String) && context =~ /^[\w_]+$/)

  # JRuby has a bug that prevents us from correctly determining the class name.
  # See: http://jira.codehaus.org/browse/JRUBY-5026
  if !(context.kind_of?(Adhearsion::DialPlan::DialplanContextProc) || context.kind_of?(Proc))
    raise Adhearsion::VoIP::DSL::Dialplan::ContextNotFoundException
  end

  if overrides.any?
    overrides = overrides.symbolize_keys
    if overrides.has_key?(:extension) && !overrides[:extension].kind_of?(Adhearsion::VoIP::DSL::PhoneNumber)
      overrides[:extension] = Adhearsion::VoIP::DSL::PhoneNumber.new overrides[:extension]
    end

    overrides.each_pair do |key, value|
      meta_def(key) { value }
    end
  end

  raise Adhearsion::VoIP::DSL::Dialplan::ControlPassingException.new(context)
end

#last_dial_statusObject

:busy, :no_answer, :cancelled, :congested, and :channel_unavailable. If :cancel is returned, the caller hung up before the callee picked up. If :congestion is returned, the dialed extension probably doesn’t exist. If :channel_unavailable, the callee phone may not be registered.



569
570
571
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 569

def last_dial_status
  DIAL_STATUSES[get_dial_status]
end

#last_dial_successful?Boolean

as reported by Asterisk. false otherwise

Returns:

  • (Boolean)

    true if your last call to dial() finished with the ANSWER state,



575
576
577
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 575

def last_dial_successful?
  last_dial_status == :answered
end

#last_dial_unsuccessful?Boolean

Opposite of last_dial_successful?()

Returns:

  • (Boolean)


580
581
582
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 580

def last_dial_unsuccessful?
  not last_dial_successful?
end

Creates an interactive menu for the caller.

The following documentation was derived from a post on Jay Phillips’ blog (see below).

The menu() command solves the problem of building enormous input-fetching state machines in Ruby without first-class message passing facilities or an external DSL.

Here is an example dialplan which uses the menu() command effectively.

from_pstn {
  menu 'welcome', 'for-spanish-press-8', 'main-ivr',
       :timeout => 8.seconds, :tries => 3 do |link|
    link.shipment_status  1
    link.ordering         2
    link.representative   4
    link.spanish          8
    link.employee         900..999

    link.on_invalid { play 'invalid' }

    link.on_premature_timeout do |str|
      play 'sorry'
    end

    link.on_failure do
      play 'goodbye'
      hangup
    end
  end
}

shipment_status {
  # Fetch a tracking number and pass it to a web service.
}

ordering {
  # Enter another menu that lets them enter credit card
  # information and place their order over the phone.
}

representative {
  # Place the caller into a queue
}

spanish {
  # Special options for the spanish menu.
}

employee {
  dial "SIP/#{extension}" # Overly simplistic
}

The main detail to note is the declarations within the menu() command’s block. Each line seems to refer to a link object executing a seemingly arbitrary method with an argument that’s either a number or a Range of numbers. The link object collects these arbitrary method invocations and assembles a set of rules. The seemingly arbitrary method name is the name of the context to which the menu should jump in case its argument (the pattern) is found to be a match.

With these context names and patterns defined, the menu() command plays in sequence the sound files you supply as arguments, stopping playback abruptly if the user enters a digit. If no digits were pressed when the files finish playing, it waits :timeout seconds. If no digits are pressed after the timeout, it executes the on_premature_timeout hook you define (if any) and then tries again a maximum of :tries times. If digits are pressed that result in no possible match, it executes the on_invalid hook. When/if all tries are exhausted with no positive match, it executes the on_failure hook after the other hook (e.g. on_invalid, then on_failure).

When the menu() state machine runs through the defined rules, it must distinguish between exact and potential matches. It’s important to understand the differences between these and how they affect the overall outcome:

|---------------|-------------------|------------------------------------------------------|
| exact matches |	potential matches	| result                                               |
|---------------|-------------------|------------------------------------------------------|
|  0	          |  0	               | Fail and start over                                  |
|  1	          |  0	               | Match found!                                         |
|  0	          | >0	               | Get another digit                                    |
| >1	          |  0	               | Go with the first exact match                        |
|  1	          | >0	               | Get another digit. If timeout, use exact match       |
| >1	          | >0	               | Get another digit. If timeout, use first exact match |
|---------------|-------------------|------------------------------------------------------|

Database integration

To do database integration, I recommend programatically executing methods on the link object within the block. For example:

menu do |link|
  for employee in Employee.find(:all)
    link.internal employee.extension
  end
end

or this more efficient and Rubyish way

menu do |link|
  link.internal *Employee.find(:all).map(&:extension)
end

If this second example seems like too much Ruby magic, let me explain — Employee.find(:all) effectively does a “SELECT * FROM employees” on the database with ActiveRecord, returning (what you’d think is) an Array. The map(&:extension) is fanciness that means “replace every instance in this Array with the result of calling extension on that object”. Now we have an Array of every extension in the database. The splat operator (*) before the argument changes the argument from being one argument (an Array) into a sequence of n arguments, where n is the number of items in the Array it’s “splatting”. Lastly, these arguments are passed to the internal method, the name of a context which will handle dialing this user if one of the supplied patterns matches.

Handling a successful pattern match

Which brings me to another important note. Let’s say that the user’s input successfully matched one of the patterns returned by that Employe.find… magic. When it jumps to the internal context, that context can access the variable entered through the extension variable. This was a tricky design decision that I think, overall, works great. It makes the menu() command feel much more first-class in the Adhearsion dialplan grammar and decouples the receiving context from the menu that caused the jump. After all, the context doesn’t necessary need to be the endpoint from a menu; it can be its own entry point, making menu() effectively a pipeline of re-creating the call.

See Also:



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 388

def menu(*args, &block)
  options = args.last.kind_of?(Hash) ? args.pop : {}
  sound_files = args.flatten

  menu_instance = Menu.new(options, &block)

  initial_digit_prompt = sound_files.any?

  # This method is basically one big begin/rescue block. When we start the Menu state machine by continue()ing, the state
  # machine will pass messages back to this method in the form of Exceptions. This decoupling allows the menu system to
  # work on, say, Freeswitch and Asterisk both.
  begin
    if menu_instance.should_continue?
      menu_instance.continue
    else
      menu_instance.execute_failure_hook
      return :failed
    end
  rescue Menu::MenuResult => result_of_menu
    case result_of_menu
      when Menu::MenuResultInvalid
        menu_instance.execute_invalid_hook
        menu_instance.restart!
      when Menu::MenuGetAnotherDigit

        next_digit = play_sound_files_for_menu(menu_instance, sound_files)
        if next_digit
          menu_instance << next_digit
        else
          # The user timed out entering another digit!
          case result_of_menu
            when Menu::MenuGetAnotherDigitOrFinish
              # This raises a ControlPassingException
              jump_to result_of_menu.match_payload, :extension => result_of_menu.new_extension
            when Menu::MenuGetAnotherDigitOrTimeout
              # This should execute premature_timeout AND reset if the number of retries
              # has not been exhausted.
              menu_instance.execute_timeout_hook
              menu_instance.restart!
          end
        end
      when Menu::MenuResultFound
        jump_to result_of_menu.match_payload, :extension => result_of_menu.new_extension
      else
        raise "Unrecognized MenuResult! This may be a bug!"
    end

    # Retry will re-execute the begin block, preserving our changes to the menu_instance object.
    retry

  end
end

#messages_waiting?Boolean

This command should be used to check if a message is waiting on the Asterisk Comedian Voicemail application.

Returns:

  • (Boolean)


272
273
274
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 272

def messages_waiting?
  not @call.inbox.empty?
end

#next_messageObject

This command should be used to advance to the next message in the Asterisk Comedian Voicemail application



267
268
269
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 267

def next_message
  @call.inbox.pop
end

#play(*arguments) ⇒ Object

Plays the specified sound file names. This method will handle Time/DateTime objects (e.g. Time.now), Fixnums (e.g. 1000), Strings which are valid Fixnums (e.g “123”), and direct sound files. When playing numbers, Adhearsion assumes you’re saying the number, not the digits. For example, play(“100”) is pronounced as “one hundred” instead of “one zero zero”.

Note: it is not necessary to supply a sound file extension; Asterisk will try to find a sound file encoded using the current channel’s codec, if one exists. If not, it will transcode from the default codec (GSM). Asterisk stores its sound files in /var/lib/asterisk/sounds.

Examples:

Play file hello-world.???

play 'hello-world'

Speak current time

play Time.now

Play sound file, speak number, play two more sound files

play %w"a-connect-charge-of 22 cents-per-minute will-apply"

Play two sound files

play "you-sound-cute", "what-are-you-wearing"


192
193
194
195
196
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 192

def play(*arguments)
  arguments.flatten.each do |argument|
    play_time(argument) || play_numeric(argument) || play_string(argument)
  end
end

#queue(queue_name) ⇒ Object



549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 549

def queue(queue_name)
  queue_name = queue_name.to_s

  @queue_proxy_hash_lock = Mutex.new unless defined? @queue_proxy_hash_lock
  @queue_proxy_hash_lock.synchronize do
    @queue_proxy_hash ||= {}
    if @queue_proxy_hash.has_key? queue_name
 	    return @queue_proxy_hash[queue_name]
    else
      proxy = @queue_proxy_hash[queue_name] = QueueProxy.new(queue_name, self)
      return proxy
    end
  end
end

#raw_response(message = nil) ⇒ Object

The underlying method executed by nearly all the command methods in this module. Used to send the plaintext commands in the proper AGI format over TCP/IP back to an Asterisk server via the FAGI protocol.

It is not recommended that you call this method directly unless you plan to write a new command method in which case use this to communicate directly with an Asterisk server via the FAGI protocol.

Parameters:

  • message (String) (defaults to: nil)

Raises:

  • (ArgumentError)

See Also:



96
97
98
99
100
101
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 96

def raw_response(message = nil)
  raise ArgumentError.new("illegal NUL in message #{message.inspect}") if message =~ /\0/
  ahn_log.agi.debug ">>> #{message}"
  write message if message
  read
end

#readObject

Utility method to read from pbx. Hangup if nil.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 52

def read
  from_pbx.gets.tap do |message|
    # AGI has many conditions that might indicate a hangup
    raise Hangup if message.nil?

    ahn_log.agi.debug "<<< #{message}"

    code, rest = *message.split(' ', 2)

    case code.to_i
    when 510
      # This error is non-fatal for the call
      ahn_log.agi.warn "510: Invalid or unknown AGI command"
    when 511
      # 511 Command Not Permitted on a dead channel
      ahn_log.agi.debug "511: Dead channel. Raising Hangup"
      raise Hangup
    when 520
      # This error is non-fatal for the call
      ahn_log.agi.warn "520: Invalid command syntax"
    when (500..599)
      # Assume this error is non-fatal for the call and try to keep running
      ahn_log.agi.warn "#{code}: Unknown AGI protocol error."
    end

    # If the message starts with HANGUP it's a silly 1.6 OOB message
    case message
    when /^HANGUP/, /^HANGUP\n?$/i, /^HANGUP\s?\d{3}/i
      ahn_log.agi.debug "AGI HANGUP. Raising hangup"
      raise Hangup
    end
  end
end

#record(*args) ⇒ Object

Records a sound file with the given name. If no filename is specified a file named by Asterisk will be created and returned. Else the given filename will be returned. If a relative path is given, the file will be saved in the default Asterisk sound directory, /var/lib/spool/asterisk by default.

Silence and maxduration is specified in seconds.

Examples:

Asterisk generated filename

filename = record

Specified filename

record '/path/to/my-file.gsm'

All options specified

record 'my-file.gsm', :silence => 5, :maxduration => 120


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 212

def record(*args)
  options = args.last.kind_of?(Hash) ? args.pop : {}
  filename = args.shift || "/tmp/recording_%d"

  if filename.index("%d")
    if @call.variables.has_key?(:recording_counter)
      @call.variables[:recording_counter] += 1
    else
      @call.variables[:recording_counter]  = 0
    end
    filename = filename % @call.variables[:recording_counter]
  end

  if (!options.has_key?(:format))
    format = filename.slice!(/\.[^\.]+$/)
    if (format.nil?)
      ahn_log.agi.warn "Format not specified and not detected.  Defaulting to \"gsm\""
      format = "gsm"
    end
    format.sub!(/^\./, "")
  else
    format = options.delete(:format)
  end

  # maxduration must be in milliseconds when using RECORD FILE
  maxduration = options.delete(:maxduration) || -1
  maxduration = maxduration * 1000 if maxduration > 0

  escapedigits = options.delete(:escapedigits) || "#"
  silence     = options.delete(:silence) || 0

  if (silence > 0)
    response("RECORD FILE", filename, format, escapedigits, maxduration, 0, "BEEP", "s=#{silence}")
  else
    response("RECORD FILE", filename, format, escapedigits, maxduration, 0, "BEEP")
  end

  # If the user hangs up before the recording is entered, -1 is returned and RECORDED_FILE
  # will not contain the name of the file, even though it IS in fact recorded.
  filename + "." + format
end

#response(command, *arguments) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 103

def response(command, *arguments)
  # Arguments surrounded by quotes; quotes backslash-escaped.
  # See parse_args in asterisk/res/res_agi.c (Asterisk 1.4.21.1)
  quote_arg = lambda { |arg|
    '"' + arg.gsub(/["\\]/) { |m| "\\#{m}" } + '"'
  }
  if arguments.empty?
    raw_response("#{command}")
  else
    raw_response("#{command} " + arguments.map{ |arg| quote_arg.call(arg.to_s) }.join(' '))
  end
end

#say_digits(digits) ⇒ Object

Speaks the digits given as an argument. For example, “123” is spoken as “one two three”.

Parameters:



884
885
886
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 884

def say_digits(digits)
  execute "saydigits", validate_digits(digits)
end

#set_variable(variable_name, value) ⇒ Object

Pass information back to the asterisk dial plan.

Keep in mind that the variables are not global variables. These variables only exist for the channel related to the call that is being serviced by the particular instance of your adhearsion application. You will not be able to pass information back to the asterisk dialplan for other instances of your adhearsion application to share. Once the channel is “hungup” then the variables are cleared and their information is gone.

Parameters:

See Also:



681
682
683
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 681

def set_variable(variable_name, value)
  response("SET VARIABLE", variable_name, value) == "200 result=1"
end

#sip_add_header(header, value) ⇒ String

Issue the command to add a custom SIP header to the current call channel example use: sip_add_header(“x-ahn-test”, “rubyrox”)

@param the name of the SIP header @param the value of the SIP header

Returns:

  • (String)

    the Asterisk response

See Also:



694
695
696
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 694

def sip_add_header(header, value)
  execute("SIPAddHeader", "#{header}: #{value}") == "200 result=1"
end

#sip_get_header(header) ⇒ String Also known as: sip_header

Issue the command to fetch a SIP header from the current call channel example use: sip_get_header(“x-ahn-test”)

@param the name of the SIP header to get

Returns:

  • (String)

    the Asterisk response

See Also:



706
707
708
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 706

def sip_get_header(header)
  get_variable("SIP_HEADER(#{header})")
end

#speak(text, engine = :none) ⇒ Object

This feature is presently experimental! Do not use it!



585
586
587
588
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 585

def speak(text, engine=:none)
  engine = AHN_CONFIG.asterisk.speech_engine || engine
  execute SpeechEngines.send(engine, text)
end

#variable(*args) ⇒ Object

Allows you to either set or get a channel variable from Asterisk. The method takes a hash key/value pair if you would like to set a variable Or a single string with the variable to get from Asterisk



714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 714

def variable(*args)
  if args.last.kind_of? Hash
    assignments = args.pop
    raise ArgumentError, "Can't mix variable setting and fetching!" if args.any?
    assignments.each_pair do |key, value|
      set_variable(key, value)
    end
  else
    if args.size == 1
      get_variable args.first
    else
      args.map { |var| get_variable(var) }
    end
  end
end

#verbose(message, level = nil) ⇒ Object

Sends a message to the console via the verbose message system.

of actions happening within Adhearsion.

verbose 'Processing call with Adhearsion' 3

Examples:

Use this command to inform someone watching the Asterisk console

Parameters:

  • message (String)
  • level (Integer) (defaults to: nil)

Returns:

  • the result of the command

See Also:



161
162
163
164
165
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 161

def verbose(message, level = nil)
    result = raw_response("VERBOSE \"#{message}\" #{level}")
    return false if error?(result)
    result
end

#voicemail(*args) ⇒ Object

Send a caller to a voicemail box to leave a message.

The method takes the mailbox_number of the user to leave a message for and a greeting_option that will determine which message gets played to the caller.

Raises:

  • (ArgumentError)

See Also:



736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 736

def voicemail(*args)
  options_hash    = args.last.kind_of?(Hash) ? args.pop : {}
  mailbox_number  = args.shift
  greeting_option = options_hash.delete(:greeting)
  skip_option     = options_hash.delete(:skip)
  raise ArgumentError, 'You supplied too many arguments!' if mailbox_number && options_hash.any?
  greeting_option = case greeting_option
    when :busy then 'b'
    when :unavailable then 'u'
    when nil then nil
    else raise ArgumentError, "Unrecognized greeting #{greeting_option}"
  end
  skip_option &&= 's'
  options = "#{greeting_option}#{skip_option}"

  raise ArgumentError, "Mailbox cannot be blank!" if !mailbox_number.nil? && mailbox_number.blank?
  number_with_context = if mailbox_number then mailbox_number else
    raise ArgumentError, "You must supply ONE context name!" if options_hash.size != 1
    context_name, mailboxes = options_hash.to_a.first
    Array(mailboxes).map do |mailbox|
      raise ArgumentError, "Mailbox numbers must be numerical!" unless mailbox.to_s =~ /^\d+$/
      "#{mailbox}@#{context_name}"
    end.join('&')
  end
  execute('voicemail', number_with_context, options)
  case variable('VMSTATUS')
    when 'SUCCESS' then true
    when 'USEREXIT' then false
    else nil
  end
end

#voicemail_main(options = {}) ⇒ Object

The voicemail_main method puts a caller into the voicemail system to fetch their voicemail or set options for their voicemail box.

Parameters:

  • options (Hash) (defaults to: {})

See Also:



774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 774

def voicemail_main(options={})
  mailbox, context, folder = options.values_at :mailbox, :context, :folder
  authenticate = options.has_key?(:authenticate) ? options[:authenticate] : true

  folder = if folder
    if folder.to_s =~ /^[\w_]+$/
      "a(#{folder})"
    else
      raise ArgumentError, "Voicemail folder must be alphanumerical/underscore characters only!"
    end
  elsif folder == ''
    raise "Folder name cannot be an empty String!"
  else
    nil
  end

  real_mailbox = ""
  real_mailbox << "#{mailbox}"  unless mailbox.blank?
  real_mailbox << "@#{context}" unless context.blank?

  real_options = ""
  real_options << "s" if !authenticate
  real_options << folder unless folder.blank?

  command_args = [real_mailbox]
  command_args << real_options unless real_options.blank?
  command_args.clear if command_args == [""]

  execute 'VoiceMailMain', *command_args
end

#with_next_message(&block) ⇒ Object

The with_next_message method…

Raises:

  • (LocalJumpError)


261
262
263
264
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 261

def with_next_message(&block)
  raise LocalJumpError, "Must supply a block" unless block_given?
  block.call(next_message)
end

#write(message) ⇒ Object

Utility method to write to pbx.

Parameters:

  • message (String)

    raw message



47
48
49
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 47

def write(message)
  to_pbx.print(message + "\n")
end