Real-time and offline tooling for classifying spectrograms of household equipment audio, training the classifiers, and streaming predictions to downstream services over MQTT.
My washer and dryer is not smart, but I want to know when it's done and get a push notice when it becomes idle after a wash cycle. That requires some kind of sensor, and to that end I've tried electricity meters (ok success) and vibration sensors (low success).
A cnn classifier that can classify multiple devices and differentiate between steps in the cycle would be even better.
A simple €10 USB microphone, and a NanoPi with 1Gb ram.
Recorded cycles, idle ambiance and random activity to sound files. Labeling in https://labelstud.io/.
The spectrograms have distinct features depending on cycle and machine. Here is an example of wash cycle, dryer and spin program, all in 1/4 resolution.
Before landing in the 2d spectrum classifier a 1d version was evaluated. The spectrum analyzer had better result in mixed conditions, at a lower resource usage. Accuracy increased again when using a resnet design.
To save memory and compute, the spectrum is halved in resolution before passing though the classifier. Future development will try to get the same result with a 1/4 resolution that takes more accurate averages of the spectrum before classification.
As of writing, the network contains about 4.3m parameters, which is a wonderful overkill for it's purpose.
Graph of classification over an entire cycle.

This one with washer and dryer running at the same time (part of the time).
The "END" label is a small beep that is periodically emitted after cycle end. This has the potential to be a very good signal to the classifier, but more data is needed to train on it properly.
- Python 3.10+
- Recommended: virtual environment with
pip install -r requirements.txt(torch, torchvision, sounddevice, librosa, scipy, pillow). - macOS audio input with sounddevice-compatible drivers.
Set the Python environment:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt- In Label Studio choose Export → JSON-MIN.
- Save the export as
annotations/annotations.json(default the training script expects). - Each entry’s
audiofield should include the original.m4aname; the trainer matches spectrogram prefixes against this suffix.
To override the file path later, pass --annotations-file /path/to/export.json when training.
Convert raw .m4a recordings into 10-second grayscale spectrogram PNGs:
python sample_create.py audio/machine_sounds.m4a data_01/machine_soundsThe script slices audio into 44.1 kHz mono blocks and creates files named <audio_stem>_<seconds>.png. Those filenames drive the annotation lookup during training.
Train a residual CNN that predicts WASHER, DRYER, SPIN, IDLE, END, FAN labels (multi-label, sigmoid outputs):
python train.py \
--data-dir data_01 \
--annotations-file annotations/annotations.json \
--epochs 15 \
--batch-size 16 \
--img-size 171 286 \
--output models/train.ptHelpful flags:
--val-split(default 0.2) to change validation ratio.--seedfor reproducible shuffles.--num-workersto tune dataloader throughput.
The checkpoint saves class metadata alongside weights so evaluators and monitors can resolve label order.
python evaluate.py \
--model models/audioclass.pt \
--spectrogram data_01/end1_690.pngSample output:
WASHER : 0.9423
SPIN : 0.1035
IDLE : 0.0112
END : 0.0021
DRYER: : 0.1231
FAN : 0.0004
Use the --device flag to force cpu|cuda|mps if needed.
monitor_torch.py and monitor_onnx.py mirror the capture loop in record.py, but feed each 10-second spectrogram directly into the trained model and publish JSON payloads to an MQTT topic (one message per window).
python monitor_torch.py \
--model models/audioclass.pt \
--mqtt-topic appliances/wash1 \
--mqtt-host localhost \
--device-index 0 \
--device mpspython monitor_onnx.py \
--onnx-model models/audioclass.onnx \
--mqtt-topic appliances/wash1 \
--mqtt-host 192.168.1.50 \
--device-index 1Each run prints the per-class probabilities and also sends a JSON document like the following to the requested topic:
{
"timestamp": "2025-12-05T13:44:48.082Z",
"probabilities": {
"WASHER": 0.463,
"SPIN": 0.658,
"IDLE": 0.607,
"END": 0.331,
"FAN": 0.104,
"DRYER": 0.1536
}
}record.py captures live spectrograms to disk every 10 seconds and optionally writes tag files (--tags washer,idle). Use this for manual verification of audio capture quality.
- Audio device index: run
python -m sounddeviceto list inputs and pass the correct--device-indextorecord.py/monitor_torch.py/monitor_onnx.py. - Missing libraries: ensure
brew install ffmpegso librosa can decode.m4asources. - MQTT connectivity: confirm your broker is reachable from the capture host; both monitor scripts log a warning any time
publish()fails.
-
Train on your workstation (PyTorch) and export the model to ONNX:
python export_onnx.py \ --checkpoint models/conv2d_model.pt \ --output export/conv2d_model.onnx \
-
Copy runtime artifacts to the Pi:
monitor_onnx.py,conv2d_model.py, the exported.onnxfile (and.meta.json), plusrequirements.min.txt. -
Install the lightweight dependencies on the Pi (no desktop PyTorch needed):
python -m venv .venv source .venv/bin/activate pip install -r requirements.min.txt -
Run the ONNX-only monitor (see section 6). Everything stays CPU-only and uses
onnxruntimefor inference, which fits well on Raspberry Pi hardware.
- Build the docker image
make dockerbuilds and outputs an image todocker/ - Load the docker image on host
docker load -i image.tar - Start the container
docker run \
--device /dev/snd/ \
--group-add audio \
-e MONITOR_MQTT_HOST=<your host> \
-e MONITOR_MQTT_TOPIC=washer \
dhogborg/washer:latest
Create a folder named debug/ and the script will write the spectrum data there. Transfer the image out of the container with docker cp <container>:/app/debug/spectrum_debug.png ~



