Asterisk検証まとめ

Asterisk検証めも
検証が進むにつれ追記していきます。たぶん。

# 環境

| OS                |Asterisk       |AGI Library   |Python     |
|:-----------------:|:-------------:|:------------:|:---------:|
| CentOS 7.3        |Asterisk 15.3  |pyst2         |Python 3.4 |

***pyst2***
https://github.com/rdegges/pyst2


# 構築
## 事前準備
必要パッケージインストール

```
# yum update
# yum install make wget openssl-devel ncurses-devel newt-devel libxml2-devel kernel-devel gcc gcc-c++ sqlite-devel libuuid-devel json-glib json-glib.i686 json-glib-devel.x86_64 jansson-devel.x86_64
# reboot
```

公式から以下のファイルをダウンロード
※今回はアナログ回線を接続しないのでdadhiとlibpriは不要

```
asterisk-15-current.tar.gz
```

Githubから以下をダウンロード（pjsip）
https://raw.githubusercontent.com/asterisk/third-party/master/pjproject/2.7.1/pjproject-2.7.1.tar.bz2
https://raw.githubusercontent.com/asterisk/third-party/master/pjproject/2.7.1/MD5SUM.TXT

```
// 解凍
# tar zxvf asterisk-15-current.tar.gz

// 本体配置
# mv asterisk-15.3.0 /usr/local/src/


// ローカルインストール用ディレクトリ作成
# mkdir /tmp/downloads

// pjsip 配置
# mv pjproject-2.7.1.tar.bz2 /tmp/downloads/
# mv MD5SUM.TXT /tmp/downloads/pjproject-2.7.1.md5

// fax用モジュールインストール
# tar zxvf spandsp-20180108.tar.gz
# cd spandsp-0.0.6
# ./configure
# make
# make install

//サウンドファイル配置 (使いたいファイルだけセットで落としてくる)
# mv asterisk-core-sounds-en-wav-1.6.1.tar.gz /tmp/downloads/
# mv asterisk-core-sounds-en-wav-1.6.1.tar.gz.sha1 /tmp/downloads/
```

## インストール
```
// Asteriskインストール
# cd /usr/local/src/asterisk-15.3.0
# ./configure --with-download-cache=/tmp/downloads
# make menuselect

※他に必要なモジュールがあればチェックを付ける（mp3やFax等）
// faxモジュール
[*] res_fax
[*] res_fax_spandsp

// mp3モジュール
[*] format_mp3

# make
# make install
# make samples
# make config

# systemctl start asterisk
# systemctl status asterisk
# systemctl enable asterisk
```

## 設定
基本的にはインストールで実施した手順でサンプルのコンフィグファイルが生成されているはずですので、
該当部分をコメントアウトすればOKだと思います。

### API設定

AsteriskのRestfulAPIをオン

```/etc/asterisk/ari.conf
enabled=yes
pretty=yes

[asterisk]
type=user
read_only=no
password=パスワード
```

```/etc/asterisk/http.conf
[general]
enable=yes
bindaddr=0.0.0.0
bindport=8088
```

言語設定（任意、デフォルトの音声データ参照先ディレクトリが変わります）

```/etc/asterisk/asterisk.conf
defaultlanguage=ja
```

###ログ出力設定

デフォルトだといくつかログ出力のレベルが低いので設定します

```/etc/asterisk/logger.conf
console => notice,warning,error,debug,dtmf,verbose,security,fax
messages => notice,warning,error,debug,dtmf,verbose,security,fax
```

## 外線設定（楽天コミュニケーションズ IP電話 B2BUA OpenGate サービス）
今回楽コムのOpenGateサービスにしたのは主に以下の理由です。
1. 支払方法・費用
2. Asteriskの動作実績

1についてですが、これはうちの会社だと開発の部署で法人名義のクレカが使えなかったためです。
そのため第一候補に挙がっていたtwilioに請求書払いが可能かどうか問い合わせたところ、
書類手続きに月5000円かかるとのことなのでこちらになりました。

第2候補ではありましたが、細かい質問に対しても割とレスポンスが早く、開発部署と連携して回答頂いたので個人的に楽コムは好印象でした。

ただ法人としての契約の場合、実印や印鑑証明書や履歴事項全部証明書を求められるので・・・・
所属しているのが大企業の場合は素直にTwilioをおすすめします。
（手続きや申請が苦にならないならどちらでも良いと思いますが）

```sip.conf
[general]
maxexpirey=3600
defaultexpirey=3600
srvlookup=no
allowguest=no

port=5060
bindaddr=aaaa.aaaa.aaaa.aaaa
transport=udp

rtptimeout=60
rtpholdtimeout=300
session-timers=refuse
session-expires=3600
session-refresher=uas

localnet=xxxx.xxxx.xxxx.xxxx/255.255.255.255(サブネット)
externaddr=yyyy.yyyy.yyyy.yyyy
register=ユーザID:パスワード@sipプロキシ/test(設定名）

[test]
type=frined
username=ユーザID
fromuser=ユーザID
secret=パスワード
host=sipプロキシ
fromdomain=okj.sip.0038.net
insecure=port,invite
context=コンテキスト名
```
※セッション周りの設定はいらないかも・・・

