Files
istrain-public/README.md
T

89 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# istrain — a passive RF train detector you can build
**Is a train blocking the crossing?** istrain answers that by listening — passively, on public
railroad radio frequencies — to the transmitters every train carries: the crew/dispatch voice and
trackside **defect detectors** on 160162 MHz, and the **End-of-Train** and **Head-of-Train**
telemetry on 457.9375 / 452.9375 MHz. It decodes the End-of-Train brake-pressure and motion data,
transcribes the voice, and fuses it all into one live verdict.
**Live instance:** [istrain.jhestyr.net](https://istrain.jhestyr.net) · [featured on rtl-sdr.com](https://www.rtl-sdr.com/istrain-detecting-if-a-train-is-blocking-a-local-crossing-by-listening-to-railroad-frequencies/)
**New here from the blog?** → the full build guide is [`APOCALYPSE-EDITION.md`](APOCALYPSE-EDITION.md) (human- or coding-agent-readable). Open problems we'd take help on are at the bottom.
It's built on a couple of ~US$30 RTL-SDR dongles, some wire, and an always-on Linux box running
Docker. No transmitting, no license needed to receive in the US, no railroad cooperation — the
trains announce themselves; you just get set up to hear them. It works at **any North American
crossing** because the EOT/HOT frequencies are continent-wide and the voice channel plans are
public — you change one config file for your location.
This repository is the **complete, working system** — the DSP, the decoders, the web dashboard, and
the container definitions — shared so anyone (or any coding agent) can build one from scratch and
learn from it.
---
## → Start here: [`APOCALYPSE-EDITION.md`](APOCALYPSE-EDITION.md)
The full from-scratch build guide: hardware shopping list, operating-system setup, freeing the
dongles from the TV driver, researching *your* crossing's channels, bringing up the container
stack step by step, transcription, and the hard-won tuning lessons. It's written to be read
straight through by a human **or** handed to an LLM coding agent pointed at this repo.
## What's here
| Path | What it is |
|---|---|
| [`APOCALYPSE-EDITION.md`](APOCALYPSE-EDITION.md) | the complete build guide (read this first) |
| `scripts/` | the signal engine + decoders — stdlib Python + NumPy only |
| `scripts/iq_hop.py`, `iq_channelize.py` | the retune-in-place 452⇄457 hop and the sub-channel splitter (the mission radio) |
| `scripts/eot/` | the two-pass FFSK End-of-Train decoder (drift-tolerant; validates via vendored [PyEOT](https://github.com/ereuter/PyEOT)) |
| `scripts/bot-recover.py` | Head-of-Train frame recovery + head/tail join |
| `scripts/transcribe-worker.py` | voice clip → comms filter → Whisper → transcript |
| `dashboard/` | `serve.py` (the API + static server, stdlib, no framework) + the web UI |
| `docker/` | Compose template + Dockerfiles (web / airband / scanhop / worker) |
| `config/` | `istrain.conf.example` (your channels go here) + the DVB-blacklist file |
## The idea in three transmitters
1. **Voice (160162 MHz):** dispatchers, crews, and defect detectors that read out milepost, axle
count, and speed in plain English. The most direct "a train just passed here" signal.
2. **End-of-Train (457.9375 MHz):** the last car's telemetry box — unit ID, **brake-pipe pressure**,
**motion flag** — a 1200-baud FFSK burst every few seconds. The brake-pressure curve tells you
passing vs. dwelling vs. cut-and-standing vs. departing.
3. **Head-of-Train (452.9375 MHz):** the locomotive's half; decode it and join head to tail for a
confirmed complete train.
Any one can be too weak to read, but a real train lights several bands at once — so istrain
correlates across all of them.
## Open problems — what we're still figuring out
Passersby with RF chops: these are the live unknowns. Pull requests, corrections, and "actually,
it works like this" all welcome.
- **Range is antenna-bound.** The rail voice band is weak and buried in house RF; gain doesn't help
(it just amplifies the noise). A grounded rooftop antenna is the open lever — height and distance
from the shack, not dBi. What we've got works; what we want is the dwellers' faint keys cleanly.
- **Mid-train / DPU bursts (±12.5 kHz off the EOT/HOT centers) are unidentified.** They're *not*
drifted EOT (our decoder says no). Next step is sub-channel labeling to separate a real
distributed-power emitter from the fixed wayside booster carrier before any raw-IQ demod.
- **The Head-of-Train frame is only half-cracked.** We recover the frame and decode the *addressed
unit* (enough to join head to tail), but the command/type field + BCH are still unread. HOT-format
docs are scarce; if you know the framing, we'd love a pointer.
- **Direction of travel** should fall out of the approach/recede signal envelope — unbuilt, and
blocked on the better antenna above.
- **The brake-pressure taxonomy** (passing / dwelling / cut-and-standing / departing) came from ~50
ground-truthed passages on one subdivision. It may read differently on other railroads and
operating patterns — more ground truth from other crossings would sharpen it.
## License & credits
**GPLv3** — see [`LICENSE`](LICENSE). The EOT decoder validates frames against
[PyEOT](https://github.com/ereuter/PyEOT) by Eric Reuter (GPLv3, vendored under `scripts/eot/`);
full attribution and the licensing note for forkers are in [`ATTRIBUTION.md`](ATTRIBUTION.md). The
communities and tools that made this possible — RTL-SDR Blog, osmocom rtl-sdr, RTLSDR-Airband,
faster-whisper / OpenAI Whisper, FFmpeg, NumPy, Docker + Dockge, and the railfan frequency
databases — are credited in the dashboard's **With Thanks** panel.
*A community project. If you build one, we'd love to hear what you heard.*