diff --git a/routes/tscm/__init__.py b/routes/tscm/__init__.py index 0ca12fa..81b5e21 100644 --- a/routes/tscm/__init__.py +++ b/routes/tscm/__init__.py @@ -490,6 +490,7 @@ def _start_sweep_internal( bt_interface: str = '', sdr_device: int | None = None, verbose_results: bool = False, + custom_ranges: list[dict] | None = None, ) -> dict: """Start a TSCM sweep without request context.""" global _sweep_running, _sweep_thread, _current_sweep_id @@ -532,7 +533,7 @@ def _start_sweep_internal( _sweep_thread = threading.Thread( target=_run_sweep, args=(sweep_type, baseline_id, wifi_enabled, bt_enabled, rf_enabled, - wifi_interface, bt_interface, sdr_device, verbose_results), + wifi_interface, bt_interface, sdr_device, verbose_results, custom_ranges), daemon=True ) _sweep_thread.start() @@ -1127,7 +1128,8 @@ def _run_sweep( wifi_interface: str = '', bt_interface: str = '', sdr_device: int | None = None, - verbose_results: bool = False + verbose_results: bool = False, + custom_ranges: list[dict] | None = None, ) -> None: """ Run the TSCM sweep in a background thread. @@ -1504,7 +1506,7 @@ def _run_sweep( 'rf_count': len(all_rf), }) # Try RF scan even if sdr_device is None (will use device 0) - rf_signals = _scan_rf_signals(sdr_device, sweep_ranges=preset.get('ranges')) + rf_signals = _scan_rf_signals(sdr_device, sweep_ranges=custom_ranges or preset.get('ranges')) # If no signals and this is first RF scan, send info event if not rf_signals and last_rf_scan == 0: diff --git a/routes/tscm/sweep.py b/routes/tscm/sweep.py index b3d580b..701578b 100644 --- a/routes/tscm/sweep.py +++ b/routes/tscm/sweep.py @@ -58,6 +58,25 @@ def start_sweep(): bt_interface = data.get('bt_interface', '') sdr_device = data.get('sdr_device') + # Validate custom frequency ranges if provided + custom_ranges = None + if sweep_type == 'custom': + raw_ranges = data.get('custom_ranges') or [] + validated = [] + for rng in raw_ranges: + try: + start = float(rng.get('start', 0)) + end = float(rng.get('end', 0)) + step = float(rng.get('step', 0.1)) + if 0 < start < end <= 6000: + validated.append({'start': start, 'end': end, 'step': step, + 'name': rng.get('name') or f'{start:.0f}–{end:.0f} MHz'}) + except (TypeError, ValueError): + pass + if not validated: + return jsonify({'status': 'error', 'message': 'custom sweep requires valid start/end MHz'}), 400 + custom_ranges = validated + result = _start_sweep_internal( sweep_type=sweep_type, baseline_id=baseline_id, @@ -68,6 +87,7 @@ def start_sweep(): bt_interface=bt_interface, sdr_device=sdr_device, verbose_results=verbose_results, + custom_ranges=custom_ranges, ) http_status = result.pop('http_status', 200) return jsonify(result), http_status diff --git a/templates/index.html b/templates/index.html index 8336d01..f2ef0a5 100644 --- a/templates/index.html +++ b/templates/index.html @@ -12082,6 +12082,12 @@ async function startTscmSweep() { const sweepType = document.getElementById('tscmSweepType').value; const baselineId = document.getElementById('tscmBaselineSelect').value || null; + const customRanges = sweepType === 'custom' ? [{ + start: parseFloat(document.getElementById('tscmCustomStartMhz').value), + end: parseFloat(document.getElementById('tscmCustomEndMhz').value), + step: 0.1, + name: `Custom ${document.getElementById('tscmCustomStartMhz').value}–${document.getElementById('tscmCustomEndMhz').value} MHz` + }] : null; const wifiEnabled = document.getElementById('tscmWifiEnabled').checked; const btEnabled = document.getElementById('tscmBtEnabled').checked; const rfEnabled = document.getElementById('tscmRfEnabled').checked; @@ -12122,7 +12128,8 @@ wifi_interface: wifiInterface, bt_interface: btInterface, sdr_device: sdrDevice ? parseInt(sdrDevice) : null, - verbose_results: verboseResults + verbose_results: verboseResults, + custom_ranges: customRanges }) }); @@ -12891,7 +12898,7 @@ if (tscmSweepStartTime) { const elapsed = (Date.now() - tscmSweepStartTime) / 1000; const sweepType = document.getElementById('tscmSweepType')?.value || 'standard'; - const durations = { quick: 120, standard: 300, full: 900 }; + const durations = { quick: 120, standard: 300, full: 900, custom: 300 }; const maxDuration = durations[sweepType] || 300; const progress = Math.min(95, (elapsed / maxDuration) * 100); updateTscmProgress({ progress: Math.round(progress), phase: 'Scanning' }); diff --git a/templates/partials/modes/tscm.html b/templates/partials/modes/tscm.html index 3f6737f..c0814ac 100644 --- a/templates/partials/modes/tscm.html +++ b/templates/partials/modes/tscm.html @@ -6,14 +6,28 @@