Click to Listen
Adding click-a-signal-to-listen to the occupancy heatmap, with one dongle and a privilege boundary worth the care.
earshot sweeps the band and draws a heatmap of occupancy: who is on, where, and for how long. Useful on its own, but the obvious next want writes itself. You see a bright spot on the waterfall and you want to click it and actually listen.
The catch is the hardware. There is one dongle. It cannot sweep the whole band and stream audio from one channel at the same time. So listening is not a second radio. It is a handoff.
The flow
Click a bin on the heatmap. earshot pauses its sweep and releases the device. The console starts OpenWebRX, tuned to the frequency you clicked. You listen. When you are done, the sweep resumes and the heatmap comes back.
OpenWebRX takes a frequency and a mode in its URL, so the console hands the browser a link that lands already tuned to the signal you picked. No hunting once the receiver comes up.
The part worth writing about
The interesting problem here is not the audio. It is who is allowed to do what.
Starting and stopping a system service is a privileged action. The easy path is to hand that privilege to the service that owns the dongle and move on. I did not want that. earshot runs sandboxed on purpose, so its load can never take the station down, and I did not want to punch a hole in that sandbox just to flip a switch.
So the privilege lives where privilege already lives. earshot stays unprivileged. Its listen and stop endpoints are plain pause and resume, nothing more. It never gains the right to start or stop anything. The console, which already holds a tightly scoped sudo allowlist for restarting its own station services, gets exactly one more entry: start and stop the OpenWebRX unit, and only that unit. One line, scoped to one service.
OpenWebRX itself runs as a normally-stopped service, started only on demand, because it would fight earshot for the one dongle the moment both were running. The component that touches the radio stays in its box. The component that already has privilege does the one privileged step, and only that step.
Status, honestly
Built and deployed. The click-to-listen UI, the pause and resume handoff, the scoped privilege, the on-demand receiver unit, all in place and running.
The live end-to-end test is waiting on a working receiver. The dongle that was meant to prove this out died the same night (that is a separate story on the bench). So the design is done and shipped, and the proof is one dongle away. When the replacement lands, the first bright spot on the waterfall gets a click.