A scanner that notices when the washes come up.
A Raspberry Pi and a $40 radio dongle sit in a window and listen to Tucson fire and EMS traffic all day. Whisper transcribes it on the Pi itself, so no audio ever leaves the network. When it rains, the Pi checks what dispatchers are saying against backyard weather stations, stream gauges and NWS warnings, and says whether a flash flood is actually underway.
The demo
Recorded 29 September 2026, with a Flash Flood Warning up for Pima County. Threads, the raw feed, the map, the day's activity. No narration.
Three spots are blurred on purpose, not broken: two medical calls with street addresses in the hourly rollup, an EMS popup on the map, one injury call in the feed.
The problem
Most of the year Tucson's washes are just sand. In monsoon season they can go from dry to moving in a few minutes, fed by rain that fell miles off where you never saw it. The NWS warnings are good, but they cover whole counties. The radio usually knows about one specific low-water crossing well before anything else does.
Monsoon Ears listens to that traffic and checks it against real sensors. A dispatcher naming a wash, plus a gauge on that same wash climbing, is worth a lot more than either one alone.
How it works
One RTL-SDR can only hear one frequency at a time, and a Pi can only transcribe so fast. Most of the design is rationing those two things.
The scanner probes each priority frequency for about a second, holds when a signal appears, and resumes after a hangover window. Probe audio is prepended to the first held chunk so short transmissions don't slip between cycles.
A cheap RMS gate throws out the noise-only chunks before anything expensive touches them. That's most of the day gone for almost nothing.
WebRTC VAD finds where the speech actually starts and stops and trims the dead air around it. Feed Whisper silence and it invents words, so this matters more than it sounds.
Whisper runs on the Pi's own CPU. It reports per-segment confidence as it goes: no-speech probability, average log-probability, compression ratio. Those numbers catch most of the ghost text it produces on a quiet channel.
A separate worker polls the database and runs a small agent graph: classify, extract structured fields against a local gazetteer, geocode. Keeping it separate means an API outage backs up the queue instead of taking capture down with it.
Every fifteen minutes a bigger model reads the recent voice traffic, the APRS weather packets, the gauges and the live NWS warnings, and decides whether they add up to a flood.
Made up, to show the format. The real captures on this page are flood traffic with house numbers masked; see the footer.
All six run end to end in the walkthrough above, if you'd rather watch it than read about it.
The showcase
An NWS warning counts for more than a rising gauge, and a rising gauge counts for more than somebody's backyard rain total. Whatever the digest concludes, it lists the event ids and readings it leaned on. Most days it concludes nothing is happening, which took far longer to get right than the alarms did.
Real output, 29 September 2026, 11:23 MST: a Flash Flood Warning and a Flood Watch both up, Sabino Creek running, and the ids behind the call.
The archive
Months of extracted incidents pile up in a SQLite file. Type a question, get
rows back. The generated query has to be one read-only SELECT over
the allowed tables or it never runs.
Also made up. It's the shape of the answer, not a real one.
The case
It lives in a printed case built around the official Pi 5 active cooler: an intake vent for the fan, exhaust at the back, vents over the radiator. Two parts, no screws.
The design isn't mine. It's Raspberry Pi 5 Case (snap fit) by pyrho // 25.wf, free on Printables, and that's where to get it. The print settings below are the designer's.
| Setting | Value |
|---|---|
| Material | PLA |
| Nozzle | 0.4 mm |
| Layer height | 0.2 mm |
| Orientation | flat side down |
| Supports | none |
| Parts | 2 |
Design by pyrho // 25.wf, licensed CC BY-SA. Multiple top variants with different port cutouts are included, so you can pick the one that matches your build.
Build one
The capture path, the gating, the transcription and the agent graph don't care where they're pointed. USGS gauges, NWS alerts and APRS all work anywhere in the US and just want a latitude and longitude. What you bring is local knowledge: your frequencies, your gauges, your street names.
| What | Where | Effort |
|---|---|---|
| Frequencies and talkgroups | config/frequencies.py | An evening on RadioReference |
| Location scalars | config/locale.py | Six values, one file |
| Stream and rain gauges | config/gauges.py | A USGS site query |
| Local place names | config/gazetteer.py | An hour with a map |
| A different hazard | agents/alert.py | One prompt, same structure |
Wildfire lines up almost one-to-one. Red Flag Warnings do the job Flash Flood Warnings do here, and fuel moisture and wind stand in for stream discharge. The canyons are your washes.