Asterisk 20 サンプル設定ファイル 解説 extensions


目次

globals

;
; グローバル設定(変数類)
;
[globals]
;外線発信時の自局番号
MYNUMBER=0312345678
;外線発信時のトランク名
MYTRUNK=outbound-trunk
;PPIセット時のドメイン
PPIDOMAIN=ntt-east.ne.jp
;外線着信させる内線番号(カンマ区切り)
RINGPHONES=201,202

最初のセクション "globals" ではグローバル変数を定義します。Asteriskの変数は明示的にグローバルと宣言されない限り、チャネル依存です。チャネルは呼が発生してから破棄されるまでAsteriskの内部に存在するのでチャネル変数はその呼内だけの "ローカル" となります。言い換えると、ある通話内で使っているローカル変数は別な通話から参照することはできません。
これに対してグローバル変数はAsterisk全体で設定/参照することができます。
グローバル変数を予め定義しておくには "globals" というセクション(コンテキスト)に書いておきます。

default

defaultコンテキストはみなさん無意識にdefaultを使っていますが、defaultである必要はなく、エンドポイントcontext=で定義するものです。これはそのエンドポイントが発信等を行う際にどのコンテキストで処理するかを指定します。つまりエンドポイントでcontext=defaultと設定されていると発信処理はこのコンテキストを使用するということになります。

内線処理部

[default]
;
;内線処理(1~9から始まる番号)
;
;内線はDBで抽象化されているので内線番号はDBに設定する
;例: database put MYPBX/EXT 201 phone1
;この設定では内線201はエンドポイントphone1となる
exten => _Z.,1,NoOp(内線)
;SIPメッセージングの場合の処理
exten => _X.,n,Set(MTARGET=${EXTEN})
exten => _X.,n,GotoIf($["${CHANNEL(name)}"="Message/ast_msg_queue"]?sipmsg,s,1)
;自局情報を取得してセット
exten => _Z.,n,Gosub(sub-whoami,s,1)
;内線番号が割り当たってなければ発信させない
exten => _Z.,n,GotoIf($["${MY_EXTEN}"=""]?nogo)
;内線番号に対応するエンドポイントをDBから取得
exten => _Z.,n,Set(TARGET=${DB(MYPBX/EXT/${EXTEN})})
;PJSIPのdial contactsに変換
;注意:ダイヤル先がREGISTERされていない場合でもCONTACTSは空になる
;(pjsip show contactsの結果を参照)
exten => _Z.,n,NoOp(${PJSIP_DIAL_CONTACTS(${TARGET})})
exten => _Z.,n,Set(TARGET=${PJSIP_DIAL_CONTACTS(${TARGET})})
;ターゲットが空(Dial先のエンドポイントがない)なら終了
exten => _Z.,n,GotoIf($["${TARGET}"=""]?nogo)
;Dialを実行
exten => _Z.,n,Dial(${TARGET})
exten => _Z.,n(nogo),Hangup

まず内線間の通話を行うための処理が書かれています。このダイヤルプランでは『内線はゼロ以外の数字から始まる』番号と定義しています。

exten => _Z.

おっといけません。extenの書き方を説明していませんでした。extenはAsteriskにおけるダイヤルプランそのもののことでエンドポイント、つまりは電話機等からダイヤルされた番号をどう処理するかを書きます。書き方にはいくつかあるのですが筆者はこの書き方が好きです。

exten => 123,1,NoOp(123)
exten => 123,n,Dial(PJSIP/123)

このような書き方をします。123の部分はexten、sまたはnの部分はpriority、NoOpやDialが書かれている部分にはAsteriskのアプリケーションあるはファンクションと呼ばれる処理を書きます。一般的な書式は以下のようになります。

exten => exten,priority,app

extenは同じ処理は同じextenを書きます。priorityは必ず"1"から始めなくてはいけません。そしてその次のpriorityは2,3,4...とひとつづつ増やしますが、"n"を書くと勝手に前のpriority +1と解釈されます。つまり先ほどの例は

