2016/02/04

Asterisk : The Cookbook 食譜 031 - 巨集 Macros + GoSub

Q031: 如果我們公司有數百個帳號要設定,某些條件要採用相同的設定,有沒有不要單純採用複製貼上而是比較快速的方法撰寫?

是的,就像程式語言中的自訂函數,或是試算表中的自訂巨集,Asterisk 也可以自訂巨集及函數。

[巨集]

我也不往複雜的說,簡單說 Macro 非常像『簡化版的 Context』,至於 Context 可以回頭找找 Q025。一樣,我們從實例看起:

[macro-voicemail]
  exten => s,1,NoOp()
    same => n,Dial(${ARG1},10)
    same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail)
    same => n(unavail),VoiceMail(${MACRO_EXTEN}@default,u)
    same => n,Hangup()
    same => n(busy),VoiceMail(${MACRO_EXTEN}@default,b)
    same => n,Hangup()

  1. 名稱一定要以 macro- 開頭
  2. 因為定義巨集時並不知道分機號碼,這邊採用 s 來代表呼叫者的分機號碼
  3. 底下介紹四個巨集內可以使用的變數:
    ${MACRO_CONTEXT}   呼叫者的 Context
    ${MACRO_EXTEN} 呼叫者的分機號
    ${MACRO_PRIORITY} 呼叫者所在的權值
    ${ARGn} 傳入的第 n 個參數,從 1 開始,例如 ${ARG1}, ${ARG2}
至於引用時很簡單:
exten => 101,1,Macro(voicemail,${JOHN})
exten => 102,1,Macro(voicemail,${JANE})
exten => 103,1,Macro(voicemail,${JACK})

比較一下巨集命名與呼叫時的引用名稱,參數,變數。
在巨集呼叫中的 ARGn, MACRO_EXTEN 會被取代成
  101, JOHN
  102, JANE
  103, JACK
請仔細閱讀這幾個呼叫範例就會明白巨集呼叫時參數用法,以及上面四個變數的用法

上面範例還有一個變形,可以比較用法的差異:
[macro-voicemail]
  exten => s,1,NoOp()
    same => n,Dial(${ARG1},20)
    same => n,Goto(s-${DIALSTATUS},1)
  exten => s-NOANSWER,1,VoiceMail(${MACRO_EXTEN}@default,u)
    same => n,Goto(incoming,s,1)
  exten => s-BUSY,1,VoiceMail(${MACRO_EXTEN}@default,b)
    same => n,Goto(incoming,s,1)
  exten => _s-.,1,NoOp()
    same => n,Goto(s-NOANSWER,1)
上例 Goto(s-XXX) 這樣的語法相當容易理解,把它想成字串就好,這樣的寫法應該更容易閱讀(如果沒有就用前一種寫法嘛)。Dial() 的傳回狀態有幾種:
ANSWER, BUSY, NOANSWER, CANCEL, CONGESTION, CHANUNAVAIL, DONTCALL, TORTURE, INVALIDARGS

如果仔細研讀上面的範例,其實可以修改成如下,讀不懂也沒關係喔,戲法人人會變,只要能正確作動,其實沒有什麼美醜好壞之別:
[macro-voicemail]
  exten => s,1,NoOp()
    same => n,Dial(${ARG1},20)
    same => n,VoiceMail(${MACRO_EXTEN}@default,${IF($[${DIALSTATUS}
= BUSY]?b:u)})

這邊沒有說到的內容是,像 Dial() 本身可以相當複雜,也可以引用巨集,有機會再另外說明 Dial() 時再說(應該沒體力寫了)

[函數]

函數看起來很像巨集,一樣直接看範例,就用上面最後一個簡化版本:

[subVoicemail]
  exten => start,1,NoOp()
    same => n,Dial(${ARG1},20)
    same => n,VoiceMail(${ARG2}@default,${IF($[${DIALSTATUS}
= BUSY]?b:u)})

有兩點不一樣:
  1. [macro-voicemail] 變成 [subVoicemail]
    GoSub() 的自訂函數其實沒有像巨集那樣的用法,比較像 Context,後面呼叫會看到,這邊的寫法是讓我們自己濾
  2. 分機號碼由 s 變成 start
  3. 巨集變數都不能用,只能取得參數 ${ARGn} 而已
  4. 不像巨集會自動返回呼叫者,GoSub() 必須自行指定是否返回,否則執行完函數就結束了
呼叫範例:
exten => 101,1,GoSub(subVoicemail,start,1(${JOHN},${EXTEN}))
exten => 102,1,GoSub(subVoicemail,start,1(${JANE},${EXTEN}))
exten => 103,1,GoSub(subVoicemail,start,1(${JACK},${EXTEN}))

為了說明返回,我們以底下的例子來說明:
[subDialer]
  exten => start,1,NoOp()
    same => n,Dial(${ARG1},${ARG2})
    same => n,Return()
[subVoicemail]
  exten => start,1,NoOp()
    same => n,VoiceMail(${ARG1}@${ARG2},${ARG3})
    same => n,Hangup()
呼叫時如下:
exten => 101,1,NoOp()
  same => n,GoSub(subDialer,start,1(${JOHN},30))
  same => n,GoSub(subVoicemail,start,1(${EXTEN},default,u))
如果自訂函數都沒有寫最後一行的 Return(), 那麼呼叫範例並不會執行最後一行的 GoSub(subVoicemail.....)
不過上例並沒有很好的把 Dial() 狀態傳回,底下是修改版本,而且 [subVoicemail] 以這個例子也不必 Return():

[subDialer]
  exten => start,1,NoOp()
    same => n,Dial(${ARG1},${ARG2})
    same => n,Return(${DIALSTATUS})
[subVoicemail]
  exten => start,1,NoOp()
    same => n,VoiceMail(${ARG1}@${ARG2},${ARG3})
    same => n,Hangup()
呼叫例使用 ${GOSUB_RETVAL} 來取得傳回值:

exten => 101,1,NoOp()
  same => n,GoSub(subDialer,start,1(${JOHN},30))
  same => n,Set(VoicemailMessage=${IF($[${GOSUB_RETVAL} = BUSY]?b:u)})
  same => n,GoSub(subVoicemail,start,1(${EXTEN},default,${VoicemailMessage}))

0 意見: