diff --git a/routes/dmr.py b/routes/dmr.py index c795e0b..92e1dc2 100644 --- a/routes/dmr.py +++ b/routes/dmr.py @@ -490,6 +490,7 @@ def start_dmr() -> Response: device = validate_device_index(data.get('device', 0)) protocol = str(data.get('protocol', 'auto')).lower() ppm = validate_ppm(data.get('ppm', 0)) + fine_tune = int(data.get('fineTune', 0) or 0) demod = str(data.get('demod', 'nfm')).lower() except (ValueError, TypeError) as e: return jsonify({'status': 'error', 'message': f'Invalid parameter: {e}'}), 400 @@ -500,6 +501,8 @@ def start_dmr() -> Response: return jsonify({'status': 'error', 'message': f'Invalid demod. Use: {", ".join(VALID_DEMODS)}'}), 400 if protocol == 'p25p2' and not is_fme: return jsonify({'status': 'error', 'message': 'P25 Phase 2 requires dsd-fme.'}), 400 + if abs(fine_tune) > 20000: + return jsonify({'status': 'error', 'message': 'Fine tune offset too large (max +/- 20000 Hz).'}), 400 # Clear stale queue try: @@ -517,7 +520,7 @@ def start_dmr() -> Response: dmr_active_device = device - freq_hz = int(frequency * 1e6) + freq_hz = int((frequency * 1e6) + fine_tune) # Build rtl_fm command (48kHz sample rate for DSD). # Squelch disabled (-l 0): rtl_fm's squelch chops the bitstream diff --git a/static/js/modes/dmr.js b/static/js/modes/dmr.js index 342e973..013c0eb 100644 --- a/static/js/modes/dmr.js +++ b/static/js/modes/dmr.js @@ -69,6 +69,7 @@ function startDmr() { const protocol = document.getElementById('dmrProtocol')?.value || 'auto'; const gain = parseInt(document.getElementById('dmrGain')?.value || 40); const ppm = parseInt(document.getElementById('dmrPPM')?.value || 0); + const fineTune = parseInt(document.getElementById('dmrFineTune')?.value || 0); const relaxCrc = document.getElementById('dmrRelaxCrc')?.checked || false; const demod = document.getElementById('dmrDemod')?.value || 'nfm'; const device = typeof getSelectedDevice === 'function' ? getSelectedDevice() : 0; @@ -84,14 +85,14 @@ function startDmr() { // Save settings to localStorage for persistence try { localStorage.setItem(DMR_SETTINGS_KEY, JSON.stringify({ - frequency, protocol, gain, ppm, relaxCrc, demod + frequency, protocol, gain, ppm, fineTune, relaxCrc, demod })); } catch (e) { /* localStorage unavailable */ } fetch('/dmr/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ frequency, protocol, gain, device, ppm, relaxCrc, demod }) + body: JSON.stringify({ frequency, protocol, gain, device, ppm, fineTune, relaxCrc, demod }) }) .then(r => r.json()) .then(data => { @@ -617,12 +618,14 @@ function restoreDmrSettings() { const protoEl = document.getElementById('dmrProtocol'); const gainEl = document.getElementById('dmrGain'); const ppmEl = document.getElementById('dmrPPM'); + const fineTuneEl = document.getElementById('dmrFineTune'); const crcEl = document.getElementById('dmrRelaxCrc'); const demodEl = document.getElementById('dmrDemod'); if (freqEl && s.frequency != null) freqEl.value = s.frequency; if (protoEl && s.protocol) protoEl.value = s.protocol; if (gainEl && s.gain != null) gainEl.value = s.gain; if (ppmEl && s.ppm != null) ppmEl.value = s.ppm; + if (fineTuneEl && s.fineTune != null) fineTuneEl.value = s.fineTune; if (crcEl && s.relaxCrc != null) crcEl.checked = s.relaxCrc; if (demodEl && s.demod) demodEl.value = s.demod; } catch (e) { /* localStorage unavailable */ } diff --git a/templates/partials/modes/dmr.html b/templates/partials/modes/dmr.html index 7c8fa1f..df8fa3a 100644 --- a/templates/partials/modes/dmr.html +++ b/templates/partials/modes/dmr.html @@ -51,6 +51,15 @@ title="Frequency error correction for your RTL-SDR dongle. Digital voice is very sensitive to frequency offset."> +