Asterisk Gateway Interace (AGI)

April 29, 2010
By

Asterisk 提供了名為 Asterisk Gateway Interface (以下簡稱為 AGI) 的功能,開發者可能以透過 Dialplan 以外的工具或程式 (E.g. Perl, Shell Script,C++, Java, PHP, Mono, etc….)來開發Asterisk的應用程序,控制Asterisk 的 telephony channel,包括Play music,讀寫DTMF等等。

AGI 是一個獨立程式,Asterisk 會經由Dialplan 調用 AGI() 這一程式來執行AGI。它執行後便會用 stdin和 stdout 來控制Asterisk。直至那 channel 收線為止。

AGI 在Asterisk 下,會根據本身的用途和行為特徵分了四種類型:

  1. AGI – AGI 應用程序是與Asterisk 一同在本機內執行。
  2. EAGI – 有別於 AGI,它除了用 stdin 和 stdout 外,它還會把   音訊輸出到 File descriptor 3。其他應用程式可以用它的File descriptor 3來搜取這channel 的音訊。
  3. FastAGI – 用 TCP 和 port 4537 來連接 AGI。因此這樣AGI 程式是可   以安裝在網路上不同時的機器上。
  4. DeadAGI – 這特別的AGI 只有在channel 掛線後才會起作用。

可參考以下的網址進一步認識 AGI
http://www.voip-info.org/wiki-Asterisk+AGI

以下網址除了有AGI的語法外,還有各種範例提供
http://home.cogeco.ca/~camstuff/agi.html

Fast AGI 的實例

上月,我幫建設了一個基於Asterisk的電話系統。因為這電話系統的需求是:

  1. 平衡負載 – 這系統有多部 Asterisk 同一時間接聽電話,和收發傳真。倘若有其中一部出現問題時,其他 Asterisk 也可以在那時間分擔任務。
  2. 我朋友的公司是從事電話服務系統相關的業務。因為那電話系統的流量極大,如果程式要在Asterisk Box 裏運行,那會加重Asterisk 的負擔。而且朋友想簡化系操作和維護程序,所以我自行開發了一個 Fast AGI Server (Application Server) 在某一伺服器上獨立執行。所有Asterisk Box 將會用Fast AGI 連接這台伺服器。因此,所有Call Flow,Business Logic,和其他與電話底層不相關的模組會全部由 Fast AGI Server 負責。而Asterisk 只負責電話底層的任務。

我們自行設計的 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包裝起來,以下是一小段例子:

 JSBool CCallFlow::Script_PlayMessage(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
 CString strMsg;
 JSBool ret = JS_FALSE;
 unsigned long scriptContextNo = 0;
 CCallFlow* lpThis = NULL;

 scriptContextNo = (unsigned long)cx;
 lpThis = (*mapScriptContext)[scriptContextNo];

 if(lpThis != NULL){
  strMsg.Format(”CCallFlow::Script_PlayMessage : Called”);
  lpThis->WriteSysLog(DDEBUG_LEVEL, strMsg);

  if(argc == 2){
   char strCmd[MAX_PATH];
   char strRet[MAX_PATH];
   char* strVoxFile;
   char* strDigit;

   ZeroMemory(strCmd, MAX_PATH);
   ZeroMemory(strRet, MAX_PATH);

   if(JSVAL_IS_STRING(argv[0])){
    strVoxFile = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]));
    if(JSVAL_IS_STRING(argv[1])){
     strDigit = JS_GetStringBytes(JSVAL_TO_STRING(argv[1]));
     if(strlen(strDigit) <= 0){
      strDigit = “”"”;
     }
     wsprintf(strCmd, “STREAM FILE %s %sn”, strVoxFile, strDigit);
     lpThis->ProcessAGI(strCmd, strRet);
     Sleep(200);
     if(strlen(strRet) <= 0){
      sprintf(strRet, “%d”, 0);
     }

     JSString* str = JS_NewStringCopyZ(cx, lpThis->mapKeyPad[strRet].c_str());
     if(!str){
      ret = JS_FALSE;
     }else{
      rval[0] = STRING_TO_JSVAL(str);
      ret = JS_TRUE;
     }

    }else{
     strMsg.Format(”CCallFlow::Script_PlayMessage : Invalid digits”);
     lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
    }
  }else{
    strMsg.Format(”CCallFlow::Script_PlayMessage : Invalid file”);
    lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
   }
  }else{
   strMsg.Format(”CScriptHandler::Script_PlayMessage : parameters not match”);
   lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
  }
 }else{
  strMsg.Format(”CScriptHandler::Script_PlayMessage[%d] : invalid script object.”, lpThis->label);
  lpThis->WriteSysLog(ERROR_LEVEL, strMsg);
 }
 return ret;
 }

