AGI(Asterisk Gateway Interface)を使って内線電話からPythonスクリプトを実行する

## はじめに
この記事は 2020 年の RevComm アドベントカレンダー 4 日目の記事です。
3 日目は @yk_saru さんの「 [SOQLクエリビルダーを使ってみた](https://qiita.com/yk_saru/private/df9108f3a695d6b6c8be "SOQLクエリビルダーを使ってみた")」でした。

こんにちは。株式会社RevCommで通信サーバの開発等を担当している宮崎です。

この記事ではOSSのIP-PBX(回線交換機)である<strong>Asterisk</strong>を用いて、電話機からの<strong>内線発信</strong>をトリガーにしてサーバ内の<strong>Pythonスクリプトを実行</strong>する手順の一例を紹介します。
<strong>「電話をかけてPythonを実行する」</strong> というのはちょっと面白いと思いませんか...？

## 前提
下記の条件で実施しました。

- 電話機からの内線発信でAsteriskの標準機能であるAGIによるPythonスクリプト実行を行うことをゴールとする
- AWSインスタンス上の<strong>Ubuntu 20.04</strong>(AMI: ami-09fdc0371d433b7b7)をPBXサーバとして利用する
- インスタンスのセキュリティグループでUDP 5060ポートを開放する
- 内線発信に利用するSIPクライアントソフトとして、macOSの[Telephoneアプリ](https://apps.apple.com/jp/app/telephone/id406825478?mt=12)を利用する

## 早速やってみましょう
### Asteriskの環境を作る
```shell
# homeディレクトリができるように設定
cat << EOF >> /etc/login.defs
CREATE_HOME        yes
EOF
# asteriskユーザ作成
useradd asterisk
```
まず、Ubuntuにasteriskユーザを作ります。
AGIから実行されるスクリプトはasteriskユーザの権限で実行されます。
また、homeディレクトリはログの出力先として使いたいので作成されるようにしておきます。



```bash
# 必要パッケージのインストール
apt-get update
apt install build-essential libedit-dev uuid-dev libxml2 libxml2-dev libsqlite3-dev
# Asteriskのダウンロード&インストール
cd /usr/local/src
wget http://downloads.asterisk.org/pub/telephony/asterisk/releases/asterisk-16.8.0.tar.gz
tar vxzf asterisk-16.8.0.tar.gz
cd asterisk-16.8.0
./configure --with-jansson-bundled
make && make install && make samples && make config
```

インストールは必要最小限のパラメータのみ指定して実行します。
音声周りの設定は変更しないので、通話等のテストがしたい場合は変更の必要があるかもしれません。


#### confファイルの設定
Asteriskには多数のconfファイルがありますが、今回は重要な2ファイルのみ設定を行います。

```shell:/etc/asterisk/sip.conf
[general]
context=test
port=5060
bindaddr=0.0.0.0
srvlookup=yes
allowguest=no
disallow=all
allow=ulaw
allow=alaw
allow=gsm
language=ja
nat=force_rport,comedia
externip=***.***.***.***  ;サーバのIPアドレス
videosupport=no
[201]
type=friend
defaultuser=201
secret=201pw
canreinvite=no
host=dynamic
directmedia=no
[202]
type=friend
defaultuser=202
secret=202pw
canreinvite=no
host=dynamic
directmedia=no
```

まずはsip.confファイルです。デフォルトの設定内容は削除し上記のように変更します。
電話機にアカウントを付与する設定ファイルで、電話機はこのファイルに記載された内容でPBXサーバと論理接続を行います。
generalセクションは全てのアカウント共通の設定を定義します。
今回は201, 202セクションで内線番号201, 202のアカウントを定義します。


```shell:/etc/asterisk/extensions.conf
[test]
exten => _2XX, 1, Dial(SIP/${EXTEN}) # 2から始まる3桁の番号を受け取ったら、SIPプロトコルで${EXTEN}(発信番号)に発信を行う
exten => _2XX, 2, Hangup() # 通話が完了したらHangupの処理を行う


exten => _3XX, 1, AGI(test_agi.py, ${EXTEN}) # 3から始まる3桁の番号を受け取ったら、引数${EXTEN} を渡してtest_agi.pyを実行
exten => _3XX, 2, Hangup()
```
次に、extensions.confの設定を行います。
これは通話ルールを定義するファイルです。「どの番号にかけたらどんなアクションをするか」を定義しています。
こちらも既に記載された内容を削除して、上記のように設定します。
sip.confのcontextにtestを指定したので今回はtestセクションを作成します。
下から2行目の```AGI(test_agi.py, ${EXTEN})```の箇所が、AGIによるPythonスクリプトの実行を定義しています。


```shell
# Asteriskの起動
asterisk
# asterisk cliに入る
asterisk -vvvvvvvvcr
# asterisk cli内で
sip reload
dialplan reload
```
Asteriskを起動し、設定を反映させます。
これでAsterisk環境構築は完了です。

### Pythonの準備
今回はPython3を使います。
PythonでAsteriskを扱うためのライブラリであるpyst2もインストールしましょう。

```shell
apt -y install python3-pip
pip3 install pyst2
```
  
Asteriskから実行するスクリプトを用意します。

```python:/var/lib/asterisk/agi-bin/test_agi.py
#!/usr/bin/python3
import os
import sys
import datetime
from asterisk.agi import *

agi = AGI()
exten =sys.argv[1] # 引数${EXTEN}を受け取る
caller_id = agi.env['agi_callerid'] # 発信者の番号を受け取る
folder_path = '/home/asterisk/log/' # 出力先フォルダ
os.makedirs(folder_path, exist_ok=True)
file_name = datetime.datetime.now().strftime(folder_path + 'out_%Y%m%d%H%M.log')

with open(file_name, 'w') as f:
    print('exten : ', exten, file=f)
    print('caller_id :', caller_id, file=f)
```
発信者の番号と入力した番号を受け取り、ファイル出力するだけのスクリプトです。



asteriskユーザで実行できるように権限を変更しておきます。

```shell
chmod 755 /var/lib/asterisk/agi-bin/test_agi.py
chown asterisk:asterisk /var/lib/asterisk/agi-bin/test_agi.py
```

これでPythonの準備は完了です。

### Telphoneのセットアップ
[Telephone](https://apps.apple.com/jp/app/telephone/id406825478?mt=12)をインストールし起動します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157023/88f8d233-62d5-0acd-3abe-d2ec0dc2a451.png" width="40%">


SIP Accountsはsip.confの内容をもとに下記の例のように設定します。


| Name    | Value  |
|:-------:|:------:|
|Full Name|201                |
|Domain   | [サーバのIPアドレス] |
|User Name| 201            |
|Password |  201pw          |


これで準備は全て完了です。

## 動かしてみる

それではいよいよ実行してみましょう。
Telephoneで365番に発信してみます。
![スクリーンショット 2020-12-03 4.02.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157023/bba44e01-9d96-e15d-70e5-57ccccf91ea2.png)

設定上受話しないのでcall declinedとなっていますがログは出力されているでしょうか...？

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157023/c95f5609-cc94-2219-45e4-faff8eb3d110.png" width="70%">

バッチリ出ました！うっすらと感動します。


## 最後に
最後まで読んでいただきありがとうございます。
今回ご紹介したのは初歩的な手順のみですが、AsteriskのDialPlanやAGIを用いることで
音声案内で番号入力を受け付けたり、通話音声の解析を行うなどの複雑な処理も可能です。

アドベントカレンダーきっかけの初投稿でしたが、Asteriskの記事はまだ少ないようなので増やしていきたいですね。

明日は @mojaja さんの記事です。