2016/02/03

Asterisk : The Cookbook 食譜 025 - 撥號計劃 Dialplan

Q025: 看前面幾條食譜,似乎撥號計劃是最核心的功能,可以進一步說明嗎?

愛因斯坦說過,任何事應該盡量抽絲撥簡,卻不要將之視為簡單(Everything should be made as simple as possible, but not simpler.)。這句話用來理解相對論似乎就說得通了,當我們看狹義相對論的時候,看似相當容易理解,可是背後的事情卻是相當深遠地影響全世界。

同樣的 Asterisk 也是如此。Asterisk 最核心的撥號計劃相當容易理解,能做到的事卻相當廣泛。簡單講,撥號計劃定義了系統如何處理一個撥號接入與轉出。根據這麼簡單的理解,在 Asterisk 前後的版本發展也容入了不同的草稿語法。話說回來,又因為簡單又有彈性的草稿語法,使得 Asterisk 對所有撥號可以客製化一個複雜的計劃(流程)。我可以大膽的聲稱,Asterisk 的能力取決於你對撥號計劃的了解多寡。

撥號計劃(extensions.conf)分成四個主要部份:Contexts, Extensions, Priorities, 及 Applications. 如果您從前一則食譜編譯 Asterisk 的話,範例應該是在 ~/pbx/asterisk-11.21.0/configs/extensions.conf.sample

[Contexts]

我們先以前則提到的內容來說,摘出一段如下:
[internal]
exten => 7001,1,Answer()
exten => 7001,2,Dial(SIP/7001,60)
exten => 7001,3,Playback(vm-nobodyavail)
exten => 7001,4,VoiceMail(7001@main)
exten => 7001,5,Hangup()

撥號計劃分成多個節,每一節稱之為一個 Context, 每一個 Context 看似獨立,卻是整個撥號計劃的一環,彼此互相連結而非單獨存在(就像眼耳手腳)。而 extension 翻譯成中文可以說是分機號碼或是電話號碼,在某一 Context 中定義時除非特別指明(引用),否則是與其他 Context 獨立無關的(也就是在別的 Context 中重複定義也沒關係)。

想像一下有兩個公司共用同一個 Asterisk 服務器(這很常見),假如將每一個公司的自動話務員放在各自的 Context 中,此時他們當然就各自獨立不互相千擾對方,也就是說各自公司都有相同的分機號碼是彼此不會干擾對方的。

有兩個特別的 Contexts: [general] 及 [globals]。[general] 定義一般的撥號計劃的設定,這邊只需要知道這兩個非一般的 Context 即可。

前文也提過「通道」的說法,一個通道不止包括 extensions.conf, 還包括 sip.conf, iax.conf, chan_dahdi.conf 等等等。通道定義中一個必要的參數就叫 context,我們就以圖來表示吧:

圖中 sip.conf 中有一通道 [0000FFFF0001] 定義,其中的 context 指向 extensions.conf 中的 [LocalSets] 這個 Context. 當然這邊的字眼既然採用同一個,能幫助記憶,也有點容易讓人混淆,就靠你自行理解記憶囉。

對 Context 一個最重要的用法其實是『加密』,系統如果沒有很好的加密的話,很有可能就變成駭客入侵最佳的平台。可以參考 Asterisk Wiki中的說明,後面也會提到。

[Extensions]
在電信世界中,分機號碼通常就只是一組號碼,但是在 Asterisk 中,要理解 Extesions 卻必須看成『用來回應一通撥號的一串步驟』(這也是為什麼要叫撥號計劃的原因)。

在撥號計劃中,Extensions 是以關鍵字 exten 來表示,後面接 => 來表達指定之意。另外要提的是,在 Asterisk 的觀念中,Extensions 並非單純的數字,它通常是數字+文字的組合,甚至可以是 email 的格式,這一點讓 Asterisk 更有彈性也讓 Asterisk 更加強大。

一個 Extesion 的每個步驟包括三個部份:分機名稱(或是號碼), 權值(也可以看成真正的順序),及 application(或是命令)。其形式就是 :
exten => name, priority, application()
以最上面顯示的來說,就像 "exten => 7001,1,Answer()"

[Priorities]
其中權值可以視為真正的順序,所以最好是由小到大遞增,這樣也有助於理解。但是因為權值用純粹的數字容易引發問題,就像早期的 basic 語言一樣,要在中間插入一條步驟的話就會有困擾,所以後來引入 n 這個權值,它代表就按出現的順序由上而下承接,要記住的是一定要從 1 開始不能省略。例如:

exten => 7001,1,Answer()
exten => 7001,n,Dial(SIP/7001,60)
exten => 7001,n,Playback(vm-nobodyavail)
exten => 7001,n,VoiceMail(7001@main)
exten => 7001,n,Hangup()

為了簡化撰寫,後來也引入了 same => 記號,上例又可以寫成如下:
exten => 7001,1,Answer()
  same => n,Dial(SIP/7001,60)
  same => n,Playback(vm-nobodyavail)
  same => n,VoiceMail(7001@main)
  same => n,Hangup()
右移內縮只是為了容易閱讀,有沒有內縮不影響設定。

權值還可以給予標記,以便後面引用,就像程式語言中的 goto,這邊暫時不提,只簡單寫出正確與錯誤的寫法:
[正確] exten => 123,n(label),application()
[錯誤] exten => 123,n,(label),application()

[Applications]
Applications 可以視為『命令』或是『動作』,用來完成一個通道的一系列回應動作。最常用的有兩個:Answer(), Hangup()。每個 Application 括號中可以加入參數(有可能沒有參數),請見上面的實例。

Answer() 用來告知通道正在『響鈴中』,不必給予參數,也不見得一定要求有此 application。

Playback() 用來播放一段錄音檔,此時撥號端的用戶輸入聲音會被忽略,所以在自動話務員中不應該使用 Playback()。錄音檔通常可以在 /var/lib/asterisk/sounds 中找到,如果是 Ubuntu 預設套件的話,應該是在 /usr/share/asterisk/sounds 中。上例的 Playback() 只需提供檔名,省略附檔名,也可以指定 full-path 格式,但是仍然省略附檔名。附檔名系統會自動找,因為有可能是 .wav, .g722,  或是 .gsm 等等等

Hangup() 則用來掛斷通道,每個 Context 的最後,應該都以 Hangup() 結尾。


0 意見: