Skip to content

DMR Network Bridge

vex-dmr-bridge links a Vexillum DMR instance to one or more DMR networks that speak the standard Homebrew/MMDVM repeater protocol. It provides the kind of network peering a legacy urfd reflector offers.

It works with any Homebrew master, including:

  • Brandmeister
  • TGIF
  • FreeDMR
  • FreeSTAR SystemX
  • DMR+
  • the DMR mode of another Vexillum or urfd reflector

It does not work with non-Homebrew protocols, such as urfd’s XLX-style Brandmeister interlink or other modes. For Brandmeister’s server-to-server protocol, see vex-openbridge-bridge.

The bridge opens one Homebrew connection for each remote network, logging in the way a hotspot would. It opens one more connection into the Vexillum DMR instance, logging in the way a repeater would. Then it relays DMRD voice frames between them.

Both sides carry the same 55-byte Homebrew DMRD frame, so the audio passes through unchanged. The bridge only rewrites the destination talkgroup and timeslot, plus the repeater ID on frames going to a remote.

You can pass -remote more than once to connect several networks to the same Vexillum talkgroup, for example Brandmeister TG 91 and TGIF TG 91 at the same time. The bridge remembers which network each call came from and does not send it back to that network when Vexillum echoes it. Without that, bridging two networks to one talkgroup would create an audio loop.

  • In the Vexillum admin interface, note the DMR instance’s Talk TG and password. Vexillum only forwards group calls to that talkgroup on slot 2.
  • Register the bridge with each remote network the way you would a hotspot, and get its repeater ID and password. Use an ID dedicated to the bridge.
git clone https://repo.kc1awv.net/Vexillum/vex-dmr-bridge.git
cd vex-dmr-bridge
go build ./cmd/vex-dmr-bridge
export VEX_DMR_PASSWORD='...'   # the Vexillum DMR instance password
export BM_PASSWORD='...'        # Brandmeister hotspot password
export TGIF_PASSWORD='...'      # TGIF hotspot password

./vex-dmr-bridge \
  -vex-master 127.0.0.1:62031 \
  -vex-id 9990001 \
  -vex-callsign N0CALL \
  -vex-talkgroup 91 \
  -remote "name=bm,master=3101.brandmeister.network:62031,id=3120001,callsign=N0CALL,talkgroup=91,slot=2" \
  -remote "name=tgif,master=tgif.network:62031,id=3120002,callsign=N0CALL,talkgroup=91,slot=2" \
  -status-addr 127.0.0.1:8099

Each remote’s password is read from <NAME>_PASSWORD, using the remote’s name in upper case, unless you set password_env. The bridge will not start if any password is missing.

Once it’s running, designate the bridge on the DMR instance, matching its -vex-id repeater ID, so its calls are reported as bridged rather than as local talkers.

Flag Default Description
-vex-master (required) Vexillum DMR instance, host:port
-vex-id (required) Repeater ID the bridge presents to Vexillum
-vex-callsign (required) Callsign the bridge presents to Vexillum
-vex-talkgroup (required) Must exactly match the DMR instance’s Talk TG
-vex-password-env VEX_DMR_PASSWORD Environment variable holding the instance password
-vex-color-code 1 Color code reported to Vexillum
-vex-power 1 Power in watts reported to Vexillum
-vex-location (empty) Location reported to Vexillum
-vex-description DMR network bridge Description reported to Vexillum
-status-addr :8099 Address for the /status endpoint

Each -remote value is a comma-separated list of key=value pairs. You need at least one -remote, and each needs a unique name.

Key Required Default Description
name yes Label used in logs, /status, and the default password variable
master yes Remote Homebrew master, host:port
id yes Repeater/hotspot ID registered with that network
callsign yes Callsign registered with that network
talkgroup yes Talkgroup on the remote network to bridge
password_env no <NAME>_PASSWORD Environment variable holding this network’s password
slot no 2 Timeslot used on the remote network
color_code no 1 Color code reported to the remote
power no 1 Power reported to the remote
reject_retry_seconds no 5 Seconds to wait before logging back in after the master rejects (MSTNAK) or closes (MSTCL) the session. 0 retries immediately.
rx_frequency, tx_frequency, latitude, longitude, height, location, description, url, options no empty/zero Repeater metadata reported to the remote

reject_retry_seconds matters when a master keeps rejecting the login, for example because your DMR ID hasn’t been authorized yet. Some masters (FreeSTAR SystemX has been seen doing this) reject repeatedly, and retrying immediately turns that into a tight loop against their server.

GET /status returns one entry named vex, plus one entry for each remote:

{
  "vex": {"state": "running", "last_event": "connected", "updated_at": "..."},
  "bm": {"state": "running", "last_event": "connected", "updated_at": "..."},
  "tgif": {"state": "awaiting-login-ack", "last_event": "timeout", "updated_at": "..."}
}