ragi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +12 -0
- data/RAGI Overview_files/filelist.xml +5 -0
- data/RAGI Overview_files/image001.gif +0 -0
- data/README +114 -0
- data/call_connection.rb +515 -0
- data/call_handler.rb +362 -0
- data/call_initiate.rb +188 -0
- data/call_server.rb +147 -0
- data/config.rb +68 -0
- data/example-call-audio.mp3 +0 -0
- data/ragi.gempsec +23 -0
- data/sample-apps/simon/simon_handler.rb +94 -0
- data/sample-apps/simon/sounds/README +9 -0
- data/sample-apps/simon/sounds/simon-1.gsm +0 -0
- data/sample-apps/simon/sounds/simon-2.gsm +0 -0
- data/sample-apps/simon/sounds/simon-3.gsm +0 -0
- data/sample-apps/simon/sounds/simon-4.gsm +0 -0
- data/sample-apps/simon/sounds/simon-5.gsm +0 -0
- data/sample-apps/simon/sounds/simon-6.gsm +0 -0
- data/sample-apps/simon/sounds/simon-7.gsm +0 -0
- data/sample-apps/simon/sounds/simon-8.gsm +0 -0
- data/sample-apps/simon/sounds/simon-9.gsm +0 -0
- data/sample-apps/simon/sounds/simon-again.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-again.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-beep.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-gameover.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-high.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-low.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-medium.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-score.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-welcome.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep.gsm +0 -0
- data/sample-apps/simon/sounds/simon-gameover.gsm +0 -0
- data/sample-apps/simon/sounds/simon-goodbye.gsm +0 -0
- data/sample-apps/simon/sounds/simon-high.gsm +0 -0
- data/sample-apps/simon/sounds/simon-low.gsm +0 -0
- data/sample-apps/simon/sounds/simon-medium.gsm +0 -0
- data/sample-apps/simon/sounds/simon-score.gsm +0 -0
- data/sample-apps/simon/sounds/simon-welcome.gsm +0 -0
- data/start_ragi.rb +41 -0
- data/test.rb +109 -0
- metadata +77 -0
data/CHANGELOG
ADDED
@@ -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
|
Binary file
|
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
|
+
|
data/call_connection.rb
ADDED
@@ -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
|