exten => 123,1,NoOp(123)
exten => 123,2,Dial(PJSIP/123)

と同じことになっていたわけです。
さてそれでは実際のサンプル設定ファイルに戻りましょう。extenが何やら呪文めいています。

exten => _Z.1,NoOp

priorityは理解してもらえたと思います。extenが謎ですね。これはパターンマッチといって番号の規則に一致するようにextenを書いています。先頭のアンダーバー(_)はパターンマッチを使う、Zは1~9の数字、ドット(.)はそれに続く任意のものという意味です。つまりこのextenは111であれ123であれ2001であれ201であっても一致するパターンということになります。ただし0123にはマッチしません。
内線番号として1~9で始まる番号はすべて内線として扱うという意味になります。
_Z.は1~9で始まるどんな番号にでもマッチするのでダイヤル先として例えば2001を実行すると当然マッチします。このときAsteriskの組み込み変数、EXTENは2001となります。

続いて以下の処理

;SIPメッセージングの場合の処理
exten => _X.,n,Set(MTARGET=${EXTEN})
exten => _X.,n,GotoIf($["${CHANNEL(name)}"="Message/ast_msg_queue"]?sipmsg,s,1)

この部分はSIPを使ったメッセージング用です。SIPのメッセージを受け取った場合にはファンクション、CHANNEL()の'name'エントリが Message/ast_msg_queueになるので、もしその文字列であったならばGoto(GotoIf)でコンテキストがsipmsg、extenがs、プライオリティが1の場所にジャンプします。メッセージング処理については後で解説します。

次の部分はちょっとしたポイントです。

exten => _Z.,n,Gosub(sub-whoami,s,1)
;内線番号が割り当たってなければ発信させない
exten => _Z.,n,GotoIf($["${MY_EXTEN}"=""]?nogo)

このダイヤルプランでの設計は『内線を抽象化』することにあります。Asteriskのサンプル設定でよくあるのはエンドポイントと内線番号をイコールにするものですがこの方法は処理が簡単になる反面、内線番号の付けなおしなどが面倒になります。ある電話機の設定が例えばエンドポイント名2001で内線2001だった場合にこの内線を2010にしたいといった場合にはエンドポイントの設定を2010に修正、つまりは電話機の設定を変更しなくてはいけなくなります。
これに対して抽象化されている内線ではエンドポイント名(設定)は常に同じものとし、内線番号とエンドポイントは『別な』方法で紐付けを行います。
さて、本題に戻りましょう。上記の処理が何をしているかというとGosubによってサブルーチンsub-whoamiを呼び出しています。ここでお気付きかもしれませんが、AsteriskにおけるGotoやGosubは常にAsteriskのextenの書式で行われるため、Gosub(context,exten,priority)のかたちで記述します。要するにプログラミング言語の一種なのですがAsteriskの書式ではジャンプしたり呼び出すにはcontext,exten,priorityのかたちとなるわけです。
sub-whoamiは後で説明しますが、内線が抽象化されていることにより電話機自体は自分の番号を「知りません」このためAsteriskに登録されているエンドポイントと内線番号を参照してエンドポイント名から内線番号を求めるという処理をsub-whoamiが行っています。
sub-whoamiは内線が登録されているとMY_EXTENという変数に内線番号をセットします。もしエンドポイントに対応する内線が登録されていない場合には値はありません。内線番号が登録されていない電話機からは発信させないようにMY_EXTENが空ならば"nogo"にジャンプします。"nogo"って何?と思われたでしょう。この"nogo"は内線処理部の最後の個所

exten => _Z.,n(nogo),Hangup

ここです。プライオリティの個所にカッコ書きで書かれているのはラベルで同一context、exten内であればそのラベルが設定されているpriorityにジャンプします。コンテキストをまたいでジャンプする場合には先ほどのGotoのようにcontext,exten,prioritのかたちでジャンプさせます。


