Twilioをつかって電話回線の監視をやってみた

# はじめに
電話回線が正常に利用できるかを監視するために、Twilioを使った方法をお試ししました。
本記事では、TwilioのFunctionsを利用して、監視対象の電話番号に発信を行い、応答結果確認できる仕組みを紹介します。

# 構成
以下の構成で電話回線の監視を行います。
1. 監視サーバからHTTP Requestを送信し、Twilio Functionsを起動
2. Twilioが電話回線に接続された監視対象の電話番号に発信
3. 自動応答装置(IVR)に着信させて、DTMF信号を含む音源を流す
   ※電話機の留守電に音源を録音して代替可能
5. TwilioがDTMFを取得
6. 通話結果を確認して、DTMFの取得結果が正しければ、電話回線が正常であると判断

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3928735/70a3348c-c251-1b8a-b7d6-129f2d8836fe.png)


:::note info
DTMFとは
電話機のボタンを押すことで発せられる「ピ、ポ、パ」という音の信号
:::
単純に通話が繋がった、音が聞こえることの確認の場合、回線の故障によって「おかけになった電話番号は～」など音声ガイダンスが流れた場合に気づけないため、正しく通話ができたことの確認をするために、DTMFを利用しています。


# Twilio Functionsの設定
ここでは、Twilio Functionsを使用して電話回線を監視するためのコードを記述します。

```javascript:/call
'use strict';
exports.handler = function(context, event, callback) {
    const twilioClient = context.getTwilioClient();
    twilioClient.calls.create({
        url: 'https://***.twil.io/get-dtmf.xml', // TwiML URL
        to: '+8152XXXXXXX', // 通話先の電話番号
        from: '+XXXXXXXXXXX' // Twilioの電話番号
    })
    .then(call => callback(null,call.sid))
    .catch(err => callback(err));
};
```
上記は、HTTPリクエストで起動する、指定された番号に電話をかけるFunctionsです。
DTMF入力を取得するためのTwiMLを含むURLを指定します。
```xml:get-dtmf.xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <Gather action="/handle-dtmf" method="POST" timeout="15">
    </Gather>
</Response>
```
上記は、DTMF音を収集するための指示をおこなうTwiMLです。着信側が入力したDTMFを/handle-dtmfエンドポイントにPOSTで送信します。
```javascript:/handle-dtmf
'use strict';
exports.handler = function(context, event, callback) {
    // DTMFの結果を取得します
    const digits = event.Digits;
    // レスポンスを作成します
    const response = {
        "dtmfResult": digits
    };
    // レスポンスを返します
    callback(null, JSON.stringify(response));
};
```
上記は、DTMF入力を受け取り、その結果をJSON形式でレスポンスとして返却するFunctionsです。入力されたDTMFのデータを記録します。

Functonsについては下記を参照ください。

https://www.twilio.com/docs/serverless/functions-assets/functions

DTMFの取得については下記を参考にしました。

https://www.twilio.com/docs/voice/twiml/gather

# 実際に動作させてみる
## 発信動作
Twilio FunctionsにHTTP RequestをPOSTして、発信してみます。
```bash:bash
$ curl -X POST https://***.twil.io/call -H "X-Twilio-Signature: <YOUR_TWILIO_SIGNATURE>"
```

## 結果取得
発信後に、Twilio Console画面にログインしてLogsを確認すると、該当呼にてDTMFが取得できていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3928735/6fc75fcc-43ea-6468-50e4-8014483a02da.png)
ボタン[1]の音が2回のため、"dtmfResult":"11"

自動で通話結果を確認するために、TwilioのAPIを利用したPythonスクリプトでも取得を試してみます。
```python:get_last_dtmf.py
import json
from twilio.rest import Client

# Twilioの認証情報
account_sid = 'YOUR_ACCOUNT_SID'
auth_token = 'YOUR_TOKEN'
client = Client(account_sid, auth_token)

# 最後の通話を取得
calls = client.calls.list(limit=1)
if calls:
    last_call = calls[0]

    # 通話の詳細を取得
    events = client.calls(last_call.sid).events.list()

    # DTMFを抽出
    dtmf_result = None
    for event in events:
        # '/handle-dtmf'のイベントを確認
        if 'handle-dtmf' in event._properties['request']['url']:
            response_body = event.response['response_body']
            try:
                # JSONとしてパース
                parsed_response = json.loads(response_body)
                dtmf_result = parsed_response.get('dtmfResult')
                break
            except json.JSONDecodeError:
                print("JSON parsing error.")

    if dtmf_result:
        call_info = {
            "call_sid": last_call.sid,
            "call_start_time": last_call.start_time.isoformat() if last_call.start_time else None,
            "dtmfResult": dtmf_result
        }
        print(json.dumps(call_info, indent=4))
    else:
        print("No DTMF results found for the last call.")
else:
    print("No calls found.")
```
スクリプトを実行してみます。
```bash:bash
$ python last_dtmf.py
{
    "call_sid": "XXXXXXXXXXXXXXXXXXXXX",
    "call_start_time": "2024-12-XXTXX:XX:XX+XX:XX",
    "dtmfResult": "11"
}
$ 
```
結果が取得できました。

発信操作と結果確認を定期的に監視サーバから実行することで、電話回線の監視をすることができそうです。
Zabbixなどを利用してDTMF取得結果と通話日時を監視すると、通話が繋がらなかった場合に通知ができます。
監視サーバからFunctionsにPOSTしたレスポンスにて、DTMF取得結果を返せたらよかったのですがそれは難しそうでした。

## よいところ
宛先の電話番号があれば、電話回線の種別は問わずに通話の確認ができます。

## いまいちなところ
上記の仕組みでは、着信側から送られてくる音で判断しているので、逆方向（発信→着信）の音が正常に聞こえていたかは確認できません。
これは、Twilioから最初に特定DTMFを送出し、自動応答装置(IVR)はTwilioのDTMFを聴取してからDTMFを流すようにIVRのシナリオを組めれば、双方向での確認ができそうです。

また、Functionsを利用するたびに課金が発生するため、定期実行で監視する場合はそれなりに費用が発生することに注意が必要です。

# おわりに
こちらの記事はctc Advent Calendar 2024の記事となります
この後もctc(中部テレコミュニケーション株式会社)のメンバーが技術にまつわる知見を投稿していきますのでご期待ください

https://qiita.com/advent-calendar/2024/ctc