Bug testing, literally: a spider in the smart fan

My homemade bathroom fan stalled twice one morning. When I unplugged it, a spider crawled out. The fan has run slower ever since. This is what its own telemetry recorded.

pico-2whome-assistantmqtthardware

It's an autonomous duct fan that learns temperature, humidity and light, and adjusts itself automatically against its baseline readings. If the humidity is too high, it leaves the fan on until it draws it out.

What the fan reports

The controller is a Pico 2 W. It switches the fan on and off (there is no speed control) and reads the fan's tachometer, so it knows how fast the fan is actually turning. Every 30 seconds, and immediately when something changes, it publishes a status message to my MQTT broker with the speed, humidity, temperature, light level, controller state and any fault. Home Assistant picks those up.

If the fan is switched on but the tachometer reads under 800 rpm, the controller turns it back off and reports fan stalled, with a counter. In my data, a healthy run sits around 4,100 rpm.

I was also recording that traffic with a packet capture, which is where most of the numbers below come from.

The smart fan build on a bathroom mirror cabinet: a small 40 mm duct fan at top left, a filament light bulb, and a breadboard with a Raspberry Pi Pico 2 W and a tangle of red, yellow and green jumper wires, powered by USB.
The fan and its controller: a Pico 2 W on a breadboard, with the 40 mm fan above it.

The morning it stopped

Timeline of October 8 from 8:04 to 8:27 AM. The light came on at 8:07 and the controller switched the fan on, but the speed stayed at zero and a stall fault was raised at 8:08. The same happened at 8:18. At about 8:21 the controller was unplugged and plugged back in, and a spider crawled out of the fan. At 8:22 the fan spun up to about 3,100 to 3,500 rpm.LightFan onFault02,0004,000rpmpower cycle,spider out8:058:108:158:208:25October 8, AM (Eastern)
Oct 8, 8:04 to 8:27 AM. Two light-triggered starts, two stall faults at 0 rpm, a power cycle at 8:20:58 (when the spider came out), then the fan spins again, but only reaches about 3,100 to 3,500 rpm.

At 8:07:58 the light came on and the controller switched the fan on. Four seconds later the tachometer still read zero, so it raised fan stalled (0 rpm), stall 1/3 and cut power. At 8:18 the same thing happened again: stall 2/3.

At 8:20 I was troubleshooting with the agent, and we couldn't find anything technically wrong with it. So I went to troubleshoot the fan and the device itself. I unplugged it and plugged it back in, and a spider came crawling out of the fan.

I found a white spider on the breadboard, on the pins of the Pico and on the fan of the project. Two seconds after I took it apart, the spider was down the toilet, so there's no photo of the culprit.

The data shows that power cut at 8:20:58. The controller's uptime counter had reached 14,073 minutes, so it had been running without a restart since Sep 28. Once it was back, the next light-triggered start at 8:22 worked: the fan spun up within four seconds.

Running slow

Steady fan speed for every run from September 29 to October 9, 2026. Before the stall on October 8 every run held between about 4,000 and 4,180 rpm, median 4,086. The spider came out at the power cycle at about 8:21 AM on October 8. After that, runs held about 3,290 to 3,500 rpm on October 8 and 3,187 rpm on October 9.3,0003,5004,0004,500Sep 29Oct 1Oct 3Oct 5Oct 7Oct 9rpm, steady part of each runstall,spider outdashed: before-stall median 4,086after removal: 3,187 to 3,502Circles: before the stall. Triangles: after the spider was removed.
Steady speed for every run, Sep 29 to Oct 9 (median of the readings taken at least a minute into the run, to skip spin-up). Short boost button taps under a minute are left out.

With the spider gone, the fan spun again, but it has not returned to its old speed. Before Oct 8 every run held between about 4,000 and 4,180 rpm. After the spider was removed it held about 3,500 for the rest of that day, and about 3,190 the next morning. That is still well above the 800 rpm stall threshold, so the controller raised no fault. Only the speed history shows it. As of the last run (Oct 9, 7:58 AM), it hasn't recovered yet.

