ragi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG +12 -0
  2. data/RAGI Overview_files/filelist.xml +5 -0
  3. data/RAGI Overview_files/image001.gif +0 -0
  4. data/README +114 -0
  5. data/call_connection.rb +515 -0
  6. data/call_handler.rb +362 -0
  7. data/call_initiate.rb +188 -0
  8. data/call_server.rb +147 -0
  9. data/config.rb +68 -0
  10. data/example-call-audio.mp3 +0 -0
  11. data/ragi.gempsec +23 -0
  12. data/sample-apps/simon/simon_handler.rb +94 -0
  13. data/sample-apps/simon/sounds/README +9 -0
  14. data/sample-apps/simon/sounds/simon-1.gsm +0 -0
  15. data/sample-apps/simon/sounds/simon-2.gsm +0 -0
  16. data/sample-apps/simon/sounds/simon-3.gsm +0 -0
  17. data/sample-apps/simon/sounds/simon-4.gsm +0 -0
  18. data/sample-apps/simon/sounds/simon-5.gsm +0 -0
  19. data/sample-apps/simon/sounds/simon-6.gsm +0 -0
  20. data/sample-apps/simon/sounds/simon-7.gsm +0 -0
  21. data/sample-apps/simon/sounds/simon-8.gsm +0 -0
  22. data/sample-apps/simon/sounds/simon-9.gsm +0 -0
  23. data/sample-apps/simon/sounds/simon-again.gsm +0 -0
  24. data/sample-apps/simon/sounds/simon-beep-again.gsm +0 -0
  25. data/sample-apps/simon/sounds/simon-beep-beep.gsm +0 -0
  26. data/sample-apps/simon/sounds/simon-beep-gameover.gsm +0 -0
  27. data/sample-apps/simon/sounds/simon-beep-high.gsm +0 -0
  28. data/sample-apps/simon/sounds/simon-beep-low.gsm +0 -0
  29. data/sample-apps/simon/sounds/simon-beep-medium.gsm +0 -0
  30. data/sample-apps/simon/sounds/simon-beep-score.gsm +0 -0
  31. data/sample-apps/simon/sounds/simon-beep-welcome.gsm +0 -0
  32. data/sample-apps/simon/sounds/simon-beep.gsm +0 -0
  33. data/sample-apps/simon/sounds/simon-gameover.gsm +0 -0
  34. data/sample-apps/simon/sounds/simon-goodbye.gsm +0 -0
  35. data/sample-apps/simon/sounds/simon-high.gsm +0 -0
  36. data/sample-apps/simon/sounds/simon-low.gsm +0 -0
  37. data/sample-apps/simon/sounds/simon-medium.gsm +0 -0
  38. data/sample-apps/simon/sounds/simon-score.gsm +0 -0
  39. data/sample-apps/simon/sounds/simon-welcome.gsm +0 -0
  40. data/start_ragi.rb +41 -0
  41. data/test.rb +109 -0
  42. metadata +77 -0
