• Models
  • Contests
  • Slicer
  • Login
  • Start Here
    thingiverse-iconprintables-iconcults3d-iconmakerworld-iconmyminifactory-icon

    3D GO

    3D ModelsContestsCollectionsSaved ModelsOn a mobile device?

3D GO

Privacy Policy
Pomodoro Neopixel Puck 3D Printer File Image 1
Pomodoro Neopixel Puck 3D Printer File Image 2
Pomodoro Neopixel Puck 3D Printer File Image 3
Pomodoro Neopixel Puck 3D Printer File Image 4
Pomodoro Neopixel Puck 3D Printer File Thumbnail 1
Pomodoro Neopixel Puck 3D Printer File Thumbnail 2
Pomodoro Neopixel Puck 3D Printer File Thumbnail 3
Pomodoro Neopixel Puck 3D Printer File Thumbnail 4

Pomodoro Neopixel Puck

Pavel Minar avatarPavel Minar

March 28, 2026

printables-icon
DescriptionCommentsTags

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.

🛠 Component List

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.


🔌 Wiring Diagram

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

🕹 How It Works

1. Standby Mode

After connecting the power, the ring remains completely dark. The timer does nothing and "sleeps," consuming minimal power.

2. Starting the Countdown

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.

3. Pausing Time

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.

4. Resetting the Timer

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.

5. End of Countdown

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.

💻 Source Code 

#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

Related Models

bakercube preview image

bakercube

iomaa profile image

iomaa

44,463

Book Page Holder V3 preview image

Book Page Holder V3

fifindr profile image

fifindr

3,556

Mini Whistle preview image

Mini Whistle

fifindr profile image

fifindr

3,566

Bottle Opener and Cap GUN! preview image

Bottle Opener and Cap GUN!

3Deddy profile image

3Deddy

44,034

Funnel Tray preview image

Funnel Tray

fifindr profile image

fifindr

3,562

Customizable Spotify Keychain / Tag preview image

Customizable Spotify Keychain / Tag

ewt profile image

ewt

3,088

Mini Tape Gun - Tape Dispenser preview image

Mini Tape Gun - Tape Dispenser

brycelowe profile image

brycelowe

23,765

Drill Paint Mixer preview image

Drill Paint Mixer

fifindr profile image

fifindr

2,511