Skip to content

ast/wjmmmdvm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MMDVM HS Hat — SM6WJM

Notes, scripts and configuration for my MMDVM HS Hat hotspot running on a Raspberry Pi 4 (hostname: mmdvm).

Hardware

  • MMDVM HS Hat rev 2.0 (SM6WJM), rev 1.6 (SM6WLH)
  • 12.288 MHz TCXO onboard (works a bit better than the more common 14.7456 MHz)
  • Raspberry Pi 4

Because of the 12.288 MHz TCXO, PiStar cannot update the firmware with a pre-compiled binary — the firmware must be built from source with the correct config (see #firmware-build).

Upstream projects

Firmware build

git clone https://github.com/juribeparada/MMDVM_HS.git
cd MMDVM_HS

# Pick the matching board/clock config — this one is for the 12 MHz HS Hat.
cp configs/MMDVM_HS_Hat-12mhz.h Config.h

make

There is also an upstream installer script if you prefer:

curl -OL https://raw.github.com/juribeparada/MMDVM_HS/master/scripts/install_fw_hshat-12mhz.sh

Flashing

On newer Raspberry Pi models the autoboot logic in stm32flash no longer toggles BOOT0/RESET correctly via /sys/class/gpio (see stm32flash hints). The script in scripts/flash.sh drives the pins manually using pinctrl:

  • GPIO 20 → BOOT0
  • GPIO 21 → RESET

Usage on the Pi:

# Default binary path is bin/mmdvm_f1.bin
./scripts/flash.sh

# Or pass an explicit path
./scripts/flash.sh path/to/firmware.bin

Requires stm32flash and pinctrl installed on the Pi.

Host configuration

The active MMDVMHost configuration lives in MMDVM.ini. Highlights:

SettingValue
CallsignSM6WJM
DMR ID2406237
ModeSimplex (Duplex=0)
RX/TX433.6375 MHz
Modes enabledDMR
DMR networkBrandmeister BM2402 (44.5.24.178:62031)
Modem port/dev/ttyAMA0 @ 115200

The Brandmeister password is no longer in MMDVM.ini — newer MMDVMHost hands off to a separate DMR gateway process (see next section), which holds the BM password.

DMR network (DMRGateway)

As of mid-2026 MMDVMHost dropped the Type=Direct mode in [DMR Network] that let it connect straight to a Brandmeister master. The new model:

RF  ──►  MMDVMHost  ──UDP 127.0.0.1──►  DMRGateway  ──►  BM2402 master

MMDVMHost now only knows about a local UDP gateway:

[DMR Network]
LocalAddress=127.0.0.1
LocalPort=62032
GatewayAddress=127.0.0.1
GatewayPort=62031

You must run DMRGateway alongside MMDVMHost on the Pi. Its config is the place where the Brandmeister password and the BM2402 endpoint (44.5.24.178:62031) now live. Sketch:

[DMR Network 1]
Enabled=1
Name=BM_2402
Address=44.5.24.178
Port=62031
Password=YOUR_BM_PASSWORD
# ...

MQTT

Newer MMDVMHost always tries to publish status to an MQTT broker — there is no Enable=0 switch. Install mosquitto on the Pi to silence the “Connection refused” warning at startup:

sudo apt install mosquitto
sudo systemctl enable --now mosquitto

The [MQTT] section in MMDVM.ini points at 127.0.0.1:1883 with Auth=0, which matches a stock mosquitto install.

Topic namespace

The Name= field in [MQTT] is used as both the MQTT client_id and the **topic prefix** — every topic below is published as mmdvm/<topic> with the current config. Set Name=hotspot-uhf if you want a distinct namespace per node.

Topics MMDVMHost publishes

TopicContent
mmdvm/logPlaintext log lines, level-gated by [Log] MQTTLevel. Same as stdout.
mmdvm/jsonStructured per-event JSON (see below) — the interesting telemetry feed.
mmdvm/display-outRaw display-protocol bytes for an external renderer (replaces OLED/Nextion/HD44780).
mmdvm/responseReplies to remote-control commands.

What’s in mmdvm/json

One JSON envelope per RF or network event:

  • Call start (DMR / D-Star / YSF / P25 / NXDN) — src DMR ID, callsign (looked up via DMRIds.dat), dst ID, group-vs-private flag
  • Call end — frame count, BER %, min/max/avg RSSI
  • lost — RF dropout / timeout
  • rejected / invalid / late_entry — access-control denials or malformed frames
  • Periodic rssi and ber during a transmission
  • Mode switch — which mode is currently active
  • POCSAG — paging RIC, type (alphanumeric / numeric / alert_1 / alert_2), text, source (local / network)
  • FM state-machine transitions — listening, kerchunk_rf, relaying_rf, timeout_rf, etc.
  • Lifecycle"MMDVMHost is starting", "Opening network connections", "Sending CW ID", etc.

Topics MMDVMHost subscribes to

  • mmdvm/display-in — input events from an external display app
  • mmdvm/command — remote-control commands, registered only when [Remote Control] Enable=1

Privacy note

Every call your hotspot handles publishes the caller’s DMR ID, callsign, destination TG, RSSI and BER to MQTT in real time. Anyone subscribed to mmdvm/# can watch the activity live. Fine for the default localhost + Auth=0 setup. If you ever bind mosquitto to a routable interface, enable auth and add a listener config that keeps port 1883 off the LAN.

Watching it live

mosquitto_sub -h 127.0.0.1 -t 'mmdvm/#' -v

Source references

From upstream g4klx/MMDVMHost@master:

  • MQTTConnection.cpp:128,175,214 — topic prefixing (Name/topic)
  • Log.cpp:86log topic; Log.cpp:104json topic
  • MMDVMHost.cpp:341–344 — subscriptions; :1200display-out
  • RemoteControl.cpp:207response topic
  • writeJSONRF / writeJSONNet / writeJSON call sites in DMRSlot.cpp, DStarControl.cpp, YSFControl.cpp, P25Control.cpp, NXDNControl.cpp, FMControl.cpp, POCSAGControl.cpp

Capturing DMR network traffic

Useful for development / debugging — observe the HBP frames that MMDVMHost would normally send to DMRGateway, or echo them to a custom process bridging somewhere else.

Testing config

MMDVM-testing.ini is a copy of MMDVM.ini with [DMR Network] pointed at a different host (192.168.1.40:62031 as committed — change to your listener’s IP) and LocalAddress=0.0.0.0 so MMDVMHost can receive replies from a non-localhost peer:

MMDVMHost MMDVM-testing.ini

Quick listen with netcat

nc -u -l -p 62031 | xxd

Payload is binary — pipe through xxd or hexdump -C. First 4 bytes of each datagram are an ASCII magic identifying the packet type.

Gotchas with nc -u:

  • OpenBSD nc (Debian default) locks onto the first peer it sees.
  • nc can’t reply, so the channel becomes observe-only and MMDVMHost won’t TX anything on RF because nothing answers.

Passive observation with tcpdump

Doesn’t terminate the UDP stream, so the hotspot keeps thinking it’s talking to a real gateway:

sudo tcpdump -i any -nXX 'udp port 62031 or udp port 62032'

For Wireshark capture (no built-in HBP dissector, but the magics are readable in the byte view):

sudo tcpdump -i any -w mmdvm.pcap 'udp port 62031 or udp port 62032'

Mock gateway with socat

Echo every datagram back to the sender so MMDVMHost sees a live peer:

socat -v UDP4-RECVFROM:62031,fork \
     UDP4-SENDTO:'$SOCAT_PEERADDR:$SOCAT_PEERPORT'

Protocol summary

HomeBrew Repeater Protocol (HBP) over UDP. Same wire format BrandMeister masters and DMRGateway speak. No authentication on this socket. Packet type is identified by a 4-byte ASCII magic at offset 0.

From → ToMagicPurpose
Pi → 192.168.1.40DMRDRF voice/data burst (55 B)
Pi → 192.168.1.40DMRGGPS embedded in radio’s LC
Pi → 192.168.1.40DMRATalker Alias block
Pi → 192.168.1.40DMRCRepeater config heartbeat (~every 10 s)
192.168.1.40 → PiDMRDNetwork frames to TX on RF
192.168.1.40 → PiDMRPPing / keepalive
192.168.1.40 → PiDMRBBeacon request

DMRD packet layout (55 bytes):

OffsetField
0–3Magic DMRD
4Sequence
5–7Source DMR ID (24-bit)
8–10Destination DMR ID (24-bit)
11–14Repeater ID (Id from [General], 32-bit BE)
15Flags: bit7=slot, bit6=group/private, bit5/4=data/voice sync, bits0–3=frame N or data type
16–19Stream ID (random per call/CSBK/data header)
20–5233-byte DMR payload (AMBE voice frames or data)
53BER
54RSSI

Reference implementations

Sanity check: with the link up and no RF activity you should still see a DMRC packet roughly every 10 seconds. If those are flowing but no DMRD, nothing is being keyed on RF.

mmdvm_sip — DMR ↔ SIP bridge (experimental)

Rust binary that will eventually bridge MMDVMHost’s [DMR Network] UDP stream (HBP) to a SIP PBX such as Asterisk. Today it has a working SIP REGISTER client and a passive DMR listener.

Build, configuration and per-subcommand usage live in mmdvm_sip/README.org.

MD-380 note

To flash an MD-380 from Windows 10 you need to install the STM Device in DFU mode driver.

Repository layout

.
├── Cargo.toml          # Workspace manifest
├── README.org          # This file
├── MMDVM.ini           # Production MMDVMHost configuration (→ local DMRGateway)
├── MMDVM-testing.ini   # Testing variant — streams DMR frames to a LAN host
├── scripts/
│   └── flash.sh        # STM32 flashing script (uses pinctrl)
└── mmdvm_sip/          # Rust DMR ↔ SIP bridge (experimental)

About

My MMDVM hat notes and configuration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors