Oh yes.... User data... So one self assinged I'd and one user supplied. I suggest a string as the data type with an arbitrary limit of 2k On Sep 19, 2008, at 6:35 AM, "Richard Open Source" <oss.richard@gmail.com
wrote:
I agree.
I have started working on a library like asterisk-java (now onhold but hopefully can continue working on it in a couple of weeks) using groovy (will name it fs-groovy :))
The way I check if the command was successful is to look for the Event with CHANNEL_EXECUTE_COMPLETE and Application and ApplicationData.
Here is how I implemented it. Still needs more work though. -- 2 3 import org.apache.mina.common.IoSession 4 import java.util.concurrent.BlockingQueue 5 import java.util.concurrent.Executors 6 import java.util.concurrent.ExecutorService 7 import java.util.concurrent.Future 8 import java.util.concurrent.Callable 9 import java.util.concurrent.ExecutionException 10 import java.util.concurrent.TimeoutException 11 import java.util.concurrent.TimeUnit 12 13 14 15 class Session { 16 private IoSession session 17 private FSEventHandler handler 18 private BlockingQueue<String> msgQ 19 private final ExecutorService executor = Executors.newSingleThreadExecutor() 20 private final static long DEFAULT_TIMEOUT = 5000 21 22 def data 23 24 def Session(IoSession s, BlockingQueue q) { 25 session = s 26 msgQ = q 27 } 28 29 private def executeAndWait(Closure task, long timeout=0) { 30 Future <CommandResult> f = executor.submit(task as Callable) 31 def result 32 def boolean success = false 33 try { 34 if (timeout != 0) { 35 result = f.get(timeout, TimeUnit.MILLISECONDS) 36 } else { 37 result = f.get() 38 } 39 if (result.code == CommandResult.OK) data = result.data 40 } catch (ExecutionException e) { 41 // Should log here 42 } catch (TimeoutException e) { 43 f.cancel(true) 44 } 45 46 return result 47 } 48 49 def answer() { 50 def task = { 51 def done = false 52 def r = new CommandResult() 53 sendMessage("answer") 54 while (! done) { 55 def m = msgQ.take() 56 if ((m?.event?.Name == "CHANNEL_EXECUTE_COMPLETE") && (m?.Application == "answer")) { 57 done = true 58 r.code = CommandResult.OK 59 r.data = m 60 } 61 } 62 return r 63 } 64 executeAndWait(task, DEFAULT_TIMEOUT) 65 } 66 67 def unset(var) { 68 def task = { 69 def done = false 70 def r = new CommandResult() 71 sendMessage("unset", var) 72 while (! done) { 73 def m = msgQ.take() 74 if ((m?.event?.Name == "CHANNEL_EXECUTE_COMPLETE") 75 && (m?.Application == "unset") 76 && (m?.ApplicationData == var)) { 77 done = true 78 r.code = CommandResult.OK 79 r.data = m 80 } 81 } 82 return r 83 } 84 executeAndWait(task, DEFAULT_TIMEOUT) 85 } 86 87 def queueDtmf(dtmfs) { 88 def task = { 89 def done = false 90 def r = new CommandResult() 91 sendMessage("queue_dtmf", dtmfs) 92 while (! done) { 93 def m = msgQ.take() 94 if ((m?.event?.Name == "CHANNEL_EXECUTE_COMPLETE") 95 && (m?.Application == "queue_dtmf") 96 && (m?.ApplicationData == dtmfs)) { 97 done = true 98 r.code = CommandResult.OK 99 r.data = m 100 } 101 } 102 return r 103 } 104 executeAndWait(task, DEFAULT_TIMEOUT) 105 } 106 107 /* 108 def hangup() { 109 def task = { 110 def done = false 111 def r = new CommandResult() 112 sendMessage("hangup") 113 while (! done) { 114 def m = msgQ.take() 115 if ((m?.event?.Name == "CHANNEL_EXECUTE_COMPLETE") 116 && (m?.Application == "queue_dtmf") 117 && (m?.ApplicationData == dtmfs)) { 118 done = true 119 r.code = CommandResult.OK 120 r.data = m 121 } 122 } 123 return r 124 } 125 executeAndWait(task, DEFAULT_TIMEOUT) 126 } 127 */ 128 129 def setVariable(String var, String value) { 130 def task = { 131 def done = false 132 def r = new CommandResult() 133 sendMessage("set", "${var}=$ {value}") 134 while (! done) { 135 def m = msgQ.take() 136 if ((m?.event?.Name == "CHANNEL_EXECUTE_COMPLETE") 137 && (m?.Application == "set") 138 && (m?.ApplicationData == "${var}=${value}")) { 139 done = true 140 r.code = CommandResult.OK 141 r.data = m 142 } 143 } 144 return r 145 } 146 executeAndWait(task, DEFAULT_TIMEOUT) 147 148 } 149 150 def export(String var) { 151 set("export_vars", var) 152 } 153 154 def bridge(String number) { 155 def task = { 156 def done = false 157 def r = new CommandResult() 158 sendMessage("bridge", number) 159 while (! done) { 160 def m = msgQ.take() 161 // println m 162 if (((m?.event?.Name == "CHANNEL_EXECUTE_COMPLETE") 163 && (m?.Application == "bridge") 164 && (m?.ApplicationData == number)) || 165 ((m?.event?.Name == "CHANNEL_UNBRIDGE") 166 && (m?.variable.bridge_channel == number)) || (m?.SESSIONCLOSED == "true")) { 167 done = true 168 r.code = CommandResult.OK 169 r.data = m 170 } 171 } 172 return r 173 } 174 executeAndWait(task) 175 } 176 177 def promptForDigits(int min, int max, String soundFile, String variableName, long timeout, String terminator) { 178 def task = { 179 def done = false 180 def r = new CommandResult() 181 def appData = "${min} ${max} $ {soundFile} ${variableName} ${timeout} ${terminator}" 182 sendMessage("read", appData, true) 183 while (! done) { 184 def m = msgQ.take() 185 if ((m?.event?.Name == "CHANNEL_EXECUTE_COMPLETE") 186 && (m?.Application == "read") 187 && (m?.ApplicationData == appData)) { 188 done = true 189 r.code = CommandResult.OK 190 r.data = m 191 } 192 } 193 return r 194 } 195 executeAndWait(task, timeout) 196 } 197 198 def originate(String url) { 199 sendMessage("originate", url, true) 200 } 201 202 def sleep(int sec) { 203 sendMessage("sleep", new Integer(sec*1000).toString()) 204 } 205 206 def say(String phrase) { 207 sendMessage("phrase", "spell,$phrase") 208 } 209 210 def script() { 211 return "jeprox" 212 } 213 214 private def sendMessage(String app, String arg=null, boolean event_lock=false) { 215 String msg = "sendmsg\ncall-command: execute \nexecute-app-name: ${app}" 216 if (arg) { 217 msg += "\nexecute-app-arg: ${arg}" 218 } 219 if (event_lock) { 220 msg += "\nevent-lock: ${event_lock}" 221 } 222 System.out.println(msg) 223 session.write("$msg\n\n") 224 } 225 }
On Thu, Sep 18, 2008 at 5:49 PM, Luke Graybill <killarny@gmail.com> wrote: I've been doing a lot of work recently with FreeSWITCH's mod_event_socket, and I wanted to comment a bit about the syntax used for commands through the socket while using asynchronous mode. I haven't tried the synchronous mode yet, as I always want to be free to be able to execute commands without waiting for other commands to finish. For instance, I need to be able to collect DTMF events while I'm playing a sound file, so that the user can do things like select menu items without listening to the entire menu first.
Asterisk's AMI protocol allows you to specify an ActionID along with every command that you send. Asterisk then includes this ActionID with every event that is related to that command, making it cake to coordinate an asynchronous client.
However, even in async mode, FreeSWITCH's mod_event_socket doesn't communicate any identifying information for command responses, or for events triggered by a previous command, unless one uses the bgapi command set. This command set is not applicable to every situation, though. It only applies to commands which manipulate the call; if one needs to manipulate the channel, then messages must be used through the sendmsg command set, which doesn't provide any specific identifying information.
Now, to complicate things, with all commands (even bgapi) the protocol works something like this: you send a command, and wait for a response from mod_event_socket. This response is assumed to be immediate before anything else the client might receive from mod_event_socket, and in the case of bgapi, this response will contain a job-id to use for comparing job-related events later.
For example, for the following command:
sendmsg call-command: execute execute-app-name: answer\n\n
The response is this:
Content-Type: command/reply Reply-Text: +OK
That response is generic to nearly every single command sent, and is only really saying "The last transmission was a valid command, and didn't immediately fail". The command may actually fail later, and command specific feedback is generally contained in later events (which have no unique identifying information).
My issue here is that this seems to be forcing an asynchronous client to rely upon a synchronous ordering of response directly following command, thus violating the very concepts of an asynchronous protocol in which there should be no assumed order. Not only that, but this method increases the complexity of a client, which must be aware of limitations that wouldn't ordinarily be required by a true asynchronous protocol. An asynchronous client should be unconcerned with listening for a synchronous response to every command.
My suggested solution is to apply the job-id concept from bgapi to messages as well, and to go a step further; borrow the Asterisk idea of transmitting an identifier along with each command. Every response and event related to that command should then contain the very same identifier in the header.
_______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org
uld then contain the very same identifier in the header. _______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org
v> _______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org