DMR setup guide

27 views
Skip to first unread message

Khalaf Almazrouei

unread,
Jul 25, 2026, 7:05:08 PM (2 days ago) Jul 25
to Open Source CAD
Hi Eric,

II'm setting up the DMR/DVSwitch bridge for my Tickets CAD and could use a pointer. My setup:
  • CAD running via Coolify/Docker (image ticketscad-newui:local), not a bare-metal install — so there's no systemd inside the container
  • I've got a working HBLink3 master on a separate VPS (Frankfurt), port 54000/UDP, my own passphrase, DMR ID 4300098 (A61KT)
  • Created a DMR channel in the CAD (talkgroup 10, Network: HBLink) and it generated a bearer token

The channel setup mentions /etc/ticketscad/dvswitch-<instance>.env and systemctl restart ticketscad-dvswitch@<instance> — but since I'm running the CAD in Docker (no systemd), that path doesn't apply.

My question: what's the correct way to deploy the DVSwitch bridge for a Dockerized CAD? Is docker-config-gen.php the tool for this, or is there a separate DVSwitch container I should run alongside? Any guide or example compose would be hugely appreciated

Eric Osterberg

unread,
Jul 25, 2026, 7:46:16 PM (2 days ago) Jul 25
to open-so...@googlegroups.com
Hi Khalaf,
You have diagnosed it correctly - the systemd/env instructions are the bare-metal path, and none of it applies to a Dockerized CAD. Let me give you the real architecture, straight answers to your three questions, the config you will need either way, and an honest note about where the rough edge is.
The moving parts. A working DMR link is not one thing but a small chain:
- Your CAD app + database (your Coolify container). Pure PHP; it never speaks DMR itself.
- The DMR bridge - services/dvswitch/hbp_client.py. A small standalone Python program that speaks HomeBrew Protocol to your HBLink3 master over UDP, builds the DMR framing (that part is all pure Python), and exposes a small HTTP control surface on port 18091.
- An AMBE vocoder - md380-emu. This is the piece that surprises people: the actual voice codec (PCM to AMBE and back) is not something we wrote or ship. It is the MD-380 radio firmware run in emulation (upstream project travisgoodspeed/md380tools), executed under qemu-user-static, listening on UDP port 2470. The Python bridge hands it each 20 ms audio frame and gets the AMBE frame back. No md380-emu, no audio - so this is the real work of any bridge deployment, Docker or not.
- The DMR proxy - the optional "voice" compose profile (proxy/dmr-proxy.php). A WebSocket relay between your browser's radio widget and the bridge's HTTP surface. It talks to the bridge; it is not the bridge.
Your three questions:
- docker-config-gen.php is not the tool here. It only turns environment variables into the app's config.php; it has nothing to do with the DMR bridge.
- Yes, the bridge runs as its own thing alongside the CAD, not inside the app container - but note it is really two things (the Python bridge plus md380-emu).
- There is no prebuilt bridge image shipped yet. That is a genuine gap you found; the bridge is only documented for bare metal so far.
The config you will need no matter how you run it:
- Your HBLink3 master details go in a standard MMDVM_Bridge.ini, which the bridge reads. It uses the [DMR Network] section: Address = your Frankfurt VPS, Port = 54000, Password = your passphrase, Id = 4300098.
- A small env for the CAD side: the bearer token the CAD generated for this channel, DMR_HTTP_PORT=18091, DMR_OPERATOR_ID=4300098, DMR_DEFAULT_TG=10.
- In the CAD, the DMR channel row stores bridge_host, bridge_port (18091) and that token. That is how the CAD finds the bridge: set bridge_host to wherever the bridge is reachable and make the token match. Then start the voice profile so the relay runs: docker compose --profile voice up -d
The best reference for exactly what a container has to contain is our bare-metal installer, services/dvswitch/install-bridge.sh. It is a shell script, so you can read top to bottom what it installs and wires up (qemu-user-static, sourcing/building md380-emu, the Python venv, the .ini and env). A Docker image is essentially "do what that script does, inside a container."
Honest bottom line: I have not yet built and tested an official Docker bridge image, and the md380-emu-under-qemu piece is fiddly enough that I do not want to hand you an untested recipe and send you chasing silent-no-audio problems. I would genuinely like to build a proper, tested Docker bridge - likely md380-emu and the Python client as two small containers on your CAD's network - plus a short guide, and your setup (a real HBLink3 master and a clean Coolify/Docker CAD) is exactly the target I would want to validate it against. If you are up for being the test case, I will build it and we will get your A61KT bridge on the air together. Either way, tell me how the bridge would reach your CAD and I can get more specific.
73, and thanks for the sharp question,
Eric


--
You received this message because you are subscribed to the Google Groups "Open Source CAD" group.
To unsubscribe from this group and stop receiving emails from it, send an email to open-source-c...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/open-source-cad/7b2a4874-a664-44a0-91a9-b0705299f433n%40googlegroups.com.

Eric Osterberg

unread,
Jul 25, 2026, 9:22:49 PM (2 days ago) Jul 25
to open-so...@googlegroups.com
Hi Khalaf,

