March 28, 2026
Description
I was looking for something with a single function: only counting down 25 minutes. Since I couldn’t find anything reasonable without multiple functions, I decided to build it myself. I generated the code using Canvas in Gemini.
To build this project, you will only need a few basic and inexpensive components:
Wemos D1 Mini – The "brain" of the entire device.
NeoPixel Ring (24 LEDs, WS2812B) – For the visual countdown display.
TTP223 Touch Sensor – A capacitive module for control.
NeoPixel Ring:
VCC ➔ Wemos 5V
GND ➔ Wemos GND
DIN (Data In) ➔ Wemos D1
TTP223 Touch Sensor:
VCC ➔ Wemos 3V3
GND ➔ Wemos GND
SIG ➔ Wemos D2
After connecting the power, the ring remains completely dark. The timer does nothing and "sleeps," consuming minimal power.
Briefly touch the sensor to immediately start the 25-minute countdown. The ring lights up in a full rainbow, and two things begin to happen:
Time Remaining: The LEDs gradually turn off one by one as time passes.
White "Second Hand": A white dot moves smoothly around the ring (counter-clockwise). It completes one rotation every 10 seconds, letting you know the device is active and time is running.
Is someone talking to you, or do you need a quick break? Briefly touch the sensor. The moving white dot stops instantly, and the brightness of the entire ring dims significantly. This clearly indicates that time is frozen. Another brief touch resumes the countdown exactly where it left off, and the brightness returns to normal.
Need to start over? At any point during the countdown or pause, hold your finger on the sensor for more than 1 second. The ring will flash blue to confirm, and the device will return to the dark standby mode, ready for a new cycle.
Once the full 25 minutes have elapsed, the ring gently flashes green three times. It then turns off completely, and the device returns to deep sleep. The next touch will wake it up for your next work session.
#include <Adafruit_NeoPixel.h>
#include <ESP8266WiFi.h> // Přidána knihovna pro ovládání Wi-Fi
// Přidáno pro přístup k hlubším systémovým funkcím (nastavení spánku CPU)
extern "C" {
#include "user_interface.h"
}
#define PIN D1
#define NUMPIXELS 24
#define TOUCH_PIN D2 // Přidán pin pro dotykový senzor TTP223
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// --- NASTAVENÍ POMODORO ČASOVAČE ---
const int timerMinutes = 25; // Použito const - šetří RAM, hodnota se nemění
unsigned long durationMs;
unsigned long startTime;
bool isFinished = false;
bool isRunning = false; // Nová proměnná hlídající, jestli už byl odpočet spuštěn
// Přidáno pro pokročilé ovládání dotykem (Pauza a Reset)
bool isPaused = false;
unsigned long elapsedAtPause = 0;
bool lastTouchState = false;
unsigned long touchPressTime = 0;
bool isLongPressHandled = false;
// --- NASTAVENÍ ANIMACE ---
const int jas = 30; // Konstantní jas
// Zpomaleno na polovinu: Čas otáčky bílé LED zvýšen z 5000 ms na 10000 ms (10 s)
const unsigned long dobaOtacky = 10000UL;
// Předpočítaný krok duhy pro jednu LED (zefektivní výpočet ve smyčce)
const uint32_t HUE_KROK = 65536UL / NUMPIXELS;
void setup() {
// Vypnutí Wi-Fi pro maximální úsporu baterie
WiFi.mode(WIFI_OFF);
WiFi.forceSleepBegin();
delay(1);
// Povolení Light Sleep módu - procesor bude během funkce delay() "spát" a šetřit energii
wifi_set_sleep_type(LIGHT_SLEEP_T);
pinMode(TOUCH_PIN, INPUT); // Nastavení pinu senzoru pro čtení dotyku
pixels.begin();
pixels.clear();
pixels.show(); // Zaručí, že po zapnutí Wemosu bude pásek zaručeně tmavý
durationMs = timerMinutes * 60UL * 1000UL;
// Zrušili jsme startTime = millis(); - ten se nyní zaznamená až po dotyku
}
void loop() {
unsigned long currentMillis = millis();
bool currentTouch = digitalRead(TOUCH_PIN) == HIGH;
// --- OBSLUHA DOTYKOVÉHO SENZORU ---
// 1. Zaznamenání začátku dotyku
if (currentTouch && !lastTouchState) {
touchPressTime = currentMillis;
isLongPressHandled = false;
}
// 2. Kontrola dlouhého stisku (více než 1000 ms)
if (currentTouch && lastTouchState) {
if ((currentMillis - touchPressTime > 1000) && !isLongPressHandled) {
isLongPressHandled = true; // Označíme, ať se nevolá znovu
// RESET ČASOVAČE
isRunning = false;
isPaused = false;
isFinished = false;
elapsedAtPause = 0;
// Vizuální potvrzení resetu - rychlé modré probliknutí
pixels.fill(pixels.Color(0, 0, jas + 20));
pixels.show();
delay(300);
pixels.clear();
pixels.show();
}
}
// 3. Kontrola krátkého stisku (při uvolnění)
if (!currentTouch && lastTouchState) {
if (!isLongPressHandled) {
if (!isRunning) {
// První spuštění z klidu
isRunning = true;
isFinished = false;
isPaused = false;
startTime = millis();
} else if (isFinished) {
// Spuštění nového odpočtu po dokončení předchozího
isFinished = false;
isRunning = true;
startTime = millis();
} else {
// Pauza / Odpauznutí probíhajícího odpočtu
isPaused = !isPaused;
if (isPaused) {
elapsedAtPause = currentMillis - startTime; // Uložíme si uplynulý čas
} else {
startTime = currentMillis - elapsedAtPause; // Posuneme start o dobu, co to spalo
}
}
}
}
lastTouchState = currentTouch;
// --- KLIDOVÉ STAVY ---
if (!isRunning && !isFinished) {
delay(50); // Úspora baterie, když čekáme na spuštění
return;
}
if (isFinished) {
// Sníženo z 1000 na 50 ms. Potřebujeme číst senzor pro restart,
// ale 50 ms s Light Sleep stále odebírá zanedbatelné minimum energie.
delay(50);
return;
}
// --- VÝPOČET ČASU PRO ANIMACI ---
unsigned long elapsed;
if (isPaused) {
elapsed = elapsedAtPause;
} else {
elapsed = currentMillis - startTime;
}
// KONTROLA KONCE ČASU
if (elapsed >= durationMs) {
// 3x zabliká zeleně na konci časovače
for(int i = 0; i < 3; i++) {
pixels.fill(pixels.Color(0, 50, 0));
pixels.show();
delay(500);
pixels.clear();
pixels.show();
delay(500);
}
isFinished = true;
return;
}
// VÝPOČET ZBÝVAJÍCÍCH LED (Optimalizováno bez použití desetinných čísel float)
unsigned long remainingMs = durationMs - elapsed;
// Celočíselná matematika je na ESP8266 mnohem rychlejší a šetří procesor
int activeLEDs = (remainingMs * NUMPIXELS + (durationMs / 2)) / durationMs;
if (activeLEDs < 1) activeLEDs = 1;
if (activeLEDs > NUMPIXELS) activeLEDs = NUMPIXELS;
// --- ANIMACE: STATICKÁ DUHA ---
pixels.clear();
// Základní odstín je nyní pevný, takže barvy na diodách se nebudou posouvat
uint32_t zakladniOdstin = 0;
// Nastavení jasu (výrazně ztlumíme při pauze pro vizuální odlišení)
int aktualniJas = isPaused ? 4 : jas;
// Výpočet pozice putující bílé LED (krouží PROTI směru hodinových ručiček)
// Použijeme rovnou 'elapsed', díky čemuž je tečka absolutně svázaná s odpočtem
// a při pauze přesně zamrzne na svém aktuálním místě bez jakéhokoliv skoku.
int bilaLED = (NUMPIXELS - 1) - (((elapsed % dobaOtacky) * NUMPIXELS) / dobaOtacky);
// Projdeme VŠECHNY LED na kroužku
for(int i = 0; i < NUMPIXELS; i++) {
// 1. Putující tečka
if (i == bilaLED) {
// Bílá tečka bude mít vždy o něco vyšší jas než okolí, aby vynikla
pixels.setPixelColor(i, pixels.ColorHSV(0, 0, aktualniJas + 20));
}
// 2. Aktivní oblast s duhou
else if (i < activeLEDs) {
// Použití předpočítané konstanty HUE_KROK zrychluje tento výpočet
uint32_t pixelHue = zakladniOdstin + (i * HUE_KROK);
// Použijeme aktualniJas (proměnlivý podle toho, jestli je pauza)
pixels.setPixelColor(i, pixels.ColorHSV(pixelHue, 255, aktualniJas));
}
}
pixels.show();
// Zvýšeno z 15 na 30 ms (cca 33 snímků za sekundu).
// Stále naprosto plynulé pro oko, ale procesor má 2x více času na spánek v Light Sleep módu!
delay(30);
}License:
Creative Commons — Attribution