Connect your Motorola IPSC repeater to MMDVM DMR Masters.
ipsc2mmdvm is a protocol bridge that translates between Motorola's IP Site Connect (IPSC) protocol and the MMDVM Protocol. This lets your IPSC-only repeater talk to one or more DMR masters such as BrandMeister and TGIF simultaneously, with DMRGateway-compatible rewrite rules for routing talkgroups between networks.
flowchart LR
A["Motorola<br>DMR Repeater"] <-->|"Ethernet<br>IPSC protocol<br>(direct cable)"| B
B["ipsc2mmdvm<br>(Raspberry Pi <br>or Linux box)"] <-->|"Internet<br>MMDVM protocol"| C[DMR Master 1<br>e.g. BrandMeister]
B <-->|"Internet<br>MMDVM protocol"| D[DMR Master 2<br>e.g. TGIF]
Your repeater connects directly via Ethernet cable to the box running ipsc2mmdvm. The software acts as an IPSC master to the repeater and forwards voice and data traffic to and from one or more DMR masters over the internet. DMRGateway-style rewrite rules let you route specific talkgroups to specific masters.
- A Motorola IPSC-capable DMR repeater
- A Raspberry Pi (any model with Wi-Fi and an Ethernet port) or any Linux box with a spare NIC
- An Ethernet cable to connect the repeater directly to the Pi/Linux box
- Internet access on the Pi/Linux box (via Wi-Fi on a Raspberry Pi, or a second NIC on a Linux box)
- A DMR Master with a registered repeater ID to connect to (e.g. BrandMeister)
Download the latest release tarball for your platform from the GitHub Releases page. For a Raspberry Pi, grab the linux_arm64 build, for desktop Linux use linux_amd64. Extract it and move the binary to your PATH:
tar xzf ipsc2mmdvm_*_linux_arm64.tar.gz
sudo mv ipsc2mmdvm /usr/local/bin/ipsc2mmdvmDownload the example config, edit it, then move it into place:
wget https://raw.githubusercontent.com/USA-RedDragon/ipsc2mmdvm/main/config.example.yaml -O ipsc2mmdvm.yaml
nano ipsc2mmdvm.yaml
sudo mv ipsc2mmdvm.yaml /etc/ipsc2mmdvm.yamlHere is the full example config with comments:
log-level: info
ipsc:
interface: "eth0" # The network interface connected to your repeater
port: 50000 # UDP port the repeater will connect to
ip: "10.10.250.1" # IP address assigned to the interface (must match repeater's Gateway IP)
subnet-mask: 24 # Subnet mask (24 = 255.255.255.0)
auth:
enabled: false # Set to true if you configured an auth key in CPS
key: "" # Hex string, up to 40 characters (must match CPS)
metrics:
enabled: false # Enable Prometheus metrics endpoint
address: ":9100" # Address to serve metrics on (e.g. ":9100" for all interfaces)
mmdvm:
- name: "BrandMeister" # Friendly name for logging
master-server: "3104.master.brandmeister.network:62031" # BrandMeister master (see below)
password: "passw0rd" # Your BrandMeister hotspot password
callsign: N0CALL # Your callsign
radio-id: 123456789 # Your registered repeater DMR ID
# Frequencies in Hz:
rx-freq: 429075000
tx-freq: 424075000
color-code: 7 # Must match your repeater's color code (0-15)
# Optional, reported to BrandMeister:
# latitude: 30.000000
# longitude: -97.000000
height: 3 # Antenna height in meters
location: "My City, ST"
# description: ""
# url: ""
# Rewrite rules (optional, DMRGateway-compatible)
# tg-rewrite:
# - from-slot: 1
# from-tg: 9
# to-slot: 1
# to-tg: 9
# range: 1
# Add more masters for multi-network support:
# - name: "TGIF"
# master-server: "tgif.network:62031"
# password: "secret"
# callsign: N0CALL
# radio-id: 123456789
# rx-freq: 429075000
# tx-freq: 424075000
# color-code: 7
# height: 3
# tg-rewrite:
# - from-slot: 2
# from-tg: 31665
# to-slot: 2
# to-tg: 31665
# range: 1Config notes:
ipsc.interface- The name of the network interface physically connected to your repeater. On a Raspberry Pi this is typicallyeth0. Runip linkto see your interface names.ipsc.ip- The IP address ipsc2mmdvm assigns to that interface. This becomes the "Master IP" in your repeater's CPS config, and also the gateway for the repeater. Pick any private IP (e.g.10.10.250.1).ipsc.port- The UDP port to listen on. The default50000works fine. Must match the "Master UDP Port" in CPS.mmdvm- A YAML array of DMR master connections. Each entry is a separate master. You can connect to as many masters as you like.mmdvm[].name- A friendly name for this network, used in log messages (e.g."BrandMeister","TGIF").mmdvm[].master-server- The master's host and port. For BrandMeister, find the master covering your region in the BrandMeister Master Server List. The format ishost:port(e.g.3104.master.brandmeister.network:62030).mmdvm[].password- Your hotspot security password, such as the one set in your BrandMeister self-care dashboard.mmdvm[].radio-id- Your repeater's DMR ID, registered at radioid.net.
Open your repeater's codeplug in the Motorola Customer Programming Software (CPS) and make the following changes:
Important: You must enable Expert Mode first: go to View → Expert in the CPS menu bar.
| Setting | Value |
|---|---|
| DHCP | Disabled |
| Ethernet IP | A static IP on the same subnet as ipsc.ip (e.g. 10.10.250.2) |
| Gateway IP | The ipsc.ip value from your config (e.g. 10.10.250.1) |
| Gateway Netmask | Matching your subnet-mask (e.g. 255.255.255.0 for /24) |
| Setting | Value |
|---|---|
| Link Type | Peer |
| Master IP | The ipsc.ip value from your config (e.g. 10.10.250.1) |
| Master UDP Port | The ipsc.port value from your config (e.g. 50000) |
| Authentication Key | (Optional) Up to 40 hex characters. If set, enable ipsc.auth.enabled and put the same key in ipsc.auth.key in the config. |
Write the codeplug to the repeater.
- Plug an Ethernet cable directly from your repeater's Ethernet port to the Ethernet port on your Raspberry Pi (or spare NIC on your Linux box).
- Make sure the Pi/Linux box has internet access through a different interface (Wi-Fi on a Pi, or a second NIC).
Note: The Ethernet interface connected to the repeater is dedicated to ipsc2mmdvm. Do not use it for anything else, ipsc2mmdvm will assign it an IP address automatically.
ipsc2mmdvm requires root privileges to configure the network interface. Run it from the directory containing your config file, or copy the config to the working directory:
sudo ipsc2mmdvmBy default, ipsc2mmdvm looks for config.yaml in the current directory. You can also place the config at a known location and run from that directory:
cd /etc && sudo ipsc2mmdvmOn startup you should see the repeater register and traffic will begin flowing to BrandMeister.
To have ipsc2mmdvm start automatically on boot, create a systemd service file:
sudo tee /etc/systemd/system/ipsc2mmdvm.service << 'EOF'
[Unit]
Description=ipsc2mmdvm - IPSC to MMDVM Bridge
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/etc
ExecStart=/usr/local/bin/ipsc2mmdvm -config /etc/ipsc2mmdvm.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOFThen enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable ipsc2mmdvm
sudo systemctl start ipsc2mmdvmCheck status and logs:
sudo systemctl status ipsc2mmdvm
sudo journalctl -u ipsc2mmdvm -fAll settings can also be set via environment variables using _ as a separator (e.g. IPSC_PORT=50000).
| Setting | Type | Default | Description |
|---|---|---|---|
log-level |
string | info |
Log verbosity: debug, info, warn, error |
| Setting | Type | Default | Description |
|---|---|---|---|
ipsc.interface |
string | - | Network interface connected to the repeater |
ipsc.port |
uint16 | - | UDP listen port |
ipsc.ip |
string | 10.10.250.1 |
IP address to assign to the interface |
ipsc.subnet-mask |
int | 24 |
CIDR subnet mask (1–32) |
ipsc.auth.enabled |
bool | false |
Enable IPSC authentication |
ipsc.auth.key |
string | - | Hex authentication key (up to 40 chars) |
| Setting | Type | Default | Description |
|---|---|---|---|
mmdvm[].name |
string | - | Friendly name for this network (used in logging) |
mmdvm[].master-server |
string | - | DMR master host:port |
mmdvm[].password |
string | - | Hotspot password |
mmdvm[].callsign |
string | - | Your amateur radio callsign |
mmdvm[].radio-id |
uint32 | - | Your registered DMR repeater ID |
mmdvm[].rx-freq |
uint | - | Receive frequency in Hz |
mmdvm[].tx-freq |
uint | - | Transmit frequency in Hz |
mmdvm[].tx-power |
uint8 | 0 |
Transmit power in dBm |
mmdvm[].color-code |
uint8 | 0 |
DMR color code (0–15) |
mmdvm[].latitude |
float64 | 0 |
Latitude (−90 to +90) |
mmdvm[].longitude |
float64 | 0 |
Longitude (−180 to +180) |
mmdvm[].height |
uint16 | 0 |
Antenna height in meters |
mmdvm[].location |
string | - | Location description |
mmdvm[].description |
string | - | Repeater description |
mmdvm[].url |
string | - | Repeater URL |
Rewrite rules control how DMR traffic is routed between the repeater and each master. They follow the same semantics as DMRGateway: the first matching rule wins. If no rewrite rules are configured for a master, all traffic passes through unmodified.
| Setting | Type | Default | Description |
|---|---|---|---|
mmdvm[].tg-rewrite[].from-slot |
uint | - | Source timeslot (1 or 2) |
mmdvm[].tg-rewrite[].from-tg |
uint | - | Source talkgroup start |
mmdvm[].tg-rewrite[].to-slot |
uint | - | Destination timeslot (1 or 2) |
mmdvm[].tg-rewrite[].to-tg |
uint | - | Destination talkgroup start |
mmdvm[].tg-rewrite[].range |
uint | 1 |
Number of contiguous TGs to map |
| Setting | Type | Default | Description |
|---|---|---|---|
mmdvm[].pc-rewrite[].from-slot |
uint | - | Source timeslot (1 or 2) |
mmdvm[].pc-rewrite[].from-id |
uint | - | Source private call ID start |
mmdvm[].pc-rewrite[].to-slot |
uint | - | Destination timeslot (1 or 2) |
mmdvm[].pc-rewrite[].to-id |
uint | - | Destination private call ID start |
mmdvm[].pc-rewrite[].range |
uint | 1 |
Number of contiguous IDs to map |
| Setting | Type | Default | Description |
|---|---|---|---|
mmdvm[].type-rewrite[].from-slot |
uint | - | Source timeslot (1 or 2) |
mmdvm[].type-rewrite[].from-tg |
uint | - | Source talkgroup start |
mmdvm[].type-rewrite[].to-slot |
uint | - | Destination timeslot (1 or 2) |
mmdvm[].type-rewrite[].to-id |
uint | - | Destination private call ID start |
mmdvm[].type-rewrite[].range |
uint | 1 |
Number of contiguous entries to map |
| Setting | Type | Default | Description |
|---|---|---|---|
mmdvm[].src-rewrite[].from-slot |
uint | - | Source timeslot (1 or 2) |
mmdvm[].src-rewrite[].from-id |
uint | - | Source subscriber ID start |
mmdvm[].src-rewrite[].to-slot |
uint | - | Destination timeslot (1 or 2) |
mmdvm[].src-rewrite[].to-id |
uint | - | Destination source ID start |
mmdvm[].src-rewrite[].range |
uint | 1 |
Number of contiguous source IDs |