Asterisk Gateway Interace (AGI) 續篇
在上一篇文中,我對 AGI 有粗略的介紹,在後段也介紹了基於 FastAGI 建立了一個
系統。這一篇將會是上一篇後段的延續,講解那系統利用 FastAGI 較為細節的部分。
我們自行設計的 Application Server 是基於 MS-Windows 設計。它是以 Windows Service 形式運作。它會開一個 TCP Socket 4573 接收來自 Asterisk 的 FastAGI 要求。接受要求後,Asterisk 的Dialplan 便把控制權移交到 Application Server 中,而Asterisk 那方便一直等待,直至 Application Server 這方的工作完成為止。
當然,Application Server 這方的控制程序並不是硬寫的。它借用了 Mozilla 的
SpiderMonkey 模組作一個 Script Engine。SpiderMonkey 原是 Mozilla 和 FireFox的 JavaScript Engine。那麼,開發人員便可以像開發DialPlan一樣地簡單,用JavaScript 在Application Server上開發 Call Flow。
http://www.mozilla.org/js/spidermonkey/
看過以下的網址後,你會發現 AGI 每一指令正是反映了Asterisk 在Dialpan 中的
一道指令。而AGI 沒有提供的便可能以用 “Exec” 指令來呼叫 Asterisk 其他的
指令。
http://www.bitflipper.ca/Documentation/agi.html
那麼,我們可以用 SpiderMonkey 來定義我們的 Call Flow 語法和指令了。例如
以下的 JavaScript:
function main(){
var ret = Answer();
if(ret == 0){
ret = PlayMsg(”hello-world”, “#”);
}
HangUp();
return 0;
}
在這個例子中,Answer(), PlayMsg(), 和 HangUp() 是我自行定義的 JavaScript 功能。
它內裏也只是調用到 Astreisk 或 AGI 的功能而已。
Application Server 一旦接收到 Asterisk 的 FastAGI 的請求後,Asterisk 會首先傳一
組資料給 Application Server。這組資料可以給 Application Server 詳細的資訊來選
擇Call Flow或其他功能。以下是 Asterisk 首先傳一組資料給 Application Server的
例子。這樣段資料中,每一行它會用 line feed 作分隔,傳送完畢後會用兩個 line feed
作完結。
agi_network: yes
agi_request: agi://192.168.0.30/frame1?param1=1¶m2=2¶m3=3
agi_channel: Zap/1-1
agi_language: en
agi_type: Zap
agi_uniqueid: 1157046217.3
agi_callerid: 88888888
agi_calleridname: unknown
agi_callingpres: 0
agi_callingani2: 0
agi_callington: 0
agi_callingtns: 0
agi_dnid: unknown
agi_rdnis: unknown
agi_context: incoming
agi_extension: s
agi_priority: 2
agi_enhanced: 0.0
agi_accountcode:
在這裡,我們可以看到什麼 channel(agi_channel) 用這AGI,對方的來電
(agi_callerid, agi_calleridname),DNIS(afi_rdnis)等等。
另外,agi_request 便是在Dialplan 裏所調用 AGI()的 URL。例如上方的
AGI request 便是由於以下的 Dialplan 調用:
[callflow]
exten => s,1,AGI(agi://192.168.0.30/frame1?param1=1¶m2=2¶m3=3)
這樣,Asterisk 便可以用URL來傳遞額外的參數,同時我們的 Application Server
便可以用種種方法拆解這URL來得到那額外的參數了。
Application Server 做完以上的動作後,接著便會調用 SpiderMonkey 來運行
我們預備好的 JavaScript。以後,所有關於 AGI 的動作將會由 JavaScript
包裝起來,以下是一小段例子:
1 JSBool CCallFlow::Script_PlayMessage(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
2 CString strMsg;
3 JSBool ret = JS_FALSE;
4 unsigned long scriptContextNo = 0;
5 CCallFlow* lpThis = NULL;
6
7 scriptContextNo = (unsigned long)cx;
8 lpThis = (*mapScriptContext)[scriptContextNo];
9
10 if(lpThis != NULL){
1 strMsg.Format(”CCallFlow::Script_PlayMessage : Called”);
2 lpThis->WriteSysLog(DDEBUG_LEVEL, strMsg);
3
4 if(argc == 2){
5 char strCmd[MAX_PATH];
6 char strRet[MAX_PATH];
7 char* strVoxFile;
8 char* strDigit;
9
20 ZeroMemory(strCmd, MAX_PATH);
1 ZeroMemory(strRet, MAX_PATH);
2
3 if(JSVAL_IS_STRING(argv[0])){
4 strVoxFile = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]));
5 if(JSVAL_IS_STRING(argv[1])){
6 strDigit = JS_GetStringBytes(JSVAL_TO_STRING(argv[1]));
7 if(strlen(strDigit) <= 0){
8 strDigit = “”"”;
9 }
30 wsprintf(strCmd, “STREAM FILE %s %sn”, strVoxFile, strDigit);
1 lpThis->ProcessAGI(strCmd, strRet);
2 Sleep(200);
3 if(strlen(strRet) <= 0){
4 sprintf(strRet, “%d”, 0);
5 }
6
7 JSString* str = JS_NewStringCopyZ(cx, lpThis->mapKeyPad[strRet].c_str());
8 if(!str){
9 ret = JS_FALSE;
40 }else{
1 rval[0] = STRING_TO_JSVAL(str);
2 ret = JS_TRUE;
3 }
4
5 }else{
6 strMsg.Format(”CCallFlow::Script_PlayMessage : Invalid digits”);
7 lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
8 }
9 }else{
50 strMsg.Format(”CCallFlow::Script_PlayMessage : Invalid file”);
1 lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
2 }
3 }else{
4 strMsg.Format(”CScriptHandler::Script_PlayMessage : parameters not match”);
5 lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
6 }
7 }else{
8 strMsg.Format(”CScriptHandler::Script_PlayMessage[%d] : invalid script object.”, lpThis->label);
9 lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
60 }
1 return ret;
2 }
大家會看到第三十和三十一行,在 JavaScript 眼裏看到是否一個 PlayMessage 的JavaScript Function,
但這PlayMessage 裏是調用了 STREAM FILE 這個 AGI Function,而同時整合了一串參數(這裡是一個
wave file name),然後調用 ProcessAGI()。ProcessAGI()只是很簡單地把這AGI Function 經
Socket 傳遞到Asterisk 中。
1 BOOL CCallFlow::ProcessAGI(char* strAGICmd, char* strRet){
2 BOOL ret = FALSE;
3 char _strRet[MAX_PATH];
4 char _strRet2[MAX_PATH];
5
6 if(sck != NULL){
7 cout << strAGICmd << endl;
8 ZeroMemory(_strRet, MAX_PATH);
9 sck->Send((const char*)strAGICmd, strlen(strAGICmd));
10 int len = sck->Receive(_strRet, MAX_PATH);
1 if(len > 0){
2 mapAGIResult.clear();
3 if(strncmp(_strRet, “200 “, replyOffset) == 0){
4
5 ZeroMemory(_strRet2, MAX_PATH);
6 strncpy(_strRet2, _strRet + replyOffset, len - replyOffset);
7
8 ParseCommand2(_strRet2, ‘ ‘, &mapAGIResult);
9
20 strcpy(strRet, mapAGIResult["result"].c_str());
1 ret = TRUE;
2 }else{
3 mapAGIResult["return_error"] = _strRet;
4 }
5 }
6 }
7 return ret;
8 }
其實,關於 SpiderMonkey 的 Document 不多,很多 SpiderMonkey 的功能是透過以下網址所得的資訊然後再推敲出來的。希望這樣篇文章可以幫到大家。
Posted: 八月 6th, 2008 under 系統設定, 開發.
Tags: AGI
Comments
Comment from wudennis
Time 2008 年 11 月 27 日 at 10:56:09
Hello,
如果需要做這功能,AGI 幫不到你了。你需要用 AMI (Asterisk Manager Interface)來獨立控制打出的線路。然後在 extensions.conf 控制密碼輸入便可。我想我會今星期開一篇文詳細討論關於這問題。
Comment from h3llobest
Time 2008 年 11 月 27 日 at 14:05:14
真的非常需要您們的技術支援,先感謝您!也期待您的指導。感謝!
Comment from h3llobest
Time 2008 年 11 月 26 日 at 11:50:27
Hello Dennis
我是一位碩士班的研究生,看到您發表的一篇文章,談到AGI的功能,想請教您一些問題,希望您可以幫助我。我想寫一個簡單功能,此功能的描述如下:當我的VoIP系統的用戶,每個用戶接到電話之前,會有一段語音,然後要求撥打方需要按一個隨機亂數所產生的兩個數字號碼,方能接通至受話端。由於我是初學者,對於網路電話領域才剛踏入,不過最近因為研究所需,必須寫出這個功能,因此不知是否可以請您指導我,將非常感謝您的幫忙。謝謝!