@@ -0,0 +1,12 @@
1
+ == 1.0.0 - 17-Dec-2005
2
+ - Moved RAGI from SourceForge to RubyForge
3
+ - Updated method names to be more consistent with Ruby and RoR naming conventions
4
+ - Packaged RAGI as a GEM
5
+ - Added instructions for launching RAGI inside of a Rails application
6
+ - Improved ability to map URI to call handlers
7
+ - Updated samples and docs as per above changes
8
+ - Added a README file for getting started quickly
9
+ - If you were using RAGI 0.0.1 from sourceforge, you will need to update your app since the API for this release is not backwards-compatible
10
+
11
+ == 0.0.1 - 12-Sep-2005
12
+ - Initial release
@@ -0,0 +1,5 @@
1
+ <xml xmlns:o="urn:schemas-microsoft-com:office:office">
2
+ <o:MainFile HRef="../RAGI%20Overview.html"/>
3
+ <o:File HRef="image001.gif"/>
4
+ <o:File HRef="filelist.xml"/>
5
+ </xml>
data/README ADDED
@@ -0,0 +1,114 @@
1
+ ************************************************
2
+ ** RAGI - Ruby Asterisk Gateway Interface **
3
+ ************************************************
4
+
5
+ Ruby Asterisk Gateway Interface (RAGI) is a useful open-source framework
6
+ for bridging the Ruby on Rails web application server environment and
7
+ Asterisk, the open-source PBX.
8
+
9
+ RAGI eases the development of interactive automated telephony applications
10
+ such as IVR, call routing, automated call center, and extended IP-PBX
11
+ functionality by leveraging the productivity of the Ruby on Rails framework.
12
+ RAGI simplifies the process of creating rich telephony and web apps with a
13
+ single common web application framework, object model and database backend.
14
+
15
+ *************************
16
+ ** RAGI License **
17
+ *************************
18
+
19
+ RAGI is available under the BSD license.
20
+
21
+ RAGI is sponsored by SnapVine (www.snapvine.com).
22
+ Please email support@snapvine.com if you have
23
+ questions about RAGI.
24
+
25
+ **********************************
26
+ ** Quick Start Instructions **
27
+ **********************************
28
+
29
+ Installing RAGI for Ruby users
30
+ ------------------------------
31
+
32
+ 1. Copy RAGI into the library path for ruby $(lib)/ragi/call*.rb
33
+
34
+ 2. Edit your Asterisk extensions.conf to send call control to your RAGI process.
35
+ For example, the following would send all calls routed to extension "102" to your
36
+ Simon Game, presuming your RAGI server is running on a machine with
37
+ IP address 192.168.2.202
38
+
39
+ exten => 102,1,Answer()
40
+ exten => 102,2,deadagi(agi://192.168.2.202)
41
+ exten => 102,3,Hangup
42
+
43
+ 3. To support the Simon Game sample, copy the sound files into your Asterisk
44
+ server's default sound directory.
45
+
46
+ 4. The file "start_ragi.rb" is an example of running RAGI with the Simon Game
47
+ call handler. To run, type the following from the command prompt:
48
+
49
+ ruby ragi\start_ragi.rb
50
+
51
+ If you wanted to send calls to a different call handler, can pass it as a parameter
52
+ to the CallServer.new. For example:
53
+
54
+ RAGI::CallServer.new( :DefaultHandler => MyNewHandler::CallHandler )
55
+
56
+ NOTE: Currently unless you are using Rails, there is no way to specify multiple
57
+ handlers and switch based on URI (see below for how RAGI does this with Rails).
58
+
59
+
60
+ Installing RAGI for Ruby on Rails
61
+ ---------------------------------
62
+ 1. Create a directory "ragi" inside the "lib" directory of your Rails application
63
+
64
+ 2. Copy all of RAGI's rb files into the ragi folder.
65
+
66
+ 3. Create a new directory in your rails directory called "handlers" under your "app" directory.
67
+ Put your call handlers in this directory. To run the Simon Game example app, place "simon_handler.rb"
68
+ in this directory.
69
+
70
+ As you may already know, a "controller" is a Rails concept and is used to provide the logic for your web app.
71
+ Controllers use "views" to render web pages. In RAGI, a phone call interaction is programmed using a handler.
72
+
73
+ 4. Configure your Rails application to boot up a RAGI server on launch as a separate thread. Add the
74
+ following to the end of your Rails environment.rb file:
75
+
76
+ Dependencies.mechanism = :require
77
+
78
+ # Simple server that spawns a new thread for the server
79
+ class SimpleThreadServer < WEBrick::SimpleServer
80
+ def SimpleThreadServer.start(&block)
81
+ Thread.new do block.call
82
+ end
83
+ end
84
+ end
85
+
86
+ require 'ragi/call_server'
87
+
88
+ RAGI::CallServer.new(
89
+
90
+
91
+ 5. Edit your Asterisk extensions.conf to send call control to your RAGI process.
92
+ For example, the following would send all calls routed to extension "102" to your
93
+ Simon Game, presuming your RAGI server is running on a machine with
94
+ IP address 192.168.2.202
95
+
96
+ exten => 102,1,Answer()
97
+ exten => 102,2,deadagi(agi://192.168.2.202/simon/dialup)
98
+ exten => 102,3,Hangup
99
+
100
+ NOTE: With RAGI, you can have multiple call handlers implemented in your application,
101
+ and you route these based on a URI. In this example, any call sent to extension 102
102
+ will be routed to the a handler called "simon_handler" in the handlers directory, and
103
+ the method "dialup" will be called when the call goes through.
104
+
105
+ If you wanted additional call handlers, you would put them in the handlers directory
106
+ and config your extensions.conf to route them as needed.
107
+
108
+ 6. To support the Simon Game sample, copy the sound files into your Asterisk
109
+ server's default sound directory.
110
+
111
+ 7. Start up your Rails app and your Asterisk server.
112
+
113
+ 8. Call extension 102 and to play the Simon Game.
114
+
@@ -0,0 +1,515 @@
1
+ #
2
+ # RAGI - Ruby classes for implementing an AGI server for Asterisk
3
+ # The BSD License for RAGI follows.
4
+ #
5
+ # Copyright (c) 2005, SNAPVINE LLC (www.snapvine.com)
6
+ # All rights reserved.
7
+
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # this list of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice,
14
+ # this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ # * Neither the name of SNAPVINE nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
24
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'ragi/call_server'
32
+ require 'ragi/call_initiate'
33
+ require 'cgi'
34
+
35
+ #
36
+ # Class - RAGI::CallConnection
37
+ #
38
+ # This class provides a Ruby API for controlling functions on the Asterisk server.
39
+ # Many, but not all, of the Asterisk AGI commands are implemented. The syntax of
40
+ # these API is designed to be convenient and familiar to a Ruby developer and thus
41
+ # do not map exactly to the method name and parameterization of Asterisk AGI.
42
+ #
43
+ # Changelog
44
+ # 7-14-05 (Joe) - added a mechanism for routing calls to the appropriate handler via environment variable "AGI_URL"
45
+ #
46
+
47
+
48
+ module RAGI
49
+ class UsageError < StandardError; end
50
+ class CmdNotFoundError < StandardError; end
51
+ class ApplicationError < StandardError; end
52
+ class SoundFileNotFoundError < StandardError; end
53
+
54
+ CALLERID = 'agi_callerid'
55
+
56
+ # USAGE: See "sample-extensions.conf" for an example of how to configure asterisk.
57
+
58
+ class CallConnection
59
+ attr_reader :params
60
+
61
+ ALL_DIGITS = "1234567890*#"
62
+ ALL_NUMERIC_DIGITS = "1234567890"
63
+ ALL_SPECIAL_DIGITS = "*#"
64
+
65
+ @socket = nil
66
+ @params = {}
67
+
68
+ # Many of the command here correspond roughly to Asterisk AGI in their
69
+ # naming and parameterization. However, we have designed these to be familiar
70
+ # to Ruby developers.
71
+
72
+ # Hang up immediately on the specified channel
73
+ def hang_up(channelName=nil)
74
+ msg="HANGUP"
75
+ if (channelName != nil)
76
+ msg = msg + " " + channelName
77
+ end
78
+ send(msg)
79
+ return get_int_result()
80
+ end
81
+
82
+ # Plays a sound over the channel, synchronously until the end of the sound.
83
+ # Your asterisk must have the appropriate codecs installed.
84
+ # The param "soundName" must be a full path to the sound file on the Asterisk server
85
+ def play_sound(soundName)
86
+ exec("playback", soundName)
87
+ end
88
+
89
+ # Plays a sound over the channel in the background and continues processing other commands.
90
+ # Your asterisk must have the appropriate codecs installed.
91
+ # The param "soundName" must be a full path to the sound file on the Asterisk server
92
+ def background(soundName) #unverified
93
+ exec("background", soundName)
94
+ end
95
+
96
+ # Dial an address.
97
+ # \param address - The address to dial e.g. SIP/1234@foo.com
98
+ # \param waittime
99
+ # \param time Maximum time for the call, in sec, or 0 to specify no time limit
100
+ # \param extraOptions Extra options to pass to asterisk, as a string
101
+ def dial(address, waittime=15, time=600, extraOptions="")
102
+ options = "g#{extraOptions}"
103
+ if time > 0
104
+ options = "#{options}S(#{time})"
105
+ end
106
+ exec("Dial", "#{address}|#{waittime.to_s}|#{options}")
107
+ end
108
+
109
+ # Answer the channel
110
+ def answer
111
+ send("ANSWER")
112
+ return get_int_result()
113
+ end
114
+
115
+ # See the Asterisk wiki for syntax.
116
+ def play_tones(tonestring)
117
+ exec("PlayTones", tonestring)
118
+ end
119
+
120
+ def play_record_tone
121
+ exec("PlayTones", "1400/500,0/15000")
122
+ end
123
+
124
+ def play_info_tone
125
+ exec("PlayTones", "!950/330,!1400/330,!1800/330,0")
126
+ end
127
+
128
+ def play_dial_tone
129
+ exec("PlayTones", "440+480/2000,0/4000")
130
+ end
131
+
132
+ def play_busy_tone
133
+ exec("PlayTones", "480+620/500,0/500")
134
+ end
135
+
136
+ def send_dtmf(digits)
137
+ exec("SendDTMF", digits.to_s)
138
+ end
139
+
140
+ # Send the caller to a group conference room, which is created on the fly.
141
+ def meet_me(confnumber)
142
+ exec("MeetMe", confnumber.to_s + "|dM")
143
+ end
144
+
145
+ # Returns several states such as "ringing...". See Asterisk wiki for more info.
146
+ #
147
+ # 0 Channel is down and available
148
+ # 1 Channel is down, but reserved
149
+ # 2 Channel is off hook
150
+ # 3 Digits (or equivalent) have been dialed
151
+ # 4 Line is ringing
152
+ # 5 Remote end is ringing
153
+ # 6 Line is up
154
+ # 7 Line is busy
155
+ def channel_status(channelName=nil)
156
+ msg = "CHANNEL STATUS"
157
+ if (channelName != nil)
158
+ msg = msg + " " + channelName
159
+ end
160
+ send(msg)
161
+ return get_int_result()
162
+ end
163
+
164
+ # Executes an arbitrary Asterisk application.
165
+ def exec(application, options)
166
+ msg = "EXEC"
167
+ if (application != nil)
168
+ msg = msg + " " + application
169
+ end
170
+ if (options != nil)
171
+ msg = msg + " " + options
172
+ end
173
+
174
+ send(msg)
175
+ return get_result()
176
+ end
177
+
178
+
179
+ #Plays a sound and reads key presses from the user (which are returned)
180
+ #timeout: the milliseconds to wait for the user before giving up.
181
+ #maxdigits: if positive, returns when that many have been read.
182
+ def get_data(filename, timeout = 2000, maxdigits = -1)
183
+ msg = "GET DATA #{filename} #{timeout}"
184
+ if (maxdigits != -1) then
185
+ msg = "#{msg} #{maxdigits}"
186
+ end
187
+ send(msg)
188
+ result = get_result()
189
+ if result == '-1' then
190
+ # todo: This happens if the user hangs up too.
191
+ raise SoundFileNotFoundError, "Error in get_data for #{filename}"
192
+ end
193
+ result
194
+ end
195
+
196
+ # Returns two values:
197
+ # 1. key pressed, as a character
198
+ # 2. Final position of the file if it was stopped prematurely or -1 if it completed
199
+ def stream_file(filename, sampleOffset, escapeDigits)
200
+ msg = "STREAM FILE #{filename} #{escape_digit_string(escapeDigits)} #{sampleOffset}"
201
+
202
+ send(msg)
203
+
204
+ results = get_multivalue_result
205
+
206
+ # Parse the return values. This is from the asterisk documentation:
207
+ #
208
+ # failure: 200 result=-1 endpos=<sample offset>
209
+ # failure on open: 200 result=0 endpos=0
210
+ # success: 200 result=0 endpos=<offset>
211
+ # digit pressed: 200 result=<digit> endpos=<offset>
212
+ key = ''
213
+ finalOffset = sampleOffset
214
+
215
+ keyCode = results["result"]
216
+ if keyCode
217
+ case keyCode
218
+ when '-1'
219
+ # Failure
220
+ if results["endpos"]
221
+ finalOffset = results["endpos"].to_i
222
+ end
223
+ when '0'
224
+ if results["endpos"] != '0'
225
+ # Successful completion of file
226
+ finalOffset = -1
227
+ end
228
+ else
229
+ key = keyCode.to_i.chr
230
+ finalOffset = results["endpos"].to_i
231
+ end
232
+ end
233
+
234
+ return key, finalOffset
235
+ end
236
+
237
+ # Returns a variable as set in extensions.conf with setVar command.
238
+ def get_variable(variableName)
239
+ cmd = "GET VARIABLE " + variableName
240
+ send(cmd)
241
+ res = get_result()
242
+ if res == '0' then
243
+ res = nil
244
+ end
245
+ res
246
+ end
247
+
248
+ # The opposite of get_variable
249
+ def set_variable(name, value)
250
+ msg="SET VARIABLE " + name + " " + value
251
+ send(msg)
252
+ return get_int_result()
253
+ end
254
+
255
+ # Calls swift.agi to speak some text. Sorry, this expects the Cepstral engine.
256
+ def speak_text(texttospeak)
257
+ fixedmessage = texttospeak
258
+ fixedmessage = fixedmessage.gsub("\r", " ")
259
+ fixedmessage = fixedmessage.gsub("\n", " ")
260
+ fixedmessage = fixedmessage.strip
261
+ exec("AGI", "swift.agi|\"" + fixedmessage + "\"")
262
+ end
263
+
264
+ #
265
+ # Usage Notes:
266
+ # Asterisk will not create directories for you, so make sure the path you specify for the sound is valid.
267
+ # Silence detection must be an int greater than 0
268
+ #
269
+ def record_file(filename, maxtimeinseconds, beep=false, silencedetect=2)
270
+ beepstr = ""
271
+ if (beep == true)
272
+ beepstr = " BEEP "
273
+ end
274
+
275
+ cmd = "RECORD FILE " + filename + " gsm " + " \"*#\" " + (maxtimeinseconds * 10000).to_s + beepstr + " s=" + silencedetect.to_s
276
+ send(cmd)
277
+ return get_result()
278
+ end
279
+
280
+ def monitor_call(outputFile)
281
+ exec("Monitor", "wav|#{outputFile}|m")
282
+ end
283
+
284
+ # Pronounce the digits, e.g. "123" will speak as "one two three"
285
+ def say_digits(digits, escapeDigits=ALL_SPECIAL_DIGITS)
286
+ msg="SAY DIGITS #{digits} #{escape_digit_string(escapeDigits)}"
287
+ send(msg)
288
+ return get_int_result()
289
+ end
290
+
291
+ # Says the number, e.g. "123" is "one hundred twenty three"
292
+ def say_number(number, escapeDigits=ALL_SPECIAL_DIGITS)
293
+ msg="SAY NUMBER #{digits} #{escape_digit_string(escapeDigits)}"
294
+ send(msg)
295
+ return get_int_result()
296
+ end
297
+
298
+ #Pass in a Ruby Time object
299
+ def say_time(time, escapeDigits=ALL_SPECIAL_DIGITS)
300
+ #calc the number of seconds elapsed since epoch (00:00:00 on January 1, 1970)
301
+ diff = time.to_i
302
+ msg = "SAY TIME #{diff} #{escape_digit_string(escapeDigits)}"
303
+ send(msg)
304
+ return get_int_result()
305
+ end
306
+
307
+ # Set the caller ID to use
308
+ # e.g., "8001235555"
309
+ def set_caller_id(idSpecification)
310
+ msg="SET CALLERID " + idSpecification.to_s
311
+ send(msg)
312
+ return get_int_result()
313
+ end
314
+
315
+ def wait_music_on_hold(seconds)
316
+ exec("WaitMusicOnHold", seconds.to_s)
317
+ end
318
+
319
+ # Synchronously hold the line for some seconds
320
+ def wait(seconds)
321
+ exec("Wait", seconds.to_s)
322
+ end
323
+
324
+ # Returns the current status of the call.
325
+ # Eventually this might have states like "ringing" or "hungup"
326
+ # TODO: get rid of "get"
327
+ def get_call_status
328
+ dialstatus = get_variable("dialstatus")
329
+ if dialstatus == nil #the dial command has not returned yet..thus the call is in progress
330
+ return :InProgress
331
+ elsif dialstatus == "ANSWER" #the dial command returned from a successfully answered call
332
+ return :Answered
333
+ elsif dialstatus == "BUSY" #the dial command met with a busy signal on the other end
334
+ return :Busy
335
+ elsif dialstatus == "NOANSWER" #the dial command aborted due to no answer
336
+ return :NoAnswer
337
+ elsif dialstatus == "CONGESTION" #the dial command failed due to congestion
338
+ return :Congestion
339
+ elsif dialstatus == "CHANUNAVAIL" #the dial command failed due to misc reasons.
340
+ return :ChannelUnavailable
341
+ else
342
+ return :Offline #not sure
343
+ end
344
+ end
345
+
346
+ # The constructor. It is called by the server.
347
+ def initialize(socket)
348
+ @socket = socket
349
+ @params = {}
350
+
351
+ parse_params
352
+ end
353
+
354
+ # Used by the server for tracking
355
+ def agi_url
356
+ script = @params['agi_network_script']
357
+ return "/#{script}"
358
+ end
359
+
360
+ # This method is used in conjunction with RAGI.place_call.
361
+ # This method returns the hashData hashtable parameter from that call, if present
362
+ # or nil if not present. In the call file, this data is stored as an ascii encoding
363
+ # in the call file variable "CallInitiate_hashdata"
364
+ def get_hash_data
365
+ return CallInitiate.decode_call_params(get_variable("CallInitiate_hashdata"))
366
+ end
367
+
368
+
369
+ #----------------HELPERS-----------------
370
+ private
371
+
372
+ def parse_params
373
+ #have to process a bunch of key pairs from asterisk first
374
+ _doit = true
375
+ while _doit
376
+ _res = read_line().chomp
377
+ if (_res == nil or _res.size<=1)
378
+ _doit = false
379
+ else
380
+ _name,_val = _res.split(/:\s/)
381
+ @params[_name]=_val
382
+ #RAGI.LOGGER.info("CallParam[#{_name}]=#{_val}")
383
+ end
384
+ end
385
+ end
386
+
387
+ def send(message)
388
+ @socket.print(message)
389
+ #RAGI.LOGGER.info("msg=#{message}");
390
+ end
391
+
392
+ def get_int_result
393
+ result=parse_result(get_result())
394
+ intresult = -1
395
+ if (result == nil)
396
+ intresult = -1
397
+ else
398
+ intresult = result.to_i
399
+ end
400
+ #RAGI.LOGGER.info("res=#{intresult}");
401
+ end
402
+
403
+ def get_result
404
+ _res = parse_result(read_line())
405
+ end
406
+
407
+ # Parse a result that consists of a 200 result code plus one or
408
+ # more name=value pairs separated by spaces. Calls through to
409
+ # parse_result if the status is not 200. Returns hash of
410
+ # name=value pairs.
411
+ def get_multivalue_result
412
+ rawResult = read_line()
413
+
414
+ if (rawResult[0..2] == "200") then
415
+ rawResult.slice!(0..2)
416
+ pairs = rawResult.split(' ')
417
+
418
+ results = {}
419
+ pairs.each do |pair|
420
+ tmp = pair.split('=')
421
+ results[tmp[0]] = tmp[1]
422
+ end
423
+
424
+ results
425
+ else
426
+ parse_result(rawResult)
427
+ end
428
+ end
429
+
430
+ def read_line
431
+ begin
432
+ _res = @socket.gets #gets
433
+ #RAGI.LOGGER.info("READ LINE: " + _res.to_s)
434
+ rescue Errno::EINVAL
435
+ raise ApplicationError
436
+ end
437
+ _res
438
+ end
439
+
440
+
441
+ # Asterisk AGI talks over a human-readable protocol. We parse that.
442
+ # Asterisk appears to be giving back multiple error responses
443
+ # before sending the "real" response. Thus, we have to
444
+ # read through error codes such as 510 and 520 and look for the 200.
445
+ def parse_result(_res)
446
+ #form of the response:
447
+ #VALID RESULTS (arbitrary value) ==> 200 result=1 (1119166301)
448
+ #VALID RESULTS (integer) ==> Response: 200 result=0
449
+ #BAD COMMAND ==> 510
450
+ #USAGE ERROR ==> 520...520..\n
451
+ #if I get 510, then the command was not found, sorry
452
+
453
+ if (_res=~/510/)==0 then # 510 = BAD COMMAND
454
+ while !(_res=~/510/) # asterisk may send more than one at at time. lame.
455
+ #RAGI.LOGGER.info("Received " + _res);
456
+ _res = read_line()
457
+ #RAGI.LOGGER.info("STUFF2: #{_res}")
458
+ end
459
+
460
+ RAGI.LOGGER.info("Received 510 Command not found error #{_res}")
461
+ _cmd = /\(.*\)/.match(_res).to_s.gsub(/[\(\)]/,'')
462
+ raise CmdNotFoundError, "Command could not be found = #{_cmd}"
463
+ return nil
464
+
465
+ #if I get 520, then the command usage was not found, sorry
466
+ elsif (_res=~/520/)==0 then # 520 = USAGE ERRROR
467
+ RAGI.LOGGER.info("Received 520 Invalid Command Usage #{_res}")
468
+ _usage = ''
469
+ _res = read_line()
470
+ #RAGI.LOGGER.info("STUFF: #{_res}")
471
+ while !(_res=~/520/) # asterisk may send more than one at at time. lame.
472
+ #RAGI.LOGGER.info("Received " + _res);
473
+ _usage += _usage
474
+ _res = read_line()
475
+ #RAGI.LOGGER.info("STUFF2: #{_res}")
476
+ end
477
+ raise UsageError, "Command Usage Incorrect, correct usage - #{_usage}"
478
+ return nil
479
+ elsif ((_res=~/200/)==0) then # 200 = Results
480
+ eqindex =_res.index("=")
481
+ if (eqindex==nil)
482
+ RAILS_DEFAULT_LOGGER.error("Error, unexpected 200 result with no value " + _res.to_s)
483
+ return nil
484
+ end
485
+
486
+ #if there is a value in parens, return it.
487
+ lb=_res.index("(")
488
+ rb=_res.rindex(")")
489
+ if (lb != nil and rb != nil)
490
+ value = _res[lb+1, rb-lb-1]
491
+ if value == "timeout" then
492
+ # in the case of "200 result=<num> (timeout)" we should return <num>
493
+ value = _res[eqindex+1,lb-eqindex-1]
494
+ end
495
+
496
+ value.chomp!(" ")
497
+ return value
498
+ else
499
+ # there is an int result we hope.
500
+ value = _res[eqindex+1, _res.length]
501
+ return value.chomp!
502
+ end
503
+ end
504
+ end
505
+
506
+ def escape_digit_string(digits)
507
+ if digits
508
+ "\"#{digits}\""
509
+ else
510
+ ""
511
+ end
512
+ end
513
+
514
+ end
515
+ end