Fan speed during the first ten minutes of three runs. On October 8 at 2:58 AM, before the stall, the fan reached about 4,200 rpm. After the spider was removed, on October 8 at 10:48 AM it held about 3,500 rpm, and on October 9 at 7:58 AM about 3,190 rpm.01,0002,0003,0004,0000 min2 min4 min6 min8 min10 minrpmOct 8, 2:58 AM (before)Oct 8, 10:48 AM (after removal)Oct 9, 7:58 AMThe 10:48 AM run was a 3-minute light run, so its line ends there.
The first ten minutes of three runs. The 2:58 AM run was the last one before the stall.
PeriodRunsMedian rpmRange (10th to 90th pct)Max
Before the stall (Sep 29 to Oct 8, 4 AM)204,0864,014 to 4,1784,396
After the spider was removed, Oct 833,4993,323 to 3,5313,554
After the spider was removed, Oct 913,1873,115 to 3,2753,293

The night before: a shower in the dark

Bathroom humidity on October 8. A shower in the dark at about 3 AM pushed humidity from 61 to 97.3 percent; the fan ran for its 60-minute maximum and stopped while humidity was still 68 percent. Two stall faults followed at 8:08 and 8:18 AM. The spider came out at the 8:21 AM power cycle, followed by three short light-triggered runs at 8:22 AM, 10:48 AM and 3:05 PM.60%70%80%90%100%midnight6 AMnoon6 PMmidnight97.3% at 3:02 AMfan stops at 60-min capstalls 8:08 and 8:18spider out 8:21relative humidityShaded green: fan running. Shaded red: stall fault.Dashed line: the learned humidity baseline.
Oct 8, midnight to midnight. Humidity (solid), the controller's learned baseline (dashed), fan runs (green) and stall faults (red).

Five hours before the stall, the fan was still running at full speed. At 2:58 AM humidity started climbing with the light essentially off, which looks like a shower in the dark. The controller started the fan on humidity alone, at full speed: median 4,086 rpm, peak 4,236. Humidity peaked at 97.3% at 3:02 AM. The run stopped at the controller's 60-minute maximum at 3:58, with humidity still at 68.4%. That was the last full-speed run in the data.

Key events

The whole sequence, in Eastern time:

When (ET)What happened
Sep 28, ~1:47 PMController boots; it runs without a restart until Oct 8
Oct 8, 2:58 AMHumidity-only run, light off; humidity 61.4% to 97.3%
Oct 8, 3:58 AMRun stops at the 60-minute maximum; last full-speed run
Oct 8, 8:07:58 AMLight on, fan switched on, tachometer reads 14 rpm
Oct 8, 8:08:02 AMFault: fan stalled (0 rpm), stall 1/3
Oct 8, 8:18:13 AMFault: stall 2/3
Oct 8, 8:20:58 AMI unplug it and plug it back in (boot reason power-on); a spider crawls out of the fan
Oct 8, 8:22:12 AMFan spins again, now slower; 3,293 rpm steady
Oct 8, 10:48 AM, 3:05 PMTwo 3-minute light runs, about 3,500 rpm
Oct 9, 7:58 AM39-minute shower run, humidity peak 95.9%; 3,187 rpm steady
Oct 9, 3:04 to 3:21 PMServer hosting the broker reboots for an update; 16.6 minutes with no fan messages. Controller resets at 3:20:43

About the data

  • Source: a packet capture of the fan's MQTT status messages (about one every 30 seconds), Sep 28 evening to Oct 9, 6:43 PM. 28,613 messages. The last fan run in that window was Oct 9 at 7:58 AM.
  • Gap filled from Home Assistant: the capture for Oct 5, 6:04 PM to Oct 6, 6:10 PM was lost before it was copied off. That day's single run (48 minutes, 4,043 rpm) comes from Home Assistant's history instead.
  • Outage: no data on Oct 9 from 3:04 to 3:21 PM while the broker's host rebooted.
  • “Steady” speed means readings at least 60 seconds into a run, so spin-up does not pull the numbers down. The tachometer value is what the controller reports. I did not measure it separately.

Bug testing, literally

I learned that bug testing sometimes actually includes testing for literal bugs. This, for me, was a first.