Many people study and work in blocks of 25 minutes of focus followed by a 5-minute break; it is called the Pomodoro technique. In this lesson you build a desk light for it. Start a session from the Blynk app or the button on the board: the cube glows red while you focus, and the app shows the minutes left. When the focus time is over, the cube turns green, a buzzer chirps and Blynk sends a notification to your phone. After the break it turns blue and waits for the next session.
What you'll learn
- How to build a timer with
BlynkTimerinstead ofdelay() - How to keep the device, the app and the button in step
- How to count completed sessions
- How to send a Blynk event
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- RGB LED module (45-in-1 Sensor Kit)
- 3 resistors, 220 Ω (ESP32 IoT Starter Kit)
- 1 push button (ESP32 IoT Starter Kit)
- Passive buzzer (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 (light cube), or the cardboard version described there
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Wiring

| From | To |
|---|---|
| RGB module R | 220 Ω resistor, then GPIO 25 |
| RGB module G | 220 Ω resistor, then GPIO 26 |
| RGB module B | 220 Ω resistor, then GPIO 33 |
| RGB module − | GND |
| Push button, one leg | GPIO 4 |
| Push button, the diagonally opposite leg | GND |
| Passive buzzer + | GPIO 23 |
| Passive buzzer − | 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 Focus Timer (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 | Running | Integer | 0–1 | Switch |
| V1 | Focus minutes | Integer | 5–60 | Slider |
| V2 | Minutes left | Integer | 0–60 | Gauge or Value display |
| V3 | Phase | String | – | Label |
| V4 | Sessions today | Integer | 0–50 | Value display |
Then open the template's Events & Notifications tab, add an event with the code break_time (name: Break time) 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 Switch (V0), a Slider (V1), a Gauge (V2), a Label (V3) and a Value display (V4). 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 "Focus Timer"
#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>
char ssid[] = "YOUR_WIFI_NAME";
char pass[] = "YOUR_WIFI_PASSWORD";
const int RED_PIN = 25, GREEN_PIN = 26, BLUE_PIN = 33;
const int BUTTON_PIN = 4;
const int BUZZER_PIN = 23;
const int BREAK_MINUTES = 5;
const byte IDLE = 0, FOCUS = 1, BREAK = 2;
byte phase = IDLE;
int focusMinutes = 25;
int secondsLeft = 0;
int sessions = 0;
bool lastButton = HIGH;
BlynkTimer timer;
void colour(int r, int g, int b) {
analogWrite(RED_PIN, r);
analogWrite(GREEN_PIN, g);
analogWrite(BLUE_PIN, b);
}
void show() {
if (phase == FOCUS) colour(255, 0, 0);
else if (phase == BREAK) colour(0, 255, 0);
else colour(0, 0, 60);
const char* names[] = {"Ready", "Focus", "Break"};
Blynk.virtualWrite(V0, phase == IDLE ? 0 : 1);
Blynk.virtualWrite(V2, (secondsLeft + 59) / 60);
Blynk.virtualWrite(V3, names[phase]);
Blynk.virtualWrite(V4, sessions);
}
void chirp() {
tone(BUZZER_PIN, 1047, 120);
delay(160);
tone(BUZZER_PIN, 1319, 160);
}
void startFocus() {
phase = FOCUS;
secondsLeft = focusMinutes * 60;
show();
}
void stopAll() {
phase = IDLE;
secondsLeft = 0;
show();
}
void everySecond() {
if (phase == IDLE) return;
secondsLeft--;
if (secondsLeft % 60 == 0) Blynk.virtualWrite(V2, secondsLeft / 60);
if (secondsLeft > 0) return;
if (phase == FOCUS) {
sessions++;
phase = BREAK;
secondsLeft = BREAK_MINUTES * 60;
chirp();
Blynk.logEvent("break_time", String("Session ") + sessions + " done. Take a " + BREAK_MINUTES + "-minute break!");
} else {
phase = IDLE;
chirp();
}
show();
}
BLYNK_CONNECTED() {
Blynk.syncVirtual(V1); // get the focus time saved in the app
show();
}
BLYNK_WRITE(V0) { // the switch in the app
if (param.asInt() == 1 && phase == IDLE) startFocus();
else if (param.asInt() == 0) stopAll();
}
BLYNK_WRITE(V1) { // the slider: minutes of focus
focusMinutes = constrain(param.asInt(), 5, 60);
}
void setup() {
Serial.begin(115200);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
colour(0, 0, 60);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
timer.setInterval(1000L, everySecond);
}
void loop() {
Blynk.run();
timer.run();
bool button = digitalRead(BUTTON_PIN);
if (button == LOW && lastButton == HIGH) { // the button starts or stops
if (phase == IDLE) startFocus();
else stopAll();
delay(200);
}
lastButton = button;
}
Try it
- Upload the sketch and open the Serial Monitor at 115200 baud until Blynk says it is connected. The cube glows a soft blue.
- Set the slider to 5 minutes for a quick test and turn on the switch in the app. The cube turns red and the gauge counts down.
- After 5 minutes the cube turns green, the buzzer chirps and your phone shows the "Break time" notification.
- Press the button on the board to stop a session; the switch in the app follows.
How it works
BlynkTimer calls everySecond() once a second while loop() keeps running, so the button and the Blynk connection never have to wait. The timer only counts down secondsLeft; everything that changes when a phase ends happens in one place, which keeps the program easy to follow.
The device is the "boss" of the state. Whether you press the button or flip the switch in the app, the sketch changes phase and then calls show(), which updates the light and every widget. That way the app always shows what the cube is doing. BLYNK_CONNECTED() asks for the slider's value after every connection, so your chosen focus time survives a restart.
Try this next
- Make every fourth break 15 minutes long, as in the original Pomodoro technique.
- Fade the cube slowly from red to orange in the last minute, so the end doesn't surprise you.
- Add a Superchart in Blynk for
Sessions todayto see your week.
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. -
No notification arrives: check that the event code is exactly
break_timeand that notifications are turned on for it in the template and on your phone. - The cube colours are mixed up: swap the wires on GPIO 25, 26 and 33.
- The cube is too dark: print it with thinner walls (vase mode) and in natural or white filament.
- 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.