分類

Slideshow

Get the Flash Player to see the slideshow.

彙整

近期迴響

Asterisk 相關

Learning

Linux

Who am I

經常收看

金錢世界

Site search

 

八月 2008
    九月 »
 12
3456789
10111213141516
17181920212223
24252627282930
31  

標籤

近期文章

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&param2=2&param3=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&param2=2&param3=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 的功能是透過以下網址所得的資訊然後再推敲出來的。希望這樣篇文章可以幫到大家。

http://www.mozilla.org/js/spidermonkey/

http://blog.gmane.org/gmane.comp.mozilla.devel.jseng

Comments

Comment from h3llobest
Time 2008 年 11 月 26 日 at 11:50:27

Hello Dennis
我是一位碩士班的研究生,看到您發表的一篇文章,談到AGI的功能,想請教您一些問題,希望您可以幫助我。我想寫一個簡單功能,此功能的描述如下:當我的VoIP系統的用戶,每個用戶接到電話之前,會有一段語音,然後要求撥打方需要按一個隨機亂數所產生的兩個數字號碼,方能接通至受話端。由於我是初學者,對於網路電話領域才剛踏入,不過最近因為研究所需,必須寫出這個功能,因此不知是否可以請您指導我,將非常感謝您的幫忙。謝謝!

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 wudennis
Time 2008 年 11 月 27 日 at 16:18:07

客氣客氣

Write a comment