大家會看到第三十和三十一行,在 JavaScript 眼裏看到是否一個 PlayMessage 的JavaScript Function,
但這PlayMessage 裏是調用了 STREAM FILE 這個 AGI Function,而同時整合了一串參數(這裡是一個
wave file name),然後調用 ProcessAGI()。ProcessAGI()只是很簡單地把這AGI Function 經
Socket 傳遞到Asterisk 中。

 BOOL CCallFlow::ProcessAGI(char* strAGICmd, char* strRet){
 BOOL ret = FALSE;
 char _strRet[MAX_PATH];
 char _strRet2[MAX_PATH];

 if(sck != NULL){
  cout << strAGICmd << endl;
  ZeroMemory(_strRet, MAX_PATH);
  sck->Send((const char*)strAGICmd, strlen(strAGICmd));
  int len = sck->Receive(_strRet, MAX_PATH);
  if(len > 0){
   mapAGIResult.clear();
   if(strncmp(_strRet, “200 “, replyOffset) == 0){

    ZeroMemory(_strRet2, MAX_PATH);
    strncpy(_strRet2, _strRet + replyOffset, len - replyOffset);

    ParseCommand2(_strRet2, ‘ ‘, &mapAGIResult);

    strcpy(strRet, mapAGIResult["result"].c_str());
    ret = TRUE;
   }else{
    mapAGIResult["return_error"] = _strRet;
   }
  }
 }
 return ret;
 }

其實,關於 SpiderMonkey 的 Document 不多,很多 SpiderMonkey 的功能是透過以下網址所得的資訊然後再推敲出來的。希望這樣篇文章可以幫到大家。

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

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

