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.
- 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
|