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)での検証済み手順に基づきます。

⚠️ 作業場所の原則。第 1 章のみ母艦(コンテナの外)で実施します。コンテナという「箱」と、箱へのネットワーク・USB の配線を作る作業だけです。第 2 章以降はすべてコンテナの中(incus exec hat1 -- bash で入ったシェル、または SSH)で実施します。プロンプトが ocv@HAT1:~$ になっていることを確認してから作業してください。

前提条件

動作確認した環境(検証時の実値)は以下のとおりです。

  • 母艦: Ubuntu 24.04 LTS(amd64)+ Incus(ホスト名 ocv-pc
  • コンテナ: Debian 12 Bookworm(images:debian/12)、名前 hat1、IP 192.168.1.172
  • モデム: MMDVM_HS ハット + USB キャリア基板(CH340、VID:PID 1a86:7523
  • 自局: JJ2ZAR / DMR ID 4402519 + ESSID 73 = 440251973 / 430.730 MHz 単信
  • 接続先: TGIF Network(DMRGateway 経由)
✅ 上記はコンテナ hat1(HAT1, 192.168.1.172)における実機検証済みの構成です。

第 1 章:母艦での作業(ここだけコンテナの外)

母艦の仕事は 3 つだけです。①箱(コンテナ)を作る ②LAN の線をつなぐ(macvlan) ③USB の線をつなぐ(パススルー)。中身の構築は一切しません。

1. モデムの物理確認

⚠️ ハットのアップグレードスイッチは運用側(横)にしてから USB を挿すこと。ファーム書換モード(B 側リセット長押し)のままではモデムを掴めません。
# 母艦で
lsusb | grep -i 1a86
ls -l /dev/ttyUSB0
⚠️ 個体によっては /dev/ttyACM0 になります。以降の手順の ttyUSB0 を読み替えてください。
✅ 確認: lsusb の出力に 1a86:7523(CH340)が見え、/dev/ttyUSB0root:dialout 所有で生成されていれば OK です。

2. コンテナ作成と macvlan

incus init images:debian/12 hat1
incus config device add hat1 eth0 nic nictype=macvlan parent=enp2s0
incus start hat1
  • parent= は母艦の物理 NIC 名(ip link で確認)。
  • macvlan により、コンテナは NAT ではなく物理 LAN に直接ぶら下がる(=家庭内の他機器から普通の 1 台として見える)。
⚠️ macvlan の制約。母艦⇄コンテナ間の直接通信は遮断されることがあります。コンテナへの SSH は LAN 上の別の端末から行うか、母艦からは 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 hat1
⚠️ gid=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.1
✅ 確認: eth0192.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 ocv
✅ ここまでで、LAN 上の別端末から ssh 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 mosquitto
⚠️ nlohmann-json3-devlibmosquitto-dev は必須。最新の G4KLX 版はログ基盤に nlohmann/json、状態出力に MQTT を使うため、無いとビルドが nlohmann/json.hpp: No such file / mosquitto.h: No such file で失敗します。
📝 mosquitto(ブローカー本体)は必須ではありませんが、両プログラムが起動時に 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/DMRGateway
⚠️ 生成物・設定ファイルの名前はハイフン付き。MMDVM-Host(バイナリ)、MMDVM-Host.ini(設定)です。MMDVMHost / MMDVMHost.ini という名前のファイルは存在しません。DMRGateway 側は DMRGateway / DMRGateway.ini です。
📝 DMR ID テーブル(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 Network

9. 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 / IdJJ2ZAR / 440251973Id は 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ログは journalMQTT へのログ出力は使わない(画面 / 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 セルフケアのパスワードを設定します。公開リポジトリに置く場合は必ずマスクすること。
⚠️ PassAll への置き換えは必須。テンプレート既定の TGRewrite0=1,1,2,1,... はスロット 1 前提のルールで、単信ホットスポット(実質 TS2 運用)の RF はどのルールにも当たらず全部捨てられます(Rule Trace に not matched so rejected と出る)。PassAllTG=2 / PassAllPC=2 で「TS2 に来たものは全部 TGIF へ」にします。
📝 RuleTrace=1 は振り分けの動作確認用です。安定後は 0 に戻します(手順 12)。
📝 DMRGateway 側に 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(送信)成立。
✅ 確認できたら両方「Ctrl + C」で止め、次章で常駐化します。

第 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-pager
✅ 両方 active (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)
⚠️ ini を編集しただけでは反映されません。必ず該当サービスを restart すること。

第 7 章:調整

13. RXOffset(BER 追い込み)

RF 受信の BER(end of voice transmission ... BER: x.x%)が高いと音が崩れます。ADF7021 系ハットは水晶の個体差で数百 Hz ずれているのが普通で、RXOffset で補正します。

手順(二分探索):

  1. journalctl -u mmdvm-host -f を流したまま、無線機から短く送信して BER を記録(例: RXOffset=0 で 6〜9%)。
  2. 値を振って再測定(例: 300 に変更してサービス再起動)。
  3. 下がれば同方向に、上がれば逆方向(-300)に。±100 刻みで最小点を探す。
  4. 本個体は -300 で BER 0.0〜0.1% に収束(目標: 1% 以下)。
sudo sed -i '/^\[Modem\]/,/^\[/{ s/^RXOffset=.*/RXOffset=300/ }' /opt/MMDVMHost/MMDVM-Host.ini
sudo systemctl restart mmdvm-host
⚠️ 最適値は個体ごとに違います。この -300 をそのまま別個体に使い回さないこと。

14. 頭切れ・音途切れ関連(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 秒)まで電波を出せない仕様送信元側で対処(例: 自動応答局なら音声先頭に無音を焼き込む)。受信側の設定変更は無意味
📝 late entry かどうかはログで判別できます。正常なら 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)に基づきます。

📝 執筆:JI2TAB(尾張旭 DMR デジピーター 管理人)
🏛 JJ2YYK あいちデジタルコミュニケーションハムクラブ

JJ2YYK
  • JJ2YYK

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です