Class: RubyAsterisk::AMI::Client

Overview

Asynchronous AMI client.

#execute registers a Promise for the outgoing command and writes it to the socket via the internal Reactor, returning the Promise immediately without blocking. Callers obtain the response by calling Promise#value on the returned Promise, which blocks only until the matching reply arrives (or a per-command timeout fires).

Data pipeline:

Client (external thread)
 Reactor#send_command (Thread::Queue + IO.pipe doorbell)
 intake Fiber  socket write
 socket read  reader Fiber  Parser
 dispatcher  Promise#resolve (Mutex+CV)
 Client#execute caller (blocks on Promise#value)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from RubyAsterisk::AMI::Commands::Monitor

#change_monitor, #monitor, #pause_monitor, #stop_monitor, #unpause_monitor

Methods included from RubyAsterisk::AMI::Commands::Sip

#sip_peers, #sip_show_peer, #sip_show_registry

Methods included from RubyAsterisk::AMI::Commands::Mailbox

#mailbox_count, #mailbox_status

Methods included from RubyAsterisk::AMI::Commands::Queue

#queue_add, #queue_pause, #queue_remove, #queue_status, #queue_summary, #queues

Methods included from RubyAsterisk::AMI::Commands::Extension

#extension_state

Methods included from RubyAsterisk::AMI::Commands::Conference

#confbridge, #confbridge_kick, #confbridge_mute, #confbridge_unmute, #confbridges, #meet_me_list

Methods included from RubyAsterisk::AMI::Commands::Channel

#atxfer, #channels, #core_show_channels, #hangup, #originate, #originate_app, #redirect, #status

Methods included from RubyAsterisk::AMI::Commands::System

#command, #device_state_list, #event_mask, #parked_calls, #ping, #skinny_devices, #skinny_lines, #wait_event

Constructor Details

#initialize(host:, port:) ⇒ Client

Returns a new instance of Client.



45
46
47
48
49
50
51
# File 'lib/ruby-asterisk/ami/client.rb', line 45

def initialize(host:, port:)
  self.host      = host.to_s
  self.port      = port.to_i
  self.connected = false
  @timeout       = 5
  @reactor       = nil
end

Instance Attribute Details

#connectedObject

Returns the value of attribute connected.



43
44
45
# File 'lib/ruby-asterisk/ami/client.rb', line 43

def connected
  @connected
end

#hostObject

Returns the value of attribute host.



43
44
45
# File 'lib/ruby-asterisk/ami/client.rb', line 43

def host
  @host
end

#portObject

Returns the value of attribute port.



43
44
45
# File 'lib/ruby-asterisk/ami/client.rb', line 43

def port
  @port
end

#timeoutObject

Returns the value of attribute timeout.



43
44
45
# File 'lib/ruby-asterisk/ami/client.rb', line 43

def timeout
  @timeout
end

Instance Method Details

#connecttrue, false

Connect to the Asterisk AMI server and start the async reactor.

Returns:

  • (true, false)


56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/ruby-asterisk/ami/client.rb', line 56

def connect
  @reactor = Reactor.new(host, port,
                         on_event: method(:handle_event),
                         on_disconnect: method(:handle_disconnect))
  @reactor.start
  self.connected = true
  true
rescue StandardError => e
  puts "Connection error: #{e.message}"
  @reactor = nil
  self.connected = false
  false
end

#disconnecttrue, false

Disconnect from Asterisk, stop the reactor, and reject pending promises.

Returns:

  • (true, false)


73
74
75
76
77
78
79
80
81
82
# File 'lib/ruby-asterisk/ami/client.rb', line 73

def disconnect
  @reactor&.stop
  @reactor&.reject_all_promises(RuntimeError.new('Client disconnected'))
  @reactor = nil
  self.connected = false
  true
rescue StandardError => e
  puts e
  false
end

#execute(command, options = {}, timeout: @timeout) ⇒ Promise

Send an AMI command asynchronously.

A caller-supplied 'ActionID' option becomes the request's own ActionID instead of an extra header: two ActionID lines in one frame would leave the response uncorrelated with its Promise.

Parameters:

  • command (String)

    AMI action name (e.g. 'Ping', 'Login')

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

    additional AMI headers

  • timeout (Numeric, nil) (defaults to: @timeout)

    seconds Promise#value waits by default for this command only (does not affect other commands); nil waits indefinitely

Returns:

Raises:



109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ruby-asterisk/ami/client.rb', line 109

def execute(command, options = {}, timeout: @timeout)
  raise Error, 'Not connected to AMI' unless @reactor

  options   = options.dup
  action_id = options.delete('ActionID')
  request   = Request.new(command, options, action_id: action_id)
  promise = Promise.new(action_id: request.action_id, command_type: command, timeout: timeout)
  promise.on_timeout = -> { @reactor&.unregister_promise(request.action_id) }
  @reactor.register_promise(request.action_id, promise)
  # Push the whole frame in one operation so commands issued from concurrent
  # threads cannot interleave their header lines on the wire.
  @reactor.send_command(request.commands.join)
  promise
end

#login(username:, secret:) ⇒ Object

Raises:



84
85
86
87
88
89
# File 'lib/ruby-asterisk/ami/client.rb', line 84

def (username:, secret:)
  connect unless connected
  raise Error, "Unable to connect to AMI at #{host}:#{port}" unless connected

  execute 'Login', { 'Username' => username, 'Secret' => secret, 'Event' => 'On' }
end

#logoffObject



91
92
93
# File 'lib/ruby-asterisk/ami/client.rb', line 91

def logoff
  execute 'Logoff'
end