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.



100
101
102
103
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 100

def answer
  response "ANSWER"
  true
end

#check_voicemailObject



756
757
758
759
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 756

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:



798
799
800
801
802
803
804
805
806
807
808
809
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 798

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:



598
599
600
601
602
603
604
605
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 598

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.



233
234
235
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 233

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



846
847
848
849
850
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 846

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.


584
585
586
587
588
589
590
591
592
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 584

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:



119
120
121
122
123
124
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 119

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:



637
638
639
640
641
642
643
644
645
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 637

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.



147
148
149
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 147

def hangup
  response 'HANGUP'
end

#input(*args) ⇒ Object

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



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 448

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



857
858
859
860
861
862
863
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 857

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:



615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 615

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.



496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 496

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.



546
547
548
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 546

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,



552
553
554
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 552

def last_dial_successful?
  last_dial_status == :answered
end

#last_dial_unsuccessful?Boolean

Opposite of last_dial_successful?()

Returns:

  • (Boolean)


557
558
559
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 557

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:



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
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
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 365

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)


249
250
251
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 249

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



244
245
246
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 244

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"


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

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

#queue(queue_name) ⇒ Object



526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 526

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:



73
74
75
76
77
78
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 73

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
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 52

def read
  returning from_pbx.gets do |message|
    ahn_log.agi.debug "<<< #{message}"
    # AGI has many conditions that might indicate a hangup
    raise Hangup if message.nil?
    raise Hangup if message.match(/^HANGUP\n?$/i)
    raise Hangup if message.match(/^HANGUP\s?\d{3}/i)
    raise Hangup if message.match(/^511 Command Not Permitted on a dead channel/i)
  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


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 189

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



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 80

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:



835
836
837
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 835

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:



658
659
660
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 658

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

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

This feature is presently experimental! Do not use it!



562
563
564
565
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 562

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



665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 665

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:



138
139
140
141
142
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 138

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:



687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 687

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:



725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 725

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)


238
239
240
241
# File 'lib/adhearsion/voip/asterisk/commands.rb', line 238

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