Asterisk+Radio

2024年10月22日 (火) 09:15時点におけるTakahashi (トーク | 投稿記録)による版 (pjsua設定ファイル)

ここでいうRadioは無線機のことなのです。
注:当初、レピータと表記していましたが、うちらの業界的にはゲートウェイだろうってことでゲートウェイに改めました。

目次

ニーズ

PBXに無線機、特定小電力を接続するニーズは結構あります。特に店舗運営をしているような場合、フロアは無線機を使っているケースが少なくありません。というわけで以前から統合したソリューションを出しているメーカー/ベンダーもあります。んじゃあAsteriskでもやってしまおうという計画。
(SIPを喋る無線基地局もあります)

注意

この方法を使うとアマチュア無線機とか業務無線機(特小とかデジ簡ではないやつ、免許局)も繋ぐことができますが、技術的なこと以外の問題点が多々あるので繋ぐ場合には自分で調べてください。業務上で無線と電話を繋いでいる例はもちろんあります。

しくみ

無線機とはいっても音声のやりとりなので基本的な接続は簡単で、Asteriskからオーディオを入出力してやれば無線と音声でやりとりできます。
AstRadio1.png
とはいえ問題はありまして、PTT(Push-To-Talk)をどうするかです。通常、無線機は半二重で誰かが喋っている間は他は聞く動作になり、喋りたい時にはPTTを押して喋ります。なのでAsteriskからPTTを制御しなくてはなりません。
AstRadio2.png
でまあ無線機のヘッドセットとか外部マイクにはPTT制御がついているため、これを何等かの方法で制御してやればAsteriskから無線機が制御できます。思いつく簡単な方法としては入手性の容易なUSBシリアルを使って、RTSとかDTRの制御線でPTTを制御する方法です。
AstRadio3.png
ですがここで再度問題が。Asteriskを入れるようなサーバとかアプライアンスはオーディオインタフェースが付いていなかったりするわけで、その場合、USBオーディオ(安い)を増設して使う方法が考えられます。
AstRadio4.png
まあ、これで良いといえば良いのですが、この方法だと回路とかケーブルを自作しないとできないのでちょいと面倒です。できれば何も自作したくないですよね。電子回路の知識とかまで要求されるのもアレですし。そして、この方法のもうひとつの問題は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/

DigiRigMobile.JPG
USBインタフェースはUSB-Cコネクタで写真の左側、右側が無線機の接続用です。無線機接続用にAUDIOとSERIALがありますが、今回使用するのはAUDIOだけです。SERIALは無線機制御(CAT)のインタフェースに繋ぐ用です。
無線機と接続するには無線機のメーカ毎のケーブルが売られているので、これも併せて買えばOKです。今回はICOM用を入手して使用しています。
DigiRigICOM.JPG
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を動かすのもアリです。
今回の接続は下図のようになります。
AstRadio5.png

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を制御するだけです。前述のようにDigiRigはUSBシリアルも内蔵しており、RTSでPTTが制御できるようになっています。RTSを制御すればいいので簡単ですねと言いたいところですが、デバイスに対するIOCTLを発行しないとなのでちょいと面倒です。なので、この部分は書いておきました。
Asteriskサンプル設定ファイルと同じところにpttctrlというプログラムを置いておきます。
https://github.com/takao-t/asterisk-conf/tree/main/pttctrl
これをコンパイルするには

gcc pttctl pttctl.c -lrt

で行います。TTYデバイスが /dev/ttyUSB0 ではない場合には書き換えてからコンパイルしてください。
このプログラム自体はデーモン(常駐)化して使用します。やり方は後で説明しますが、とりあえずフォアグラウンドで試してみてください。
PTTを制御するにはパイプ(FIFO)に対して書き込みを行います。/tmp/PTTCTL という名前でパイプをつくりますので、これに対して書き込んでください。使えるコマンドはON,OFF,ALTでPTTをオンにする(押した状態)なら

echo "ON" > /tmp/PTTCTL

オフにする(離す)なら

echo "OFF" > /tmp/PTTCTL

を実行します。ですがこれだけだと押したかどうかを判断するのが面倒なので、状態反転も持たせてあります。

echo "ALT" > /tmp/PTTCTL

で現在の状態を反転します。オフならオンに、オンならオフになります。
なお、特小の場合には連続送信は3分未満となっている(無線機が勝手に切る)ので、このプログラムでは送信状態になってから170秒でオフにするようにしています。

PTT制御をネットワーク化する

今回、別な筐体にpjsuaを入れたので制御が少し面倒になります。AsteriskからPTTを制御するにはpjsuaが動作しているマシン(Raspberry Pi)に対してPTTの制御を投げる必要があります。
処理を簡単にするため、汎用のTCP通信ツールを使ってしまいましょう。くれぐれもこのままインターネット上に公開しないように。セキュリティが必要な場合にはファイアウォールするか別な手段を使ってください。
ここではnetrwを使います。netcatでもかまいませんが。
https://mamuti.net/netrw/index.en.html
Debian系にはパッケージが用意されているのでインストールします。

apt-get install netrw

Raspberry Pi側はネットからコマンドを受信して処理するので netread を使います。こんな感じ

netread -o /tmp/PTTCRL 5091

Netreadptt.png
ネットから受信した内容をパイプに書き込むので、送信すべきデータはパイプに書き込むものと同じになるのでとても単純です。ただし、netreadは1行読み込むと終了するので、以下のようなシェルを作ります。

#!/bin/sh

while :
do
    netread -o /tmp/PTTCTL 5091
done

簡単ですね。


PTTのテストをする場合には

echo "ALT" | netwrite localhost(あるいはIPアドレス) 5091

とやるとテストできます。
そもそもの話、PTT制御をsocketで書けば簡単じゃないかという話もありますが暇があったら書きます。

まとめる

ここまでの作業をまとめて無線ゲートウェイをつくります。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から使う

このシステムの完成図は次のようになります。
AstRadio6.png
無線ゲートウェイができたので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のデバイスが複数あっても明示指定して使えるようにしたい →解決(したつもり)

  • 無線機が音を出してない時にパツパツ音がするのを止めたい →どうも無線機のせいかも。無線機の電源を切る/ケーブルを抜く、で止まる。