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:
Ryan Malloy 2026-02-20 10:55:50 -07:00
parent 696d2dd387
commit bbdcb243dc
175 changed files with 56794 additions and 56794 deletions

View file

@ -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 (9502150 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.1500 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 (9502150 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.1500 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)