Asterisk+Radio
ここでいうRadioは無線機のことなのです。
注:当初、レピータと表記していましたが、うちらの業界的にはゲートウェイだろうってことでゲートウェイに改めました。
目次
ニーズ
PBXに無線機、特定小電力を接続するニーズは結構あります。特に店舗運営をしているような場合、フロアは無線機を使っているケースが少なくありません。というわけで以前から統合したソリューションを出しているメーカー/ベンダーもあります。んじゃあAsteriskでもやってしまおうという計画。
(SIPを喋る無線基地局もあります)
注意
この方法を使うとアマチュア無線機とか業務無線機(特小とかデジ簡ではないやつ、免許局)も繋ぐことができますが、技術的なこと以外の問題点が多々あるので繋ぐ場合には自分で調べてください。業務上で無線と電話を繋いでいる例はもちろんあります。
しくみ
無線機とはいっても音声のやりとりなので基本的な接続は簡単で、Asteriskからオーディオを入出力してやれば無線と音声でやりとりできます。
とはいえ問題はありまして、PTT(Push-To-Talk)をどうするかです。通常、無線機は半二重で誰かが喋っている間は他は聞く動作になり、喋りたい時にはPTTを押して喋ります。なのでAsteriskからPTTを制御しなくてはなりません。
でまあ無線機のヘッドセットとか外部マイクにはPTT制御がついているため、これを何等かの方法で制御してやればAsteriskから無線機が制御できます。思いつく簡単な方法としては入手性の容易なUSBシリアルを使って、RTSとかDTRの制御線でPTTを制御する方法です。
ですがここで再度問題が。Asteriskを入れるようなサーバとかアプライアンスはオーディオインタフェースが付いていなかったりするわけで、その場合、USBオーディオ(安い)を増設して使う方法が考えられます。
まあ、これで良いといえば良いのですが、この方法だと回路とかケーブルを自作しないとできないのでちょいと面倒です。できれば何も自作したくないですよね。電子回路の知識とかまで要求されるのもアレですし。そして、この方法のもうひとつの問題はUSBケーブル2本挿しというあまり美しくない構成になることです。
参考:無線機によってはVOX(Voice Operated eXchanged)という機能を備えているものがあります。これは音声によって自動的に送信するもので、この機能があればPTT制御を音声で行うことができますが特小機では本体にこの機能を備えるものが少ない(ICOM IC-4310,4400などに本体にこの機能がある)ことと、音声以外のノイズでも送信状態になってしまうので、運用に注意が必要です。VOXを使えばPTT制御はなしで使えるので、通常のオーディオインタフェースで接続することができますが無線機に対応したケーブルを作ることが必要となります。
DigiRig mobile
探せばある!
上記の構成を考えた場合、必要なコンポーネントとしてはUSBオーディオとUSBシリアルで、これを1台にまとめる(USBケーブル1本)場合にはUSBハブが必要になります。PTT制御回路を加えたものを基板を起こして作れば無線インタフェースのできあがりなので、作るかと思ったのですが作り始める前によく考えようというわけでして。
もともとRaspberry PiをPBX化するPIASTではリレー制御回路を用意したりしてましたが、Raspberry PiはオーディオINを持たない(OUTのみ)ので別途USBかSPIのオーディオインタフェースを付けてやる必要があり、難儀してしました。
んでまあ、こんなのは誰か作って売ってるだろうと探してみたらありました!どうやって探したかというと何のことはないアマチュア無線用です。最近のアマチュア無線機ではデジタルモード(FT8等)通信のためにオーディオIFと無線機制御が付いていて、USB接続できるようになっていますが、ちょっと昔のアマチュア無線機にはオーディオ入出力(要するにマイクとスピーカ)しか付いておらず、オーディオしかない無線機でデジタルモードを使うにはPCのサウンドカードやらを使います。なので昔のアマチュア無線機(リグ)をデジタル対応にする機器類が売られているので、よさげなのを探したらありました!
K0TX 局が開発/販売している DigiRig がそれで、OSHWとして公開されているので自分でも作ろうと思えば作れなくはありませんが、売られているものの出来がとても良く、買った方が早いでしょう。
https://digirig.net/
今回使っているのは DigiRig mobile というタイプです。
https://digirig.net/product/digirig-mobile/
USBインタフェースはUSB-Cコネクタで写真の左側、右側が無線機の接続用です。無線機接続用にAUDIOとSERIALがありますが、今回使用するのはAUDIOだけです。SERIALは無線機制御(CAT)のインタフェースに繋ぐ用です。
無線機と接続するには無線機のメーカ毎のケーブルが売られているので、これも併せて買えばOKです。今回はICOM用を入手して使用しています。
ICOMの特小機なら"ICOM HT Cable for Digirig Mobile"が使えます。ケンウッド機の場合には"Baofeng HT Digirig Cables Set"が使えると思いますが、試してはいません。八重洲(スタンダード:たぶん3.5Φ 4P方式)、アルインコ(たぶんアイコム互換)については調査していませんが、わかれば追記します。
基本的にDigiRig Mobileとケーブルを買えば接続できるので工作等は必要ありません。
注意1:同じICOMでもIC-4130はヘッドセット用のコネクタが異なります。この場合にはICOMのオプションケーブル(OPC-2132)を使用することで2ピンタイプに変換してからDigiRigのケーブルと接続します。
注意2:ICOMとケンウッドはコネクタの形状が似ていますが、PTTの制御方式が異なるのでケーブルは兼用できません。そもそもプラグ間のピッチが異なります。ケンウッドがなんで Baofeng 用なのかというとケンウッドのPTT方式をパクった中国製が多いからです。なんでケンウッド式を使ったのかは謎ですが。
ここで一回躓く
さて、シカケは揃いました。あとはAsteriskに接続して・・・と、やってみたのですが上手くないです。理由がどうにもこうにもわからないので諦めました。
当初の目論見ではAsteriskのConsoleデバイスで繋ごうと思ってたのですが、どうもオーディオがおかしくなりまともに音が出ないことが多くあります。たまに出ても数秒しか持たないなどさんざんでした。おそらくALSA+Pulseaudio+chan_consoleの組み合わせが悪いのか何なのかなのですが、もし解決方法を気付いた人がいたら教えてください。chan_console のテストをやっていて発見しました。USBオーディオの種類を変えてみても症状が治まらないので諦めました。
無線ゲートウェイを作る
というわけで、Asteriskで直収するのは諦めてSIPを喋る無線ゲートウェイをつくりましょう。話は簡単です。SIPのソフトフォンを入れたSBC等を用意して、それにDigiRigで無線機を繋ぎ、ヘッドセット等の代わりが無線機になっていればOKです。
でまあ、今回はとりあえずRaspberry Pi 3B+を使いました。ソフトフォンを動かすだけなら余ってるRaspberry Piとかでいけます。
参考:SIP,RTPのポートについて考慮すればAsteriskと同居させることはできます。なので、サーバ内でpjsuaを動かすのもアリです。
今回の接続は下図のようになります。
pjsua
ソフトフォンとしてPjSIPのUA、pjsuaを使います。そういえばpjproject自体のコンパイル等はVoIP-Infoで説明していない(AsteriskのPjSIPだけ)ことに今更気付きました。
Raspberry Piセットアップ
まずRaspberry Piをセットアップします。Raspberry Pi Imagerで最新のものを入れてください。ここではbookwormベースのRaspberry Pi OSを例にしています。インストールしたら最低限のセットアップ/アップデートを行っておきます。なお、ソフトフォンとして使うのでIPアドレスを固定する必要はありません。Asteriskに対してREGISTERして使います。
Raspberry Pi OSの場合デフォルトのインストールでほとんどのものは足りているはずなのですが、pjsuaでALSAを使いたいのでALSAとPortaudioの開発ライブラリを入れておいてください。
apt-get install libasound2-dev portaudio19-dev
オンボードオーディオの無効化
必ずしもやる必要はないと思います。
もし、pjsuaがオーディオデバイスを見失って正しく送受話できない場合にはRaspberry Piの設定を変更します。
/boot/firmware/config.txt を編集します。以下の2か所を例のように修正し、BCMオーディオ(ヘッドフォン)とHDMIオーディオを無効にします。
# Enable audio (loads snd_bcm2835) #dtparam=audio=on dtparam=audio=off
# Enable DRM VC4 V3D driver #dtoverlay=vc4-kms-v3d dtoverlay=vc4-kms-v3d,noaudio max_framebuffers=2
以下のようにサウンドカードがひとつだけになれば正解です
# cat /proc/asound/cards 0 [Device ]: USB-Audio - USB Audio Device C-Media Electronics Inc. USB Audio Device at usb-3f980000.usb-1.3.2, full speed
PjSIPコンパイル
PjSIP自体はPjProjectのページからダウンロードして展開します。
https://www.pjsip.org/
展開するとバージョンに応じたディレクトリ(pjproject-2.14.1)ができるので、その下で作業します。
./configure --with-external-pa make dep make
するとコンパイルできます。
コンパイルが完了すると pjproject-2.14.1/pjsip-apps/bin の下に pjsua-aarch64-unknown-linux-gnu というプログラムができあがっているので、これを適当なディレクトリに pjsua という名前でコピーしておきます。
pjsua設定ファイル
以下のような内容でAsteriskに対してREGISTERするソフトフォンとして動作させます。無線機と接続するのでオートアンサモードにしておきます。
# PJSUA config --local-port 5090 --capture-dev 1 --playback-dev 1 --no-vad --id sip:phone10@192.168.254.234 --registrar sip:192.168.254.234:5070 --realm * --username phone10 --password password --auto-answer 200
auto-answerの指定値は応答コード(200 OK)です。
設定ファイルを使用しての起動は以下のようにします。
./pjsua --config-file pjsua.conf
SIPのトレースが出てウルサイのを消したい場合には
# PJSUA config --local-port 5090 --log-level 0 --capture-dev 1 --playback-dev 1 --no-vad --id sip:phone10@192.168.254.234 --registrar sip:192.168.254.234:5070 --realm * --username phone10 --password password --auto-answer 200
--log-level で出力される情報を変更します。
--local-port はpjsua側のSIPポートなので他のものと被らない値を指定してください。
オーディオデバイスは --capture-devと--playback-devで指定しますが、これはpjsua(pjsip)が認識しているオーディオデバイスのIDです。この例ではALSAが認識しているデバイスは
# cat /proc/asound/cards 0 [vc4hdmi ]: vc4-hdmi - vc4-hdmi vc4-hdmi 1 [Headphones ]: bcm2835_headpho - bcm2835 Headphones bcm2835 Headphones 2 [Device ]: USB-Audio - USB Audio Device C-Media Electronics Inc. USB Audio Device at usb-3f980000.usb-1.3.2, full spee
となっているので、ALSAの認識では0,1,2(hw:0,0、hw:1,0とか)になるのですが、pjsuaはこの通りには認識しません。pjsuaが認識しているデバイスIDを確認するにはpjsip-apps/binの下にあるpjsystestを使います。
# cd pjsip-apps/bin # ls PJSYSTEST.TXT pjsua-aarch64-unknown-linux-gnu samples PJSYSTEST_RESULT.TXT pjsystest-aarch64-unknown-linux-gnu
pjsystest-aarch64-unknown-linux-gnuがそれにあたりますので(pjsua同様にアーキテクチャ名とかが付く)、これを実行します。ログがわーっと出てメニューが出ますので、10の"View Devices"を実行します。
M E N U : --------- 0: Tests 00: Run test wizard 01: Device Test 02: Play Tone 03: Play WAV File1 04: Play WAV File2 05: Record Audio 06: Latency Test 07: AEC/AES Test 09: Exit 1: Options 10: View Devices 11: View Settings Enter the menu number: 10 18:03:50.311 systest.c Running Audio Device List Audio Device List Found 2 devices 0: PA [bcm2835 Headphones: - (hw:1,0)] (0/8) 1: PA [USB Audio Device: - (hw:2,0)] (1/2) 1:OK
上の例をみるとわかるようにpjsuaは0をオンボードのヘッドフォンジャック、1をDigiRigのUSBオーディオと認識しているので、この場合の--capture-devと--playback-devの指定は 1 となります。
言い訳け:--capture-devで指定できるのがどうもALSAデバイスではないというのには気付いてたんですが、sndinfoで見ろってコマンドラインのヘルプに書いてあるのにsndinfoが無いんで悩んでましたが、今のバージョンだとpjsystestで見られるというのにようやく気付いた次第。上の例だとたぶんPA(Portaudio)経由の認識で、HDMIサウンドは「使えない」と判断してるようです。
テスト
ここまでで動作を確認できます。Asteriskに対するREGISTERが確認できれば、エンドポイント(ここの例ではphone10)に対してダイヤルすれば無線機と繋がります。Asteriskから
exten => 1234,1,Dial(PJSIP/phone10)
を行えばpjsuaは自動で応答し繋がりますので、無線で何か喋ると電話側で聞こえるはずです。音量の調整は無線機のボリュームと、pjsua側のalsamixerでよしなに調整してください。電話機から喋りたい場合にはpjsuaに繋いだ無線機のPTTを手で押します。このPTTをソフト的に制御するのが次の課題です。
PTT制御
注意:初期公開版から変更し、PTT制御をsocket対応としました。必要な処理が減ります。ですが、pttctl自体がかなりザルなので注意して使用してください。
音声の入出力ができるようになったので、後はPTTを制御するだけです。前述のようにDigiRigはUSBシリアルも内蔵しており、RTSでPTTが制御できるようになっています。RTSを制御すればいいので簡単ですねと言いたいところですが、デバイスに対するIOCTLを発行しないとなのでちょいと面倒です。なので、この部分は書いておきました。
Asteriskサンプル設定ファイルと同じところにpttctrlというプログラムを置いておきます。
https://github.com/takao-t/asterisk-conf/tree/main/pttctrl
これをコンパイルするには
gcc pttctl pttctl.c -lrt
使い方は
pttctl /dev/ttyUSB0 5091
のように引数に対象のデバイスと、使うポート番号を指定して起動します。
このプログラム自体はデーモン(常駐)化して使用します。やり方は後で説明しますが、とりあえずフォアグラウンドで試してみてください。
PTTを制御するにはTCPで指定のポートに対して書き込みを行います。ただし前置詞として"NETPTT:"を付けます。書き込みはnetcat(nc)が簡単です。ローカルで試すならnetcat-traditionalパッケージをインストールしてください。
PTTをオン、つまり送信するなら
echo "NETPTT:ON" | nc localhost 5091
オフにする(離す)なら
echo "NETPTT:OFF" | nc localhost 5091
を実行します。ですがこれだけだと押したかどうかを判断するのが面倒なので、状態反転も持たせてあります。
echo "NETPTT:ALT" | nc localhost 5091
で現在の状態を反転します。オフならオンに、オンならオフになります。
なお、特小の場合には連続送信は3分未満となっている(無線機が勝手に切る)ので、このプログラムでは送信状態になってから170秒でオフにするようにしています。
まとめる
ここまでの作業をまとめて無線ゲートウェイをつくります。Raspberry Pi(3b+)の場合ですが
- オンボードのオーディオデバイスを全て無効化しDigiRigだけ使うようにする
- pjsuaをコンパイルし、/usr/local/bin/pjsua とする
- pjsuaの設定ファイルを /usr/local/etc/pjsua.conf とする
- PTT制御プログラムをコンパイルし /usr/local/bin/pttctl とする
- PTTをネットワークから制御するため netrwを入れ、シェルスクリプトを /usr/local/etc/netptt.sh とする
以上を行い、/etc/rc.local に以下を書いて再起動しても無線ゲートウェイとして動くようにします。
#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi nohup /usr/local/bin/pttctl > /dev/null 2>&1 & nohup /usr/local/etc/netptt.sh > /dev/null 2>&1 & screen -dmS pjsua /usr/local/bin/pjsua --config-file /usr/local/etc/pjsua.conf & exit 0
pjsuaはttyから切り離されたがらないのでscreenでバックグラウンド起動させます(デフォルトで入っていない場合はaptでscreenを入れて)。
これで無線ゲートウェイ、要するにSIPを喋る無線機アダプタのできあがりです。
性能的にはpi zero系でもかまわないと思うのですが、OTGケーブルが要るのがちょっと面倒かも。
Asteriskから使う
このシステムの完成図は次のようになります。
無線ゲートウェイができたのでAsteriskから使いましょう。無線区間と"通話"するだけなら単にSIPの電話機として扱うだけです。Dialしてやれば無線の区間で何が話されているのかは聞くことができます。ですが、こちらから発話しようとするとPTTを押さないといけないので、PTT制御が必要になります。
まず、PTT制御するハンドラを書きます。これはnetwriteで無線ゲートウェイに対して制御を投げます。ただし、IPアドレスを明示指定して書いてしまうと無線ゲートウェイのIPアドレスが変わった場合に書き換えが必要になってしまうので、Asteriskが『知っている』エンドポイント名で指定できるようにします。
PJSIP_CONTACTS DBがIPアドレスを含むコンタクトを持っているので、そこからIPアドレスだけ取り出します。netwriteする場合のポート番号はnetreadで指定したものと同じものを設定してください。無線ゲートウェイのエンドポイント名が変わった場合にはphone10の部分を書き換える必要があります。
[sub-pttctl] exten => s,1,NoOp exten => s,n,Set(TARGET=phone10) exten => s,n,Set(RCONTACT=${PJSIP_DIAL_CONTACTS(${TARGET})}) exten => s,n,Set(RCONTACT=${CUT(RCONTACT,@,2)}) exten => s,n,Set(RCONTACT=${CUT(RCONTACT,:,1)}) exten => s,n,System(echo ${ARG1} | /usr/bin/netwrite ${RCONTACT} 5091) exten => s,n,Return
ダイヤルして無線ゲートウェイと通話中に、このハンドラを使えるようにします。DYNAMIC_FEATUREで指定します。
features.confに以下のようにハンドラを登録します。登録するのはapplicationmapになります。
[applicationmap] pttctl => **,self/caller,Gosub(sub-pttctl,s,1("ALT"))
内線のextenとしては以下のような感じになります。内線8890を無線機区間用として例示します。
exten => 8890,1,NoOp exten => 8890,n,Set(__DYNAMIC_FEATURES=pttctl) exten => 8890,n,Dial(PJSIP/phone10) exten => 8890,n,Hangup
DYNAMIC_FEATUREを登録したので、無線ゲートウェイに対して通話中に"**"を押すとPTTのオン、オフを行うことができます。これで無線でしゃべることもできるようになります。
ただし、これは少し問題があります。PTTをオンにしたままAsterisk側の電話機を切ってしまうとPTTホールド状態で終了してしまい電波を掴んだままになってしまい、pttctlのタイムアウト(170秒)まで送信状態が続いてしまうので、電話を切ったらPTTをオフにしてやらなくてはいけません。次のように修正します。
exten => 8890,1,NoOp exten => 8890,n,Set(CHANNEL(hangup_handler_push)=sub-pttctl,s,1("OFF")) exten => 8890,n,Set(__DYNAMIC_FEATURES=pttctl) exten => 8890,n,Dial(PJSIP/phone10) exten => 8890,n,Hangup
ハングアップハンドラとしてPTTオフを登録します。
電話機側も無線みたいに入りたい
電話機側も複数入って無線区間とやりとりしたい場合には音声会議につっこむ手段があります。例えば無線ゲートウェイを以下のコマンドで会議、radioに参加させます。
channel originate PJSIP/phone10 application ConfBridge radio
これでConfBridge(radio)に電話機側から参加すれば無線区間同様に同時に通話できます。が、しかしPTTの制御ができないので少し工夫します。音声会議の場合、DTMFが音声会議のメニュー制御に取られるのでDYNAMIC_FEATURESの方法が使えないため、ConfBridgeのメニューでPTTを制御します。ただこのとき引数を受け付けないようなので、まずPTT制御用のextenを以下のように書きます。
[sub-pttctl2] exten => s,1,NoOp exten => s,n,Set(TARGET=phone10) exten => s,n,Set(RCONTACT=${PJSIP_DIAL_CONTACTS(${TARGET})}) exten => s,n,Set(RCONTACT=${CUT(RCONTACT,@,2)}) exten => s,n,Set(RCONTACT=${CUT(RCONTACT,:,1)}) exten => s,n,System(echo "ALT" | /usr/bin/netwrite ${RCONTACT} 5091)
次にconfbridge.confには以下のような無線用メニューを書きます
[radiomenu] type=menu **=dialplan_exec(sub-pttctl2,s,1) *1=toggle_mute *4=decrease_listening_volume *6=increase_listening_volume *7=decrease_talking_volume *8=leave_conference *9=increase_talking_volume
"**"以外はまあ適当に。
その上でダイヤルするには以下のように書きます
exten => 8891,1,NoOp exten => 8891,n,Set(CHANNEL(hangup_handler_push)=sub-pttctl,s,1("OFF")) exten => 8891,n,Answer exten => 8891,n,ConfBridge(radio,,,radiomenu) exten => 8891,n,Hangup
これで会議"radio"に参加するとPTTの制御もできるようになります。ただし『誰か』がPTTをオンにすると会議内の全員の声が無線に飛びますから、あまりわいわいしないようにとか、オンにしたのとは別な人がオフにもできるので運用上で注意してください。また、上の例ではPTTで発話中に誰かが回線を切るとPTTがオフになります。
解決したい
情報お持ちの方、更新して!*ALSAのデバイスが複数あっても明示指定して使えるようにしたい →解決(したつもり)
- 無線機が音を出してない時にパツパツ音がするのを止めたい →どうも無線機のせいかも。無線機の電源を切る/ケーブルを抜く、で止まる。