class Punchblock::Translator::Asterisk::Call

Constants

HANGUP_CAUSE_TO_END_REASON
InvalidCommandError
OUTBOUND_CHANNEL_MATCH

Attributes

agi_env[R]
channel[R]
direction[R]
id[R]
pending_joins[R]
translator[R]

Public Class Methods

new(channel, translator, ami_client, connection, agi_env = nil, id = nil) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 28
def initialize(channel, translator, ami_client, connection, agi_env = nil, id = nil)
  @channel, @translator, @ami_client, @connection = channel, translator, ami_client, connection
  @agi_env = agi_env || {}
  @id = id || Punchblock.new_uuid
  @components = {}
  @answered = false
  @pending_joins = {}
  @progress_sent = false
  @block_commands = false
  @channel_variables = {}
  @hangup_cause = nil
end

Public Instance Methods

after(*args, &block) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 335
def after(*args, &block)
  translator.after(*args, &block)
end
answered?() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 96
def answered?
  @answered
end
channel=(other) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 106
def channel=(other)
  @channel = other
end
channel_var(variable) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 58
def channel_var(variable)
  @channel_variables[variable] || fetch_channel_var(variable)
end
component_with_id(component_id) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 49
def component_with_id(component_id)
  @components[component_id]
end
deregister_component(id) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 45
def deregister_component(id)
  @components.delete id
end
dial(dial_command) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 67
def dial(dial_command)
  @direction = :outbound
  channel = dial_command.to || ''
  channel.match(OUTBOUND_CHANNEL_MATCH) { |m| channel = m[:channel] }
  params = { :async       => true,
             :context     => REDIRECT_CONTEXT,
             :exten       => REDIRECT_EXTENSION,
             :priority    => REDIRECT_PRIORITY,
             :channel     => channel,
             :callerid    => dial_command.from
           }
  params[:variable] = variable_for_headers dial_command.headers
  params[:timeout] = dial_command.timeout unless dial_command.timeout.nil?

  originate_action = Punchblock::Component::Asterisk::AMI::Action.new :name => 'Originate',
                                                                      :params => params
  originate_action.request!
  translator.async.execute_global_command originate_action
  dial_command.response = Ref.new uri: id
end
execute_agi_command(command, *params) click to toggle source

@return [Hash] AGI result

@raises RubyAMI::Error, ChannelGoneError

# File lib/punchblock/translator/asterisk/call.rb, line 293
def execute_agi_command(command, *params)
  agi = AGICommand.new Punchblock.new_uuid, channel, command, *params
  response = Celluloid::Future.new
  register_tmp_handler :ami, [{name: 'AsyncAGI', [:[], 'SubEvent'] => 'Exec'}, {name: 'AsyncAGIExec'}], [{[:[], 'CommandID'] => agi.id}, {[:[], 'CommandId'] => agi.id}] do |event|
    response.signal Celluloid::SuccessResponse.new(nil, event)
  end
  agi.execute @ami_client
  event = response.value
  return unless event
  agi.parse_result event
end
execute_command(command) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 192
def execute_command(command)
  if @block_commands
    command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{id}", id
    return
  end
  if command.component_id
    if component = component_with_id(command.component_id)
      component.execute_command command
    else
      command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id
    end
  end
  case command
  when Command::Accept
    if outbound?
      command.response = true
    else
      execute_agi_command 'EXEC RINGING'
      command.response = true
    end
  when Command::Answer
    execute_agi_command 'ANSWER'
    @answered = true
    command.response = true
  when Command::Hangup
    send_hangup_command
    @hangup_cause = :hangup_command
    command.response = true
  when Command::Join
    other_call = translator.call_with_id command.call_uri
    if other_call
      @pending_joins[other_call.channel] = command
      execute_agi_command 'EXEC Bridge', "#{other_call.channel},F(#{REDIRECT_CONTEXT},#{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY})"
    else
      command.response = ProtocolError.new.setup :service_unavailable, "Could not find join party with address #{command.call_uri}", id
    end
  when Command::Unjoin
    other_call = translator.call_with_id command.call_uri
    redirect_back other_call
    command.response = true
  when Command::Reject
    case command.reason
    when :busy
      execute_agi_command 'EXEC Busy'
    when :decline
      send_hangup_command 21
    when :error
      execute_agi_command 'EXEC Congestion'
    else
      execute_agi_command 'EXEC Congestion'
    end
    command.response = true
  when Command::Redirect
    execute_agi_command 'EXEC Transfer', command.to
    status = channel_var 'TRANSFERSTATUS'
    command.response = case status
    when 'SUCCESS'
      true
    else
      ProtocolError.new.setup 'error', "TRANSFERSTATUS was #{status}", id
    end
  when Punchblock::Component::Asterisk::AGI::Command
    execute_component Component::Asterisk::AGICommand, command
  when Punchblock::Component::Output
    execute_component Component::Output, command
  when Punchblock::Component::Input
    execute_component Component::Input, command
  when Punchblock::Component::Prompt
    component_class = case command.input.recognizer
    when 'unimrcp'
      case command.output.renderer
      when 'unimrcp'
        Component::MRCPPrompt
      when 'asterisk'
        Component::MRCPNativePrompt
      else
        raise InvalidCommandError, 'Invalid recognizer/renderer combination'
      end
    else
      Component::ComposedPrompt
    end
    execute_component component_class, command
  when Punchblock::Component::Record
    execute_component Component::Record, command
  else
    command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id
  end
