Washing machines beep when they finish, but you don't hear it in another room, and wet washing left in the drum starts to smell. In this lesson you build a sensor that feels the machine shaking. It works out when a wash has started and, when the shaking stops for three minutes, sends a notification to your phone. It works with any washing machine or dryer, because nothing is connected to the machine: the sensor just sits on top.
What you'll learn
- How to read an accelerometer directly over I2C with the
Wirelibrary - How to measure vibration as the change between readings
- How to tell a real stop from a short pause
- How to show a level and a status in Blynk
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- GY-521 MPU-6050 module (45-in-1 Sensor Kit)
- 4 female-to-male jumper wires, long enough to reach from the machine to the ESP32
- A free Blynk account and the Blynk IoT app on a phone
- A 2.4 GHz Wi-Fi network
- A strong double-sided mounting strip
- The printed parts from the 3D printing box below (sensor pod and lid), or the cardboard version described there
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Wiring

| From | To |
|---|---|
| MPU-6050 VCC | ESP32 3V3 |
| MPU-6050 GND | GND |
| MPU-6050 SCL | ESP32 GPIO 22 |
| MPU-6050 SDA | ESP32 GPIO 21 |
Safety: a washing machine uses mains electricity and water. Only stick the pod on the outside of the machine, keep the ESP32 and its USB charger away from water, and never open the machine.
Set up Blynk
In the Blynk console, create a new template called Laundry Alert (hardware ESP32, connection type Wi-Fi) and add these datastreams. Lesson 1 of this path shows each step in detail.
| Virtual pin | Name | Data type | Range | Phone widget |
|---|---|---|---|---|
| V0 | Status | String | – | Label |
| V1 | Vibration | Integer | 0–100 | Gauge or Superchart |
| V2 | Minutes running | Integer | 0–300 | Value display |
Then open the template's Events & Notifications tab, add an event with the code laundry_done (name: Laundry done) and turn on push notifications for it. The event code must match the sketch exactly.
Save the template, create a new device from it and copy its three BLYNK_… lines. In the phone app, open the device, tap the wrench icon and add a Label (V0), a Gauge or Superchart (V1) and a Value display (V2). Link each widget to its datastream.

The sketch
Replace the three BLYNK_… lines at the top with the ones from your device's Device info page, and add your Wi-Fi name and password (2.4 GHz only). The BLYNK_… lines must stay above #include <BlynkSimpleEsp32.h>.
This sketch reads the MPU-6050 directly with the built-in Wire library, so it needs no extra sensor library.
#define BLYNK_TEMPLATE_ID "YOUR_TEMPLATE_ID"
#define BLYNK_TEMPLATE_NAME "Laundry Alert"
#define BLYNK_AUTH_TOKEN "YOUR_AUTH_TOKEN"
#define BLYNK_PRINT Serial // show connection messages in the Serial Monitor
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <Wire.h>
char ssid[] = "YOUR_WIFI_NAME";
char pass[] = "YOUR_WIFI_PASSWORD";
const int MPU = 0x68; // I2C address of the MPU-6050
const int SHAKING = 12; // vibration level that counts as "running"
const unsigned long START_AFTER = 60000; // shaking for 1 min = a wash has started
const unsigned long DONE_AFTER = 180000; // still for 3 min = the wash is over
int16_t lastX, lastY, lastZ;
long vibrationSum = 0;
int samples = 0;
bool running = false;
unsigned long shakingSince = 0, stillSince = 0, startedAt = 0;
BlynkTimer timer;
void readAccel(int16_t &x, int16_t &y, int16_t &z) {
Wire.beginTransmission(MPU);
Wire.write(0x3B); // first acceleration register
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6);
x = (Wire.read() << 8) | Wire.read();
y = (Wire.read() << 8) | Wire.read();
z = (Wire.read() << 8) | Wire.read();
}
// Called 50 times a second: how much has the sensor moved since last time?
void sample() {
int16_t x, y, z;
readAccel(x, y, z);
vibrationSum += abs(x - lastX) + abs(y - lastY) + abs(z - lastZ);
samples++;
lastX = x; lastY = y; lastZ = z;
}
// Called every 2 seconds: decide whether the machine is running
void decide() {
int level = constrain(vibrationSum / max(samples, 1) / 10, 0, 100);
vibrationSum = 0;
samples = 0;
unsigned long now = millis();
bool shaking = level >= SHAKING;
if (shaking) {
stillSince = 0;
if (shakingSince == 0) shakingSince = now;
} else {
shakingSince = 0;
if (stillSince == 0) stillSince = now;
}
if (!running && shakingSince && now - shakingSince > START_AFTER) {
running = true;
startedAt = now;
Blynk.virtualWrite(V0, "Washing...");
}
if (running && stillSince && now - stillSince > DONE_AFTER) {
running = false;
int minutes = (stillSince - startedAt) / 60000;
Blynk.virtualWrite(V0, "Done! Empty the machine");
Blynk.logEvent("laundry_done", String("Your washing is done after ") + minutes + " minutes.");
}
Blynk.virtualWrite(V1, level);
Blynk.virtualWrite(V2, running ? (int)((now - startedAt) / 60000) : 0);
Serial.printf("vibration %d %s\n", level, running ? "(running)" : "");
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
Wire.beginTransmission(MPU);
Wire.write(0x6B); // power register
Wire.write(0); // wake the sensor up
Wire.endTransmission();
readAccel(lastX, lastY, lastZ);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
Blynk.virtualWrite(V0, "Waiting for a wash");
timer.setInterval(20L, sample);
timer.setInterval(2000L, decide);
}
void loop() {
Blynk.run();
timer.run();
}
Tune it to your machine
- Upload the sketch with the pod lying still on the table and open the Serial Monitor at 115200 baud. The vibration level should be 0 to 5.
- Tap the table next to it: the level jumps up for a moment.
- Stick the pod on the machine and start a wash. Watch the Vibration gauge in the app: when the drum turns it should stay above 12 most of the time.
- If it doesn't, make
SHAKINGsmaller. If the level is above 12 even when the machine is off (for example because of a fridge nearby), make it bigger.
How it works
The MPU-6050 measures acceleration in three directions. When it lies still, the readings hardly change; when the machine shakes, they change a lot from one reading to the next. sample() adds up those changes 50 times a second, and decide() turns the average into a vibration level from 0 to 100.
A wash has pauses: the drum stops to change direction, and the machine waits while it fills with water. That is why the sketch only says "running" after a full minute of shaking and only says "done" after three minutes of stillness. Times like these, which stop a short change from switching the state, are called debouncing, the same idea as for buttons, only much slower.
Try this next
- Also send a notification when the wash starts, with the time it is likely to finish.
- Count washes per week and show them in a Superchart.
- Use the same sensor on a tumble dryer or dishwasher. Do you need different times?
Troubleshooting
-
The device shows "Offline" in the app: check the Serial Monitor at 115200 baud. "Invalid auth token" means the
BLYNK_AUTH_TOKENline is wrong; if it never connects, check the Wi-Fi name and password, and that your network is 2.4 GHz. - The vibration level is always 0: check SDA on GPIO 21 and SCL on GPIO 22. The module needs 3V3, not 5V.
-
"Done" arrives in the middle of a wash: make
DONE_AFTERlonger, for example 300000 (5 minutes). -
It never says "Washing": make
SHAKINGsmaller, see above. - A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.