Dispatch / Yard rail-voice captures β the "AIS" for trains
Live clips appear in the Captures panel (one per transmission, playable); transcription (whisper) still pending.
scan status: service
istrain-airband β live status & controls in the Supervisor panel below
Defect Detectors reference β NS Dayton District (CJ)
β’ CJ 146.4 β β nearest, ~2.6 mi east (New Rome OH). HBD-DED Β· talks every train (Talk-On-Defect-Only: No) β routine call-out on westbounds Β· record
β’ CJ 144.7 β ~4.3 mi east, high-car (over-height) β defect-only
β’ CJ 157.8 β ~8.8 mi west, warns eastbounds β type TBD
Source: defectdetector.net β static records (location/type/freq/audio), pullable per detector by id. One-time import, not a live poller.
Community Feeds live audio β when up
Their rooftop hears more than our desk whip β "they catch it, we don't" = range, not broken. Someday: we become the local contributor.
Signal Flow how it fits together β the air βΈ the dongles βΈ meaning
iSTRAIN β SIGNAL FLOW the air βΈ the dongles βΈ meaning
mission: is a train blocking the NS Amity Rd crossing (Galloway)?
legend: β
TCXO (precise) [nnnn] = dongle serial ββΆ data path
updated 2026-06-28: voice live (1002); 1001 WEIGHTED hop 452/457 (clock-step-proof); EOT decoded LIVE (ID+brake+motion); HOT head-end ADDRESS cracked β head+tail join; MID standing carrier ID'd = 457.925 EOT booster, carded. ATCS dropped.
updated 2026-07-05: EOT decode is TWO-PASS β off-frequency emitters (Β±800 Hz, ~100 ms bursts) now decode
via adaptive tones + fuzzy sync (O-11) β dwelling trains identified w/ brake psi: held ~60 = stopped
solid Β· bled to 0 + head polling = CUT & standing (crossing possibly open). Booster floor subtracted
from contacts & correlate (O-9).
updated 2026-07-18: THE WHOLE RIG MOVED TO THE MILL β both dongles + capture + decode + transcription
+ this site now run as one Docker (Dockge) stack on a TrueNAS box; the old VM runs zero radio
services; data is born where it's served. Voice transcription is LIVE (not planned β see below).
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PRIMARY ββ RAIL VHF VOICE β 160β162 MHz Β· AAR Β· NBFM, unencrypted ββ
β defect detectors ("MP 123, no defects, 45 axles, 60 mph") + dispatcher
ββΆ RTLSDR-Airband (multi-ch) ββΆ ffmpeg comms-filter ββΆ faster-whisper (small.en + VAD,
LIVE on the Mill since 07-10) ββΆ transcripts.jsonl β "a train is moving through," in words
βΈ the most direct, gettable "is MY crossing blocked" signal
βΈ proven 2026-06-16: same band as NOAA 162.550 β RX confirmed here
βΈ CONFIRMED channel (2026-06-18): 160.980 (ch58) NS Dayton District, MP 148.99;
detectors CJ 144.7 / 157.8 announce here. Plan + sources β CHANNELS.md
βΈ first intelligible speech 2026-07-10, the night the tuned antenna went in:
detector CJ at MP 146.4, transcribed (O-22)
stick: [1002 β
] Blog V4 β Airband demods the 5 pinned channels at once (160.86β161.19 @ 1.024 MS/s)
antenna: tuned railroad-band vertical (upgraded 07-10 β β2.4Γ the desk whip's real content;
office placement, rooftop mast = the standing next lever)
SECONDARY ββ EOT-SYSTEM UHF β 1001 WEIGHTED HOP (452 β 457) Β· 1200-baud FFSK ββ
β a train is a chain of RF emitters front-to-back; 1001 hops to watch both ends:
βΈ 457.9375 EOT/FRED β the REAR unit; DECODED live: ID Β· brake Β· MOTION (rolling/stopped) ββΆ eot.jsonl + clips
decode is TWO-PASS (07-05): exact fast path + adaptive/fuzzy escalation for the
off-frequency short-burst emitters that used to read "strong but undecodable" (O-11)
βΈ 452.9375 BOT / head-end (HOT) β the FRONT unit; frame RECOVERED + addressed-unit DECODED ββΆ bot.jsonl
βΈ hop: iq_hop.py β retune-in-place, BOT 10s / EOT 10s (rebalanced 07-07: the head = the join + the EARLY WARNING; was 7/14 EOT-favored before the HOT decoder existed)
βΈ EOT decode LIVE 2026-06-27 β real trains identified; brake-release + motion=1 = departed (crossing cleared)
βΈ HOT address CRACKED 2026-06-28 from our own air (invert Β· sync 0110101010 Β· bit-49) β headβtail JOIN:
a HOT frame addressing a card's own EOT = confirmed complete consist. Command/type + BCH still open.
stick: [1001] generic β drifts, ppm-calibrate for a clean FSK decode; 450 band clean (no filtering)
antenna: ~6" vertical β ΒΌΞ» β 6.4" @ 457, well-matched
βΈ MID / DPU β Β±12.5k off both centers (452.925/.950, 457.925/.950), watched in-hop β midtrain.jsonl
found β a STANDING carrier on 457.925 = an FCC railroad EOT SIGNAL-BOOSTER (fixed wayside repeater, not a
train); carded as a STANDING SIGNAL. A booster here β marginal headβtail RF β likely why our range is short.
SYNTHESIS ββ the mission is DETECT a train, not decode one signal ββ
β a train lights several bands at once β co-firing in the same 15-min slot = a candidate,
even when each is too weak to decode: 160.980 voice + 452 BOT + 457 EOT (+ Β±12.5k DPU,
booster-floor subtracted so the fixed carrier can't fake a band β O-9)
βΈ correlate panel = week heatmap (one row/band, aligned bins) β read DOWN a column
βΈ BOT/mid/EOT strips below = the RECENT ZOOM (1-min bins, last 6 h) β correlate's magnifier
βΈ live meter = bouncing dBFS bar for whichever band the hop is on (level.json)
MEANING ββ the AIR CURVE is the state β psi tells the story silence can't (O-10 Β· O-11) ββ
β from decoded EOT frames, per train:
psi ~85β90 + moving = PASSING through
psi drops to a HELD ~55β65 + motion 0 = DWELLING (brakes set β possibly blocking)
psi bleeds to 0 AND HOLDS + head still polling = CUT & STANDING β crews cut the crossing
(eyes-confirmed 07-05: ~11 h split, crossing open; never read the bleed as a departure)
psi RECHARGES toward ~88 + motion 0β1 = DEPARTED (the roll-out is the event time)
sanity β NOAA 162.550 (Columbus) β the proven known-good RX test on this band
(the per-radio toggle is parked since the Mill move; control relay TBD)
config β radioβtask is swappable, not hardcoded β reassign per band
dropped β ATCS (900 MHz): LOS-limited + FCC-retired Sep 2025 β encrypted PTC;
our 900 "carriers" were a dongle spur. Why β radio/sdr/atcs/tuning-log.md
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
why voice first: unencrypted, easy at 160 MHz, and it says a train is
moving through your crossing in plain words β the direct commute signal.
System Map the istrain dashboard & the services behind it
iSTRAIN β SYSTEM MAP the dashboard & the services behind it
how the pieces talk Β· keep current as it changes
legend: ββΆ request / data β produced by β£ = a container in the Dockge stack `istrain`
updated 2026-07-18 (the Mill cutover): ONE BOX β everything below is a container on the Mill
(a TrueNAS SCALE machine running Dockge); both dongles on its USB; every log AUTHORED where
it's served (the VM-era push pipeline is retired); istrain.jhestyr.net serves this stack.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
BROWSER βΈ istrain.jhestyr.net (Cloudflare ββΆ the Mill's β£ web container)
β
βββΆ β£ web serve.py β stdlib Python, no framework
β (keeps a jsonl PARSE CACHE keyed on file mtime/size + a 4 s TTL on the card
β compute β polls are ~free; the logs only change every ~20-40 s)
β static index.html Β· main.js Β· style.css Β· panels/strip.js (TWO grids: aligned week/15-min
β for correlate Β· RECENT1M = 6h/1-min zoom for the band strips)
β panels/{trains,live,supervisor,correlate,bot,midtrain,eot,checklist}
β GET /api/version β dashboard/VERSION (stamped by mill/deploy.sh) ββΆ header build stamp
β GET /api/trains β eot.jsonl + bot.jsonl (cards: tail+head βjoin, βparked, βstanding,
β air-curve states: passing/dwelling/CUT&standing/departed) ββΆ Trains
β GET /api/istrain β strength-gated isTRAIN? verdict (+ dwelling-join fallback) ββΆ Banner
β GET /api/level β level.json (live dBFS) ββΆ Live meter (2Γ/s)
β GET /api/heard β heard.jsonl (voice detections) ββΆ Correlate (voice row)
β GET /api/eot?hours=6 β eot.jsonl ββΆ EOT zoom strip (no ?hours = week ββΆ Correlate)
β GET /api/bot?hours=6 β bot.jsonl ββΆ BOT zoom strip (ditto)
β GET /api/midtrain?hours=6 β midtrain.jsonl ββΆ Mid-train zoom strip (Β±12.5k DPU + booster)
β GET /api/correlate β scripts/correlate.py (recent-window why; booster-floor aware) ββΆ Correlate
β GET /api/supervisor β docker state via supervisor-probe (host cron, 1/min) + DATA-FLOW
β liveness: hop = level.json freshness Β· voice = last-capture age
β POST /api/noaa Β· /api/supervisor/{ctl,reset} (parked since the move β buttons decline
gracefully; a docker control relay is TBD)
DONGLES β USB by serial, both on the Mill through a hub Β· dvb driver blacklisted on the host
β 1001 βΈ UHF hop β 452(BOT) β 457(EOT) β 1002 β
βΈ rail voice 160β162 / NOAA
CAPTURE β voice + the UHF hop run at once β separate dongles, separate containers
β β£ airband rtl_airband v5.2.0 (rtl-sdr-blog fork, V4-capable) -c istrain.conf
β ββΆ 5 NFM channels on 1002 ββΆ captures/*.mp3 ββΆ heard.jsonl
β β£ scanhop rtl_tcp + iq_hop.py: retune-in-place hop on 1001 (BOT 10s β EOT 10s, monotonic dwell β step-proof)
β 452 ββΆ bot.jsonl + bot_clips/*.s16 (presence + clip)
β 457 ββΆ eot.jsonl + eot_clips/*.s16 (presence + clip)
β both ββΆ level.json (live meter + the hop's liveness signal)
DECODE β containers Β· no radio Β· CPU on saved clips Β· NEVER cull
β β£ eot-decode ββΆ eot-decode.py β eot_scan (TWO-PASS: exact + adaptive/fuzzy for off-freq
β emitters, O-11) β unitΒ·brakeΒ·motion (+dq provenance) β enrich eot.jsonl
β β£ bot-recover ββΆ bot-recover.py: BOT clips β recover HOT frame + DECODE addressed unit β enrich bot.jsonl
β β£ transcribe-worker ββΆ voice clips >5 KB ββΆ ffmpeg comms-filter ββΆ the whisper stack
(faster-whisper small.en + VAD, a sibling Dockge stack, localhost) ββΆ sidecar .txt per clip;
real speech only ββΆ transcripts.jsonl (durable)
HOUSEKEEPING β β£ trim β 24 h voice-audio prune (ingest-gated; heard.jsonl KEPT)
β supervisor-probe β host cron, 1/min: docker truth into the snapshot
DATA β born on the Mill's ZFS datasets β jsonl logs = keep forever Β· clips accumulate
(keep-everything standing policy) Β· the VM-era corpus is archived alongside
build vs operate β the hands build the code; the radios are operated by services β
never fired ad hoc. deploy = mill/deploy.sh: rsync ββΆ build ββΆ up ββΆ smoke, idempotent.
history β the VM-capture / NAS-web era (2026-06-15 β 07-18) is preserved in MIGRATION.md;
the VM units stay on disk, disabled β the standing rollback.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