# 発信
Asteriskでの発信方法は概ね以下の3つになる。

1. call fileの作成
2. channels API（ARI）の利用
3. SIPクライアントからの直接発信

## ARI と AGI
AsteriskでのREST APIは **ARI (Asterisk REST Interface)**という名前でまとめられていて、
逆にローカルで直接Asteriskを操作するようなインターフェースは
**AGI (Asterisk Gateway Interface)**を利用します。

*ARI*
https://wiki.asterisk.org/wiki/pages/viewpage.action?pageId=29395573

*AGI*
https://wiki.asterisk.org/wiki/pages/viewpage.action?pageId=32375589

## channles API
まず今回主に利用するchannles APIですが、書式は以下です。

***API***

| method           | URI                   |   optional         |
|:----------------:|:---------------------:|:------------------:|
| POST             | /channels/{channle_id}|  channel_id        |

***パラメータ***

| param            | required               |type|   description         |
|:----------------:|:----------------------:|:---:|:------------------:|
| endpoint         | true                   | string | 宛先              |
| extension        | exclusive              | string | extensions.confのextension  |
| context          | exclusive              | string | extensions.confのconext     |
| callerId         | false                  | string | 発信元            |
| timeout          | false                  | int | タイムアウト(sec)            |

その他のオプションは今回あまり使わないので知りたい場合はリファレンスを参照してください

## API経由での発信
SIPクライアント同士がAsteriskを経由して通話を行うのは比較的簡単です。
しかしAsteriskのAPIをトリガーに複雑な制御を行った発信を行うにはいくつか注意が必要です。

例えば、APIリクエスト時には発信先を指定せずリクエストに載せた追加パラメータに沿って複数に発信したい場合などです。
何故ならAsteriskでは実在する（レジストされた）SIPデバイスしかendpointに指定出来ないので、
リクエスト時に発信先を必ず指定する必要があるからです。
つまりこの方法でやるとなると、通常は代表番号のようなものを用意して必ず同じ番号を1つendpointに設定することになります。

### ローカルチャンネル
上記の問題を解決するのがローカルチャンネルです。
ローカルチャンネルはAsterisk内部に仮想endpointを生成し、そこを経由することにより実デバイス同士が通信しているように見せかけます。

***Asterisk ====> 仮想エンドポイント ====> ブリッジ ====> 宛先***

以下のようにextension.confを設定し、リクエストに所定のパラメータを設定するとtalkコンテキストに飛んで「ハロー」という音声が流れるはずです。

あとはcallコンテキストの部分をスクリプトで置き換え、やりたいことに合わせてスクリプトで発信の制御を行えば順番に発信したり一斉に発信したりすることが可能です。

``` extension.conf

[call]
exten => s, 1, Call(SIP/111)

[talk]
exten => s, 1, Answer()
exten => s, n, Playback(hello)
exten => s, n, Hangup()
```

```python:ari.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import requests
import json


class ARI: 

    def __init__(self, *, user='asterisk', password='asterisk', ip_addr='localhost', port='8088'):
        self.user = user
        self.password = password
        self.port = port
        self.base_url = 'http://{user}:{password}@{ip_addr}:{port}/ari/'.format(
            user=user, password=password, ip_addr=ip_addr, port=port    
        )

    def call(self, params, data, headers):
        api = "channels"
        url = self.base_url + api
        print("URL:" + url)
        print(json.dumps(data))
        response = requests.post(url, params=params, data=json.dumps(data), headers=headers)
        return response


def main():
    asterisk = ARI(user="asterisk", password="asterisk")

    headers = {'content-type':'application/json'}
    call_params = {
        "endpoint":"Local/s@call/n",
        "extension":"s",
        "context":"talk",
        "priority":"1",
        "timeout":"10",
    }
    data = {"variables": {"call_list":"[111,222]"}}
    print(asterisk.call(call_params, data, headers).text)

if __name__ == '__main__':
    main()

```


## CallFileでの発信

## 発信時のステータス遷移

発信時にはAsterisk内部でchannelが生成され、クライアント側・サーバ側の動作によりこのchannel内部のchannelStatusが変化する。
現在のチャンネルの状態はこのchannelStatusを確認することで判断することが出来る。
この値は**extensions.conf**であれば**${DIALSTAUTS}**で取得可能。

なお、帰ってくる値は対向のSIP Gatewayによる場合もあるので必ずしもこうなるとは限りません。
例えば楽天経由で050から0120番号へかけるとCONGESTIONが帰ってきます（050からは普通繋がらない）。

|動作                        |ステータス            |
|:--------------------------:|:-------------------:|
|宛先端末への疎通不可          |CHANUNAVAIL          |
|タイムアウト                 |CANCEL               |
|応答拒否                     |BUSY                 |
|応答なし                     |NOANSWER             |
|応答                        |ANSWER               |
|混雑中                       |CONGESTION           |

https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+Application_Dial
https://www.voip-info.org/asterisk-variable-dialstatus/

リファレンス等を見ると他にも色々ありますが、よく出てくるのは上記6つ程度かと思います。