Asteriskで発信用の携帯電話をBluetoothで収容する

# 概要
昨今、携帯キャアリア(MNO, MVNO)からは特色あるプランが提供され、通話で使いたい回線と通信で使いたい回線が必ずしも一致しないことがある。また通話回線だけでも、カケホーダイ契約の有無による使い分けもある。これに対する解決策としては、スマートフォンのデュアルSIM機能を使った回線の使い分けが挙げられるが、対応したスマートフォンが必要であること、また**デュアル**の名の通り2回線までの使い分けとなることが制限となる。
そこで今回、通話発信用の回線(SIM)を挿した携帯電話をBluetoothでAsteriskへ収容し、離れた場所から通話発信出来るようにする。
[前回](https://qiita.com/kmorimoto/items/d99cd9edcf7436eea7cc)同様、BluetoothでAsteriskへ収容する情報は存在しているものの非常に少なく断片的で、また古くて参考にしにくい状況であるため、今回も通しで紹介するものである。

# ポイント/狙い
- 2023年時点で使える具体的な手順や設定内容を**通しで**紹介する。
- それに至る試行錯誤も合わせて紹介して、今後の仕様変更に対応できる手がかりを残したい。

# 背景
作業の前提....の前に、その背景を説明する。
元々はスマホ1台にドコモ回線(SIM 1枚)で通話と通信を賄っていた。そこへ新たに仕事用のドコモ回線(SIM 1枚)が増えたのだが、自身のスマホがiPhone SE(1st Gen.)でSIMの2枚挿しが出来ないため、前者の私用スマホと後者の仕事用スマホの2台を持ち歩いていた。どちらの回線もそこそこ通話発着信するので仕方ないのだが、普通に考えてこの状態は面倒くさい。そこでスマホ買い換え以外の解決方法を考えてみた。

通話着信は両者をファミリー契約にすれば無料転送できるのだが、通話発信はどうしてもそのSIMが挿されたスマホからの発信が必要になる。そこでこれをAsteriskでなんとか解決しようと考えた次第である。ちなみにSMSはAppleのiMessage機能を使うことで、SIMが挿さっていないデバイスからのSMS送受信が可能である。

話を通話発信に戻すが、今回の話であればスマホ買い換えが現実的ではあるものの、今後更に通話回線が増えた場合や、格安のデータ専用回線での運用などを考えると、遠隔で通話発信出来ることの価値は小さくないと思われる。

# 作戦
- 2回線(SIM)のうち、一方の回線(A)を持ち歩き、もう一方の回線(B)を自宅に置いておく。
- 自宅の回線Bのスマホと、Asteriskが動くRaspberry PiとをBluetooth接続する。
- Asteriskで回線Bのスマホから発信出来るよう、chan_mobileを組み込んで設定する。

なおAsteriskへモバイル回線(SIM)を収容する方法はいくつかあるが、個人で現実的な方法は
1. chan_mobile(旧称 chan_cellphone)を使ってスマホをBluetoothで収容
1. chan_dongleを使ってSIMの挿さったUSBモデムを直接収容

の2つがありそう。
後者の方が後述の通話品質の観点では良いのだが、SIMを挿すUSBモデムの入手性が非常に悪いことと、入手したとしても数年後のキャリアによる停波に抗えないことから、安価で持続性の高い前者とした。

# 前提
### 構成
```
(発信は回線B, 着信はキャリア内転送で回線Aへ)
     |                 (回線0ABJで発着信)
     |                         |                  (回線Aで発着信(+回線Bの転送着信))
     |                         |                                 |
+---------+               +--------+                        +---------+
|iPhone 5S|               |Asterisk|                        |iPhone SE|
|  SIM B  |--(Bluetooth)--|  0ABJ  |----------(4G)----------|  SIM A  |
+---------+               +--------+                        +---------+
   (自宅)                    (自宅)                            (自宅外)
                                                            Asterisk経由で
                                                            回線0ABJでの発着信と
                                                            回線Bでの発信が可能
```

- 右のiPhone SEだけを持ち歩く
  - iPhone SEは買い換えない(＝デュアルSIM対応スマホへ買い換えない)
- 左のAsteriskとiPhone 5Sは自宅に置いておく
  - Asteriskは[前回の記事](https://qiita.com/kmorimoto/items/d99cd9edcf7436eea7cc)で稼働している
- 回線Aと回線Bは同一キャリアで、ファミリー契約になっている
  - 両回線間の転送通話料は無料
  - 回線Bの転送をキャリア転送にした理由は後ほど触れるが、Asterisk経由だと音声品質がイマイチだから。
  - 参考情報だが、キャリアが異なる場合も回線Bから回線Aへの通話転送(有料)は出来るし、あるいは回線Bの着信をAsteriskで受けて回線Aへ転送すれば良い。

# 手順
作業内容は先の通り、大きく分けて2つ。
1つはBluetoothが使えるようにすること、もう1つはAsteriskでBluetooth(iPhone)が使えるようにすること、である。
後者のAsteriskに関する作業は、前者のBluetoothがないと進めないところがあるので、まずは前者のBluetoothをやる。それが出来たら後者をやるわけだが、後者は相対的に需要が大きそうなので、多少は詳しめに書いておく。

## Raspberry PiとiPhoneをBluetoothでつなぐ
### Bluetoothドングルの準備
そもそもRaspberry Pi自体がBluetoothを持っているのだが、安定性や相性の観点であまり評判がよくない。そのため別途USBのBluetoothドングルを用意してつけることにした。
具体的には、多くの記事で活躍が確認された Buffalo BSBT4D09BK をメルカリで探して買ってきた。

### Bluetoothドングルの認識
内蔵のBluetoothにせよ外付けドングルにせよ、Raspberry Pi(というかDebian)でBluetoothを扱うためには`bluetooth`(service)と、プロトコルスタックの実装(BlueZ)が必要となる。
両方ともDebianに入っている場合もあるが、Raspberry PiおよびAsteriskで使うためには追加も含め以下のように入れる。
（最初に言ったとおりこれを先にしっかり入れておかないと、後でAsteriskでchan_mobileを入れられない）
```
# apt install bluez libbluetooth-dev
```

### プロファイル(A2DP)を入れる
上記の状態ではBluetoothのペアリングは出来るが機能はしない(connectしない)。
```
# service bluetooth status
● bluetooth.service - Bluetooth service
   Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2022-08-26 23:26:06 JST; 23h ago
(略) 
 8月 27 23:02:10 xxx.ne.jp bluetoothd[26285]: a2dp-source profile connect failed for xx:xx:xx:xx:xx:E5: Protocol not available
```
エラーの通り、A2DPプロファイルを入れなければならない。
DebianでA2DPに対応するにはいくつか方法があるが、手っ取り早いのはPulseAudioを入れて動かすことである。
ということでまず入れる。
```
# apt install pulseaudio pulseaudio-module-bluetooth
```
そしてこれを以下のようにdaemon化する。
```
# cat <<'EOF' >> /etc/systemd/system/pulseaudio.service
[Unit]
Description=Pulse Audio
 
[Service]
Type=simple
ExecStart=/usr/bin/pulseaudio --system --disallow-exit --disable-shm
 
[Install]
WantedBy=multi-user.target
EOF
```
あとPulseAudioのconfigを変更し、BlueZに対応する。（allowでbluez行を追加している）
```vim /etc/dbus-1/system.d/pulseaudio-system.conf
<busconfig>
 
  <policy user="pulse">
    <allow own="org.pulseaudio.Server"/>
    <allow send_destination="org.bluez"/>
  </policy>
 
</busconfig>
```
つづいて起動時に備えて、以下のように既存ファイルへ追記する。
```
# cat <<'EOF' >> /etc/pulse/system.pa
 
### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-policy.so
load-module module-bluetooth-policy
.endif
 
.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover
.endif
EOF
```
あとは使えるようにして、念のため再起動する。
```
# chmod +x /etc/systemd/system/pulseaudio.service
# systemctl daemon-reload
# systemctl enable pulseaudio
# reboot
```
### `bluetooth`のエラー修正
環境依存かもしれないが、これで`bluetooth`を起動するとエラーが出る場合がある。
`service bluetooth status`で様子を見てみると、うちの場合は`Operation not permitted (1)`というエラーが出ていた。
ここはあまり理解していないが、調べたところ`bluetooth`の引数を変えることで押さえ込めるようだ。
```vim /etc/systemd/system/bluetooth.target.wants/bluetooth.service
// ExecStart=/usr/lib/bluetooth/bluetoothd 変更前
ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap //変更後
```
### Bluetooth接続の確認と自動接続の設置
ここまでで、Raspberry Pi(Debian)とiPhoneのBluetooth接続という点では問題なく完了した。
実際にそれを確認していく。

まずBluetoothドングルを以下の`hciconfig`コマンドで確認する。
```
# hciconfig
hci0:   Type: Primary  Bus: USB
        BD Address: xx:xx:xx:xx:xx:xx  ACL MTU: 310:10  SCO MTU: 64:8
        UP RUNNING
        RX bytes:642 acl:0 sco:0 events:40 errors:0
        TX bytes:1684 acl:0 sco:0 commands:40 errors:0
```
期待通り、挿しただけで(追加ドライバなしで)動いてくれている。

次にBlueZで提供される`bluetoothctl`コマンドで色々やっていく。
まずはBluetoothとしてon状態のハズだが、念のため(実験も兼ねて)`on`してみる。
```
# bluetoothctl
Agent registered
[bluetooth]# power on
Changing power on succeeded
```
動いているので周囲をスキャンしてみる。
```
[bluetooth]# scan on
(略)
[bluetooth]# scan off
[bluetooth]# devices
Device xx:xx:xx:xx:xx:xx xx-xx-xx-xx-xx-xx
Device xx:xx:xx:xx:xx:xx OMVR-V190
Device xx:xx:xx:xx:xx:xx xx-xx-xx-xx-xx-xx
Device xx:xx:xx:xx:xx:xx xx-xx-xx-xx-xx-xx
Device xx:xx:xx:xx:xx:xx Q-SL2
Device xx:xx:xx:xx:xx:xx Remo-xxxxxx
Device xx:xx:xx:xx:xx:xx xx-xx-xx-xx-xx-xx
Device xx:xx:xx:xx:xx:xx Remo-xxxxxx
Device xx:xx:xx:xx:xx:xx Remo-xxxxxx
Device xx:xx:xx:xx:xx:xx Remo-xxxxxx
Device xx:xx:xx:xx:xx:xx kemorimo の iPhone5s
```
ペアリングしたいiPhoneが出てきた。
ここで表示されたMACアドレスとiPhone側で表示されたMACアドレスとに相違ないことを確認し、ペアリングする。
このときiPhone側でもパスキー入力など色々操作が必要なので、iPhoneを手に取りながら行う。
```
[bluetooth]# pair xx:xx:xx:xx:xx:xx
Attempting to pair with xx:xx:xx:xx:xx:xx
[CHG] Device xx:xx:xx:xx:xx:xx Connected: yes
Request confirmation
[agent] Confirm passkey 649872 (yes/no): yes
(略)
```
入力後、ペアリングが出来たら信頼設定をしておく。
```
[bluetooth]# trust xx:xx:xx:xx:xx:xx
[CHG] Device xx:xx:xx:xx:xx:xx Trusted: yes
Changing xx:xx:xx:xx:xx:xx trust succeeded
```
ここまで進めて「ペアリングは出来ても接続出来ない」という場合は、`systemctl status bluetooth`でエラーを見て対応する。
エラーなく接続出来たら以下の通り情報が得られる。
```
[kemorimo の iPhone5s]# info xx:xx:xx:xx:xx:xx
Device xx:xx:xx:xx:xx:xx (public)
        Name: kemorimo の iPhone5s
        Alias: kemorimo の iPhone5s
        Class: 0x007a020c
        Icon: phone
        Paired: yes
        Trusted: yes
        Blocked: no
        Connected: yes
        LegacyPairing: no
        UUID: Vendor specific           (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: Service Discovery Serve.. (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: Audio Source              (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: A/V Remote Control Target (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: A/V Remote Control        (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: NAP                       (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: Handsfree Audio Gateway   (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: Phonebook Access Server   (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: Message Access Server     (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: PnP Information           (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        UUID: Vendor specific           (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        Modalias: bluetooth:xxxxxxxxxxxxxxx
```
ってことで接続およびその詳細が確認出来た。
この後`bluetoothctl`は`exit`で抜けられる。

接続としてはこれで良いのだが、このままだと再起動時に接続が切れてそのままになる。
なので有志が作ったっぽいbluetoothctl-autoconnectorを[GitHub](https://github.com/noraworld/bluetoothctl-autoconnector)から持ってきて入れる。
```
# cd /usr/local/bin/
# git clone https://github.com/noraworld/bluetoothctl-autoconnector.git
# cd bluetoothctl-autoconnector
# ./setup.sh
 
crontab registered
 
*/1 * * * * /usr/local/bin/bluetoothctl-autoconnector/cron.sh
```
`README.md`の通りだが、`setup.sh`を実行すればcronへ追加される。
これで仮に切れてもすぐ繋いでくれる。

## Asteriskにchan_mobileを組み込む
Asteriskの導入方法は色々あるが、[前回](https://qiita.com/kmorimoto/items/d99cd9edcf7436eea7cc)の理由によりソースから入れている。
とするとchan_mobileはデフォルトで組み込まれないので、`./configure`からやり直さなければならない。
具体的には以下のように、引数で`--with-bluetooth`を与えなければならない。
```
# cd /usr/src/asterisk-18.2.0/
# ./configure --with-bluetooth
(略)
```

さらに`make`時に`make menuselect`すると、以下のようなGUI?で設定が出来る。

```
# make menuselect
**************************************************
    Asterisk Module and Build Option Selection
**************************************************
 
         Press 'h' for help.
 
---> Add-ons (See README-addons.txt)
     Applications
     Bridging Modules
     Call Detail Recording
     Channel Event Logging
     Channel Drivers
     Codec Translators
     Format Interpreters
     Dialplan Functions
     PBX Modules
     Resource Modules
     Test Modules
     Compiler Flags
     Utilities
     AGI Samples
     Core Sound Packages
     Music On Hold File Packages
     Extras Sound Packages
```
Add-onsのを選ぶと以下のようになるので、chan_mobileを選択状態する。
```
**************************************************
    Asterisk Module and Build Option Selection
**************************************************
 
         Press 'h' for help.
 
         --- Extended ---
     [*] chan_mobile
     [ ] chan_ooh323
     [ ] format_mp3
     XXX res_config_mysql
         --- Deprecated ---
     XXX app_mysql
     XXX cdr_mysql
```

ここでもしchan_mobileが選べない状態だとすると、ここまでの設定に誤りや抜け漏れがあるハズである。
ネットで色々見た＆自分で色々試したところ、
1. `apt install libbluetooth-dev`
1. `./configure --with-bluetooth`

のいずれかで失敗しているパターンが多そうだった。
前者は先にも書いたが、最初からBlueZが入っている(`bluetoothctl`コマンドが通る)ので油断して入れていないとか、後者は情報が少ないので忘れているとかがありそう。

それらに注意して`make`および`make install`すると、ファイルの存在という意味でchan_mobileのインストールが成功する。
```
# find / |grep chan_mobile
/etc/asterisk/chan_mobile.conf
/usr/src/asterisk-18.2.0/addons/chan_mobile.so
/usr/src/asterisk-18.2.0/addons/chan_mobile.o
/usr/src/asterisk-18.2.0/addons/chan_mobile.exports
/usr/src/asterisk-18.2.0/addons/chan_mobile.c
/usr/src/asterisk-18.2.0/addons/.chan_mobile.o.d
/usr/src/asterisk-18.2.0/addons/.chan_mobile.makeopts
/usr/src/asterisk-18.2.0/addons/.chan_mobile.moduleinfo
/usr/src/asterisk-18.2.0/configs/samples/chan_mobile.conf.sample
/usr/lib/asterisk/modules/chan_mobile.so
```
あとはこれをAsteriskで読み込めば、機能的にもインストール完了となる。
以前は読み込むために`/etc/asterisk/modules.conf`で指定する必要があったようだが、最近は最初から以下のように設定されているので、`/usr/lib/asterisk/modules`の中に`so`があるだけで読み込まれる。
```/etc/asterisk/modules.conf
[modules]
autoload=yes
```

## `chan_mobile.conf`と`extension.conf`を設定をする
### `chan_mobile.conf`を設定する
confファイルは`/etc/asterisk/chan_mobile.conf`にあるのだが、編集する前にそこで設定するBluetoothドングルのMACアドレスを調べる。
```
# hcitool dev
Devices:
        hci0    xx:xx:xx:xx:xx:xx
```
このMACアドレスをconfファイルで設定する。
```/etc/asterisk/chan_mobile.conf
[adapter]
id=hci0
address=xx:xx:xx:xx:xx:xx
```
この状態でAsteriskを起動してiPhoneを見ると、ポート番号がわかる。
このときBluetoothがconnect状態だと見えないとか、近くで何度かon/offしないと出ないとか、色々試行が必要だった。
が、最終的には以下のように表示されて、ポート番号が分かった。
```
# asterisk -rvvvvv
Asterisk 18.2.0, Copyright (C) 1999 - 2018, Digium, Inc. and others.
Created by Mark Spencer <markster@digium.com>
Asterisk comes with ABSOLUTELY NO WARRANTY; type 'core show warranty' for details.
This is free software, with components licensed under the GNU General Public
License version 2 and other licenses; you are welcome to redistribute it under
certain conditions. Type 'core show license' for details.
=========================================================================
Connected to Asterisk 18.2.0 currently running on pbx (pid = 17784)
pbx*CLI> mobile search
Address           Name                           Usable Type    Port
xx:xx:xx:xx:xx:xx kemorimo の iPhone5s          Yes    Phone   8
pbx*CLI>
```
このポート番号も含めて、`/etc/asterisk/chan_mobile.conf`へ追記する。
```
# cat <<'EOF' >> /etc/asterisk/chan_mobile.conf
[iPhone5s]
address=xx:xx:xx:xx:xx:xx       ; the address of the phone
port=8                          ; the rfcomm port number (from mobile search)
context=from-iphone5s           ; dialplan context for incoming calls
adapter=hci0                    ; adapter to use
;group=1                        ; this phone is in channel group 1
sms=no                          ; support SMS, defaults to yes
;nocallsetup=yes                ; set this only if your phone reports that it supports call progress notification, but does not do it. Motorola L6 for example.
EOF
```
ポイントはiPhoneの名前(`[iPhone5s]`)とcontext(`context=from-iphone5s`)で、後の`extension.conf`で関係してくる。
このように設定すれば、Asteriskからも以下のように見える。
```
# asterisk -rvvvvv
Asterisk 18.2.0, Copyright (C) 1999 - 2018, Digium, Inc. and others.
Created by Mark Spencer <markster@digium.com>
Asterisk comes with ABSOLUTELY NO WARRANTY; type 'core show warranty' for details.
This is free software, with components licensed under the GNU General Public
License version 2 and other licenses; you are welcome to redistribute it under
certain conditions. Type 'core show license' for details.
=========================================================================
Connected to Asterisk 18.2.0 currently running on pbx (pid = 18393)
pbx*CLI> mobile show devices
ID              Address           Group Adapter         Connected State      SMS
iPhone5s        xx:xx:xx:xx:xx:xx 0     hci0            Yes       Free       No
```

### `extension.conf`を設定する
最初に言った作戦や前提の通り、今回は通話発信だけ出来るようにする。
発信と聞くと当初は「`pjsip.conf`とかでトランクを作らないといけないのかな」と考えたのだが、色々調べた＆試した結果、不要であった。
何故なら先のchan_mobileで設定することにより、`Mobile/iPhone5s`が出来るからだ。
そのため発信だけであれば、例えばシンプルに`/etc/asterisk/extensions.conf`で
```/etc/asterisk/extensions.conf
exten => _X.,1,Dial(Mobile/iPhone5s/${EXTEN:1})
exten => _X.,n,Hangup
```
という設定で発信は全てiPhone5s(回線B)から出ることになる。

もし既存のひかり回線0ABJと共存する＝回線Bからの発信をゼロ発信にする場合は以下のようになる。
```/etc/asterisk/extensions.conf
exten => _00Z.,1,Dial(Mobile/iPhone5s/${EXTEN:1})
exten => _00Z.,n,Hangup
exten => _X.,1,Set(CALLERID(num)=${MYNUMBER})
exten => _X.,2,Set(CALLERID(name)=${MYNUMBER})
exten => _X.,3,Dial(PJSIP/${EXTEN}@HIKARI-DENWA)
exten => _X.,n,Hangup
```
前回同様あまり`extensions.conf`が得意でないのだが、最初に00で始まる場合(0090-, 003-など)はiPhone5sへ流すようにする。ただし頭のゼロは余計なので`${EXTEN:1}`とする。（普通は`${EXTEN}`）
それに引っかからなかったもの、最初に00で始まらなかった場合(090-, 03-など)は下で引っかかる＝ひかり回線0ABJから発信するようになる。
ただ”最初に00で始まらなかった場合”を条件にして発信を振り分けると、ひかり回線から00で始まる番号では発信ができない。しかし、そんな番号があるのかよく分からないので、ここでは考えないことにした。

発信としては以上で、もし回線Bの着信もAsteriskで処理したい場合は`/etc/asterisk/chan_mobile.conf`で設定した`context=from-iphone5s`を`/etc/asterisk/extensions.conf`で作ってやればいい。

# 考察
これで2台持ちから解放され、普段は回線Aを持ち歩きつつも時々回線Bでの発着信が出来るようになった。
通話品質だが、音質としては非常にクリアでSIPやBluetoothを挟んでいるとは分からないレベルだ。しかし遅延が若干気になるレベルで発生する。当初は音質がよい故に遅延発生に気づかず、
```
自分「もしもし」
相手「....」
自分「もしもし聞こえますか！」
相手「もしもし」
自分「先日の件ですが～」
相手「ああ、聞こえます」
自分「え？聞こえにくいですか？」
相手「あ、はい」
自分「え、やっぱり聞こえない!?」
相手「いや、聞こえますよ」
自分「どっちなんですか!!」
```
という通話が起きていた。
面倒くさいので切り分けていないが、経験的にはおそらくBluetoothによる遅延だろう。具体的にはコーデックの問題で、先の通りA2DPなのでSBCがイマイチなのかもしれない。じゃあこれをaptXにすればいいのか、Bluetoothドングルを替えればいいのかという話だが、面倒なので今のところ調べてもいない。
ということで対策?としては通話の中で遅延を考慮し、相手の発言に被せ気味に話すことぐらいである....。

なので今のところ回線Bの着信は、ファミリーを組んた回線Aへのキャリア内転送としている。
将来、格安のデータ専用回線を持ち歩くスタイルになれば、全ての発着信をAsterisk経由にせねばならず、原因究明や対処をするかもしれない。