bandsaunter/INSTALL.md
The Dust Council df080f6571 A window on the sky, and flags on the routes
The terminal board says what is overhead.  This says where: a real map
with the aircraft moving on it as the frames arrive, and beside each one a
box carrying everything known about the flight -- type and registration,
who operates it, where it came from and where it is going, height with a
rate of climb, speed and heading, how far away and on what bearing, its
position, how many frames it has sent and how long since the last one.

Qt is asked for and not required.  Four bindings are tried, the module
imports on a machine with none of them, and asking for the window without
one gets the instructions rather than a traceback -- before the receiver
is opened, since nothing is gained by taking the dongle for a window that
cannot be drawn.

In the menu, "listen now" is now "passive capture" with a realtime
display beside it.  Closing the window leaves exactly the files pressing
control-C leaves, because listen and watch share one read loop and one
finishing step; the receiver runs on its own thread, so a slow repaint
cannot cost a frame and a slow tile fetch cannot stall the picture.

The animation's labels grew to match: flight level and speed, type and
registration, and both ends of the route, each with a small flag of the
country its airport is in.  The flags are a table rather than a network --
twelve pixels by eight, where a flag is the arrangement that makes one
recognisable rather than a rendering of the real thing -- and a country
not in the table is named by its two letters, since a flag that is nearly
another country's is worse than none.  Where a route arrives as bare
codes the country comes from the ICAO prefix.

Four things found on the way.  The window ignored --seconds, so "listen
for ten minutes" meant something different with a window open; it closes
itself now.  The register was being asked twice per aircraft, once for
labels and once for airport positions.  Cached routes had no country in
them, so the first real redraw drew no flags at all -- routes are
versioned now.  And past fourteen aircraft on one frame the labels go
back to the callsign, the height and the speed, because five lines beside
each of three hundred aircraft is a page of overlapping text with a map
somewhere behind it.

Long names are folded rather than allowed to stretch a box, breaking at
the arrow of a route so the two ends stay whole; and the animation's
label placement gained the same ring search the window uses, having only
ever tried four spots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
2026-09-04 13:38:47 -07:00

367 lines
14 KiB
Markdown

