USB HAT MMDVM 環境構築手順書
Incus コンテナ(Debian 12 / amd64)+ MMDVM_HS USB HAT + TGIF 直結版
概要
実機 MMDVM_HS ハット(USB / CH340 接続)を x86 母艦上の Incus コンテナに収容し、MMDVMHost + DMRGateway(G4KLX 最新版をソースビルド)で TGIF に直結する RF ホットスポットを構築する手順です。Pi-Star / WPSD は使いません(ARM 前提のため x86 では動かない)。コンテナ hat1(実機ホスト名 HAT1)での検証済み手順に基づきます。
incus exec hat1 -- bash で入ったシェル、または SSH)で実施します。プロンプトが ocv@HAT1:~$ になっていることを確認してから作業してください。前提条件
動作確認した環境(検証時の実値)は以下のとおりです。
- 母艦: Ubuntu 24.04 LTS(amd64)+ Incus(ホスト名
ocv-pc) - コンテナ: Debian 12 Bookworm(
images:debian/12)、名前hat1、IP192.168.1.172 - モデム: MMDVM_HS ハット + USB キャリア基板(CH340、VID:PID
1a86:7523) - 自局: JJ2ZAR / DMR ID 4402519 + ESSID 73 = 440251973 / 430.730 MHz 単信
- 接続先: TGIF Network(DMRGateway 経由)
第 1 章:母艦での作業(ここだけコンテナの外)
母艦の仕事は 3 つだけです。①箱(コンテナ)を作る ②LAN の線をつなぐ(macvlan) ③USB の線をつなぐ(パススルー)。中身の構築は一切しません。
1. モデムの物理確認
# 母艦で
lsusb | grep -i 1a86
ls -l /dev/ttyUSB0/dev/ttyACM0 になります。以降の手順の ttyUSB0 を読み替えてください。lsusb の出力に 1a86:7523(CH340)が見え、/dev/ttyUSB0 が root:dialout 所有で生成されていれば OK です。2. コンテナ作成と macvlan
incus init images:debian/12 hat1
incus config device add hat1 eth0 nic nictype=macvlan parent=enp2s0
incus start hat1parent=は母艦の物理 NIC 名(ip linkで確認)。- macvlan により、コンテナは NAT ではなく物理 LAN に直接ぶら下がる(=家庭内の他機器から普通の 1 台として見える)。
incus exec hat1 -- bash を使ってください。3. USB モデムのパススルー
incus config device add hat1 mmdvm unix-char source=/dev/ttyUSB0 path=/dev/ttyUSB0 gid=20 mode=0660
incus config device add hat1 mmdvm-usb usb vendorid=1a86 productid=7523
incus restart hat1gid=20(dialout)を必ず付ける。付けないとコンテナ内で root root 所有になり、一般ユーザーの MMDVMHost がシリアルを開けません(gid はコンテナ内 getent group dialout の値。Debian 12 は 20)。usb 型デバイスの併用は USB 抜き差し時の自動復帰用の保険です。4. コンテナに入る
incus exec hat1 -- bashプロンプトが変わったら、以降の作業はすべてこの中です。incus ... コマンドはコンテナ内では使えません(母艦専用)。
hostnamectl 等が Failed to connect to bus で失敗する場合は起動レース(systemd がまだ上がりきっていない)です。数秒待って再実行してください。第 2 章:コンテナ内・OS 初期設定
以降、プロンプトは root@hat1 または ocv@HAT1 の想定です。
5. 固定 IP の設定
Debian 12 コンテナは systemd-networkd + DHCP が既定なので、静的設定に書き換えます。
cat > /etc/systemd/network/eth0.network << 'EOF'
[Match]
Name=eth0
[Network]
Address=192.168.1.172/24
Gateway=192.168.1.1
DNS=192.168.1.1
EOF
systemctl restart systemd-networkd確認(IP 付与とゲートウェイ・外向き疎通):
ip -4 addr show eth0
ping -c3 192.168.1.1
ping -c3 1.1.1.1eth0 に 192.168.1.172 が付き、ゲートウェイと外向き(apt に必要)の両方で ping が通れば OK です。6. ホスト名・SSH・ユーザー・タイムゾーン
hostnamectl set-hostname HAT1
apt update
apt install -y openssh-server sudo locales
systemctl enable --now ssh
# 作業ユーザー ocv(sudo 可・dialout 所属)
useradd -m -s /bin/bash -G sudo,dialout ocv
passwd ocv
# パスワード認証を許可
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart ssh
# タイムゾーン・ロケール
timedatectl set-timezone Asia/Tokyo
sed -i 's/^# *ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen
locale-gen
update-locale LANG=ja_JP.UTF-8
# journal 閲覧権限(ログ確認を ocv で行うため)
usermod -aG systemd-journal ocvssh ocv@192.168.1.172 で入れます。以降は SSH 越しに ocv で作業しても、root のままでも構いません(コマンド例は ocv + sudo 前提で書きます)。第 3 章:コンテナ内・ビルド
7. 依存パッケージ
sudo apt install -y git build-essential nlohmann-json3-dev libmosquitto-dev mosquitto mosquitto-clients
sudo systemctl enable --now mosquittonlohmann-json3-dev と libmosquitto-dev は必須。最新の G4KLX 版はログ基盤に nlohmann/json、状態出力に MQTT を使うため、無いとビルドが nlohmann/json.hpp: No such file / mosquitto.h: No such file で失敗します。localhost:1883 へ接続を試みるため、立てておくと警告が消え、将来のダッシュボード連携(MQTT 購読)にもそのまま使えます。8. MMDVMHost / DMRGateway のビルド
cd /opt
sudo git clone https://github.com/g4klx/MMDVMHost
sudo git clone https://github.com/g4klx/DMRGateway
sudo chown -R ocv:ocv /opt/MMDVMHost /opt/DMRGateway
cd /opt/MMDVMHost && make -j$(nproc)
cd /opt/DMRGateway && make -j$(nproc)確認(バイナリが生成されたか):
ls -l /opt/MMDVMHost/MMDVM-Host /opt/DMRGateway/DMRGatewayMMDVM-Host(バイナリ)、MMDVM-Host.ini(設定)です。MMDVMHost / MMDVMHost.ini という名前のファイルは存在しません。DMRGateway 側は DMRGateway / DMRGateway.ini です。DMRIds.dat)と XLX ホストファイル(XLXHosts.txt)はリポジトリに同梱されているため、別途ダウンロード不要です。第 4 章:コンテナ内・設定
構成の全体像です。MMDVMHost は TGIF に直接繋がず、必ずローカルの DMRGateway を経由します(TG の振り分け・書き換えのため)。
無線機 ⇄ RF(430.730 MHz) ⇄ MMDVM_HS(USB /dev/ttyUSB0)
│
[MMDVMHost] ← モデム制御・RF 送受信
│ UDP 62031/62032 (localhost)
[DMRGateway] ← ネットワーク振り分け
│ UDP 62031
TGIF Network9. MMDVM-Host.ini の設定
原本を退避してから、sed で必要箇所のみ書き換えます(テンプレートは全モード網羅の英国仕様なので、差分方式が安全)。対象パスは /opt/MMDVMHost/MMDVM-Host.ini です。
cd /opt/MMDVMHost
cp MMDVM-Host.ini MMDVM-Host.ini.orig
sed -i \
-e '/^\[General\]/,/^\[/{ s/^Callsign=.*/Callsign=JJ2ZAR/; s/^Id=.*/Id=440251973/; s/^Duplex=.*/Duplex=0/; s/^RFModeHang=.*/RFModeHang=300/; s/^NetModeHang=.*/NetModeHang=300/ }' \
-e '/^\[Info\]/,/^\[/{ s/^RXFrequency=.*/RXFrequency=430730000/; s/^TXFrequency=.*/TXFrequency=430730000/ }' \
-e '/^\[Log\]/,/^\[/{ s/^MQTTLevel=.*/MQTTLevel=0/ }' \
-e '/^\[DMR Id Lookup\]/,/^\[/{ s#^File=.*#File=/opt/MMDVMHost/DMRIds.dat# }' \
-e '/^\[Modem\]/,/^\[/{ s/^Protocol=.*/Protocol=uart/; s#^UARTPort=.*#UARTPort=/dev/ttyUSB0#; s/^RXOffset=.*/RXOffset=-300/ }' \
-e '/^\[CW Id\]/,/^\[/{ s/^Enable=.*/Enable=0/ }' \
-e '/^\[POCSAG\]/,/^\[/{ s/^Enable=.*/Enable=0/ }' \
-e '/^\[FM\]/,/^\[/{ s/^Enable=.*/Enable=0/ }' \
-e '/^\[DMR Network\]/,/^\[/{ s/^Enable=.*/Enable=1/; s/^Jitter=.*/Jitter=200/ }' \
-e '/^\[FM Network\]/,/^\[/{ s/^Enable=.*/Enable=0/ }' \
MMDVM-Host.ini各設定の意味と注意:
| 項目 | 値 | 説明 |
|---|---|---|
| Callsign / Id | JJ2ZAR / 440251973 | Id は 7 桁 DMR ID + 2 桁 ESSID の 9 桁。既存機(素の 4402519)と並行運用するため ESSID=73 で分離。同一 ID で TGIF に二重ログインすると 1〜2 分周期の再接続ループになる |
| Duplex=0 | 単信 | MMDVM_HS 単体はデュプレクサ無し=単信(RX=TX 同一周波数) |
| RFModeHang / NetModeHang=300 | — | テンプレート既定は 10 / 3 と短く、通話のたびにモード再設定が走って頭にラグが乗る。Pi-Star 同等の 300 に |
| Protocol=uart / UARTPort | /dev/ttyUSB0 | テンプレート既定は Protocol=null(=モデム無し)。キー名は Port= ではなく UARTPort= |
| RXOffset=-300 | 個体値 | 本個体の実測最適値(BER 0.0〜0.1%)。個体差があるので手順 13 の方法で各自追い込むこと |
| MQTTLevel=0 | ログは journal | MQTT へのログ出力は使わない(画面 / journal で見る) |
| [DMR Network] Enable=1 | — | 既定値が LocalPort=62032 / GatewayPort=62031(127.0.0.1)で、そのまま DMRGateway と噛み合う。Enable を 1 にするだけ |
| CW/POCSAG/FM Enable=0 | — | 英国仕様テンプレートで既定 ON のものを止める |
10. DMRGateway.ini の設定(TGIF)
テンプレートには TGIF 用ブロックが [DMR Network 3](Name=TGIF_Network)として最初から用意されています。新規に書くのではなく、そこを有効化します。対象パスは /opt/DMRGateway/DMRGateway.ini です。
cd /opt/DMRGateway
cp DMRGateway.ini DMRGateway.ini.orig
# 基本設定と TGIF 有効化
sed -i \
-e '/^\[General\]/,/^\[/{ s/^RuleTrace=.*/RuleTrace=1/ }' \
-e '/^\[Log\]/,/^\[/{ s/^MQTTLevel=.*/MQTTLevel=0/ }' \
-e '/^\[Voice\]/,/^\[/{ s/^Enabled=.*/Enabled=0/ }' \
-e '/^\[XLX Network\]/,/^\[/{ s/^Enabled=.*/Enabled=0/ }' \
-e '/^\[DMR Network 3\]/,/^\[/{ s/^Enabled=.*/Enabled=1/; s/^Password=.*/Password="<YOUR_TGIF_PASSWORD>"/ }' \
DMRGateway.ini
# 既定の TG リライトを撤去し、TS2 全通し(PassAll)に置き換え
sed -i \
-e '/^\[DMR Network 3\]/,/^\[/{ /^TGRewrite0=/d; /^SrcRewrite0=/d }' \
DMRGateway.ini
sed -i \
'/^\[DMR Network 3\]/,/^\[/ s/^\(Name=TGIF_Network\)/\1\nPassAllTG=2\nPassAllPC=2/' \
DMRGateway.ini確認(この形になっていること):
sed -n '/^\[DMR Network 3\]/,/^# Local HBLink 1/p' DMRGateway.ini期待する最終形:
[DMR Network 3] Enabled=1 Name=TGIF_Network PassAllTG=2 PassAllPC=2 Address=tgif.network Password="<YOUR_TGIF_PASSWORD>" Port=62031 Location=0 Debug=0
Password には TGIF セルフケアのパスワードを設定します。公開リポジトリに置く場合は必ずマスクすること。TGRewrite0=1,1,2,1,... はスロット 1 前提のルールで、単信ホットスポット(実質 TS2 運用)の RF はどのルールにも当たらず全部捨てられます(Rule Trace に not matched so rejected と出る)。PassAllTG=2 / PassAllPC=2 で「TS2 に来たものは全部 TGIF へ」にします。RuleTrace=1 は振り分けの動作確認用です。安定後は 0 に戻します(手順 12)。Id= は書きません。ログイン ID は MMDVMHost の [General] Id がそのまま使われます。第 5 章:試験起動と確認
11. 手動起動と成功サイン
ターミナルを 2 つ開き(SSH を 2 本張る)、DMRGateway → MMDVMHost の順で起動します。
# ターミナル A
/opt/DMRGateway/DMRGateway /opt/DMRGateway/DMRGateway.ini# ターミナル B
/opt/MMDVMHost/MMDVM-Host /opt/MMDVMHost/MMDVM-Host.ini成功サイン(この順で出る):
- B:
MMDVM protocol version: 1, description: MMDVM_HS-v1.6.1 ...→ USB モデムを掴めた決定的証拠。ここで止まる場合は付録 A 参照。 - A:
MMDVM has connected→ MMDVMHost⇄DMRGateway のローカル接続成立。 - A:
TGIF_Network, Logged into the master successfully→ TGIF ログイン成功。 - 無線機からカーチャンク → B に
received RF voice header from <ID> to TG <n>、A にRule Trace, ... PassAllTG TGIF_Network Slot=2: matched→ RF→TGIF の振り分け成立。 - TGIF 側から音声が来ると B に
received network voice header ...→ TGIF→RF(送信)成立。
第 6 章:常駐化(systemd)
12. サービス化と日常運用コマンド
手動起動はターミナルを閉じると止まります。systemd に載せて「コンテナ起動=ホットスポット稼働」にします。
sudo tee /etc/systemd/system/dmrgateway.service > /dev/null << 'EOF'
[Unit]
Description=DMRGateway
After=network-online.target mosquitto.service
[Service]
User=ocv
ExecStart=/opt/DMRGateway/DMRGateway /opt/DMRGateway/DMRGateway.ini
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo tee /etc/systemd/system/mmdvm-host.service > /dev/null << 'EOF'
[Unit]
Description=MMDVMHost
After=dmrgateway.service
Wants=dmrgateway.service
[Service]
User=ocv
ExecStart=/opt/MMDVMHost/MMDVM-Host /opt/MMDVMHost/MMDVM-Host.ini
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now dmrgateway mmdvm-host
systemctl status dmrgateway mmdvm-host --no-pageractive (running) になれば完成です。動作確認が済んだら RuleTrace を戻しておきます。
sudo sed -i '/^\[General\]/,/^\[/{ s/^RuleTrace=.*/RuleTrace=0/ }' /opt/DMRGateway/DMRGateway.ini
sudo systemctl restart dmrgateway日常運用コマンド(すべてコンテナ内):
systemctl status mmdvm-host dmrgateway # 生死確認
journalctl -u mmdvm-host -f # RF/QSO ログを流し見
journalctl -u dmrgateway -f # TGIF 接続・振り分けログ
sudo systemctl restart mmdvm-host # ini 変更の反映(MMDVM-Host.ini)
sudo systemctl restart dmrgateway # ini 変更の反映(DMRGateway.ini)第 7 章:調整
13. RXOffset(BER 追い込み)
RF 受信の BER(end of voice transmission ... BER: x.x%)が高いと音が崩れます。ADF7021 系ハットは水晶の個体差で数百 Hz ずれているのが普通で、RXOffset で補正します。
手順(二分探索):
journalctl -u mmdvm-host -fを流したまま、無線機から短く送信して BER を記録(例:RXOffset=0で 6〜9%)。- 値を振って再測定(例: 300 に変更してサービス再起動)。
- 下がれば同方向に、上がれば逆方向(-300)に。±100 刻みで最小点を探す。
- 本個体は -300 で BER 0.0〜0.1% に収束(目標: 1% 以下)。
sudo sed -i '/^\[Modem\]/,/^\[/{ s/^RXOffset=.*/RXOffset=300/ }' /opt/MMDVMHost/MMDVM-Host.ini
sudo systemctl restart mmdvm-host14. 頭切れ・音途切れ関連(ModeHang / Jitter)
| 症状 | 原因 | 対処 |
|---|---|---|
| 2 オーバー目以降も毎回頭にラグ | ModeHang が短い(既定 10 / 3 s)でモードが冷める | RFModeHang / NetModeHang=300(手順 9 で設定済み) |
| ネット→RF の立ち上がりが遅い | Jitter バッファ充填 | Jitter=200(既定 360)。下げすぎるとネット揺らぎで音切れ |
| ネット音声が「プツッ」と途切れ、ログに lost audio for XXXms filling in | ネット側パケットロス | Jitter を 240〜300 へ戻す(立ち上がりとのトレードオフ) |
| 特定の局だけ頭が約 1 秒欠け、ログが received network late entry(voice header ではない) | 送信元 / 経路の問題。ホットスポットでは直せない。ヘッダ無しで届いたストリームは、埋め込み LC の再構成完了(約 0.7〜1 秒)まで電波を出せない仕様 | 送信元側で対処(例: 自動応答局なら音声先頭に無音を焼き込む)。受信側の設定変更は無意味 |
received network voice header from ...、異常なら received network late entry from ...。他のホットスポット(Pi-Star 等)でも同じ送信が late entry になるなら、受信側はシロと確定できます。付録 A:トラブルシューティング早見表
| 症状 | 原因 | 対処 |
|---|---|---|
| ビルドが nlohmann/json.hpp / mosquitto.h で失敗 | dev パッケージ不足 | apt install nlohmann-json3-dev libmosquitto-dev(手順 7) |
| MMDVMHost.ini が見つからない | ファイル名はハイフン付き | MMDVM-Host.ini / バイナリは MMDVM-Host(手順 8) |
| モデム版数が出ず起動が止まる/Cannot open the serial port | ①アップグレードスイッチが書換側 ②/dev/ttyUSB0 の権限 ③二重起動 | ①スイッチを運用側に ②パススルーに gid=20 mode=0660(手順 3) ③pgrep -a -f MMDVM-Host で古いプロセスを kill |
| Protocol=null のまま/Port= を書いても効かない | キー名違い | Protocol=uart + UARTPort=(手順 9) |
| TGIF ログインは成功するが RF が流れない。Rule Trace が not matched so rejected | 既定 TGRewrite がスロット 1 前提 | TGRewrite0 / SrcRewrite0 を消し PassAllTG=2 / PassAllPC=2(手順 10) |
| TGIF が 1〜2 分周期でタイムアウト・再接続を繰り返す | DMR ID(ESSID 含む 9 桁)が他機と重複 | ESSID を変えて分離(手順 9) |
| ini を直したのに挙動が変わらない | サービス未再起動 | systemctl restart mmdvm-host(または dmrgateway) |
| USB を抜き差ししたらモデムが見えない | デバイスノード消失 | 母艦で incus restart hat1(usb 型デバイス併用登録で大抵は自動復帰) |
| 母艦から ssh ocv@192.168.1.172 に繋がらない | macvlan の母艦⇄コンテナ遮断 | LAN 上の別端末から SSH するか、母艦からは incus exec hat1 — bash |
| journalctl で自分のログが見えない | 権限 | usermod -aG systemd-journal ocv して再ログイン(手順 6) |
付録 B:設計メモ
なぜ MMDVMHost + DMRGateway をソースビルドするのか
- Pi-Star / WPSD は Raspberry Pi(ARM)用ディストリビューションで、x86 母艦では使えない。
- x86 で実機モデムを扱う標準構成が「素の G4KLX スタック」。
- DMRGateway を挟むのは、TG の振り分け・書き換え・複数ネットワーク併用のため(TGIF 単独でも、将来の拡張を考えると挟んでおくのが定石)。
OLED が使えない理由(USB 接続時)
MMDVM_HS ハット上の OLED(SSD1306)は、モデムチップ(STM32)ではなく 40 ピンヘッダ越しの Raspberry Pi の I2C に配線されており、描画するのは Pi 上の MMDVMHost です。USB キャリア基板は UART と電源しか結線していないため、OLED への電気的経路が存在しません。Display=OLED にしても動かないのは仕様です。物理表示が欲しい場合の現実解:
- Nextion ディスプレイ — モデムのシリアルにトンネルできる(
Display=Nextion/Port=modem)唯一の直結表示。USB 接続でも動く。 - MQTT 連携の自作表示器 / ダッシュボード — MMDVMHost の
MQTTLevel=1で状態を mosquitto に流し、ESP32+OLED や Web ページで購読・表示。
DMR ID と ESSID
- ネットワークログイン ID = 7 桁 DMR ID + 2 桁 ESSID の 9 桁(例: 4402519 + 73 = 440251973)。
- 同一 ID で同一ネットワークに複数ログインすると重複扱いで接続ループになるため、機体ごとに ESSID を変える。
- ID は MMDVMHost の
[General]Idに書く(DMRGateway はそれを引き継ぐ)。
付録 C:XLX を併用したい場合
DMRGateway は TGIF と XLX の同時接続が可能です(本構成では TGIF 専用に決定したため無効化済み)。使う場合の要点のみ:
# XLX ホストファイルを公式 API から自動生成(既存ファイルは上書きされる)
sudo bash /opt/DMRGateway/XLXHostsupdate.sh /opt/DMRGateway/XLXHosts.txt
grep -E '^<番号>;' /opt/DMRGateway/XLXHosts.txt # 目的の XLX があるか確認[XLX Network] の主要キー: Enabled=1 / Startup=<XLX番号> / Module=<モジュール文字> / Base=<TGベース> / Slot=2 / File=/opt/DMRGateway/XLXHosts.txt。
- モジュール切替 TG = Base + モジュール番号(A=1 … Z=26。例: Base=4026 で module Z → TG4052)。
- 公式リスト(xlxapi.rlx.lu)に未登録の私設 XLX は自動生成に含まれないため、
<番号>;<IP>;<既定TG>形式で 1 行手動追記する(その場合は XLXHostsupdate.sh を実行すると消えるので再実行しないこと)。 - XLX と TGIF は同じ TS2 に同居するため、
RuleTrace=1で振り分けを必ず目視確認すること。
まとめ
USB HAT MMDVM 環境構築手順書(Incus コンテナ / Debian 12 / amd64 / TGIF 直結版)。コンテナ hat1(HAT1, 192.168.1.172)における実機検証(2026-07-13〜15)に基づきます。
🏛 JJ2YYK あいちデジタルコミュニケーションハムクラブ