発信側の情報が取得できたので相手先(着信側)にダイヤルさせるようになるのですが、内線を抽象化しているため、内線番号からエンドポイントに変換しなくてはなりません。

;内線番号に対応するエンドポイントをDBから取得
exten => _Z.,n,Set(TARGET=${DB(MYPBX/EXT/${EXTEN})})

内線番号とエンドポイントの紐付けはAstDBで行っています。DBのファミリ MYPBX/EXTのキーが内線番号、値がエンドポイントです。DBファンクションでエンドポイント名を求めます。

;PJSIPのdial contactsに変換
;注意:ダイヤル先がREGISTERされていない場合でもCONTACTSは空になる
;(pjsip show contactsの結果を参照)
exten => _Z.,n,NoOp(${PJSIP_DIAL_CONTACTS(${TARGET})})
exten => _Z.,n,Set(TARGET=${PJSIP_DIAL_CONTACTS(${TARGET})})
;ターゲットが空(Dial先のエンドポイントがない)なら終了
exten => _Z.,n,GotoIf($["${TARGET}"=""]?nogo)

エンドポイントを取得した後はPJSIPの"contacts"を取得します。これはPJSIPでは同一エンドポイントに複数のエンティティ、つまりは電話機がレジストすることができるためで、親子電話のように複数の電話機をひとつのエンドポイントとして扱うことができるためです。エンドポイントがレジストしてきていない場合にはこの結果は空になります。
これでようやくダイヤルすることができます。

exten => _Z.,n,Dial(${TARGET})

内線の処理は以上です。

外線発信部

;
; 外線発信(プレフィクス0)
;
exten => _0.,1,NoOp(外線発信)
exten => _0.,n,Gosub(sub-whoami,s,1)
;内線番号が割り当たってなければ発信させない
exten => _0.,n,GotoIf($["${MY_EXTEN}"=""]?nogo)
exten => _0.,n,Set(CALLERID(num)=${MYNUMBER})
exten => _0.,n,Set(CALLERID(name)=${MYNUMBER})
exten => _0.,n,Dial(PJSIP/${EXTEN:1}@${MYTRUNK},,b(sub-addppi,s,1(${MYNUMBER})))
exten => _0.,n(nogo),Hangup

外線は0発信(0+03-1234-5678)で行えるようにしてあります。ただし、この場合のCIDではなく自局番号(MYNUMBER)に書き換えています。
あわせてPPI(P-Prefferd-Identity)ヘッダをセットします。PJSIPでのヘッダのセットはDila()時のサブルーチンコールで行う必要があります。

sub-whoami

[sub-whoami]
exten => s,1,NoOp(WHOAMI)
exten => s,n,Set(MY_EXTEN=)
;自局のエンドポイントを求める
exten => s,n,Set(MY_ENDP=${CHANNEL(endpoint)})
;エンドポイントから内線番号を得る
exten => s,n,Gosub(sub-endpoint2ext,s,1(${MY_ENDP}))
;内線が当たってない場合にはそのまま復帰
exten => s,n,GotoIf($["${T_EXTEN}"=""]?ret)
exten => s,n,Set(MY_EXTEN=${T_EXTEN})
;CIDを内線番号に設定
exten => s,n,Set(CALLERID(num)=${MY_EXTEN})
exten => s,n,Set(CALLERID(name)=${MY_EXTEN})
exten => s,n(ret),Return

『自分の』情報を求めるサブルーチンです。内線を抽象化しているため、エンドポイント、つまり電話機等は自分の内線番号を『知りません』。このため内線番号を求めるためには、AsteriskのAstDBを調べて自分のエンドポイントに割り当てられている内線番号を調べる必要があります。
サンプルの設定ファイルではsub-whoamiが呼び出されると、MY_EXTENに自分の内線番号がセットされるとともにCID類にも設定します。これにより「呼び出された」内線側には呼び出した内線の情報が通知されることになります。
エンドポイントの名前自体はCHANNEL(endpoint)で求めることができますので、ここからAstDBを調べて割り当てられている内線番号を求めます。この処理を行っているのはsub-endpoint2extです。