Thanks for the sharp question - you found a real gap, not a configuration mistake. There was no Docker path for the DMR bridge; everything assumed bare metal with systemd. So I built one, and it is now in the repository ready for you to try.

First, the architecture, because it explains why the systemd instructions did not apply. The bridge is not part of the CAD container, and it is really two programs that must run together:

The bridge itself is hbp_client.py. It speaks HomeBrew Protocol over UDP to your HBLink3 master and serves a small HTTP control surface on port 18091 that the CAD talks to.

The vocoder is md380-emu. This is the piece that surprises people: the actual AMBE voice codec is not something we wrote or ship. It is MD-380 radio firmware in emulation, listening on UDP 2470, and the bridge hands it each 20 ms of audio. Without it you get a bridge that connects perfectly and passes silence.

Those two run in a single container, because the bridge reaches the vocoder at 127.0.0.1:2470 - they have to share a network namespace. And to answer your original question directly: docker-config-gen.php is not involved at all; it only turns environment variables into the CAD's config.php.

To install it, from a git clone of the repository:

    cd services/dvswitch/docker
    cp .env.example .env
    docker compose up -d --build

Fill in .env with your DMR ID 4300098, callsign A61KT, your Frankfurt master and port 54000, your passphrase, and the bearer token the CAD generated for the channel. You do not need to hand-write an MMDVM_Bridge.ini - the container generates it from those variables at startup. If you would rather supply your own, mount it at /etc/ticketscad/MMDVM_Bridge.ini and it takes precedence.

Then in the CAD, edit that DMR channel and set the bridge host to wherever the bridge is reachable from the CAD container, the bridge port to 18091, and the same bearer token. If you want browser push-to-talk as well, start the CAD's voice profile with: docker compose --profile voice up -d

Verify it in this order, because it isolates problems quickly.

Step 1 - Test the vocoder on its own. No radio or network needed:
    docker run --rm ticketscad-dmr-bridge:local selftest
You want to see "md380-emu is answering on UDP 2470" and "codec round-trip succeeds." Do not be alarmed by the small peak amplitude number it prints - 40 to 60 is normal and my production bridge measures 55.

Step 2 - Watch it log in to your master:
    docker compose logs -f dmr-bridge
You should see the config load, "bound local UDP 62032", and "sent RPTL" followed by a successful login. If it says "login stalled in state 1, retrying" then the master is not answering - check the address and port, the passphrase, and that your HBLink3 config authorises DMR ID 4300098.

Step 3 - Confirm the CAD can reach it. The channel should show connected in Settings, Communications, DMR. From the bridge host you can also run:
    curl -H "Authorization: Bearer YOUR_TOKEN" http://127.0.0.1:18091/health

The full guide is docs/RADIO-DMR-DOCKER.md in the repository, with a troubleshooting table.

Now the honest part about what is and is not proven. On a real Docker host I verified that the image builds, that the vocoder answers and the AMBE round-trip passes, that the ini renders correctly from the environment variables, and that the bridge loads its config, binds its UDP socket and sends the HBP login. What I could not test is the part that needs a real master: the login completing, the HTTP control surface coming up (it starts listening after login succeeds), and actual audio on the air. You have the one thing I do not - a working HBLink3 master - so you are effectively the first real-world test of this path. If it works, you will be the reason it exists for everyone after you. If it does not, send me the output of "docker compose logs dmr-bridge" and I will fix it quickly.

Two things I found while building this that may save you time. The vocoder has to come from the DVSwitch project's package repository - building it from the upstream md380tools source produces a version without the UDP server mode the bridge needs, so it starts and then refuses. The container handles that for you. And a self-test in our own code was reporting failure on hardware that was actually working fine; that is fixed, so pull the latest before you build.

73, and thanks again - this is exactly the kind of report that makes the software better.

Eric
Message has been deleted
Message has been deleted
Message has been deleted

Khalaf Almazrouei

unread,
Jul 27, 2026, 6:22:11 PM (8 hours ago) Jul 27
to Open Source CAD

Hi Eric,

Hope you are well. With a lot of hours and some help from Claude, I got the Docker bridge working — and found five problems along the way, two of which stop it working for anyone.

It authenticated to my HBLink3 master first time, the vocoder passes its round-trip, and I now have two-way voice between TicketsCAD and the talkgroup. So the parts you said were unproven are proven.

I have written the whole thing up — architecture, the five defects with file and line numbers, my workarounds, and the logs from both ends. It is attached. Hope this helps.

Happy to test any patch. I have a working master and a clean Coolify CAD, and I can rebuild the bridge in a couple of minutes.

73,
Khalaf — A61KT


TicketsCAD-DMR-Docker-Guide.md

Eric Osterberg

unread,
Jul 27, 2026, 6:33:24 PM (8 hours ago) Jul 27
to Open Source CAD
Thank you, this is great news. 
Would you mind putting this information in an issue in GitHub? 
I really appreciate your assistance.

Reply all
Reply to author
Forward
0 new messages