# Installing bandsaunter
Step by step, from nothing to a scan running. The whole of it is four
dependencies and one C library; there is nothing to compile and no model to
download unless you want speech transcription.
If you are on Debian, Ubuntu or Mint, [build the package](#a-debian-ubuntu-mint-the-package)
— it is two commands and apt does the rest. Anywhere else, use
[a virtual environment](#b-anywhere-else-a-virtual-environment).
**You can try the whole program before buying or plugging in a receiver.**
`--simulate` runs the scanner against a synthetic band, and `bandsaunter adsb
--simulate` flies imaginary aircraft past an imaginary receiver. Neither needs
hardware or a network.
---
## What you need
| | |
|---|---|
| **Python** | 3.10 or newer |
| **A receiver** | any RTL2832U dongle (RTL-SDR Blog v3/v4, NESDR, generic) — *optional*, everything except live scanning works without one |
| **An aerial** | whatever came with the dongle to start; a band-cut aerial for anything serious |
| **Disk** | ~250 MB for the program and its dependencies; recordings are what actually fill a disk |
| **Network** | only for installing, and for callsign and aircraft lookups afterwards |
Operating systems: developed and packaged on Debian; runs on any Linux with
Python 3.10+, and on macOS with `brew install librtlsdr`. On Windows it will
run under WSL2 with USB passthrough, which is more trouble than it is worth —
a Raspberry Pi is the easier answer.
---
## A. Debian, Ubuntu, Mint: the package
```bash
git clone <the repository> bandsaunter
cd bandsaunter
./packaging/build-deb.sh # writes dist/bandsaunter_<version>_all.deb
sudo apt install ./dist/bandsaunter_*.deb
```
That is the whole of it. `build-deb.sh` needs only `dpkg-deb` and `fakeroot`,
both already present on a normal system; apt then pulls in numpy, scipy, rich,
PyYAML and librtlsdr itself.
The package also does the one piece of system setup that a `pip` install
cannot: it blacklists the DVB-T television driver, which claims these dongles
on sight. Read what it prints — if a receiver was plugged in during the
install, unplug and replug it, or run `sudo rmmod dvb_usb_rtl28xxu` once.
Uninstall with `sudo apt remove bandsaunter`, which also removes the blacklist.
### For several machines: your own apt repository
`./packaging/build-repo.sh` builds the application, a speech recogniser and a
model into a small apt repository under `dist/repo`, so that `apt install
bandsaunter` on every other machine brings transcription with it. See
**Install → From your own apt repository** in the README.
---
## B. Anywhere else: a virtual environment
**Debian, Ubuntu and Fedora refuse `pip install` outside a virtual
environment** (PEP 668: *"externally-managed-environment"*). That is the error
most people hit first, and a virtual environment is the answer:
```bash
sudo apt install librtlsdr0 # Debian/Ubuntu/Mint
# sudo dnf install rtl-sdr # Fedora
# sudo pacman -S rtl-sdr # Arch
# brew install librtlsdr # macOS
git clone <the repository> bandsaunter
cd bandsaunter
python3 -m venv .venv
. .venv/bin/activate
pip install . # a few wheels; under a minute
```
That installs four wheels — numpy, scipy, rich, PyYAML — and puts two commands
on the path, `bandsaunter` and `saunterbrowse`. Nothing is built from source.
Use `pip install -e .` instead if you intend to change the code.
The commands only exist while the environment is active. To have them always,
install with [pipx](https://pipx.pypa.io) instead, which keeps its own
environment and puts the commands on your path permanently:
```bash
sudo apt install pipx && pipx install /path/to/bandsaunter
```
As a last resort — a throwaway machine, a container — `pip install
--break-system-packages .` overrides the refusal. It puts the dependencies in
among the system ones, which is what the warning is about.
---
## Check that it worked
```bash
bandsaunter --version
bandsaunter bands # the built-in US band plan; no hardware needed
bandsaunter scan -b 2m --simulate # a whole scan against a synthetic band
bandsaunter adsb --simulate --seconds 30
bandsaunter flights # draws what the last command heard
```
With a receiver plugged in:
```bash
bandsaunter devices # should list the dongle
bandsaunter devices --test # opens it and captures a block
bandsaunter # interactive setup, then scanning
```
`bandsaunter devices` is the command to run when something is wrong: it says
whether librtlsdr loaded, whether a dongle was found, and what to do about it
if not.
---
## Optional dependencies
Everything below is genuinely optional. The program starts, scans, records,
identifies, decodes Morse and draws waterfalls with none of it, and says
plainly when a feature is unavailable rather than failing.
| Install | Gives you | Without it |
|---|---|---|
| `sudo apt install espeak-ng` | clearer spoken timestamps on combined recordings, rendered about three times faster | a built-in formant synthesiser does the same job, less clearly |
| `sudo apt install ffmpeg` | `bandsaunter flights --out sky.mp4` writes a video | a GIF is written instead, and it says so |
| `sudo apt install rtl-sdr` | `rtl_test`, `rtl_sdr` and friends, for diagnosing hardware | nothing missing from bandsaunter itself |
| `pip install faster-whisper` | speech transcription of recorded voice — see [the step-by-step below](#speech-transcription-step-by-step) (~250 MB installed, plus a 148 MB model) | transcription is off; scans report that no recogniser is installed |
| `pip install vosk` | a smaller, weaker recogniser (~10 MB plus a 40 MB model) | as above |
| `sudo apt install python3-pyqt6` | the realtime aircraft window (`bandsaunter adsb --window`) | the terminal board still shows every aircraft, and the maps are still drawn afterwards |
| `pip install pyte` | the terminal-resize tests | those tests skip |
**The aircraft window needs Qt**, and any of four bindings will do — PyQt6,
PyQt5, PySide6 or PySide2 — because distributions disagree about which they
package. `python3-pyqt6` on Debian and Fedora, `python-pyqt6` on Arch, or
`pip install PyQt6` in the environment bandsaunter runs from. Without it the
window is the only thing missing, and the program says so rather than failing.
**Aircraft and callsign lookups need no installation**, only a network. They
ask public registers about a callsign or a 24-bit address and cache the
answers for a month; `--no-lookup` turns them off, and what the address and
the callsign say on their own is worked out offline either way.
---
## Speech transcription, step by step
This is the only part with a real download in it, and the only part Debian
cannot supply, so it gets its own section. Two pieces are needed: the
**recogniser** (a Python package) and the **model** it loads (the weights).
Scanning, recording, identifying and CW decoding all work without either.
### Step 1 — install the recogniser where bandsaunter can see it
The recogniser must go into the same environment bandsaunter itself runs
from, or bandsaunter will not find it.
If you installed into a virtual environment:
```bash
. .venv/bin/activate # the same one bandsaunter is in
pip install faster-whisper
```
If you installed for your user with `pip install --user` (the layout this
project was developed with, everything under `~/.local`):
```bash
pip install --user --break-system-packages faster-whisper
```
`--break-system-packages` is what gets past the PEP 668 refusal described
above; with `--user` it writes to `~/.local/lib/python3.x/site-packages` and
touches nothing the distribution owns.
This pulls in `ctranslate2`, `av`, `huggingface-hub`, `tokenizers` and
`onnxruntime` — about 250 MB installed, no compiler needed, all wheels.
### Step 2 — check bandsaunter can see it
```bash
bandsaunter transcribe --engines
```
```
engine installed how to get it
faster-whisper yes pip install faster-whisper (best on radio audio)
whisper no pip install openai-whisper
whisper-cli no whisper.cpp, no Python dependencies
vosk no pip install vosk (small, weaker on noisy audio)
pocketsphinx no pip install pocketsphinx (tiny, poor on radio audio)
auto would use faster-whisper
```
`installed: yes` against `faster-whisper` and a last line naming it is the
whole test. If it says `no` while pip says the package is there, they are in
different environments — see [When it does not work](#when-it-does-not-work).
### Step 3 — get the model
The model is **not** in the pip package. By default `base.en` is used, and
the first transcription downloads it from Hugging Face
(`Systran/faster-whisper-base.en`) into `~/.cache/huggingface/hub/`, about
140 MB. Everything after that is offline.
You can leave it to happen by itself, but a scan that appears to hang for a
minute on its first voice capture is unnerving, so it is better to fetch it
deliberately:
```bash
python3 -c "from faster_whisper import WhisperModel; WhisperModel('base.en', device='cpu', compute_type='int8')"
du -sh ~/.cache/huggingface # about 140 MB when it has finished
```
To watch what any engine is doing, including a download in progress:
```bash
BANDSAUNTER_ENGINE_OUTPUT=1 bandsaunter transcribe recording.wav --stdout
```
Model sizes, measured from the repository listings:
| Model | Download | Notes |
|---|---|---|
| `tiny.en` | 78 MB | fastest; misses words on noisy radio audio |
| `base.en` | 148 MB | **the default**; keeps up comfortably on an ordinary machine |
| `small.en` | 486 MB | noticeably better on weak signals, several times slower |
| `medium.en` | 1.5 GB | better again; wants a fast machine and patience |
| `large-v3` | 3.1 GB | multilingual, and slow on a CPU |
Everything runs **on the CPU**, at int8 — no GPU, no CUDA, no nvidia
packages, on purpose: a scanner should not need a graphics card, and int8 on
a CPU keeps up with a scan.
### Step 4 — turn it on
```bash
bandsaunter transcribe recording.wav --stdout # one file, straight away
bandsaunter transcribe ~/bandsaunter # a whole directory
bandsaunter scan -b 2m --transcribe # during a scan
bandsaunter config transcribe=true # or make that the default
bandsaunter config transcribe_model=small.en # and choose the model
```
During a scan, transcription runs on its own thread after each recording is
written, so it never holds up the sweep. Transcripts land beside the audio as
`<recording>_transcription.txt`, and `saunterbrowse` shows them.
### Doing it without a network, or on many machines
`./packaging/build-repo.sh` collects the recogniser and the model into two
Debian packages, so no machine you install on ever reaches the network:
```bash
MODEL=base.en ./packaging/build-repo.sh # any size from the table above
```
That produces `bandsaunter-transcribe` (the wheels, unpacked into
`/usr/lib/bandsaunter/vendor`) and `bandsaunter-model-base-en` (the weights,
in `/usr/share/bandsaunter/models/base.en`), and an apt repository serving
them alongside `bandsaunter` itself. Both are `Recommends`, so a plain `apt
install bandsaunter` from that repository brings the lot.
For a machine that is not using apt, copy the model directory anywhere and
point at it:
```bash
export BANDSAUNTER_MODEL_DIR=/opt/models # holds base.en/, small.en/ ...
bandsaunter config transcribe_model=/opt/models/base.en # or an outright path
```
A model name that matches a directory under `BANDSAUNTER_MODEL_DIR` is used
from disk; anything else is left to the recogniser to download.
### A smaller alternative
`vosk` is a tenth of the size and much weaker on the noisy, clipped audio
radio produces. It is worth it on a Raspberry Pi and not otherwise:
```bash
pip install vosk # ~10 MB
bandsaunter config transcribe_engine=vosk
```
It fetches its own small model on first use, by language; point
`transcribe_model` at an unpacked model directory to use a specific one.
---
## When it does not work
**`externally-managed-environment` from pip.** Use a virtual environment or
pipx, as above. This is the distribution protecting its own Python, not a
problem with bandsaunter.
**`bandsaunter transcribe --engines` says `no` although pip installed it.**
The recogniser went into a different environment from bandsaunter. Ask each
of them where it is and compare:
```bash
python3 -c "import bandsaunter, sys; print(sys.executable); print(bandsaunter.__file__)"
python3 -c "import faster_whisper; print(faster_whisper.__file__)"
```
Install the recogniser with the same `pip` that owns the first path — the
venv's `pip` for a venv install, `pip install --user --break-system-packages`
for a `~/.local` one. On a `.deb` install, bandsaunter runs as the system
`python3` and looks in `/usr/lib/bandsaunter/vendor` as well, which is what
the `bandsaunter-transcribe` package fills.
**The first transcription takes a minute and prints nothing.** It is
downloading the model. `BANDSAUNTER_ENGINE_OUTPUT=1` shows the progress, and
Step 3 above fetches it deliberately instead.
**`librtlsdr was not found`.** Install it: `librtlsdr0` on Debian/Ubuntu,
`rtl-sdr` on Fedora and Arch, `brew install librtlsdr` on macOS. Everything
that does not touch the receiver keeps working without it.
**`bandsaunter devices` finds nothing, or cannot open the dongle.** Almost
always the DVB-T driver, which claims these dongles the moment they are
plugged in:
```bash
echo 'blacklist dvb_usb_rtl28xxu' | sudo tee /etc/modprobe.d/blacklist-rtl.conf
sudo rmmod dvb_usb_rtl28xxu
```
Then unplug and replug the receiver. The `.deb` does this for you.
**Permission denied opening the device.** On Debian and Ubuntu the
`librtlsdr0` package installs the udev rule that grants access, so this is
rare; if you built librtlsdr yourself, you need the rule from its source tree
in `/etc/udev/rules.d`, then `sudo udevadm control --reload` and a replug.
**Nothing is recorded.** That is usually correct behaviour rather than a
fault: captures are kept only when they carry voice, decodable CW or an
identified digital scheme. `bandsaunter scan -b 2m --simulate` proves the
whole path end to end without an aerial.
---
## Running the tests
```bash
pip install pytest pyte
python3 -m pytest tests/ -q
```
About 1500 tests and around fifteen minutes; they need no hardware and no
network.
---
## Licence
GNU General Public License, version 3 or later. The full text is in
[LICENSE](LICENSE), and in an installed package at
`/usr/share/doc/bandsaunter/copyright`.