sub-endpoint2ext

;
; エンドポイントから内線番号を求める
; ARG1:内線番号
; T_EXTEN:求めた内線番号
;
[sub-endpoint2ext]
exten => s,1,NoOp(エンドポイントから内線)
exten => s,n,Set(T_EXTEN=)
;DB中の要素数をセットする
exten => s,n,Set(KCOUNT=${DB_KEYCOUNT(MYPBX/EXT)})
;DB中のキーをカンマ列挙で取得
exten => s,n,Set(DKEYS=${DB_KEYS(MYPBX/EXT)})
;キー数がゼロなら終了
exten => s,n,GotoIf($["${KCONT}"="0"]?ret)
;DBエントリをループ処理
exten => s,n,While(${KCOUNT}>0)
;カウンタ位置の内線番号を取得
exten => s,n,Set(TMP_EXT=${CUT(DKEYS,\,,${KCOUNT})})
;内線番号を自局と比較する
exten => s,n,GotoIf($["${ARG1}"="${DB(MYPBX/EXT/${TMP_EXT})}"]?match)
;カウンタを-1する
exten => s,n,Set(KCOUNT=${DEC(KCOUNT)})
exten => s,n,EndWhile()
;ここまでループした場合は内線登録なし
exten => s,n,Goto(ret)
;マッチした場合には変数をセット
exten => s,n(match),Set(T_EXTEN=${TMP_EXT})
exten => s,n(ret),Return

エンドポイントから内線番号を求めるルーチンです。ループ処理でDB内で見つかるまで繰り返すため、最悪総ナメとなります。本来この方法はあまり良くはないのですが内線数がそれほど多くなく、かつ、マシンの性能が高ければあまり気になりません。なぜループしなければならないかというと、AstDBには以下のように内線番号と対応するエンドポイントが登録されます。

MYPBX/EXT 201 phone1

この場合、${DB(MYPBX/EXT/201)}の値はphone1となり、内線番号からエンドポイントを引くのには都合が良いのですが、逆にエンドポイントから内線番号を引くことはできません。ですのでループで回してphone1のあるエントリを探し出し、その内線を求めています。
ループ処理しないで解決させる方法は、いわゆる『逆引き』を設けることなのですが、サンプル設定ファイルではそこまでやっていません。内線登録や削除の際に正引きだけ行えばできるようにしているからです。逆引きを設けた場合には内線登録や削除の場合には正逆2つのエントリを処理しなくてはいけません。

MYPBX/EREV phone1 201

このような形で逆引きエントリをつくれば処理は軽くなります。ご自身でチャレンジしてみてください。

sub-addppi

;
; 発信時PPI設定ルーチン
; ARG1 = PPIに設定する番号(自局番号)
; PPIDOMAINにSIPドメインを設定すること
;
[sub-addppi]
exten => s,1,NoOp(AddPPI)
exten => s,n,Set(PJSIP_HEADER(add,P-Preferred-Identity)=<sip:${ARG1}@${PPIDOMAIN}>)
exten => s,n,Return

外線発信時にPPIヘッダを追加するサブルーチンです。PJSIPではDial()からコールされるサブルーチンでヘッダを設定する必要があります。

外線からの着信

そろそろ書くのも面倒になってきたので読んでみてください。
外線からの着信は別なcontextにしています。トランクのエンドポイントcontextをここにすることで着信させます。着信方法は「どんな番号でも」落とせるキャッチオールにしてあります。なのでextenは_X.となっています。
処理が少々長いのは複数の内線に同時に落とせるようにしているためで、着信先のエンドポイントをPSJIP/phone1のような形で取得したものと PJSIP/phone1&PJSIP/phone2のように&で結合しています。Dial時に頭1文字の削っているのは先頭に余計な&が付くので、これを除去するためです。