rescue InvalidCommandError => e
  command.response = ProtocolError.new.setup :invalid_command, e.message, id
rescue ChannelGoneError
  command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{id}", id
rescue RubyAMI::Error => e
  command.response = ProtocolError.new.setup 'error', e.message, id
rescue Celluloid::DeadActorError
  command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id
end
handle_hangup_event(code = nil, timestamp = nil) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 325
def handle_hangup_event(code = nil, timestamp = nil)
  code ||= 16
  reason = @hangup_cause || HANGUP_CAUSE_TO_END_REASON[code]
  @block_commands = true
  @components.each_pair do |id, component|
    component.call_ended
  end
  send_end_event reason, code, timestamp
end
inbound?() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 92
def inbound?
  direction == :inbound
end
inspect()
Alias for: to_s
logger_id() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 305
def logger_id
  "#{self.class}: #{id}"
end
outbound?() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 88
def outbound?
  direction == :outbound
end
process_ami_event(ami_event) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 110
def process_ami_event(ami_event)
  if Asterisk.event_passes_filter?(ami_event)
    send_pb_event Event::Asterisk::AMI::Event.new(name: ami_event.name, headers: ami_event.headers)
  end

  case ami_event.name
  when 'Hangup'
    handle_hangup_event ami_event['Cause'].to_i, ami_event.best_time
  when 'AsyncAGI'
    component_for_command_id_handle ami_event

    if @answered == false && ami_event['SubEvent'] == 'Start'
      @answered = true
      send_pb_event Event::Answered.new(timestamp: ami_event.best_time)
    end
  when 'AsyncAGIStart'
    component_for_command_id_handle ami_event

    if @answered == false
      @answered = true
      send_pb_event Event::Answered.new(timestamp: ami_event.best_time)
    end
  when 'AsyncAGIExec', 'AsyncAGIEnd'
    component_for_command_id_handle ami_event
  when 'Newstate'
    case ami_event['ChannelState']
    when '5'
      send_pb_event Event::Ringing.new(timestamp: ami_event.best_time)
    end
  when 'BridgeEnter'
    if other_call = ami_event['OtherCall']
      event = Event::Joined.new call_uri: other_call.id, timestamp: ami_event.best_time
      send_pb_event event

      other_call_event = Event::Joined.new call_uri: id, timestamp: ami_event.best_time
      other_call_event.target_call_id = other_call.id
      translator.handle_pb_event other_call_event
    end
   when 'BridgeLeave'
    if other_call = ami_event['OtherCall']
      event = Event::Unjoined.new call_uri: other_call.id, timestamp: ami_event.best_time
      send_pb_event event

      other_call_event = Event::Unjoined.new call_uri: id, timestamp: ami_event.best_time
      other_call_event.target_call_id = other_call.id
      translator.handle_pb_event other_call_event
    end
  when 'OriginateResponse'
    if ami_event['Response'] == 'Failure' && ami_event['Uniqueid'] == '<null>'
      send_end_event :error, nil, ami_event.best_time
    end
  when 'BridgeExec'
    join_command   = @pending_joins.delete ami_event['Channel1']
    join_command ||= @pending_joins.delete ami_event['Channel2']
    join_command.response = true if join_command
  when 'Bridge'
    other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
    if other_call = translator.call_for_channel(other_call_channel)
      event = case ami_event['Bridgestate']
      when 'Link'
        Event::Joined.new call_uri: other_call.id, timestamp: ami_event.best_time
      when 'Unlink'
        Event::Unjoined.new call_uri: other_call.id, timestamp: ami_event.best_time
      end
      send_pb_event event
    end
  when 'Unlink'
    other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
    if other_call = translator.call_for_channel(other_call_channel)
      send_pb_event Event::Unjoined.new(call_uri: other_call.id, timestamp: ami_event.best_time)
    end
  when 'VarSet'
    @channel_variables[ami_event['Variable']] = ami_event['Value']
  end
  trigger_handler :ami, ami_event
end
redirect_back(other_call = nil) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 309
def redirect_back(other_call = nil)
  redirect_options = {
    'Channel'   => channel,
    'Exten'     => Asterisk::REDIRECT_EXTENSION,
    'Priority'  => Asterisk::REDIRECT_PRIORITY,
    'Context'   => Asterisk::REDIRECT_CONTEXT
  }
  redirect_options.merge!({
    'ExtraChannel' => other_call.channel,
    'ExtraExten'     => Asterisk::REDIRECT_EXTENSION,
    'ExtraPriority'  => Asterisk::REDIRECT_PRIORITY,
    'ExtraContext'   => Asterisk::REDIRECT_CONTEXT
  }) if other_call
  send_ami_action 'Redirect', redirect_options
end
register_component(component) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 41
def register_component(component)
  @components[component.id] ||= component
end
send_message(body) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 187
def send_message(body)
  execute_agi_command 'EXEC SendText', body
rescue
end
send_offer() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 53
def send_offer
  @direction = :inbound
  send_pb_event offer_event
end
send_progress() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 100
def send_progress
  return if answered? || outbound? || @progress_sent
  @progress_sent = true
  execute_agi_command "EXEC Progress"
end
to_s() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 62
def to_s
  "#<#{self.class}:#{id} Channel: #{channel.inspect}>"
end
Also aliased as: inspect

Private Instance Methods

component_for_command_id_handle(ami_event) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 385
def component_for_command_id_handle(ami_event)
  if component = component_with_id(ami_event['CommandID'] || ami_event['CommandId'])
    component.handle_ami_event ami_event
  end
end
execute_component(type, command, options = {}) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 360
def execute_component(type, command, options = {})
  type.new(command, self).tap do |component|
    register_component component
    component.execute
  end
end
fetch_channel_var(variable) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 341
def fetch_channel_var(variable)
  result = @ami_client.send_action 'GetVar', 'Channel' => channel, 'Variable' => variable
  result['Value'] == '(null)' ? nil : result['Value']
end
offer_event() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 372
def offer_event
  Event::Offer.new :to      => agi_env.values_at(:agi_dnid, :agi_extension).detect { |e| e && e != 'unknown' },
                   :from    => "#{agi_env[:agi_calleridname]} <#{[agi_env[:agi_type], agi_env[:agi_callerid]].join('/')}>",
                   :headers => sip_headers
end
send_ami_action(name, headers = {}) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 350
def send_ami_action(name, headers = {})
  AMIErrorConverter.convert { @ami_client.send_action name, headers }
end
send_end_event(reason, code = nil, timestamp = nil) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 354
def send_end_event(reason, code = nil, timestamp = nil)
  end_event = Event::End.new(reason: reason, platform_code: code, timestamp: timestamp)
  send_pb_event end_event
  translator.deregister_call id, channel
end
send_hangup_command(cause_code = 16) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 346
def send_hangup_command(cause_code = 16)
  send_ami_action 'Hangup', 'Channel' => channel, 'Cause' => cause_code
end
send_pb_event(event) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 367
def send_pb_event(event)
  event.target_call_id = id
  translator.handle_pb_event event
end
sip_headers() click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 378
def sip_headers
  agi_env.to_a.inject({}) do |accumulator, element|
    accumulator['X-' + element[0].to_s] = element[1] || ''
    accumulator
  end
end
variable_for_headers(headers) click to toggle source
# File lib/punchblock/translator/asterisk/call.rb, line 391
def variable_for_headers(headers)
  variables = { :punchblock_call_id => id }
  header_counter = 51
  headers.each do |name, value|
    variables["SIPADDHEADER#{header_counter}"] = "\"#{name}: #{value}\""
    header_counter += 1
  end
  variables.inject([]) do |a, (k, v)|
    a << "#{k}=#{v}"
  end.join(',')
end