Add a manual page, and explain every setting in plain words
Every setting now carries a paragraph saying what it is in everyday terms and why someone who does not already speak radio would turn it up, down, on or off: what the squelch knob actually is, why automatic gain is a bad idea for scanning, why a bias tee can damage equipment, why setting the transcription language matters on noisy audio. The menus and `config --describe` show it alongside the existing technical detail. packaging/make-man.py generates bandsaunter(1) from that same table, so the manual cannot document a setting the program lacks or miss one it has -- tests check both, that the page renders through groff without a single warning, and that the guidance survives into the rendered output. Around it are hand-written sections on the commands, entering frequencies, the band plan, lock-outs, the keys during a scan, HF, single sideband, files, environment variables and worked examples. The .deb regenerates and installs it rather than shipping a copy, so an installed manual always matches the installed program. The README picks up what the last few commits added: the plain display as a saved setting, what the settings tests now guarantee, and where to read the manual before installing. Version is the day's build: 2026-08-22_01. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
44c98b11e3
commit
ba6c925351
8 changed files with 1711 additions and 4 deletions
383
packaging/make-man.py
Executable file
383
packaging/make-man.py
Executable file
|
|
@ -0,0 +1,383 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate the bandsaunter manual page from the settings table.
|
||||
|
||||
The settings are described in exactly one place -- bandsaunter/settings.py --
|
||||
so the manual cannot drift from the program. Every setting appears here with
|
||||
its command-line flag, its default, and the plain-language guidance that says
|
||||
what it is and when someone would change it.
|
||||
"""
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
import bandsaunter # noqa: E402
|
||||
from bandsaunter import settings as st # noqa: E402
|
||||
from bandsaunter.config import ScanConfig # noqa: E402
|
||||
|
||||
|
||||
def esc(text: str) -> str:
|
||||
"""Escape for troff: a leading dot or apostrophe is a request."""
|
||||
out = text.replace("\\", "\\e")
|
||||
return "\n".join(("\\&" + ln if ln[:1] in (".", "'") else ln)
|
||||
for ln in out.split("\n"))
|
||||
|
||||
|
||||
def settings_section() -> list[str]:
|
||||
out = []
|
||||
defaults = ScanConfig()
|
||||
for group in st.GROUPS:
|
||||
out.append(f'.SS {esc(group)}')
|
||||
for s in st.in_group(group):
|
||||
flags = " ".join(s.flags)
|
||||
if s.off_flags:
|
||||
flags += " / " + " ".join(s.off_flags)
|
||||
shown = st.format_value(s, getattr(defaults, s.key))
|
||||
unit = f" ({s.unit})" if s.unit and s.kind not in ("bool",) else ""
|
||||
out.append('.TP')
|
||||
out.append(f'.B {esc(flags)}')
|
||||
out.append(f'{esc(s.label)} \\[em] {esc(s.help)}{esc(unit)}.')
|
||||
out.append('.br')
|
||||
out.append(f'Setting name \\fB{esc(s.key)}\\fR, '
|
||||
f'default \\fB{esc(shown)}\\fR.')
|
||||
accepts = s.describe_range()
|
||||
if accepts:
|
||||
out.append('.br')
|
||||
out.append(f'Accepts: {esc(accepts)}.')
|
||||
if s.guidance:
|
||||
# Indented to the entry it belongs to, not back out to the
|
||||
# left margin, so an entry reads as one block.
|
||||
out.append('.RS')
|
||||
out.append('.PP')
|
||||
out.append(esc(s.guidance))
|
||||
out.append('.RE')
|
||||
out.append('.PP')
|
||||
return out
|
||||
|
||||
|
||||
HEAD = r'''.\" Generated by packaging/make-man.py -- do not edit by hand.
|
||||
.TH BANDSAUNTER 1 "{date}" "bandsaunter {version}" "User Commands"
|
||||
.SH NAME
|
||||
bandsaunter \- scan, record and identify radio signals with an RTL-SDR
|
||||
.SH SYNOPSIS
|
||||
.B bandsaunter
|
||||
.RI [ command ]
|
||||
.RI [ options ]
|
||||
.br
|
||||
.B bandsaunter scan
|
||||
.BI \-r " RANGE"
|
||||
.RI [ options ]
|
||||
.br
|
||||
.B bandsaunter
|
||||
.RI "(no arguments: interactive menus)"
|
||||
.SH DESCRIPTION
|
||||
.B bandsaunter
|
||||
sweeps any set of frequency ranges with an RTL-SDR receiver, stops on
|
||||
signals that rise above the background noise, records them, and works out
|
||||
what kind of signal each one was. Morse is decoded to text and speech can be
|
||||
transcribed.
|
||||
.PP
|
||||
Ranges are given by hand or chosen from a built-in US band plan. There is no
|
||||
limit on how many may be scanned at once.
|
||||
.PP
|
||||
Captures that turn out to be noise, static or interference are discarded
|
||||
rather than saved, so what ends up on disk is transmissions rather than hiss.
|
||||
This is the behaviour of
|
||||
.B \-\-require\-signal
|
||||
and it is on by default.
|
||||
.PP
|
||||
Every setting can be given as a command-line option, set in the menus, or
|
||||
saved to a settings file; the three are the same list, described under
|
||||
.B SETTINGS
|
||||
below.
|
||||
.SH COMMANDS
|
||||
.TP
|
||||
.B scan
|
||||
Run a scan. Without
|
||||
.B \-r
|
||||
or
|
||||
.B \-b
|
||||
the interactive menus open instead.
|
||||
.TP
|
||||
.B bands
|
||||
Browse the built-in US band plan: amateur, marine, aviation, public service,
|
||||
business, railroad, GMRS/FRS, CB, ISM, weather, and more.
|
||||
.TP
|
||||
.B config
|
||||
Show or change the saved settings.
|
||||
.B "config KEY=VALUE"
|
||||
sets one and saves it,
|
||||
.B "config \-\-show"
|
||||
prints them all,
|
||||
.B "config \-\-describe KEY"
|
||||
explains one in full, and
|
||||
.B "config \-\-edit"
|
||||
opens the menus.
|
||||
.TP
|
||||
.B transcribe
|
||||
Transcribe existing recordings, or list which speech recognisers are
|
||||
installed with
|
||||
.BR \-\-engines .
|
||||
.TP
|
||||
.B devices
|
||||
List attached receivers.
|
||||
.TP
|
||||
.B profiles
|
||||
List saved profiles.
|
||||
.TP
|
||||
.B analyze
|
||||
Identify a signal in an already-recorded file, or decode Morse from it.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BI \-r " RANGE\fR, \fP" \-\-range " RANGE"
|
||||
A frequency range to sweep, such as
|
||||
.IR 144M\-148M .
|
||||
Repeatable, and a comma-separated list is accepted. See
|
||||
.B ENTERING FREQUENCIES
|
||||
below.
|
||||
.TP
|
||||
.BI \-b " KEY\fR, \fP" \-\-band " KEY"
|
||||
A band-plan preset, such as
|
||||
.IR gmrs " or " marine\-vhf .
|
||||
Repeatable.
|
||||
.B bandsaunter bands
|
||||
lists them.
|
||||
.TP
|
||||
.BI \-\-mode " MODE"
|
||||
Force one demodulator for every range: nfm, wfm, am, usb, lsb, cw or raw.
|
||||
Without this each range is demodulated according to what the signal turns out
|
||||
to be, which is normally what you want.
|
||||
.TP
|
||||
.BI \-p " NAME\fR, \fP" \-\-profile " NAME"
|
||||
Start from a saved profile instead of the saved default settings.
|
||||
.TP
|
||||
.BI \-\-save\-profile " NAME"
|
||||
Save the settings this run would have used, under that name, and exit.
|
||||
.TP
|
||||
.B \-\-save
|
||||
Save the settings this run would have used as the new defaults, and exit.
|
||||
.TP
|
||||
.B \-\-no\-config
|
||||
Ignore the saved settings file and start from the built-in defaults.
|
||||
.TP
|
||||
.B \-\-simulate
|
||||
Use a synthetic receiver instead of real hardware. Everything else behaves
|
||||
normally, so the program can be tried out with no dongle attached.
|
||||
.TP
|
||||
.B \-\-dry\-run
|
||||
Print the sweep plan \[em] every tuner step and how long a pass will take \[em]
|
||||
and exit without receiving anything.
|
||||
.TP
|
||||
.B \-\-keep\-carriers
|
||||
Also record steady unmodulated carriers, which are otherwise discarded as
|
||||
having no content. Useful for beacon hunting or for tracking down a source of
|
||||
interference.
|
||||
.SH SETTINGS
|
||||
Each of these can be given as a command-line option, changed in the menus
|
||||
under
|
||||
.BR "bandsaunter config" ,
|
||||
or written into the settings file. The command line wins for one run; the
|
||||
settings file is what every run starts from.
|
||||
'''
|
||||
|
||||
TAIL = r'''.SH ENTERING FREQUENCIES
|
||||
Frequencies may be written with a unit or without:
|
||||
.IR 146.52M ", " "146.52 MHz" ", " 146520k ", " 146520000 .
|
||||
A bare number under 10000 is read as megahertz, since that is how people
|
||||
write frequencies.
|
||||
.PP
|
||||
A range is a pair:
|
||||
.IR 144M\-148M ", " 144\-148M " (the unit carries over), " "144M to 148M" ", "
|
||||
.IR 144M..148M .
|
||||
A single frequency on its own is treated as a narrow range around it.
|
||||
.PP
|
||||
A step and a demodulator may be attached:
|
||||
.I 144M\-148M/25k@nfm
|
||||
sweeps in 25 kHz steps and demodulates narrowband FM.
|
||||
.PP
|
||||
Several may be given at once, separated by commas, and
|
||||
.B \-r
|
||||
may be repeated. There is no limit on how many ranges a scan may cover.
|
||||
.SH BAND PLAN
|
||||
.B bandsaunter bands
|
||||
lists over a hundred presets from the US band plan, each carrying the right
|
||||
step size and demodulator for that service, so
|
||||
.B "\-b gmrs"
|
||||
is enough to scan GMRS properly.
|
||||
.PP
|
||||
Presets that stand for several others expand automatically:
|
||||
.I all\-cw
|
||||
sweeps every Morse segment of every amateur band, and
|
||||
.IR 2m\-complete ", " 70cm\-complete
|
||||
and their like sweep a whole amateur band end to end rather than one segment
|
||||
of it.
|
||||
.SH LOCK-OUTS
|
||||
Every receiving setup has a few frequencies not worth stopping on: a pager
|
||||
transmitter down the road, a nearby data link, or a spurious signal the
|
||||
receiver manufactures itself. Locking one out makes the scan skip it.
|
||||
.PP
|
||||
Pressing
|
||||
.B l
|
||||
during a scan locks out whatever is being received. Unless
|
||||
.B \-\-no\-save\-lockouts
|
||||
is given, it is written back to the settings file the run started from, so it
|
||||
stays locked out on later runs. Only the lock-out list is written back \[em]
|
||||
options given on the command line for a single run stay one-off.
|
||||
.PP
|
||||
Lock-outs can also be given directly, several at a time, as single
|
||||
frequencies or as spans:
|
||||
.PP
|
||||
.RS
|
||||
.EX
|
||||
bandsaunter scan \-r 144M\-148M \-\-lockout "162.55M, 450M\-455M"
|
||||
.EE
|
||||
.RE
|
||||
.PP
|
||||
A single frequency is widened by
|
||||
.BR \-\-lockout\-width ;
|
||||
a span is used exactly as written.
|
||||
.SH KEYS DURING A SCAN
|
||||
.TP
|
||||
.B q
|
||||
Stop.
|
||||
.TP
|
||||
.B p
|
||||
Pause and resume.
|
||||
.TP
|
||||
.B s
|
||||
Abandon this recording and resume sweeping.
|
||||
.TP
|
||||
.B l
|
||||
Lock out this frequency, now and in future runs.
|
||||
.TP
|
||||
.B "+ \fRand\fB \-"
|
||||
Raise or lower the squelch threshold by 1 dB.
|
||||
.SH OUTPUT
|
||||
Recordings are named
|
||||
.IR frequency \-\- date _ time \- modulation .wav ,
|
||||
with the frequency padded to four digits so that an ordinary directory
|
||||
listing sorts by frequency. Beside them are the run log, as JSON lines and as
|
||||
CSV, and optionally a transcript per recording and the raw samples.
|
||||
.PP
|
||||
With
|
||||
.B \-\-combine
|
||||
every transmission on one frequency is appended to a single growing file for
|
||||
that frequency, with a spoken date and time before each one, so a scan can be
|
||||
played back as a recording of that channel rather than clicked through as
|
||||
hundreds of fragments.
|
||||
.SH HF RECEPTION
|
||||
These receivers cannot normally tune below about 24 MHz. Below that they can
|
||||
sample the antenna directly instead, which opens up shortwave: broadcast,
|
||||
amateur HF, marine, aviation. It is switched on automatically when a scan
|
||||
goes below 24 MHz. A direct connection to a suitable antenna is needed; the
|
||||
whip supplied with most dongles will hear very little.
|
||||
.SH SINGLE SIDEBAND
|
||||
Single sideband is the one mode where tuning must be exact: its demodulator
|
||||
is a filter that opens at the suppressed carrier, so tuning to the middle of
|
||||
the voice discards its lower half and shifts the rest. bandsaunter measures
|
||||
where the carrier is rather than assuming, and identifies upper from lower
|
||||
sideband by which way the signal's energy leans, so
|
||||
.B \-\-mode usb
|
||||
is not needed. The frequency in the filename is the carrier \[em] the
|
||||
frequency to dial into a radio.
|
||||
.SH FILES
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/config.yaml
|
||||
The settings every run starts from.
|
||||
.TP
|
||||
.I ~/.config/bandsaunter/*.yaml
|
||||
Named profiles.
|
||||
.TP
|
||||
.I ~/bandsaunter/
|
||||
Where recordings, transcripts and logs are written, unless
|
||||
.B \-\-output
|
||||
says otherwise. Chosen on first run.
|
||||
.TP
|
||||
.I /etc/modprobe.d/blacklist-rtlsdr.conf
|
||||
Written by the package to keep the DVB-T television driver from claiming the
|
||||
receiver.
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B BANDSAUNTER_CONFIG_DIR
|
||||
Where settings and profiles live, instead of
|
||||
.IR ~/.config/bandsaunter .
|
||||
.TP
|
||||
.B BANDSAUNTER_LIBRTLSDR
|
||||
Path to a particular librtlsdr shared library, when the system one is not the
|
||||
one wanted.
|
||||
.TP
|
||||
.B BANDSAUNTER_DRIVER_MESSAGES
|
||||
Set to 1 to let the receiver driver print its own chatter, which is
|
||||
suppressed by default because it draws over the live display.
|
||||
.TP
|
||||
.B BANDSAUNTER_VENDOR_DIR
|
||||
Where a packaged speech recogniser is installed. Default
|
||||
.IR /usr/lib/bandsaunter/vendor .
|
||||
.TP
|
||||
.B BANDSAUNTER_MODEL_DIR
|
||||
Where packaged recognition models are installed. Default
|
||||
.IR /usr/share/bandsaunter/models .
|
||||
.TP
|
||||
.B BANDSAUNTER_ENGINE_OUTPUT
|
||||
Set to 1 to let the speech recogniser print its own progress.
|
||||
.SH EXAMPLES
|
||||
.TP
|
||||
.B bandsaunter
|
||||
Interactive menus: pick bands, change settings, start scanning.
|
||||
.TP
|
||||
.B bandsaunter scan \-b 2m \-b 70cm \-\-record 0 \-\-hang 6
|
||||
Scan two amateur bands, following each conversation to its end and allowing
|
||||
six seconds of silence between overs.
|
||||
.TP
|
||||
.B bandsaunter scan \-b marine\-vhf \-\-combine \-\-transcribe
|
||||
Scan marine VHF, keeping one growing file per channel with spoken timestamps,
|
||||
and write out what was said.
|
||||
.TP
|
||||
.B bandsaunter scan \-r 14.0M\-14.35M
|
||||
Scan the 20 metre amateur band. Direct sampling switches on by itself.
|
||||
.TP
|
||||
.B bandsaunter scan \-b all\-cw \-\-decode\-morse
|
||||
Sweep every Morse segment of every amateur band and decode what is heard.
|
||||
.TP
|
||||
.B bandsaunter scan \-b gmrs \-\-plain \-\-duration 3600
|
||||
Scan GMRS for an hour with line-per-hit output, suitable for a log file or a
|
||||
remote session.
|
||||
.TP
|
||||
.B bandsaunter config threshold_db=12
|
||||
Raise the squelch threshold and save it as the new default.
|
||||
.SH EXIT STATUS
|
||||
0 on success, 1 for a bad option or an unusable configuration, 2 when the
|
||||
receiver could not be opened.
|
||||
.SH SEE ALSO
|
||||
.BR rtl_test (1),
|
||||
.BR rtl_sdr (1),
|
||||
.BR espeak-ng (1)
|
||||
.PP
|
||||
The README shipped with the package covers the same ground at greater length,
|
||||
including why the detection thresholds are what they are.
|
||||
.SH BUGS
|
||||
The DVB-T television driver claims these dongles on sight. If the receiver
|
||||
cannot be opened, that is almost always why: the package blacklists the
|
||||
driver on install, but the module must be unloaded once with
|
||||
.B "rmmod dvb_usb_rtl28xxu"
|
||||
or the dongle replugged.
|
||||
'''
|
||||
|
||||
|
||||
def main() -> int:
|
||||
out = [HEAD.format(date=date.today().isoformat(),
|
||||
version=bandsaunter.__version__)]
|
||||
out += settings_section()
|
||||
out.append(TAIL)
|
||||
text = "\n".join(out)
|
||||
text = text.replace("\n\n", "\n") # troff dislikes blank lines
|
||||
target = Path(sys.argv[1] if len(sys.argv) > 1
|
||||
else Path(__file__).parent / "bandsaunter.1")
|
||||
target.write_text(text)
|
||||
print(target)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue