ragi 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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