Every treasure needs a guard. This box looks ordinary, but under its false floor an MPU-6050 feels every movement. When you arm it from the Blynk app, it remembers which way is down. If someone picks the box up, shakes it or tips it, the red LED flashes, the buzzer sounds for ten seconds and your phone gets a notification, wherever you are. Disarm it from the app before you open it yourself. The app also counts how many times it was moved.
What you'll learn
- How an accelerometer knows which way is down
- How to measure a tilt as the angle between two directions
- How to give yourself time to walk away before the alarm is armed
- How to switch a device on and off from the Blynk app and keep the switch after a restart
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- GY-521 MPU-6050 module (45-in-1 Sensor Kit)
- Active buzzer module (45-in-1 Sensor Kit)
- Red LED and 1 resistor, 220 Ω (ESP32 IoT Starter Kit)
- A free Blynk account and the Blynk IoT app on a phone
- A 2.4 GHz Wi-Fi network
- The printed parts from the 3D printing box below (treasure box), or the cardboard version described there
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Wiring

| From | To |
|---|---|
| MPU-6050 VCC and GND | 3V3 and GND |
| MPU-6050 SCL | ESP32 GPIO 22 |
| MPU-6050 SDA | ESP32 GPIO 21 |
| Buzzer module S | ESP32 GPIO 26 |
| Buzzer module + and − | 3V3 and GND |
| ESP32 GPIO 25 | 220 Ω resistor, then the red LED's long leg |
| LED short leg | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
Set up Blynk
In the Blynk console, create a new template called Treasure Box (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 | Armed | Integer | 0–1 | Switch |
| V1 | Status | String | – | Label |
| V2 | Moves | Integer | 0–1000 | Value display |
| V3 | Siren | Integer | 0–1 | Switch |
Then open the template's Events & Notifications tab, add an event with the code box_moved (name: Box moved) 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 two Switches (V0 and V3), a Label (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>.
#define BLYNK_TEMPLATE_ID "YOUR_TEMPLATE_ID"
#define BLYNK_TEMPLATE_NAME "Treasure Box"
#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;
const int BUZZER_PIN = 26;
const int LED_PIN = 25;
const float SHAKE = 0.08; // shake in g that sets it off
const float TILT = 15; // tilt in degrees that sets it off
const unsigned long GRACE = 10000; // 10 s to put the box down after arming
const unsigned long SIREN = 10000; // alarm lasts 10 s
BlynkTimer timer;
bool armed = false;
bool siren = true;
unsigned long armAt = 0; // when arming really starts (0 = done)
float refX = 0, refY = 0, refZ = 1; // "down" when it was armed
float baseline = 1.0;
int moves = 0;
unsigned long alarmUntil = 0, lastNotify = 0;
void readAccel(float& x, float& y, float& z) {
Wire.beginTransmission(MPU);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU, 6);
int16_t ax = Wire.read() << 8 | Wire.read();
int16_t ay = Wire.read() << 8 | Wire.read();
int16_t az = Wire.read() << 8 | Wire.read();
x = ax / 16384.0;
y = ay / 16384.0;
z = az / 16384.0;
}
// Angle in degrees between "down" now and "down" when armed
float tilt(float x, float y, float z) {
float dot = x * refX + y * refY + z * refZ;
float len = sqrt(x * x + y * y + z * z) * sqrt(refX * refX + refY * refY + refZ * refZ);
if (len == 0) return 0;
return acos(constrain(dot / len, -1.0f, 1.0f)) * 180 / PI;
}
void status(const char* text) {
Blynk.virtualWrite(V1, text);
Serial.println(text);
}
void alarm() {
if (millis() >= alarmUntil) { // a new alarm, not the same one going on
moves++;
Blynk.virtualWrite(V2, moves);
status("Moved!");
}
alarmUntil = millis() + SIREN;
if (lastNotify == 0 || millis() - lastNotify > 60000) { // at most one notification a minute
Blynk.logEvent("box_moved", "Someone is moving your treasure box!");
lastNotify = millis();
}
}
void check() { // 50 times a second
float x, y, z;
readAccel(x, y, z);
float g = sqrt(x * x + y * y + z * z);
baseline = baseline * 0.95 + g * 0.05;
float shake = fabs(g - baseline);
if (armed && armAt != 0 && millis() >= armAt) { // grace time over: remember "down"
refX = x; refY = y; refZ = z;
armAt = 0;
status("Armed");
}
if (armed && armAt == 0 && (shake > SHAKE || tilt(x, y, z) > TILT)) alarm();
bool on = millis() < alarmUntil && (millis() / 200) % 2;
digitalWrite(LED_PIN, on || (armed && armAt != 0 && (millis() / 1000) % 2)); // slow blink while arming
digitalWrite(BUZZER_PIN, on && siren);
}
BLYNK_WRITE(V0) { // Armed switch
armed = param.asInt();
alarmUntil = 0;
if (armed) {
armAt = millis() + GRACE;
status("Arming in 10 s...");
} else {
armAt = 0;
status("Off");
}
}
BLYNK_WRITE(V3) { // Siren switch
siren = param.asInt();
}
BLYNK_CONNECTED() {
Blynk.syncVirtual(V0, V3); // keep the switches after a restart
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
Wire.begin(21, 22);
Wire.beginTransmission(MPU);
Wire.write(0x6B); // power register
Wire.write(0); // wake up
Wire.endTransmission();
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
timer.setInterval(20L, check);
}
void loop() {
Blynk.run();
timer.run();
}
Try it
- Put the box on a table and turn on Armed in the app. The LED blinks slowly for 10 seconds while you step back, then the status says "Armed".
- Lift the box: the LED flashes, the buzzer sounds and your phone gets a notification. The Moves counter goes up.
- Turn Armed off to stop the alarm. Turn Siren off for a silent alarm that only notifies you.
How it works
Even when the box stands still, the accelerometer feels gravity pulling down. The three numbers x, y and z together are an arrow pointing down, seen from the box. When you arm it, the sketch waits 10 seconds and then saves that arrow as refX, refY and refZ.
If the box is tipped, the arrow points somewhere else. The dot product of the old and the new arrow, divided by their lengths, is the cosine of the angle between them, and acos() turns it back into degrees. More than 15° means someone has tipped it.
Lifting the box straight up hardly tips it, but it causes a short jolt. A slow average of the total acceleration, baseline, stays near 1 g, so any quick difference from it is a shake. Either sign sets off the alarm.
Blynk.syncVirtual() asks the server for the switches' last values when the ESP32 connects, so the box stays armed even after a power cut.
Try this next
- Add the photoresistor module under the lid to notice when the box is opened, even without moving it.
- Make the alarm louder each time: a longer siren after every move.
- Send the angle it was tipped to the app, to see how roughly it was handled.
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. -
It goes off by itself: the box stands on a table that wobbles, or the module is loose. Fix it with sticky tack, or raise
SHAKEto 0.12. -
The buzzer sounds all the time: some buzzer modules switch on with LOW. Swap them round:
digitalWrite(BUZZER_PIN, !(on && siren)). - Nothing happens when you lift it: check SDA on GPIO 21, SCL on GPIO 22, and that Armed is on and the 10 s are over.
- 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.