Normalize line endings to LF across entire repository
Apply .gitattributes normalization to convert all CRLF line endings inherited from Windows-origin source files to Unix LF. 175 files, zero content changes.
This commit is contained in:
parent
696d2dd387
commit
bbdcb243dc
175 changed files with 56794 additions and 56794 deletions
|
|
@ -1,206 +1,206 @@
|
|||
"""Spectrum screen — sweep analyzer across the IF range.
|
||||
|
||||
Displays a bar chart of power vs. frequency, optionally with a rolling
|
||||
waterfall beneath it. Uses threaded workers for the blocking USB sweep.
|
||||
"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Container, Horizontal, Vertical
|
||||
from textual.widgets import Label, Input, Button, Static, ProgressBar, Checkbox
|
||||
from textual import work
|
||||
from textual.worker import Worker
|
||||
|
||||
from skywalker_tui.widgets.spectrum_plot import SpectrumPlot
|
||||
from skywalker_tui.widgets.waterfall import WaterfallDisplay
|
||||
|
||||
|
||||
class SpectrumScreen(Container):
|
||||
"""Spectrum analyzer with bar chart and optional waterfall."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
SpectrumScreen {
|
||||
layout: vertical;
|
||||
}
|
||||
SpectrumScreen #spec-main {
|
||||
height: 1fr;
|
||||
layout: vertical;
|
||||
}
|
||||
SpectrumScreen #spec-progress-row {
|
||||
height: 3;
|
||||
layout: horizontal;
|
||||
padding: 0 2;
|
||||
background: #0e1018;
|
||||
}
|
||||
SpectrumScreen #spec-progress-row Static {
|
||||
width: auto;
|
||||
margin: 1 1 0 0;
|
||||
}
|
||||
SpectrumScreen #spec-progress-row ProgressBar {
|
||||
width: 1fr;
|
||||
margin: 1 1 0 0;
|
||||
}
|
||||
SpectrumScreen #spec-controls {
|
||||
height: auto;
|
||||
padding: 1 2;
|
||||
background: #0e1018;
|
||||
border-top: solid #1a2a3a;
|
||||
layout: horizontal;
|
||||
}
|
||||
SpectrumScreen #spec-controls Label {
|
||||
width: auto;
|
||||
margin: 1 1 0 0;
|
||||
color: #506878;
|
||||
}
|
||||
SpectrumScreen #spec-controls Input {
|
||||
width: 10;
|
||||
margin: 0 1;
|
||||
}
|
||||
SpectrumScreen #spec-controls Button {
|
||||
margin: 0 1;
|
||||
}
|
||||
SpectrumScreen #spec-controls Checkbox {
|
||||
margin: 1 1 0 0;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, bridge, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._bridge = bridge
|
||||
self._sweeping = False
|
||||
self._sweep_worker: Worker | None = None
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical(id="spec-main"):
|
||||
yield SpectrumPlot(title="Spectrum Analyzer", id="spec-plot")
|
||||
yield WaterfallDisplay(title="Waterfall", id="spec-waterfall")
|
||||
|
||||
with Horizontal(id="spec-progress-row"):
|
||||
yield Static("[#506878]Ready[/]", id="spec-status")
|
||||
yield ProgressBar(total=100, show_eta=False, id="spec-pbar")
|
||||
|
||||
with Horizontal(id="spec-controls"):
|
||||
yield Label("Start:")
|
||||
yield Input("950", id="spec-start")
|
||||
yield Label("Stop:")
|
||||
yield Input("2150", id="spec-stop")
|
||||
yield Label("Step:")
|
||||
yield Input("5", id="spec-step")
|
||||
yield Label("Dwell:")
|
||||
yield Input("10", id="spec-dwell")
|
||||
yield Label("LNB LO:")
|
||||
yield Input("0", id="spec-lnb")
|
||||
yield Checkbox("Continuous", id="spec-continuous")
|
||||
yield Button("Sweep", id="spec-sweep-btn", variant="success")
|
||||
yield Button("Stop", id="spec-stop-btn", variant="error")
|
||||
|
||||
def on_show(self) -> None:
|
||||
if self._bridge.is_demo and not self._sweeping:
|
||||
self._start_sweep()
|
||||
|
||||
def on_hide(self) -> None:
|
||||
self._stop_sweep()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "spec-sweep-btn":
|
||||
self._start_sweep()
|
||||
elif event.button.id == "spec-stop-btn":
|
||||
self._stop_sweep()
|
||||
|
||||
def _start_sweep(self) -> None:
|
||||
if self._sweeping:
|
||||
return
|
||||
|
||||
# Validate inputs
|
||||
try:
|
||||
start = float(self.query_one("#spec-start", Input).value or "950")
|
||||
stop = float(self.query_one("#spec-stop", Input).value or "2150")
|
||||
step = float(self.query_one("#spec-step", Input).value or "5")
|
||||
dwell = int(float(self.query_one("#spec-dwell", Input).value or "10"))
|
||||
lnb_lo = float(self.query_one("#spec-lnb", Input).value or "0")
|
||||
except ValueError:
|
||||
self._update_status("Invalid input — check numeric fields")
|
||||
return
|
||||
|
||||
if not (950 <= start <= 2150) or not (950 <= stop <= 2150):
|
||||
self._update_status("Frequency out of range (950–2150 MHz)")
|
||||
return
|
||||
if start >= stop:
|
||||
self._update_status("Start must be less than Stop")
|
||||
return
|
||||
if not (0.1 <= step <= 500):
|
||||
self._update_status("Step out of range (0.1–500 MHz)")
|
||||
return
|
||||
|
||||
continuous = self.query_one("#spec-continuous", Checkbox).value
|
||||
self._sweeping = True
|
||||
self._sweep_worker = self._do_sweep(start, stop, step, dwell, lnb_lo, continuous)
|
||||
|
||||
def _stop_sweep(self) -> None:
|
||||
self._sweeping = False
|
||||
if self._sweep_worker:
|
||||
self._sweep_worker.cancel()
|
||||
self._sweep_worker = None
|
||||
|
||||
@work(thread=True)
|
||||
def _do_sweep(self, start: float, stop: float, step: float,
|
||||
dwell: int, lnb_lo: float, continuous: bool) -> None:
|
||||
"""Background sweep worker."""
|
||||
import time
|
||||
|
||||
try:
|
||||
self._bridge.ensure_booted()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sweep_num = 0
|
||||
while self._sweeping:
|
||||
sweep_num += 1
|
||||
step_count = [0]
|
||||
|
||||
def progress_cb(freq, step_num, total, result):
|
||||
step_count[0] = step_num + 1
|
||||
pct = (step_num + 1) / total * 100
|
||||
self.app.call_from_thread(self._update_progress, pct, freq, sweep_num)
|
||||
|
||||
self.app.call_from_thread(self._update_status, f"Sweeping #{sweep_num}...")
|
||||
|
||||
freqs, powers, results = self._bridge.sweep_spectrum(
|
||||
start, stop, step, dwell, sr_ksps=20000, callback=progress_cb,
|
||||
)
|
||||
|
||||
self.app.call_from_thread(self._update_plot, freqs, powers, results, lnb_lo)
|
||||
self.app.call_from_thread(self._update_waterfall, powers)
|
||||
|
||||
if not continuous:
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
self._sweeping = False
|
||||
self.app.call_from_thread(self._update_status, "Complete")
|
||||
|
||||
def _update_progress(self, pct: float, freq: float, sweep_num: int) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-pbar", ProgressBar).update(progress=pct)
|
||||
self.query_one("#spec-status", Static).update(
|
||||
f"[#00d4aa]Sweep #{sweep_num}[/] [#506878]{freq:.0f} MHz[/]"
|
||||
)
|
||||
|
||||
def _update_status(self, msg: str) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-status", Static).update(f"[#506878]{msg}[/]")
|
||||
self.query_one("#spec-pbar", ProgressBar).update(progress=0)
|
||||
|
||||
def _update_plot(self, freqs, powers, results, lnb_lo) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-plot", SpectrumPlot).update_data(
|
||||
freqs, powers, results, lnb_lo=lnb_lo,
|
||||
)
|
||||
|
||||
def _update_waterfall(self, powers) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-waterfall", WaterfallDisplay).add_sweep(powers)
|
||||
"""Spectrum screen — sweep analyzer across the IF range.
|
||||
|
||||
Displays a bar chart of power vs. frequency, optionally with a rolling
|
||||
waterfall beneath it. Uses threaded workers for the blocking USB sweep.
|
||||
"""
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Container, Horizontal, Vertical
|
||||
from textual.widgets import Label, Input, Button, Static, ProgressBar, Checkbox
|
||||
from textual import work
|
||||
from textual.worker import Worker
|
||||
|
||||
from skywalker_tui.widgets.spectrum_plot import SpectrumPlot
|
||||
from skywalker_tui.widgets.waterfall import WaterfallDisplay
|
||||
|
||||
|
||||
class SpectrumScreen(Container):
|
||||
"""Spectrum analyzer with bar chart and optional waterfall."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
SpectrumScreen {
|
||||
layout: vertical;
|
||||
}
|
||||
SpectrumScreen #spec-main {
|
||||
height: 1fr;
|
||||
layout: vertical;
|
||||
}
|
||||
SpectrumScreen #spec-progress-row {
|
||||
height: 3;
|
||||
layout: horizontal;
|
||||
padding: 0 2;
|
||||
background: #0e1018;
|
||||
}
|
||||
SpectrumScreen #spec-progress-row Static {
|
||||
width: auto;
|
||||
margin: 1 1 0 0;
|
||||
}
|
||||
SpectrumScreen #spec-progress-row ProgressBar {
|
||||
width: 1fr;
|
||||
margin: 1 1 0 0;
|
||||
}
|
||||
SpectrumScreen #spec-controls {
|
||||
height: auto;
|
||||
padding: 1 2;
|
||||
background: #0e1018;
|
||||
border-top: solid #1a2a3a;
|
||||
layout: horizontal;
|
||||
}
|
||||
SpectrumScreen #spec-controls Label {
|
||||
width: auto;
|
||||
margin: 1 1 0 0;
|
||||
color: #506878;
|
||||
}
|
||||
SpectrumScreen #spec-controls Input {
|
||||
width: 10;
|
||||
margin: 0 1;
|
||||
}
|
||||
SpectrumScreen #spec-controls Button {
|
||||
margin: 0 1;
|
||||
}
|
||||
SpectrumScreen #spec-controls Checkbox {
|
||||
margin: 1 1 0 0;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, bridge, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._bridge = bridge
|
||||
self._sweeping = False
|
||||
self._sweep_worker: Worker | None = None
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical(id="spec-main"):
|
||||
yield SpectrumPlot(title="Spectrum Analyzer", id="spec-plot")
|
||||
yield WaterfallDisplay(title="Waterfall", id="spec-waterfall")
|
||||
|
||||
with Horizontal(id="spec-progress-row"):
|
||||
yield Static("[#506878]Ready[/]", id="spec-status")
|
||||
yield ProgressBar(total=100, show_eta=False, id="spec-pbar")
|
||||
|
||||
with Horizontal(id="spec-controls"):
|
||||
yield Label("Start:")
|
||||
yield Input("950", id="spec-start")
|
||||
yield Label("Stop:")
|
||||
yield Input("2150", id="spec-stop")
|
||||
yield Label("Step:")
|
||||
yield Input("5", id="spec-step")
|
||||
yield Label("Dwell:")
|
||||
yield Input("10", id="spec-dwell")
|
||||
yield Label("LNB LO:")
|
||||
yield Input("0", id="spec-lnb")
|
||||
yield Checkbox("Continuous", id="spec-continuous")
|
||||
yield Button("Sweep", id="spec-sweep-btn", variant="success")
|
||||
yield Button("Stop", id="spec-stop-btn", variant="error")
|
||||
|
||||
def on_show(self) -> None:
|
||||
if self._bridge.is_demo and not self._sweeping:
|
||||
self._start_sweep()
|
||||
|
||||
def on_hide(self) -> None:
|
||||
self._stop_sweep()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "spec-sweep-btn":
|
||||
self._start_sweep()
|
||||
elif event.button.id == "spec-stop-btn":
|
||||
self._stop_sweep()
|
||||
|
||||
def _start_sweep(self) -> None:
|
||||
if self._sweeping:
|
||||
return
|
||||
|
||||
# Validate inputs
|
||||
try:
|
||||
start = float(self.query_one("#spec-start", Input).value or "950")
|
||||
stop = float(self.query_one("#spec-stop", Input).value or "2150")
|
||||
step = float(self.query_one("#spec-step", Input).value or "5")
|
||||
dwell = int(float(self.query_one("#spec-dwell", Input).value or "10"))
|
||||
lnb_lo = float(self.query_one("#spec-lnb", Input).value or "0")
|
||||
except ValueError:
|
||||
self._update_status("Invalid input — check numeric fields")
|
||||
return
|
||||
|
||||
if not (950 <= start <= 2150) or not (950 <= stop <= 2150):
|
||||
self._update_status("Frequency out of range (950–2150 MHz)")
|
||||
return
|
||||
if start >= stop:
|
||||
self._update_status("Start must be less than Stop")
|
||||
return
|
||||
if not (0.1 <= step <= 500):
|
||||
self._update_status("Step out of range (0.1–500 MHz)")
|
||||
return
|
||||
|
||||
continuous = self.query_one("#spec-continuous", Checkbox).value
|
||||
self._sweeping = True
|
||||
self._sweep_worker = self._do_sweep(start, stop, step, dwell, lnb_lo, continuous)
|
||||
|
||||
def _stop_sweep(self) -> None:
|
||||
self._sweeping = False
|
||||
if self._sweep_worker:
|
||||
self._sweep_worker.cancel()
|
||||
self._sweep_worker = None
|
||||
|
||||
@work(thread=True)
|
||||
def _do_sweep(self, start: float, stop: float, step: float,
|
||||
dwell: int, lnb_lo: float, continuous: bool) -> None:
|
||||
"""Background sweep worker."""
|
||||
import time
|
||||
|
||||
try:
|
||||
self._bridge.ensure_booted()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sweep_num = 0
|
||||
while self._sweeping:
|
||||
sweep_num += 1
|
||||
step_count = [0]
|
||||
|
||||
def progress_cb(freq, step_num, total, result):
|
||||
step_count[0] = step_num + 1
|
||||
pct = (step_num + 1) / total * 100
|
||||
self.app.call_from_thread(self._update_progress, pct, freq, sweep_num)
|
||||
|
||||
self.app.call_from_thread(self._update_status, f"Sweeping #{sweep_num}...")
|
||||
|
||||
freqs, powers, results = self._bridge.sweep_spectrum(
|
||||
start, stop, step, dwell, sr_ksps=20000, callback=progress_cb,
|
||||
)
|
||||
|
||||
self.app.call_from_thread(self._update_plot, freqs, powers, results, lnb_lo)
|
||||
self.app.call_from_thread(self._update_waterfall, powers)
|
||||
|
||||
if not continuous:
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
self._sweeping = False
|
||||
self.app.call_from_thread(self._update_status, "Complete")
|
||||
|
||||
def _update_progress(self, pct: float, freq: float, sweep_num: int) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-pbar", ProgressBar).update(progress=pct)
|
||||
self.query_one("#spec-status", Static).update(
|
||||
f"[#00d4aa]Sweep #{sweep_num}[/] [#506878]{freq:.0f} MHz[/]"
|
||||
)
|
||||
|
||||
def _update_status(self, msg: str) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-status", Static).update(f"[#506878]{msg}[/]")
|
||||
self.query_one("#spec-pbar", ProgressBar).update(progress=0)
|
||||
|
||||
def _update_plot(self, freqs, powers, results, lnb_lo) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-plot", SpectrumPlot).update_data(
|
||||
freqs, powers, results, lnb_lo=lnb_lo,
|
||||
)
|
||||
|
||||
def _update_waterfall(self, powers) -> None:
|
||||
if not self.is_mounted:
|
||||
return
|
||||
self.query_one("#spec-waterfall", WaterfallDisplay).add_sweep(powers)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue