AsteriskRuby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ #!/usr/local/bin/ruby -wKU -I ../../lib
2
+ ## Copyright (c) 2007, Vonage Holdings
3
+ ##
4
+ ## All rights reserved.
5
+ ##
6
+ ## Redistribution and use in source and binary forms, with or without
7
+ ## modification, are permitted provided that the following conditions are met:
8
+ ##
9
+ ## * Redistributions of source code must retain the above copyright
10
+ ## notice, this list of conditions and the following disclaimer.
11
+ ## * Redistributions in binary form must reproduce the above copyright
12
+ ## notice, this list of conditions and the following disclaimer in the
13
+ ## documentation and/or other materials provided with the distribution.
14
+ ## * Neither the name of Vonage Holdings nor the names of its
15
+ ## contributors may be used to endorse or promote products derived from this
16
+ ## software without specific prior written permission.
17
+ ##
18
+ ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ ## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ ## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ ## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ ## POSSIBILITY OF SUCH DAMAGE.
29
+ ##
30
+ ## Author: Michael Komitee <mkomitee@gmail.com>
31
+
32
+ require 'AGIServer'
33
+ require 'yaml'
34
+
35
+ trap('INT') { AGIServer.shutdown }
36
+ trap('TERM') { AGIServer.shutdown }
37
+
38
+ logger = Logger.new(STDERR)
39
+ logger.level = Logger::DEBUG
40
+
41
+ config = YAML.load_file('config/example-config.yaml')
42
+ config[:logger] = logger
43
+ config[:params] = {:custom1 => 'data1'}
44
+
45
+ begin
46
+ MyAgiServer = AGIServer.new(config)
47
+ rescue Errno::EADDRINUSE
48
+ error = "Cannot start MyAgiServer, Address already in use."
49
+ logger.fatal(error)
50
+ puts error
51
+ exit
52
+ else
53
+ puts "#{$$}"
54
+ end
55
+
56
+ trap('INT') { AGIServer.shutdown }
57
+ trap('TERM') { AGIServer.shutdown }
58
+
59
+ MyAgiServer.start do |agi,params|
60
+ agi.answer
61
+ print "PARAMS = #{params.pretty_inspect}"
62
+ agi.hangup
63
+ end
64
+ MyAgiServer.finish
65
+
66
+ ## Copyright (c) 2007, Vonage Holdings
67
+ ##
68
+ ## All rights reserved.
69
+ ##
70
+ ## Redistribution and use in source and binary forms, with or without
71
+ ## modification, are permitted provided that the following conditions are met:
72
+ ##
73
+ ## * Redistributions of source code must retain the above copyright
74
+ ## notice, this list of conditions and the following disclaimer.
75
+ ## * Redistributions in binary form must reproduce the above copyright
76
+ ## notice, this list of conditions and the following disclaimer in the
77
+ ## documentation and/or other materials provided with the distribution.
78
+ ## * Neither the name of Vonage Holdings nor the names of its
79
+ ## contributors may be used to endorse or promote products derived from this
80
+ ## software without specific prior written permission.
81
+ ##
82
+ ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
83
+ ## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
84
+ ## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
85
+ ## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
86
+ ## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
87
+ ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
88
+ ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
89
+ ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
90
+ ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
91
+ ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
92
+ ## POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,65 @@
1
+ #!/usr/local/bin/ruby -wKU -I ../../lib
2
+ ## Copyright (c) 2007, Vonage Holdings
3
+ ##
4
+ ## All rights reserved.
5
+ ##
6
+ ## Redistribution and use in source and binary forms, with or without
7
+ ## modification, are permitted provided that the following conditions are met:
8
+ ##
9
+ ## * Redistributions of source code must retain the above copyright
10
+ ## notice, this list of conditions and the following disclaimer.
11
+ ## * Redistributions in binary form must reproduce the above copyright
12
+ ## notice, this list of conditions and the following disclaimer in the
13
+ ## documentation and/or other materials provided with the distribution.
14
+ ## * Neither the name of Vonage Holdings nor the names of its
15
+ ## contributors may be used to endorse or promote products derived from this
16
+ ## software without specific prior written permission.
17
+ ##
18
+ ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ ## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ ## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ ## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ ## POSSIBILITY OF SUCH DAMAGE.
29
+ ##
30
+ ## Author: Michael Komitee <mkomitee@gmail.com>
31
+
32
+ require 'AGIState'
33
+
34
+ state = AGIState.new(:threshold => 10)
35
+ 10.times do
36
+ state.failure_inc
37
+ end
38
+
39
+ ## Copyright (c) 2007, Vonage Holdings
40
+ ##
41
+ ## All rights reserved.
42
+ ##
43
+ ## Redistribution and use in source and binary forms, with or without
44
+ ## modification, are permitted provided that the following conditions are met:
45
+ ##
46
+ ## * Redistributions of source code must retain the above copyright
47
+ ## notice, this list of conditions and the following disclaimer.
48
+ ## * Redistributions in binary form must reproduce the above copyright
49
+ ## notice, this list of conditions and the following disclaimer in the
50
+ ## documentation and/or other materials provided with the distribution.
51
+ ## * Neither the name of Vonage Holdings nor the names of its
52
+ ## contributors may be used to endorse or promote products derived from this
53
+ ## software without specific prior written permission.
54
+ ##
55
+ ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
56
+ ## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
57
+ ## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
58
+ ## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
59
+ ## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
60
+ ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
61
+ ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
62
+ ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
63
+ ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
64
+ ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
65
+ ## POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,1072 @@
1
+ =begin rdoc
2
+ Copyright (c) 2007, Vonage Holdings
3
+
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ * Neither the name of Vonage Holdings nor the names of its
15
+ contributors may be used to endorse or promote products derived from this
16
+ software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ Author: Michael Komitee <mkomitee@gmail.com>
31
+
32
+ The AGI object can be used to interact with an Asterisk. It can be used within the AGIServer framework, or independantly. Simply instantiate an AGI object and pass it input and output IO objects. Asterisk extensions can exec an agi script (in asterisk parlance, agi or deadagi), in which case you'll want to use stdin and stdout, or asterisk extensions can connect to a daemonized agi server (in asterisk parlance, fagi) and you'd want to use a tcp socket.
33
+
34
+ agi = AGI.new(:input => STDIN, :output => STDOUT)
35
+ agi.init
36
+ agi.answer
37
+ agi.hangup
38
+
39
+ Note, all agi instances will be uninitialized. The initialization process of an agi channel must be performed before any other interaction with the channel can be accomplished.
40
+ =end
41
+
42
+ #AGI is the Asterisk Gateway Interface, an interface for adding functionality to asterisk. This class implements an object that knows the AGI language and can therefore serve as a bridge between ruby and Asterisk. It can interact with any IO object it's given, so can be used in a sockets based FastAGI or a simple STDIN/STDOUT based Fork-Exec'd AGI. Please see {The Voip Info Asterisk AGI site}[http://www.voip-info.org/wiki-Asterisk+AGI] for more details.
43
+ require 'AGIExceptions.rb'
44
+ require 'AGIResponse.rb'
45
+ require 'logger'
46
+
47
+ #AGI is the Asterisk Gateway Interface, an interface for adding functionality to asterisk. This class implements an object that knows the AGI language and can therefore serve as a bridge between ruby and Asterisk. It can interact with any IO object it's given, so can be used in a sockets based FastAGI or a simple STDIN/STDOUT based Fork-Exec'd AGI. Please see {The Voip Info Asterisk AGI site}[http://www.voip-info.org/wiki-Asterisk+AGI] for more details.
48
+ class AGI
49
+ # Channel Parameters, populated by init
50
+ attr_reader :channel_params
51
+ # Additional AGI parameters provided to new
52
+ attr_reader :init_params
53
+ #Creates an AGI Object based on the provided Parameter Hash.
54
+ #* :input sets the IO object to use for AGI inbound communication from Asterisk, Defaults to STDIN.
55
+ #* :output sets the IO object to use for AGI outbound communication to Asterisk, Defaults to STDOUT:
56
+ #* :logger sets the Logger object to use for logging. Defaults to Logger.new(STDERR).
57
+ #
58
+ #Please note, everything else provided in the hash is available in the resulting object's init_params accessor.
59
+ def initialize(params={})
60
+ @input = params[:input] || STDIN
61
+ @output = params[:output] || STDOUT
62
+ @logger = params[:logger] || Logger.new(STDERR)
63
+ @init_params = params # To store other options for user
64
+ @channel_params = {}
65
+ @last_response = nil
66
+ @initialized = false
67
+ end
68
+
69
+ #Causes Asterisk to answer the channel.
70
+ #
71
+ #Returns an AGIResponse object.
72
+ def answer
73
+ response = AGIResponse.new
74
+ command_str = "ANSWER"
75
+ begin
76
+ response.native = execute(command_str)
77
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
78
+ raise
79
+ end
80
+ if response.native == -1 then
81
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
82
+ elsif response.native == 0
83
+ response.success = true
84
+ end
85
+ return response
86
+ end
87
+
88
+
89
+ #Signals Asterisk to stream the given audio file(s) to the channel. If digits are provided, allows the user to terminate the audio transmission by supplying DTMF. This differs from stream file because it does not accept an offset, and accepts an Array of sound files to play. It actually uses multiple calls to stream_file to accomplish this task.
90
+ #
91
+ #Please see stream_file for Returns, as this is a wrapper for that method
92
+ def background(audio, digits='')
93
+ result = nil
94
+ if audio.class == Array then
95
+ audio.each do |file|
96
+ begin
97
+ result = stream_file(file, digits)
98
+ rescue AGITimeoutError, AGICommandError, AGIHangupError, AGIChannelError
99
+ raise
100
+ end
101
+ return result unless result.success?
102
+ return result if result.data
103
+ end
104
+ return result
105
+ elsif audio.class == String then
106
+ begin
107
+ result = stream_file(audio, digits)
108
+ rescue AGITimeoutError, AGICommandError, AGIHangupError, AGIChannelError
109
+ raise
110
+ end
111
+ end
112
+ end
113
+
114
+ #Is a combination of asterisks background functionality and say_digits. It says the designated digit_string. If digits are provided, allows the user to terminate the audio transmission by supplying DTMF. Allows an optional directory which contains digit-audio. (1.gsm, 2.gsm, ...)
115
+ #
116
+ #Please see background for returns, as this is a wrapper for that method
117
+ def background_digits(digit_string, digits='', path='digits')
118
+ audio = []
119
+ digit_string.to_s.scan(/./m) { |digit| audio << "#{path}/#{digit}" }
120
+ begin
121
+ response = background(audio, digits)
122
+ rescue Exception
123
+ raise
124
+ end
125
+ return response
126
+ end
127
+ alias_method :background_say_digits, :background_digits
128
+
129
+ #Queries Asterisk for the status of the named channel. If no channel is named, defaults to the current channel.
130
+ #
131
+ #Returns an AGIResponse object with data signifying the status of the channel:
132
+ #- 0, 'DOWN, UNAVAILABLE', Channel is down and available
133
+ #- 1, 'DOWN, RESERVED', Channel is down, but reserved
134
+ #- 2, 'OFF HOOK', Channel is off hook
135
+ #- 3, 'DIGITS DIALED', Digits (or equivalent) have been dialed
136
+ #- 4, 'LINE RINGING', Line is ringing
137
+ #- 5, 'REMOTE RINGING', Remote end is ringing
138
+ #- 6, 'UP', Line is up
139
+ #- 7, 'BUSY', Line is busy
140
+ def channel_status(channel=nil)
141
+ response = AGIResponse.new
142
+ if channel.nil? then
143
+ command_str = "CHANNEL STATUS"
144
+ else
145
+ command_str = "CHANNEL STATUS #{channel}"
146
+ end
147
+ begin
148
+ response.native = execute(command_str)
149
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
150
+ raise
151
+ end
152
+ if response.native == -1 then
153
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
154
+ else
155
+ response.success = true
156
+ end
157
+ if response.native == 0 then
158
+ response.data = "DOWN, AVAILABLE"
159
+ elsif response.native == 1 then
160
+ response.data = "DOWN, RESERVED"
161
+ elsif response.native == 2 then
162
+ response.data = "OFF HOOK"
163
+ elsif response.native == 3 then
164
+ response.data = "DIGITS DIALED"
165
+ elsif response.native == 4 then
166
+ response.data = "LINE RINGING"
167
+ elsif response.native == 5 then
168
+ response.data = "REMOTE RINGING"
169
+ elsif response.native == 6 then
170
+ response.data = "UP"
171
+ elsif response.native == 7 then
172
+ response.data = "BUSY"
173
+ end
174
+ return response
175
+ end
176
+
177
+ #Signals Asterisk to stream the given audio file to the channel starting at an optional offset until either the entire file has been streamed or the user provides one of a set of DTMF digits. Unlike stream_file, this allows the user on the channel to control playback using a fast-forward key, a rewind key, and a pause key.
178
+ #
179
+ #Returns an AGIResponse including the DTMF digit provided by the channel.
180
+ def control_stream_file(file, digits='""', skipms=3000, ffchar='*', rewchar='#', pausechar=nil)
181
+ response = AGIResponse.new
182
+ if pausechar.nil?
183
+ command_str = "CONTROL STREAM FILE file digits skipms ffchar rewchar"
184
+ else
185
+ command_str = "CONTROL STREAM FILE file digits skipms ffchar rewchar pausechar"
186
+ end
187
+ begin
188
+ response.native = execute(command_str)
189
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
190
+ raise
191
+ end
192
+ if response.native == -1 then
193
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
194
+ elsif response.native == 0
195
+ response.success = true
196
+ else
197
+ response.success = true
198
+ response.data = response.native.chr
199
+ end
200
+ return response
201
+ end
202
+
203
+ #Signals Asterisk to delete the appropriate ASTDB database key/value.
204
+ #
205
+ #Returns an AGIResponse object
206
+ def database_del(family, key)
207
+ response = AGIResponse.new
208
+ command_str = "DATABASE DEL #{family} #{key}"
209
+ begin
210
+ response.native = execute(command_str)
211
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
212
+ raise
213
+ end
214
+ if response.native == -1 then
215
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
216
+ elsif response.native == 1 then
217
+ response.success = true
218
+ end
219
+ return response
220
+ end
221
+
222
+ #Signals Asterisk to delete the appropriate ASTDB database key/value family.
223
+ #
224
+ #Returns an AGIResponse object
225
+ def database_deltree(family, keytree=nil)
226
+ response = AGIResponse.new
227
+ if keytree.nil? then
228
+ command_str = "DATABASE DELTREE #{family}"
229
+ else
230
+ command_str = "DATABASE DELTREE #{family} #{keytree}"
231
+ end
232
+ begin
233
+ response.native = execute(command_str)
234
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
235
+ raise
236
+ end
237
+ if response.native == -1 then
238
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
239
+ elsif response.native == 1 then
240
+ response.success = true
241
+ end
242
+ return response
243
+ end
244
+
245
+ #Signals Asterisk to return the appropriate ASTDB database key's value
246
+ #
247
+ #Returns an AGIResponse object with data including the value of the requested database key
248
+ def database_get(family, key)
249
+ response = AGIResponse.new
250
+ command_str = "DATABASE GET #{family} #{key}"
251
+ begin
252
+ response.native = execute(command_str)
253
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
254
+ raise
255
+ end
256
+ if response.native == -1 then
257
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
258
+ elsif response.native == 1 then
259
+ response.success = true
260
+ response.data = parse_response
261
+ end
262
+ return response
263
+ end
264
+
265
+ #Signals Asterisk to insert the given value into the ASTDB database
266
+ #
267
+ #Returns an AGIResponse
268
+ def database_put(family, key, value)
269
+ response = AGIResponse.new
270
+ command_str = "DATABASE PUT #{family} #{key} #{value}"
271
+ begin
272
+ response.native = execute(command_str)
273
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
274
+ raise
275
+ end
276
+ if response.native == -1 then
277
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
278
+ elsif response.native == 1 then
279
+ response.success = true
280
+ end
281
+ return response
282
+ end
283
+
284
+ #Signals Asterisk to execute the given Asterisk Application by sending the command "EXEC" to Asterisk using the AGI
285
+ #
286
+ #Returns an AGIResponse.
287
+ #
288
+ #Please note, the success? method to this AGIResponse indicates success of the EXEC command, not the underlying Asterisk Application. If Asterisk provides data in it's standard format, it will be included as data in the AGIResponse object. If it does not, the native asterisk response will be.
289
+ def exec(string)
290
+ response = AGIResponse.new
291
+ command_str = "EXEC #{string}"
292
+ begin
293
+ response.native = execute(command_str)
294
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
295
+ raise
296
+ end
297
+ if response.native == -2 then
298
+ raise AGICommandError.new(@last_response, "Application Not Found in (#{command_str})")
299
+ elsif response.native == -1 then
300
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
301
+ else
302
+ response.success = true
303
+ response.data = parse_response
304
+ response.data ||= response.native
305
+ end
306
+ return response
307
+ end
308
+
309
+ #Signals Asterisk to collect DTMF digits from the channel while playing an audio file. Optionally accepts a timeout option (in seconds) and a maximum number of digits to collect.
310
+ #
311
+ #Returns an AGIResponse with data available which denotes the DTMF digits provided by the channel.
312
+ def get_data(file, timeout=nil, max_digits=nil)
313
+ response = AGIResponse.new
314
+ if timeout.nil? then
315
+ command_str = "GET DATA #{file}"
316
+ elsif max_digits.nil? then
317
+ command_str = "GET DATA #{file} #{(timeout.to_i * 1000)}"
318
+ else
319
+ command_str = "GET DATA #{file} #{(timeout.to_i * 1000)} #{max_digits}"
320
+ end
321
+ begin
322
+ response.native = execute(command_str)
323
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
324
+ raise
325
+ end
326
+ if response.native == -1 then
327
+ raise AGIChannelError.new(@last_response,"Channel Failure in #{command_str}")
328
+ else
329
+ response.success = true
330
+ response.data = response.native
331
+ end
332
+ return response
333
+ end
334
+
335
+ #Signals Asterisk to return the contents of the requested (complex) channel variable
336
+ #
337
+ #Returns an AGIResponse with the variable's value
338
+ def get_full_variable(variablename, channel=nil)
339
+ response = AGIResponse.new
340
+ if channel.nil?
341
+ command_str = "GET FULL VARIABLE #{variablename}"
342
+ else
343
+ command_str = "GET FULL VARIABLE #{variablename} #{channel}"
344
+ end
345
+ begin
346
+ response.native = execute(command_str)
347
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
348
+ raise
349
+ end
350
+ if response.native == -1 then
351
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
352
+ elsif response.native == 1
353
+ response.success = true
354
+ response.data = parse_response
355
+ end
356
+ return response
357
+ end
358
+
359
+ #Signals Asterisk to stream the given audio file to the channel starting at an optional offset until either the entire file has been streamed or the user provides one of a set of DTMF digits. Unlike stream_file, this accepts a timeout option.
360
+ #
361
+ #Returns an AGIResponse including the DTMF digit provided by the channel.
362
+ def get_option(file, digits='""', timeout=0)
363
+ response = AGIResponse.new
364
+ command_str = "GET OPTION #{file} #{digits} #{(timeout.to_i * 1000)}"
365
+ begin
366
+ response.native = execute(command_str)
367
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
368
+ raise
369
+ end
370
+ if response.native == -1 then
371
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
372
+ elsif response.native == 0
373
+ response.success = true
374
+ else
375
+ response.success = true
376
+ response.data = response.native.chr
377
+ end
378
+ return response
379
+ end
380
+
381
+
382
+ #Signals Asterisk to return the contents of the requested channel variable
383
+ #
384
+ #Returns an AGIResponse with the variable's value
385
+ def get_variable(variablename)
386
+ response = AGIResponse.new
387
+ command_str = "GET VARIABLE #{variablename}"
388
+ begin
389
+ response.native = execute(command_str)
390
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
391
+ raise
392
+ end
393
+ if response.native == -1 then
394
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
395
+ elsif response.native == 1
396
+ response.success = true
397
+ response.data = parse_response
398
+ end
399
+ return response
400
+ end
401
+
402
+ #Signals Asterisk to hangup the requested channel. If no channel is provided, defaults to the current channel.
403
+ #
404
+ #Returns an AGIResponse.
405
+ def hangup(channel=nil)
406
+ response = AGIResponse.new
407
+ if channel.nil? then
408
+ command_str = "HANGUP"
409
+ else
410
+ command_str = "HANGUP #{channel}"
411
+ end
412
+ begin
413
+ response.native = execute(command_str)
414
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
415
+ raise
416
+ end
417
+ if response.native == -1 then
418
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
419
+ elsif response.native == 1
420
+ response.success = true
421
+ end
422
+ return response
423
+ end
424
+
425
+ #Initializes the channel by getting all variables provided by Asterisk as it initiates the connection. These values are then stored in the instance variable @channel_params, a Hash object. While initializing the channel, the IO object(s) provided to new as :input and :output are set to operate synchronously.
426
+ #
427
+ #Note, a channel can only be initialized once. If the AGI is being initialized a second time, this will throw an AGIInitializeError. If this is desired functionality, please see the reinit method.
428
+ def init
429
+ if @initialized
430
+ raise AGIInitializeError.new(nil, "Tried to init previously initialized channel. If this is desired, use reinit()")
431
+ end
432
+ begin
433
+ @input.sync = true
434
+ @output.sync = true
435
+ while( line = @input.gets.chomp )
436
+ if line =~ %r{^\s*$} then
437
+ break
438
+ elsif line =~ %r{^agi_(\w+)\:\s+(.+)$} then
439
+ if @channel_params.has_key?($1) then
440
+ @logger.warn{"AGI Got Duplicate Channel Parameter for #{$1} (was \"#{@channel_params[$1]}\" reset to \"#{$2})\""}
441
+ end
442
+ @channel_params[$1] = $2
443
+ @logger.debug{"AGI Got Channel Parameter #{$1} = #{$2}"}
444
+ end
445
+ end
446
+ rescue NoMethodError => error
447
+ if error.to_s =~ %r{chomp} then
448
+ raise AGIHangupError.new(nil, "Channel Hungup during init")
449
+ else
450
+ raise
451
+ end
452
+ end
453
+ @initialized = true
454
+ end
455
+
456
+ #Signals Asterisk to ... do nothing
457
+ #
458
+ #Returns an AGIResponse. (just in case you want to make sure asterisk successfully didnt do anything. Don't ask me, I just implement them)
459
+ def noop(string=nil)
460
+ response = AGIResponse.new
461
+ if string.nil? then
462
+ command_str = "NOOP"
463
+ else
464
+ command_str = "NOOP #{string}"
465
+ end
466
+ begin
467
+ response.native = execute(command_str)
468
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
469
+ raise
470
+ end
471
+ if response.native == -1 then
472
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
473
+ else
474
+ response.success = true
475
+ end
476
+ return response
477
+ end
478
+
479
+ #Signals Asterisk to query the channel to request a single text character from the channel
480
+ #
481
+ #Returns an AGIResponse including the character received.
482
+ def receive_char(timeout=0)
483
+ response = AGIResponse.new
484
+ command_str = "RECEIVE CHAR #{(timeout.to_i * 1000)}"
485
+ begin
486
+ response.native = execute(command_str)
487
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
488
+ raise
489
+ end
490
+ if response.native == -1 then
491
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
492
+ elsif response.native == 0
493
+ raise AGICommandError.new(@last_response, "Channel doesn't support TEXT in (#{command_str})")
494
+ else
495
+ response.success = true
496
+ response.data = response.native.chr
497
+ end
498
+ return response
499
+ end
500
+
501
+ #Signals Asterisk to query the channel to provide audio data to record into a file. Asterisk will record until certain digits are provided as DTMF, or the operation times out, or silence is detected for a second timeout. Can optionally cause asterisk to send a beep to the channel to signal the user the intention of recording sound. By default, there is no timeout,no silence detection, and no beep.
502
+ #
503
+ #Returns an AGIResponse.
504
+ def record_file(filename, format, digits, timeout=-1, beep=false, silence=nil)
505
+ beep_str = ''
506
+ if ( beep == true ) then
507
+ beep_str = "BEEP"
508
+ end
509
+ silence_str = ''
510
+ unless silence.nil?
511
+ silence_str = "s=#{silence}"
512
+ end
513
+ response = AGIResponse.new
514
+ command_str = "RECORD FILE #{filename} #{format} #{digits} #{(timeout.to_i * 1000)} #{beep_str} #{silence_str}"
515
+ begin
516
+ response.native = execute(command_str)
517
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
518
+ raise
519
+ end
520
+ if response.native == -1 then
521
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
522
+ elsif response.native > 0
523
+ response.success = true
524
+ response.data = response.native.chr
525
+ else
526
+ response.success = true
527
+ end
528
+ return response
529
+ end
530
+
531
+
532
+ #Initializes the channel by getting all variables provided by Asterisk as it initiates the connection. These values are then stored in the instance variable @channel_params, a Hash object. While initializing the channel, the IO object(s) provided to new as :input and :output are set to operate synchronously.
533
+ #
534
+ #Note, unlike the init method, this can be called on an AGI object multiple times. Realize, however, that each time you do, the channel will have to provide a set of initialization data, and all previously stored channel parameters will be forgotten.
535
+ def reinit
536
+ @initialized = false
537
+ @channel_params = {}
538
+ @last_response = nil
539
+ init
540
+ end
541
+
542
+ #Signals Asterisk to announce the string provided as a series of characters If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
543
+ #
544
+ #Returns an AGIResponse including the DTMF digit provided by the channel.
545
+ def say_alpha(string, digits='""')
546
+ response = AGIResponse.new
547
+ command_str = "SAY ALPHA '#{string}' #{digits}"
548
+ begin
549
+ response.native = execute(command_str)
550
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
551
+ raise
552
+ end
553
+ if response.native == -1 then
554
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
555
+ elsif response.native == 0
556
+ response.success = true
557
+ else
558
+ response.success = true
559
+ response.data = response.native.chr
560
+ end
561
+ return response
562
+ end
563
+
564
+ #Signals Asterisk to announce the given date. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF. Can accept either a Time object or an integer designation of the number of seconds since 00:00:00 January 1, 1970, Coordinated Universal Time (UTC). Defaults to now.
565
+ #
566
+ #Returns an AGIResponse including the DTMF digit provided by the channel, if any are.
567
+ def say_date(time=Time.now, digits='""')
568
+ response = AGIResponse.new
569
+ command_str = "SAY DATE #{time.to_i} #{digits}"
570
+ begin
571
+ response.native = execute(command_str)
572
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
573
+ raise
574
+ end
575
+ if response.native == -1 then
576
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
577
+ elsif response.native == 0
578
+ response.success = true
579
+ else
580
+ response.success = true
581
+ response.data = response.native.chr
582
+ end
583
+ return response
584
+ end
585
+
586
+ #Signals Asterisk to announce the given date and time. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF. Can accept either a Time object or an integer designation of the number of seconds since 00:00:00 January 1, 1970, Coordinated Universal Time (UTC). Defaults to now.
587
+ #
588
+ #Returns an AGIResponse including the DTMF digit provided by the channel, if any are.
589
+ def say_datetime(time=Time.now, digits='""', format="ABdY", timezone=nil)
590
+ response = AGIResponse.new
591
+ if timezone.nil?
592
+ command_str = "SAY DATETIME #{time.to_i} #{digits} #{format}"
593
+ else
594
+ command_str = "SAY DATETIME #{time.to_i} #{digits} #{format} #{timezone}"
595
+ end
596
+ begin
597
+ response.native = execute(command_str)
598
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
599
+ raise
600
+ end
601
+ if response.native == -1 then
602
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
603
+ elsif response.native == 0
604
+ response.success = true
605
+ else
606
+ response.success = true
607
+ response.data = response.native.chr
608
+ end
609
+ return response
610
+ end
611
+
612
+ #Signals Asterisk to announce the number provided as a series of digits. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
613
+ #
614
+ #Returns an AGIResponse including the DTMF digit provided by the channel.
615
+ def say_digits(number, digits='""')
616
+ response = AGIResponse.new
617
+ command_str = "SAY DIGITS #{number} #{digits}"
618
+ begin
619
+ response.native = execute(command_str)
620
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
621
+ raise
622
+ end
623
+ if response.native == -1 then
624
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
625
+ elsif response.native == 0
626
+ response.success = true
627
+ else
628
+ response.success = true
629
+ response.data = response.native.chr
630
+ end
631
+ return response
632
+ end
633
+
634
+ #Signals Asterisk to announce the number provided as a single number. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
635
+ #
636
+ #Returns an AGIResponse including the DTMF digit provided by the channel.
637
+ def say_number(number, digits='""')
638
+ response = AGIResponse.new
639
+ command_str = "SAY NUMBER #{number} #{digits}"
640
+ begin
641
+ response.native = execute(command_str)
642
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
643
+ raise
644
+ end
645
+ if response.native == -1 then
646
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
647
+ elsif response.native == 0
648
+ response.success = true
649
+ else
650
+ response.success = true
651
+ response.data = response.native.chr
652
+ end
653
+ return response
654
+ end
655
+
656
+ #Signals Asterisk to announce the string provided as a series of characters. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
657
+ #
658
+ #Returns an AGIResponse including the DTMF digit provided by the channel.
659
+ def say_phonetic(string, digits='""')
660
+ response = AGIResponse.new
661
+ command_str = "SAY PHONETIC '#{string}' #{digits}"
662
+ begin
663
+ response.native = execute(command_str)
664
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
665
+ raise
666
+ end
667
+ if response.native == -1 then
668
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
669
+ elsif response.native == 0
670
+ response.success = true
671
+ else
672
+ response.success = true
673
+ response.data = response.native.chr
674
+ end
675
+ return response
676
+ end
677
+
678
+ #Signals Asterisk to announce the given time. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF. Can accept either a Time object or an integer designation of the number of seconds since 00:00:00 January 1, 1970, Coordinated Universal Time (UTC). Defaults to now.
679
+ #
680
+ #Returns an AGIResponse including the DTMF digit provided by the channel, if any are.
681
+ def say_time(time=Time.now, digits='""')
682
+ response = AGIResponse.new
683
+ command_str = "SAY TIME #{time.to_i} #{digits}"
684
+ begin
685
+ response.native = execute(command_str)
686
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
687
+ raise
688
+ end
689
+ if response.native == -1 then
690
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
691
+ elsif response.native == 0
692
+ response.success = true
693
+ else
694
+ response.success = true
695
+ response.data = response.native.chr
696
+ end
697
+ return response
698
+ end
699
+
700
+ #Signals Asterisk to transfer an image across the channel.
701
+ #
702
+ #Returns an AGIResponse.
703
+ #
704
+ #Please note, at present Asterisk returns the same value to the AGI if the image is sent and if the channel does not support image transmission. The AGIResponse, therefore, reflects the same. AGIResponse.success? will be true for both successful transmission and for channel-doesn't support.
705
+ def send_image(image)
706
+ response = AGIResponse.new
707
+ command_str = "SEND IMAGE #{image}"
708
+ begin
709
+ response.native = execute(command_str)
710
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
711
+ raise
712
+ end
713
+ if response.native == -1 then
714
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
715
+ elsif response.native == 0
716
+ response.success = true
717
+ end
718
+ return response
719
+ end
720
+
721
+ #Signals Asterisk to transfer text across the channel.
722
+ #
723
+ #Returns an AGIResponse.
724
+ #
725
+ #Please note, at present Asterisk returns the same value to the AGI if the text is sent and if the channel does not support text transmission. The AGIResponse, therefore, reflects the same. AGIResponse.success? will be true for both successful transmission and for channel-doesn't support.
726
+ def send_text(text)
727
+ response = AGIResponse.new
728
+ command_str = "SEND TEXT '#{text}'"
729
+ begin
730
+ response.native = execute(command_str)
731
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
732
+ raise
733
+ end
734
+ if response.native == -1 then
735
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
736
+ elsif response.native == 0
737
+ response.success = true
738
+ end
739
+ return response
740
+ end
741
+
742
+ #Signals Asterisk to hangup the channel after a given amount of time has elapsed.
743
+ #
744
+ #Returns an AGIResponse.
745
+ def set_autohangup(time)
746
+ response = AGIResponse.new
747
+ command_str = "SET AUTOHANGUP #{time}"
748
+ begin
749
+ response.native = execute(command_str)
750
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
751
+ raise
752
+ end
753
+ if response.native == -1 then
754
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
755
+ elsif response.native == 0
756
+ response.success = true
757
+ end
758
+ return response
759
+ end
760
+
761
+ #Signals Asterisk to set the callerid on the channel.
762
+ #
763
+ #Returns an AGIResponse.
764
+ def set_callerid(callerid)
765
+ response = AGIResponse.new
766
+ command_str = "SET CALLERID #{callerid}"
767
+ begin
768
+ response.native = execute(command_str)
769
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
770
+ raise
771
+ end
772
+ if response.native == -1 then
773
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
774
+ elsif response.native == 1
775
+ response.success = true
776
+ end
777
+ return response
778
+ end
779
+
780
+ #Signals Asterisk to set the context for the channel upon AGI completion.
781
+ #
782
+ #Returns an AGIResponse.
783
+ def set_context(context)
784
+ response = AGIResponse.new
785
+ command_str = "SET CONTEXT #{context}"
786
+ begin
787
+ response.native = execute(command_str)
788
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
789
+ raise
790
+ end
791
+ if response.native == -1 then
792
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
793
+ elsif response.native == 0
794
+ response.success = true
795
+ end
796
+ return response
797
+ end
798
+
799
+ #Signals Asterisk to set the extension for the channel upon AGI completion.
800
+ #
801
+ #Returns an AGIResponse.
802
+ def set_extension(extension)
803
+ response = AGIResponse.new
804
+ command_str = "SET EXTENSION #{extension}"
805
+ begin
806
+ response.native = execute(command_str)
807
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
808
+ raise
809
+ end
810
+ if response.native == -1 then
811
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
812
+ elsif response.native == 0
813
+ response.success = true
814
+ end
815
+ return response
816
+ end
817
+
818
+ #Signals Asterisk to enable or disable music-on-hold for the channel. If music_class is provided, it will select music from the apropriate music class, if it is not provided, asterisk will use music from the default class. The toggle option can either be 'on' or 'off'.
819
+ #
820
+ #Returns an AGIResponse.
821
+ def set_music(toggle, music_class=nil)
822
+ unless ( toggle == 'on' || toggle == 'off')
823
+ raise ArgumentError, "Argument 1 must be 'on' or 'off' to set music"
824
+ end
825
+ response = AGIResponse.new
826
+ if music_class.nil? then
827
+ command_str = "SET MUSIC #{toggle}"
828
+ else
829
+ command_str = "SET MUSIC #{toggle} #{music_class}"
830
+ end
831
+ begin
832
+ response.native = execute(command_str)
833
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
834
+ raise
835
+ end
836
+ if response.native == -1 then
837
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
838
+ elsif response.native == 0
839
+ response.success = true
840
+ end
841
+ return response
842
+ end
843
+
844
+ #Signals Asterisk to set the priority for the channel upon AGI completion.
845
+ #
846
+ #Returns an AGIResponse.
847
+ def set_priority(priority)
848
+ response = AGIResponse.new
849
+ command_str = "SET PRIORITY #{priority}"
850
+ begin
851
+ response.native = execute(command_str)
852
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
853
+ raise
854
+ end
855
+ if response.native == -1 then
856
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
857
+ elsif response.native == 0
858
+ response.success = true
859
+ end
860
+ return response
861
+ end
862
+
863
+ #Signals Asterisk to set the contents of the requested channel variable.
864
+ #
865
+ #Returns an AGIResponse
866
+ def set_variable(variable, value)
867
+ response = AGIResponse.new
868
+ command_str = "SET VARIABLE #{variable} #{value}"
869
+ begin
870
+ response.native = execute(command_str)
871
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
872
+ raise
873
+ end
874
+ if response.native == -1 then
875
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
876
+ elsif response.native == 1
877
+ response.success = true
878
+ end
879
+ return response
880
+ end
881
+
882
+ #Signals Asterisk to stream the given audio file to the channel starting at an optional offset until either the entire file has been streamed or the user provides one of a set of DTMF digits.
883
+ #
884
+ #Returns an AGIResponse including the DTMF digit provided by the channel.
885
+ def stream_file(file, digits='', offset=nil)
886
+ digits.gsub!(/['"]/, '')
887
+ response = AGIResponse.new
888
+ if offset.nil? then
889
+ command_str = "STREAM FILE #{file} '#{digits}'"
890
+ else
891
+ command_Str = "STREAM FILE #{file} ''#{digits}' #{offset}"
892
+ end
893
+ begin
894
+ response.native = execute(command_str)
895
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
896
+ raise
897
+ end
898
+ if response.native == -1 then
899
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
900
+ elsif response.native == 0
901
+ response.success = true
902
+ else
903
+ response.success = true
904
+ response.data = response.native.chr
905
+ end
906
+ return response
907
+ end
908
+
909
+ #Signals Asterisk to enable or disable tdd mode for the channel
910
+ #
911
+ #Returns an AGIResponse.
912
+ def tdd_mode(toggle)
913
+ unless ( toggle == 'on' || toggle == 'off')
914
+ raise ArgumentError, "Argument 1 must be 'on' or 'off' to set tdd mode"
915
+ end
916
+ command_str = "TDD MODE #{toggle}"
917
+ response = AGIResponse.new
918
+ begin
919
+ response.native = execute(command_str)
920
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
921
+ raise
922
+ end
923
+ if response.native == -1 then
924
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
925
+ elsif response.native == 0
926
+ raise AGIChannelError.new(@last_response, "Channel is not TDD-Capable")
927
+ elsif response.native == 1
928
+ response.success = true
929
+ end
930
+ return response
931
+ end
932
+
933
+ #Signals Asterisk to log the given message using the given log level to asterisk's verbose log.
934
+ #
935
+ #Returns an AGIResponse.
936
+ def verbose(message, level)
937
+ response = AGIResponse.new
938
+ command_str = "VERBOSE #{message} #{level}"
939
+ begin
940
+ response.native = execute(command_str)
941
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
942
+ raise
943
+ end
944
+ if response.native == -1 then
945
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
946
+ elsif response.native == 1
947
+ response.success = true
948
+ end
949
+ return response
950
+ end
951
+
952
+ #Signals Asterisk to collect a single DTMF digit from the channel while waiting for an optional timeout.
953
+ #
954
+ #Returns an AGIResponse with data available which denotes the DTMF digits provided by the channel.
955
+ def wait_for_digit(timeout=-1)
956
+ response = AGIResponse.new
957
+ command_str = "WAIT FOR DIGIT #{timeout}"
958
+ begin
959
+ response.native = execute(command_str)
960
+ rescue AGITimeoutError, AGICommandError, AGIHangupError
961
+ raise
962
+ end
963
+ if response.native == -1 then
964
+ raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
965
+ elsif response.native == 0
966
+ response.success = true
967
+ else
968
+ response.success = true
969
+ response.data = response.native.chr
970
+ end
971
+ return response
972
+ end
973
+
974
+ private
975
+ def execute(command_str)
976
+ @last_response = nil
977
+ _execcommand(command_str)
978
+ begin
979
+ result = _readresponse()
980
+ response = _checkresult(result)
981
+ rescue AGIHangupError => error
982
+ @logger.warn{"Received AGI Hangup Error in command (#{command_str}): #{error.to_s}"}
983
+ raise
984
+ rescue AGICommandError => error
985
+ @logger.warn{"Received AGI Command Error in command (#{command_str}): #{error.to_s}"}
986
+ raise
987
+ rescue AGITimeoutError => error
988
+ @logger.warn{"Received AGI Timeout Error in command (#{command_str}): #{error.to_s}"}
989
+ raise
990
+ else
991
+ return response
992
+ end
993
+ end
994
+
995
+ def _execcommand(command_str)
996
+ # returns nothing, merely sends a command string to asterisk
997
+ @logger.debug{"AGI Sent to Asterisk: #{command_str}"}
998
+ @output.print("#{command_str}\n")
999
+ return nil
1000
+ end
1001
+
1002
+ def _readresponse
1003
+ # returns the data returned by asterisk
1004
+ begin
1005
+ response = @input.gets.chomp
1006
+ rescue NoMethodError
1007
+ # NoMethodError here implies chomp called on nil result of gets,
1008
+ # reraise as Hangup
1009
+ raise AGIHangupError.new(nil, "Channel Hungup during command execution")
1010
+ rescue Errno::ECONNRESET
1011
+ raise AGIHangupError.new(nil, "Channel Hungup during command execution")
1012
+ else
1013
+ @logger.debug{"AGI Received from Asterisk: #{response}"}
1014
+ end
1015
+ return response
1016
+ end
1017
+
1018
+ def _checkresult(response)
1019
+ # returns what will be interpreted as asterisks native response
1020
+ if response.nil? then
1021
+ return false
1022
+ end
1023
+ @last_response = response.chomp
1024
+ if ( response =~ %r{^200} && response =~ %r{result=(0[\d*#]+)}i ) then
1025
+ return $1.to_s
1026
+ elsif ( response =~ %r{^200} && response =~ %r{result=(-?[\d*#]+)}i ) then
1027
+ return $1.to_i
1028
+ elsif ( response =~ %r{^200} && response =~ %r{\(timeout\)} ) then
1029
+ raise AGITimeoutError.new(@last_response, "Timed out waiting for response from User")
1030
+ else
1031
+ raise AGICommandError.new(@last_response, "Invalid or nil response from Asterisk: \"#{response}\"")
1032
+ end
1033
+ end
1034
+
1035
+ def parse_response
1036
+ if @last_response =~ %r{\((.*)\)} then
1037
+ return $1
1038
+ else
1039
+ return nil
1040
+ end
1041
+ end
1042
+ end
1043
+
1044
+ =begin
1045
+ Copyright (c) 2007, Vonage Holdings
1046
+
1047
+ All rights reserved.
1048
+
1049
+ Redistribution and use in source and binary forms, with or without
1050
+ modification, are permitted provided that the following conditions are met:
1051
+
1052
+ * Redistributions of source code must retain the above copyright
1053
+ notice, this list of conditions and the following disclaimer.
1054
+ * Redistributions in binary form must reproduce the above copyright
1055
+ notice, this list of conditions and the following disclaimer in the
1056
+ documentation and/or other materials provided with the distribution.
1057
+ * Neither the name of Vonage Holdings nor the names of its
1058
+ contributors may be used to endorse or promote products derived from this
1059
+ software without specific prior written permission.
1060
+
1061
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1062
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1063
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1064
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
1065
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1066
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
1067
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
1068
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1069
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1070
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1071
+ POSSIBILITY OF SUCH DAMAGE.
1072
+ =end