29 Responses to “ Asterisk Gateway Interace (AGI) ”

  1. Roy on May 2, 2010 at 1:01 PM

    不好意思我目前在研究所研究VoIP領域,想請教一下專家要如何把我們所撰寫的程式放進Elastix系統裡作聯繫來取的通話資訊,因為我要做的實驗是要把一個分類演算法與Elastix Server作聯繫來取的通話資訊,之前研究一下是要透過AMI端口來取得,但目前小弟不知AMI是在哪哩,也不知道要怎麼把程式放進系統作聯繫(因為Elastix安裝完畢是文字介面),請專家救救我><

  2. admin on May 3, 2010 at 9:23 AM

    AMI 是 Asterisk Manager Interface (即是 Fast AGI), 我記得如果 fresh install 的 Asterisk, 這設定是關閉的。你可以用 vi 打開 /etc/asterisk/manager.conf 看看 AMI 是否有開啟。

    [general]
    enabled = yes << 把這裡設定為 yes
    ;webenabled = yes
    port = 5038

  3. Roy on May 3, 2010 at 8:03 PM

    謝謝你寶貴的意見,那程式要怎麼放入系統呢??要怎麼連結到AMI呢(工具??)

  4. admin on May 4, 2010 at 9:17 AM

    如果你用 Java 開發的話,可以用 Asterisk Java,除了 FastAGI 外,她也有提供了 ManagerAPI 經 AMI 控制 Asterisk。應該合乎你的要求。

    http://asterisk-java.org/
    http://asterisk-java.org/0.2/tutorial.html

    用 .Net 或 Mono 的,也有 Asterisk.Net

    http://sourceforge.net/projects/asterisk-dotnet/

  5. Roy on May 6, 2010 at 11:03 AM

    謝謝你題共這些網頁對我幫助很大,
    那如果java不熟的話可以利用c或者c#嗎??

  6. admin on May 6, 2010 at 5:14 PM

    Yes, you can find most resources in Internet.

  7. admin on May 9, 2010 at 10:20 PM

    看看有沒有合作空間

  8. Roy on May 13, 2010 at 4:53 PM

    哈謝謝你的關照,那想問一下專家該怎麼去Server裡的資料庫抓取通話紀錄呢?

  9. Roy on May 17, 2010 at 10:07 AM

    想請教一下專家們,小弟現在已成功經由Java連到AMI,那現在遇到一個問題:測試許久都無法擷取系統裡的通話紀錄,想請問是否有範例程式能讓Server裡通話紀錄(CDREvents)自動傳送到本電腦嗎?拜託各位專家們幫忙><

  10. admin on May 17, 2010 at 10:43 PM

    對不起,我現在在旅遊中,要回港才能詳細回覆

  11. Roy on May 17, 2010 at 11:25 PM

    沒關係!!先好好放鬆一下吧^^

  12. admin on May 22, 2010 at 12:59 AM

    AMI 是不會傳回 CDR Event 的。Asterisk可以把CDR設定為記錄在 MySQL 中,然後你可以經由 JDBC搜取 CDR。

    可參考:
    http://www.voip-info.org/wiki/view/Asterisk+cdr+mysql

  13. Roy on May 24, 2010 at 11:17 AM

    哈你回來了啊!!我最近有利用JAVA還有C#(Asterisk NET)連進AMI,
    但C#連進去後,ELASTIX網頁裡的EXTENSION按進去
    是空白的,那也不能打電話了超奇怪><,那用JAVA就蠻順利的,
    但遇到一個問題是我研究需要即時性所以需要一個範例程式(API)能自動讓通話紀錄傳送到CLIENT,那次要選擇可以到MySQL取得CDR,不過可能就沒那麼即時性了,那想問一下專家一些有關範例程式能參考^^

  14. Roy on May 28, 2010 at 2:48 PM

    那如果是直接去MYSQL抓取CDR的話,因為我是用Elastix系統,那還要載Asterisk-addons嗎??

  15. admin on June 9, 2010 at 3:37 PM

    不需要。你只用 MySQL 的 JDBC 或 ODBC 連接便可

  16. Roy on June 24, 2010 at 2:25 PM

    那想請問你是否知道有提供通話紀錄的機購或公司,因為我研究上需要
    通話紀錄(call-id、duration…..等),那也想問你是否知道可以產生正常流量或垃圾語音流量的工具!!謝謝~><

  17. admin on June 29, 2010 at 11:42 PM

    >那想請問你是否知道有提供通話紀錄的機購或公司
    不太明白意思,可否詳細一點呢?

  18. Roy on June 30, 2010 at 2:06 PM

    不好意思,沒說清楚~我的意思是因為我需要像一筆CDR通話紀錄(call-id、duration、ip…等)來作實驗,所以想問您知不知道有哪裡可以提供下載或公司可以提供呢??

  19. admin on July 1, 2010 at 11:28 AM

    沒有的。

  20. Roy on July 2, 2010 at 12:00 PM

    說的也是><,謝謝你捏一直煩你~~

  21. admin on July 3, 2010 at 1:34 AM

    客氣客氣~

  22. Roy on July 5, 2010 at 12:14 PM

    因為研究上有些問題沒辦法解決才跟您請教,希望未來能跟你一起共事XD

  23. Roy on July 22, 2010 at 5:02 PM

    不好意思我想請問一下我用一個軟體產生出.call file ,但要如何利用elastix發送出去呢??

  24. admin on August 3, 2010 at 5:25 PM

    把你的 .call file 上載到 /var/spool/asterisk/outgoing 中。

  25. Roy on August 9, 2010 at 2:13 PM

    那放到裡面去之後就會自動撥打嗎??還是還需要哪些動作呢??

  26. admin on August 10, 2010 at 10:28 PM

    Yes.會自動撥打

  27. Roy on August 11, 2010 at 12:45 PM

    那請問一下!!專家知道asterisk權限密碼嗎??

  28. admin on August 11, 2010 at 4:31 PM

    asterisk的什麼權限密碼呢?

  29. Roy on August 12, 2010 at 1:15 PM

    因為我要播.CALL檔關係,但因為我們是ROOT權限好像沒辦法撥!要以使用者asterisk權限才行,但需要密碼才能換權限,所以才想請問密碼是什麼呢><

Leave a Reply

合作伙伴




Slideshow