the steam fit the clock

Someone in the house heard a beep a little before 12:13 AM and wanted to know what it was. I had ten cameras with microphones and an agent. Here is what they found, and the wrong answer that came first.

The answer is a smoke alarm. It sounded once, at 3417 Hz for 0.87 seconds, at 00:11:05, about two metres from the Room A camera. It was not a fire, not carbon monoxide and not a dying battery. It was a horn that went off once and stopped.

Getting there took one rule, two bad passes and a story that fit the clock and nothing else.

The rule

The recorder is a Shinobi NVR in a virtual machine. All ten cameras record audio, AAC at 16 kHz mono, in 15-minute segments. The agent got in through the hypervisor’s guest agent and read the camera list out of Shinobi’s own database.

All processing happens on the recorder. Only finished clips leave it. Raw footage never does.

So the agent built a scratch directory on the box, put numpy and scipy in a private Python environment, read the recordings without touching them, and deleted the scratch directory when it was done. What came off the machine was a few candidate reels, a 15-second clip, and a blurred four-room video with level meters burned in. That’s the whole export.

The rule worked. Nothing about the investigation needed the footage to go anywhere.

The detector can’t hear

A beep is a narrow, steady tone. Speech, footsteps and dishes smear energy across the spectrum. So the detector ran a spectrogram at two resolutions, 64 ms frames for sustained tones and 16 ms for short chirps, and in each frame kept the strongest bin between 0.7 and 7.8 kHz only if it stood 15–18 dB above both its own usual level and the rest of the frame. Then it had to hold the same pitch for several frames running.

It found plenty of tones. The first reels were birds.

Outdoor cameras are full of birdsong, and birdsong is a narrow, steady tone. The detector did exactly what it was told. It has no idea what a bird is. One question would have saved that round trip: indoors or out? Indoors, and the window widened to 00:03–00:13.

The fix wasn’t a better detector. It was a better question about the house. A real household beep is one sound heard in several rooms at once. Group same-pitch hits within 80 Hz and 0.6 seconds across cameras, rank by how many rooms heard it, and the event at 00:11:05 falls straight out: all six indoor cameras, about 30 dB above anything else.

What it is

A narrow filter at 3417 Hz, 10 ms resolution, with a purity check against 2.9 and 3.95 kHz on either side. One continuous tone, 0.87 seconds, the same length on every camera. No real overtones.

An earlier, looser pass had flagged two repeats at 00:13. With the purity check on they turned out to be broadband noise that happened to have energy in the band. Two days of audio from two rooms had no other long, pure beep at that pitch. One beep, once.

Residential smoke and CO horns are piezos in the 3–4 kHz range, loud enough to cross a house. Appliance beepers can’t reach five rooms. Then the pattern rules things out:

  • a low-battery chirp repeats every 30–60 seconds for days;
  • a fire alarm runs Temporal-3, three half-second pulses, over and over;
  • a CO alarm runs Temporal-4, four short pulses, over and over.

One note and silence fits none of them. It fits a hardwired alarm reacting to its own electronics.

Where it is

Arrival-time triangulation was out. Segment start times are only good to about a second, and the camera clocks drift by up to five. So the agent used loudness instead.

CameraPeak (dBFS)vs Room A
Room A−22.90
Room B−32.8−9.9 dB
Room C−35.1−12.2 dB
Room D−48.7−25.8 dB
Room E−67.7−44.8 dB
Room F−70.5−47.6 dB

Same camera model, same stream profile, noise floors all between −87 and −92 dBFS in that band, so treat the mics as equal. Sound falls about 6 dB per doubling of distance, so a level gap is a distance ratio: B is 3.1 times farther from the alarm than A is, and C is 4.1 times. Each ratio puts the source on a small circle around camera A, with a radius of about a third of the camera spacing or less. B and C are only 2.3 dB apart, so the alarm sits about equally far from both. With cameras five or six metres apart, that’s about two metres from camera A, just outside its field of view.

D, E and F are down 26 to 48 dB, which is mostly walls and doors. That rules those rooms out and adds no geometry.

The first version of this table was wrong too. It measured each camera against its own room’s background instead of in absolute dBFS, which is a fine way to ask “did this room hear something” and a bad way to convert decibels into metres.

The steam

Then the agent looked at the rest of the house. Home-automation history from 00:00 to 00:20, read-only.

Nothing happened between midnight and 00:12. No smart smoke alarms on the system. UPS input flat at 123.3–123.9 V, no transfer to battery. All 572 entities kept reporting through 00:10:30–00:11:40, so nothing dropped long enough to reset a smart plug.

Except the bathroom. Humidity climbed from 58% at 23:48 to 75.8% by 00:07 and held until about 00:40. Somebody had showered right before the beep.

The first write-up led with that. Shower steam, smoke alarm, done. It’s a clean story and it lands on the minute.

It’s wrong twice.

Wrong pattern. Steam trips the sensor, and a tripped sensor runs the full alarm cycle for as long as the steam hangs around. It does not play one note and quit.

Wrong place. Steam thick enough to trip an alarm rarely gets past the hallway outside the bathroom. The level map says the horn was by Room A.

The humidity spike matched the clock and failed every other test. Timing is the cheapest evidence there is. Everything that happened that night happened near 00:11. The useful questions were whether the clue produces this pattern, in this place.

What’s left is boring and more likely. Hardwired alarms with battery backup beep once when AC power comes back after a sag. A dip of a few cycles is enough, from a compressor kicking on or a grid blip, and that’s far too short for any Wi-Fi or Zigbee device to notice. The level map shows one loud source, not a chorus, so it wasn’t the whole interconnected set chirping on a wire glitch. One unit near Room A reacted on its own.

Why 00:11:05 exactly? Nothing logged says. A sub-second sag on one branch circuit is invisible to every sensor in the house. That’s as far as the evidence goes, and the write-up says so.

What I actually did

The agent wrote the STFT code, the clustering, the purity filter and the Apollonius circles, and it ran the whole thing on a box I didn’t want footage leaving. That part is fast now and I’m not going back.

The corrections were all questions. Indoors or out. Absolute or relative. Is that repeat a tone or just noise in the band. Does the steam explain the pattern, or only the time. Each one was cheap to ask and each one changed the answer. The machine is very good at running an FFT. It will also hand you a tidy story that fits the clock, and it will put it first.

The requester listened to the clip and said yes, that’s the one.

The last step is a step stool. Press test on the alarm nearest Room A, listen for 3.4 kHz, and read the date on the back.