From 4e168ff502c68c9030236846eb3a0e666e1baed5 Mon Sep 17 00:00:00 2001 From: Smittix Date: Mon, 9 Feb 2026 10:31:44 +0000 Subject: [PATCH] Fix dsd-fme DMR flag (-fd is D-STAR, not DMR) and audio output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -fd means D-STAR in dsd-fme, not DMR — causing sync detection (shared C4FM modulation) but no decoded data. DMR Simplex is -fs. Also fix -o - (invalid in dsd-fme) to -o null for headless servers, add D-STAR flag mapping, and handle TGT/SRC output format in parser. Co-Authored-By: Claude Opus 4.6 --- routes/dmr.py | 21 +++++++++++++-------- tests/test_dmr.py | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/routes/dmr.py b/routes/dmr.py index 64f3ab5..6924b37 100644 --- a/routes/dmr.py +++ b/routes/dmr.py @@ -56,14 +56,16 @@ _DSD_PROTOCOL_FLAGS = { } # dsd-fme remapped several flags from classic DSD: -# -fp = ProVoice (NOT P25), -fi = NXDN48 (NOT D-Star), -# -f1 = P25 Phase 1, -ft = XDMA multi-protocol decoder +# -fs = DMR Simplex (NOT -fd which is D-STAR!), +# -fd = D-STAR (NOT DMR!), -fp = ProVoice (NOT P25), +# -fi = NXDN48 (NOT D-Star), -f1 = P25 Phase 1, +# -ft = XDMA multi-protocol decoder _DSD_FME_PROTOCOL_FLAGS = { 'auto': ['-ft'], # XDMA: auto-detect DMR/P25/YSF - 'dmr': ['-fd'], # DMR (classic flag, works in dsd-fme) + 'dmr': ['-fs'], # DMR Simplex (-fd is D-STAR in dsd-fme!) 'p25': ['-f1'], # P25 Phase 1 (-fp is ProVoice in dsd-fme!) 'nxdn': ['-fn'], # NXDN96 - 'dstar': [], # No dedicated flag in dsd-fme; auto-detect + 'dstar': ['-fd'], # D-STAR (-fd in dsd-fme, NOT DMR!) 'provoice': ['-fp'], # ProVoice (-fp in dsd-fme, not -fv) } @@ -132,8 +134,9 @@ def parse_dsd_output(line: str) -> dict | None: # is captured as a call event rather than a bare slot event. # Classic dsd: "TG: 12345 Src: 67890" # dsd-fme: "TG: 12345, Src: 67890" or "Talkgroup: 12345, Source: 67890" + # "TGT: 12345 | SRC: 67890" (pipe-delimited variant) tg_match = re.search( - r'(?:TG|Talkgroup)[:\s]+(\d+)[,\s]+(?:Src|Source)[:\s]+(\d+)', line, re.IGNORECASE + r'(?:TGT?|Talkgroup)[:\s]+(\d+)[,|\s]+(?:Src|Source|SRC)[:\s]+(\d+)', line, re.IGNORECASE ) if tg_match: result = { @@ -378,9 +381,11 @@ def start_dmr() -> Response: rtl_cmd.extend(['-p', str(ppm)]) # Build DSD command - # Use -o - to send decoded audio to stdout (piped to DEVNULL) - # instead of PulseAudio which may not be available under sudo - dsd_cmd = [dsd_path, '-i', '-', '-o', '-'] + # dsd-fme uses '-o null' to discard decoded audio (PulseAudio + # unavailable on headless/remote servers); classic dsd uses '-o -' + # to send audio to stdout which we pipe to DEVNULL. + audio_out = 'null' if is_fme else '-' + dsd_cmd = [dsd_path, '-i', '-', '-o', audio_out] if is_fme: dsd_cmd.extend(_DSD_FME_PROTOCOL_FLAGS.get(protocol, [])) dsd_cmd.extend(_DSD_FME_MODULATION.get(protocol, [])) diff --git a/tests/test_dmr.py b/tests/test_dmr.py index 523bfa1..e5b15b4 100644 --- a/tests/test_dmr.py +++ b/tests/test_dmr.py @@ -66,6 +66,16 @@ def test_parse_talkgroup_dsd_fme_format(): assert result['source_id'] == 67890 +def test_parse_talkgroup_dsd_fme_tgt_src_format(): + """Should parse dsd-fme TGT/SRC pipe-delimited format.""" + result = parse_dsd_output('Slot 1 | TGT: 12345 | SRC: 67890') + assert result is not None + assert result['type'] == 'call' + assert result['talkgroup'] == 12345 + assert result['source_id'] == 67890 + assert result['slot'] == 1 + + def test_parse_talkgroup_with_slot(): """TG line with slot info should capture both.""" result = parse_dsd_output('Slot 1 Voice LC, TG: 100, Src: 200') @@ -106,10 +116,10 @@ def test_dsd_fme_flags_differ_from_classic(): def test_dsd_fme_protocol_flags_known_values(): """dsd-fme flags use its own flag names (NOT classic DSD mappings).""" assert _DSD_FME_PROTOCOL_FLAGS['auto'] == ['-ft'] # XDMA - assert _DSD_FME_PROTOCOL_FLAGS['dmr'] == ['-fd'] + assert _DSD_FME_PROTOCOL_FLAGS['dmr'] == ['-fs'] # Simplex (-fd is D-STAR!) assert _DSD_FME_PROTOCOL_FLAGS['p25'] == ['-f1'] # NOT -fp (ProVoice in fme) assert _DSD_FME_PROTOCOL_FLAGS['nxdn'] == ['-fn'] - assert _DSD_FME_PROTOCOL_FLAGS['dstar'] == [] # No dedicated flag + assert _DSD_FME_PROTOCOL_FLAGS['dstar'] == ['-fd'] # -fd is D-STAR in dsd-fme assert _DSD_FME_PROTOCOL_FLAGS['provoice'] == ['-fp'] # NOT -fv