;
; 外線からの着信
; 注:トランクを設定する場合incoming contextに着信させ番号で落とすこと
;
[incoming]
;キャッチオール(どんな番号でも)する
exten => _X.,1,NoOp(外線着信)
;リストの位置カウンタを1に,リスト初期化
exten => _X.,n,Set(CNT=1)
exten => _X.,n,Set(D_LIST=)
;セーフティ:一定以上ループさせない
exten => _X.,n,While(CNT<33)
exten => _X.,n,Set(TMP_EXT=${CUT(RINGPHONES,\,,${CNT})})
;要素がなくなったらチェックへ
exten => _X.,n,GotoIf($["${TMP_EXT}"=""]?gochk)
;要素がある場合エンドポイント取得
exten => _X.,n,Set(TMP_ENDP=${DB(MYPBX/EXT/${TMP_EXT})})
;エンドポイントが存在しない場合にはループ継続へ
exten => _X.,n,GotoIf($["${TMP_ENDP}"=""]?cont)
;エンドポンとをPJSIPのDIAL CONTACTへ変換
exten => _X.,n,Set(TMP_ENDP=${PJSIP_DIAL_CONTACTS(${TMP_ENDP})})
;DIAL CONTACTがない場合にはループ継続へ
exten => _X.,n,GotoIf($["${TMP_ENDP}"=""]?cont)
;ダイヤルするリストに結合
exten => _X.,n,Set(D_LIST=${D_LIST}&${TMP_ENDP})
;カウンタをインクリメントして継続
exten => _X.,n(cont),Set(CNT=${INC(CNT)})
exten => _X.,n,EndWhile
;リストが空だったらダイヤルしない
exten => _X.,n(gochk),GotoIf($["${D_LIST}"=""]?nogo)
;リストの頭1文字は&が余計についているので削ってダイヤル.テクノロジ(PJSIP/)はすでに付いている
exten => _X.,n,Dial(${D_LIST:1})
exten => _X.,n(nogo),Hangup

SIPメッセージング

;
; SIPメッセージング処理
;
[sipmsg]
exten => s,1,NoOp(メッセージング)
exten => s,n,NoOp(${MESSAGE(from)})
;Toの中身取り出し
exten => s,n,Set(MSGTO=${MESSAGE(to)})
exten => s,n,Set(DEST=${CUT(MSGTO,@,2)})
;エンドポイントから内線番号を求める
;Fromは内線番号を表示させるのでまずエンドポイント取得
exten => s,n,Set(MSGFRM=${CUT(MESSAGE(from),:,2)})
exten => s,n,Set(MSGFRM=${CUT(MSGFRM,@,1)})
;エンドポイントから内線を求める
exten => s,n,Gosub(sub-endpoint2ext,s,1(${MSGFRM}))
;Fromを内線番号でセット
exten => s,n,Set(MFROM=${T_EXTEN})
;変数で引き渡された送信先を取得
exten => s,n,Set(D_EXT=${MTARGET})
;MESSAGE(to)を内線番号ではなくエンドポイントにセットしなおす
exten => s,n,Set(D_ENDP=${DB(MYPBX/EXT/${D_EXT})})
;SIPメッセージングで送信
exten => s,n,MessageSend(pjsip:${D_ENDP},${MFROM},sip:${D_ENDP}@${DEST})
;送ったら切断
exten => s,n,Hangup

内線発信部の最初で処理したものです。ここへジャンプすることでSIPによるメッセージング機能をサポートします。ブラウザフォンでメッセージの送受ができると便利なので付けています。要はチャットですよね。
ダイヤルして通話を成立させるのではなく、相手のエンドポイントを求めてMessageSend()でテキストメッセージを送信します。