/***************************************************************
╔═════════════════════════════════════════════════════════════╗
║ HNGR13 BOMB TIMER - KORRIGIERTE VERSION ║
║ ESP32 Version - Funktionierende Pins ║
╚═════════════════════════════════════════════════════════════╝
****************************************************************/
#include <Arduino.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <TM1637Display.h>
#include <Adafruit_NeoPixel.h>
// =============================================================================
// PIN-DEFINITIONEN FÜR ESP32 - KORRIGIERT
// =============================================================================
#define BTN_START 4
#define BTN_UP 2
#define BTN_DOWN 15
#define DISP_CLK 32
#define DISP_DIO 33
#define RING1_PIN 26 // GEÄNDERT: war 0 (Boot-Pin!)
#define RING2_PIN 27 // GEÄNDERT: war 16
#define RELAY1 12 // GEÄNDERT: war 34 (INPUT-ONLY!)
#define RELAY2 14 // GEÄNDERT: war 35 (INPUT-ONLY!)
#define BUZZER 17
#define LED_BTNS 25
#define MAIN_SW 39 // OK - INPUT only
#define VBAT 36 // OK - INPUT only
#define TFT_CS 5
#define TFT_DC 18
#define TFT_RST 19
#define TFT_MOSI 23
#define TFT_CLK 22
#define TFT_MISO 21
// =============================================================================
// NOTEN-DEFINITIONEN
// =============================================================================
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_C5 523
#define NOTE_B4 494
#define NOTE_AS4 466
#define NOTE_A4 440
#define NOTE_E5 659
#define NOTE_DS5 622
#define NOTE_D5 587
#define REST 0
int pinkPantherTempo = 120;
int pinkPantherMelody[] = {
REST,2, REST,4, REST,8, NOTE_DS4,8,
NOTE_E4,-4, REST,8, NOTE_FS4,8, NOTE_G4,-4, REST,8, NOTE_DS4,8,
NOTE_E4,-8, NOTE_FS4,8, NOTE_G4,-8, NOTE_C5,8, NOTE_B4,-8, NOTE_E4,8, NOTE_G4,-8, NOTE_B4,8,
NOTE_AS4,2, NOTE_A4,-16, NOTE_G4,-16, NOTE_E4,-16, NOTE_DS4,-16,
NOTE_E4,2, REST,4, REST,8, NOTE_DS4,4,
NOTE_E4,-4, REST,8, NOTE_FS4,8, NOTE_G4,-4, REST,8, NOTE_DS4,8,
NOTE_E4,-8, NOTE_FS4,8, NOTE_G4,-8, NOTE_C5,8, NOTE_B4,-8, NOTE_G4,8, NOTE_B4,-8, NOTE_E5,8,
NOTE_DS5,1,
NOTE_D5,2, REST,4, REST,8, NOTE_DS4,8,
NOTE_E4,-4, REST,8, NOTE_FS4,8, NOTE_G4,-4, REST,8, NOTE_DS4,8,
NOTE_E4,-8, NOTE_FS4,8, NOTE_G4,-8, NOTE_C5,8, NOTE_B4,-8, NOTE_E4,8, NOTE_G4,-8, NOTE_B4,8,
NOTE_AS4,2, NOTE_A4,-16, NOTE_G4,-16, NOTE_E4,-16, NOTE_DS4,-16,
NOTE_E4,-4, REST,4,
REST,4, NOTE_E5,-8, NOTE_D5,8, NOTE_B4,-8, NOTE_A4,8, NOTE_G4,-8, NOTE_E4,-8,
NOTE_AS4,16, NOTE_A4,-8, NOTE_AS4,16, NOTE_A4,-8, NOTE_AS4,16, NOTE_A4,-8, NOTE_AS4,16, NOTE_A4,-8,
NOTE_G4,-16, NOTE_E4,-16, NOTE_DS4,-16, NOTE_E4,16, NOTE_E4,16, NOTE_E4,2,
};
int pinkPantherNotes = sizeof(pinkPantherMelody) / sizeof(pinkPantherMelody[0]) / 2;
bool melodyPlaying = false;
int melodyNoteIndex = 0;
unsigned long melodyNoteStart = 0;
int melodyNoteDuration = 0;
// =============================================================================
// HARDWARE-OBJEKTE
// =============================================================================
TM1637Display tmDisplay(DISP_CLK, DISP_DIO);
Adafruit_NeoPixel ring1(35, RING1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ring2(35, RING2_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);
// =============================================================================
// FARB-DEFINITIONEN
// =============================================================================
#define COLOR_BG 0x0841
#define COLOR_PANEL 0x2945
#define COLOR_ACCENT 0xFD20
#define COLOR_SUCCESS 0x07E0
#define COLOR_WARNING 0xFFE0
#define COLOR_DANGER 0xF800
#define COLOR_INFO 0x07FF
#define COLOR_TEXT 0xFFFF
#define COLOR_TEXT_DIM 0x8410
// =============================================================================
// KONFIGURATION
// =============================================================================
const unsigned long HOLD_MS = 5000UL;
const int R2_LEDS = 35;
const int STEP_TONE_MS = 100;
const unsigned long CONFIRM_BEEP_MS = 500UL;
const int SEGMENT_HUNDREDTHS = 6000;
const int FREQ_LOW = 400;
const int FREQ_HIGH = 2000;
const int FREQ_STEP = (FREQ_HIGH - FREQ_LOW) / (R2_LEDS - 1);
const float VBAT_MIN = 9.0;
const float VBAT_RATIO = 9.39;
const unsigned long TFT_UPDATE_MS = 500;
const unsigned long BTN_DEBOUNCE_MS = 200;
// =============================================================================
// SYSTEM-VARIABLEN
// =============================================================================
unsigned long previousMillis = 0;
unsigned long displayBlinkMillis = 0;
unsigned long ringBlinkMillis = 0;
unsigned long lastTftUpdate = 0;
bool dispBlinkState = true;
bool colonOn = true;
int totalLeds = 10;
int ledsRemaining = totalLeds;
int prevRing2Lit = -1;
int timeValue = SEGMENT_HUNDREDTHS;
bool running = false;
bool hasStarted = false;
unsigned long holdStart = 0;
bool actionExecutedWhileHeld = false;
bool wasRunningAtHoldStart = false;
bool pausedForHold = false;
int lastSecondBeepSec = -1;
bool tftPresent = true;
char tftStatus[40] = "Bereit";
int lastLedsRemaining = -1;
int lastTotalLeds = -1;
bool systemEnabled = false;
bool forceRedraw = false;
bool lastSystemEnabled = false;
unsigned long lastVbatCheck = 0;
bool vbatCritical = false;
unsigned long lastUpPress = 0;
unsigned long lastDownPress = 0;
bool greetingAck = false;
unsigned long animStartTime = 0;
bool animActive = false;
bool exploded = false;
unsigned long explosionStart = 0;
bool explosionToneOn = false;
unsigned long relay1OnTime = 0;
const unsigned long EXPLOSION_BUZZ_MS = 2000UL;
const unsigned long RELAY1_PULSE_MS = 7000UL;
bool relay1Triggered = false;
// OPTIMIERUNGS-VARIABLEN
bool tftNeedsFullRedraw = true;
int lastDisplayedSeconds = -1;
// =============================================================================
// MELODIE-FUNKTIONEN
// =============================================================================
void startPinkPantherMelody() {
melodyPlaying = true;
melodyNoteIndex = 0;
melodyNoteStart = millis();
melodyNoteDuration = 0;
}
void stopPinkPantherMelody() {
melodyPlaying = false;
noTone(BUZZER);
}
void updatePinkPantherMelody() {
if (!melodyPlaying) return;
unsigned long now = millis();
if (now - melodyNoteStart < melodyNoteDuration) return;
if (melodyNoteIndex >= pinkPantherNotes * 2) melodyNoteIndex = 0;
int wholenote = (60000 * 4) / pinkPantherTempo;
int divider = pinkPantherMelody[melodyNoteIndex + 1];
if (divider > 0) {
melodyNoteDuration = wholenote / divider;
} else if (divider < 0) {
melodyNoteDuration = wholenote / abs(divider);
melodyNoteDuration *= 1.5;
}
int note = pinkPantherMelody[melodyNoteIndex];
if (note != REST) tone(BUZZER, note, melodyNoteDuration * 0.9);
else noTone(BUZZER);
melodyNoteStart = now;
melodyNoteIndex += 2;
}
// =============================================================================
// HILFSFUNKTIONEN
// =============================================================================
void beepBlocking(int freq, int dur) {
tone(BUZZER, freq, dur);
delay(dur);
noTone(BUZZER);
}
void shortBeepNonBlock(int freq, int dur) {
tone(BUZZER, freq, dur);
}
void checkVbat() {
int raw = 0;
for(int i=0; i<5; i++) {
raw += analogRead(VBAT);
delay(1);
}
float v = (raw/5.0 * 3.3/4095.0) * VBAT_RATIO;
vbatCritical = (v < VBAT_MIN);
Serial.print("Battery: ");
Serial.print(v);
Serial.println("V");
}
void fillRing1(int count, int blinkIndex = -1, bool blinkOn = true) {
ring1.clear();
for (int i = 0; i < count && i < ring1.numPixels(); ++i)
ring1.setPixelColor(i, ring1.Color(255, 0, 255));
if (blinkIndex >= 0 && blinkIndex < ring1.numPixels() && blinkOn)
ring1.setPixelColor(blinkIndex, ring1.Color(255, 131, 250));
ring1.show();
}
void pulseRelay(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}
void showTimeValueOnSeg(int tv, bool show = true, bool colon = true) {
if (!show) { tmDisplay.clear(); return; }
uint8_t mask = colon ? 0b01000000 : 0x00;
tmDisplay.showNumberDecEx(tv, mask, true);
}
void clearRing2() {
ring2.clear();
ring2.show();
}
void updateRing2DuringHold(float progress, bool wasRunningWhenHoldStarted) {
if (progress < 0.0f) progress = 0.0f;
if (progress > 1.0f) progress = 1.0f;
int lit;
if (!wasRunningWhenHoldStarted) lit = (int)floor(progress * R2_LEDS + 0.0001f);
else { lit = (int)ceil((1.0f - progress) * R2_LEDS - 0.0001f); if (lit < 0) lit = 0; }
if (lit > R2_LEDS) lit = R2_LEDS;
ring2.clear();
for (int i = 0; i < lit && i < ring2.numPixels(); ++i)
ring2.setPixelColor(i, ring2.Color(255,215,0));
ring2.show();
if (lit != prevRing2Lit) {
int toneIndex = -1;
if (!wasRunningWhenHoldStarted) {
if (lit > prevRing2Lit) toneIndex = lit - 1;
} else {
if (lit < prevRing2Lit) toneIndex = lit;
}
if (toneIndex >= 0) {
int freq = FREQ_LOW + toneIndex * FREQ_STEP;
shortBeepNonBlock(freq, STEP_TONE_MS);
}
prevRing2Lit = lit;
}
}
void tftSetStatus(const char* status) {
strncpy(tftStatus, status, sizeof(tftStatus) - 1);
tftStatus[sizeof(tftStatus) - 1] = '\0';
}
void drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) {
tft.drawRoundRect(x, y, w, h, r, color);
}
void fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) {
tft.fillRoundRect(x, y, w, h, r, color);
}
void drawGradientBar(int16_t x, int16_t y, int16_t w, int16_t h, int16_t progress, int16_t maxVal, uint16_t color1, uint16_t color2) {
fillRoundRect(x, y, w, h, 3, COLOR_PANEL);
drawRoundRect(x, y, w, h, 3, COLOR_TEXT_DIM);
if (maxVal > 0 && progress > 0) {
int16_t fillW = (w * progress) / maxVal;
if (fillW > w-2) fillW = w-2;
if (fillW > 0) fillRoundRect(x+1, y+1, fillW, h-2, 2, color1);
}
}
void drawLoadingDots(int16_t x, int16_t y, unsigned long time, uint16_t color) {
int dots = ((time / 300) % 4);
tft.fillRect(x, y, 30, 8, COLOR_PANEL);
tft.setTextColor(color);
tft.setCursor(x, y);
for(int i = 0; i < dots; i++) tft.print(".");
}
// =============================================================================
// OPTIMIERTE TFT-DRAW FUNKTION
// =============================================================================
void tftDraw() {
if (!tftPresent) return;
unsigned long now = millis();
if (tftNeedsFullRedraw) {
tft.fillScreen(COLOR_BG);
tftNeedsFullRedraw = false;
forceRedraw = true;
}
// SYSTEM AUS
if (!systemEnabled) {
static bool offScreenDrawn = false;
if (!offScreenDrawn || forceRedraw) {
tft.fillScreen(COLOR_BG);
fillRoundRect(20, 60, 280, 120, 10, COLOR_PANEL);
drawRoundRect(20, 60, 280, 120, 10, COLOR_WARNING);
tft.drawCircle(160, 100, 15, COLOR_WARNING);
tft.drawLine(160, 85, 160, 105, COLOR_WARNING);
tft.drawLine(155, 110, 165, 110, COLOR_WARNING);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(90, 130);
tft.print("System");
tft.setCursor(70, 150);
tft.print("einschalten!");
offScreenDrawn = true;
}
return;
}
// BEGRÜSSUNG
if (!greetingAck) {
static bool greetingDrawn = false;
if (!greetingDrawn || forceRedraw) {
tft.fillScreen(COLOR_BG);
fillRoundRect(10, 10, 300, 50, 8, COLOR_PANEL);
drawRoundRect(10, 10, 300, 50, 8, COLOR_ACCENT);
tft.setTextColor(COLOR_ACCENT);
tft.setTextSize(2);
tft.setCursor(25, 20);
tft.print("HNGR13");
tft.setTextSize(1);
tft.setCursor(25, 40);
tft.print("Bomb Timer System v2.0");
fillRoundRect(10, 80, 300, 40, 8, COLOR_PANEL);
drawRoundRect(10, 80, 300, 40, 8, COLOR_INFO);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(1);
tft.setCursor(20, 90);
tft.print("Bereit zum Start");
tft.setTextColor(COLOR_INFO);
tft.setCursor(20, 105);
tft.print(">> START druecken zum Fortfahren");
greetingDrawn = true;
forceRedraw = false;
}
drawLoadingDots(250, 105, now, COLOR_INFO);
return;
}
// EXPLOSION
if (exploded) {
unsigned long explosionTime = now - explosionStart;
bool fastBlink = explosionTime < 2000;
int blinkSpeed = fastBlink ? 150 : 400;
bool flash = ((explosionTime / blinkSpeed) % 2) == 0;
tft.fillScreen(flash ? COLOR_DANGER : ILI9341_BLACK);
if (flash) {
tft.drawRect(0, 0, 320, 240, COLOR_TEXT);
tft.drawRect(2, 2, 316, 236, COLOR_TEXT);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(4);
tft.setCursor(70, 70);
tft.print("BOOM!");
tft.setTextSize(2);
tft.setCursor(50, 120);
tft.print("EXPLOSION!");
tft.setTextSize(1);
tft.setCursor(75, 160);
tft.print("Bombe explodiert!");
tft.drawCircle(160, 100, 60, COLOR_WARNING);
tft.drawCircle(160, 100, 80, COLOR_TEXT);
}
if (explosionTime < 3000) {
for (int i = 0; i < ring1.numPixels(); ++i) {
int colorChoice = (i + (explosionTime / 80)) % 5;
uint32_t color;
switch (colorChoice) {
case 0: color = ring1.Color(255, 0, 0); break;
case 1: color = ring1.Color(255, 80, 0); break;
case 2: color = ring1.Color(255, 255, 0); break;
case 3: color = ring1.Color(255, 255, 255); break;
case 4: color = ring1.Color(255, 50, 150); break;
}
ring1.setPixelColor(i, color);
}
ring1.show();
int rotPhase = (explosionTime / 40) % ring2.numPixels();
ring2.clear();
for (int i = 0; i < 18; i++) {
int pos = (rotPhase + i * 2) % ring2.numPixels();
uint32_t brightness = 255 - (i * 25);
ring2.setPixelColor(pos, ring2.Color(brightness, brightness/3, 0));
}
ring2.show();
} else {
bool pulse = ((explosionTime / 300) % 2) == 0;
uint32_t pulseColor = pulse ? ring1.Color(255, 100, 0) : ring1.Color(80, 30, 0);
for (int i = 0; i < ring1.numPixels(); ++i) ring1.setPixelColor(i, pulseColor);
ring1.show();
int wavePos = (explosionTime / 120) % ring2.numPixels();
ring2.clear();
for (int i = 0; i < 12; i++) {
int pos = (wavePos + i) % ring2.numPixels();
uint32_t brightness = 200 - (i * 30);
ring2.setPixelColor(pos, ring2.Color(brightness, 0, 0));
}
ring2.show();
}
return;
}
// NORMALBETRIEB - NUR ÄNDERUNGEN ZEICHNEN
static char lastStatus[40] = "";
if (strcmp(tftStatus, lastStatus) != 0 || forceRedraw) {
fillRoundRect(5, 5, 310, 20, 4, COLOR_PANEL);
drawRoundRect(5, 5, 310, 20, 4, running ? COLOR_SUCCESS : COLOR_INFO);
tft.setTextColor(running ? COLOR_SUCCESS : COLOR_INFO);
tft.setTextSize(1);
tft.setCursor(10, 12);
tft.print("Status: ");
tft.print(tftStatus);
strcpy(lastStatus, tftStatus);
}
// LED BAR - nur bei Änderung
if (ledsRemaining != lastLedsRemaining || totalLeds != lastTotalLeds || forceRedraw) {
uint16_t ledColor = COLOR_SUCCESS;
if (ledsRemaining <= totalLeds * 0.3) ledColor = COLOR_DANGER;
else if (ledsRemaining <= totalLeds * 0.6) ledColor = COLOR_WARNING;
drawGradientBar(12, 35, 296, 20, ledsRemaining, totalLeds, ledColor, ledColor);
if (forceRedraw) {
tft.fillRect(12, 28, 100, 6, COLOR_BG);
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(12, 28);
tft.print("LEDs");
}
char ledText[20];
snprintf(ledText, sizeof(ledText), "%02d/%02d", ledsRemaining, totalLeds);
tft.fillRect(250, 28, 60, 6, COLOR_BG);
tft.setTextColor(ledColor);
tft.setCursor(260, 28);
tft.print(ledText);
lastLedsRemaining = ledsRemaining;
lastTotalLeds = totalLeds;
}
// ZEIT BAR - nur bei Sekundenwechsel
int secondsRemain = timeValue / 100;
if (secondsRemain != lastDisplayedSeconds || forceRedraw) {
if (secondsRemain < 0) secondsRemain = 0;
if (secondsRemain > 60) secondsRemain = 60;
uint16_t timeColor = COLOR_INFO;
if (secondsRemain <= 15) timeColor = COLOR_DANGER;
else if (secondsRemain <= 30) timeColor = COLOR_WARNING;
drawGradientBar(12, 70, 296, 20, secondsRemain, 60, timeColor, timeColor);
if (forceRedraw) {
tft.fillRect(12, 63, 100, 6, COLOR_BG);
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(12, 63);
tft.print("Zeit pro Segment");
}
char timeText[20];
snprintf(timeText, sizeof(timeText), "%02ds", secondsRemain);
tft.fillRect(270, 63, 40, 6, COLOR_BG);
tft.setTextColor(timeColor);
tft.setCursor(270, 63);
tft.print(timeText);
lastDisplayedSeconds = secondsRemain;
}
// STATUS PANEL - nur bei Zustandsänderung
static bool lastRunningState = false;
static bool lastHasStartedState = false;
if (running != lastRunningState || hasStarted != lastHasStartedState || forceRedraw) {
fillRoundRect(50, 110, 220, 60, 10, COLOR_PANEL);
if (running) {
drawRoundRect(50, 110, 220, 60, 10, secondsRemain <= 10 ? COLOR_DANGER : COLOR_SUCCESS);
tft.setTextColor(secondsRemain <= 10 ? COLOR_DANGER : COLOR_SUCCESS);
tft.setTextSize(2);
tft.setCursor(90, 125);
tft.print("AKTIV");
tft.setTextSize(1);
tft.setCursor(70, 150);
tft.print("Timer laeuft");
} else {
drawRoundRect(50, 110, 220, 60, 10, COLOR_WARNING);
tft.setTextColor(COLOR_WARNING);
tft.setTextSize(2);
tft.setCursor(70, 125);
if (!hasStarted) tft.print("KONFIGURATION");
else tft.print("PAUSIERT");
tft.setTextSize(1);
tft.setCursor(80, 150);
if (!hasStarted) tft.print("UP/DOWN: LEDs einstellen");
else tft.print("START halten zum Fortfahren");
}
lastRunningState = running;
lastHasStartedState = hasStarted;
}
// FOOTER
if (forceRedraw) {
tft.fillRect(5, 190, 310, 25, COLOR_BG);
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(10, 190);
if (!hasStarted) tft.print("UP/DOWN: LEDs | START: Begin Timer");
else tft.print("START (5s halten): Start/Stop Toggle");
}
// BATTERIE WARNUNG
if (vbatCritical && ((now/500)%2)==0) {
tft.fillRect(5, 220, 100, 15, COLOR_DANGER);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(1);
tft.setCursor(10, 225);
tft.print("AKKU LEER!");
} else if (forceRedraw) {
tft.fillRect(5, 220, 100, 15, COLOR_BG);
}
forceRedraw = false;
}
// =============================================================================
// SETUP
// =============================================================================
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n\n=== HNGR13 ESP32 Starting ===");
// Pin-Modes setzen
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(RELAY1, OUTPUT);
pinMode(RELAY2, OUTPUT);
pinMode(BUZZER, OUTPUT);
pinMode(LED_BTNS, OUTPUT);
pinMode(MAIN_SW, INPUT_PULLUP);
Serial.println("Pins configured");
// Relais ausschalten
digitalWrite(RELAY1, LOW);
digitalWrite(RELAY2, LOW);
digitalWrite(LED_BTNS, LOW);
// 7-Segment Display initialisieren
Serial.println("Init 7-Segment Display...");
tmDisplay.setBrightness(7);
showTimeValueOnSeg(timeValue, false, true);
// LED-Ringe initialisieren
Serial.println("Init LED Rings...");
ring1.begin();
ring1.show();
ring2.begin();
ring2.show();
fillRing1(ledsRemaining, ledsRemaining - 1, true);
// TFT Display initialisieren
Serial.println("Init TFT Display...");
tft.begin();
tft.setRotation(1);
tft.fillScreen(COLOR_BG);
tftNeedsFullRedraw = true;
forceRedraw = true;
lastDisplayedSeconds = -1;
tftDraw();
lastTftUpdate = millis();
// Batterie-Überwachung
pinMode(VBAT, INPUT);
checkVbat();
Serial.println("HNGR13 ESP32 Version - Ready");
Serial.println("================================\n");
// Startup-Beep
beepBlocking(1000, 100);
delay(50);
beepBlocking(1500, 100);
}
// =============================================================================
// LOOP
// =============================================================================
void loop() {
unsigned long now = millis();
updatePinkPantherMelody();
// Batterie-Status alle 10 Sekunden prüfen
if (now - lastVbatCheck >= 10000) {
checkVbat();
lastVbatCheck = now;
if (vbatCritical) {
running = false;
digitalWrite(RELAY1, LOW);
digitalWrite(RELAY2, LOW);
for(int i=0; i<3; i++) {
tone(BUZZER,800,150);
delay(200);
}
noTone(BUZZER);
}
}
// System-Enable Status prüfen
bool currentSystemEnabled = (digitalRead(MAIN_SW) == LOW);
if (currentSystemEnabled != systemEnabled) {
tftNeedsFullRedraw = true;
if (!currentSystemEnabled) stopPinkPantherMelody();
}
systemEnabled = currentSystemEnabled;
// System deaktiviert
if (!systemEnabled) {
tmDisplay.clear();
ring1.clear();
ring1.show();
ring2.clear();
ring2.show();
digitalWrite(LED_BTNS, LOW);
running = false;
hasStarted = false;
greetingAck = false;
exploded = false;
if (tftPresent && now - lastTftUpdate >= TFT_UPDATE_MS) {
tftDraw();
lastTftUpdate = now;
}
delay(50);
return;
}
// Begrüßungsbildschirm
if (systemEnabled && !greetingAck) {
if (!melodyPlaying) startPinkPantherMelody();
if (digitalRead(BTN_START) == LOW) {
greetingAck = true;
stopPinkPantherMelody();
tftNeedsFullRedraw = true;
tftSetStatus("Konfig: LEDs einstellen");
tftDraw();
lastTftUpdate = millis();
delay(300); // Entprellen
return;
}
if (tftPresent && now - lastTftUpdate >= TFT_UPDATE_MS) {
tftDraw();
lastTftUpdate = now;
}
return;
}
// Melodie stoppen nach Begrüßung
if (greetingAck && melodyPlaying) stopPinkPantherMelody();
// Explosions-Animation
if (exploded) {
unsigned long nowE = now;
if (!explosionToneOn && nowE - explosionStart < EXPLOSION_BUZZ_MS) {
tone(BUZZER, 400);
explosionToneOn = true;
}
if (explosionToneOn && nowE - explosionStart >= EXPLOSION_BUZZ_MS) {
noTone(BUZZER);
explosionToneOn = false;
digitalWrite(RELAY1, HIGH);
relay1OnTime = nowE;
}
if (relay1OnTime != 0 && nowE - relay1OnTime >= RELAY1_PULSE_MS) {
digitalWrite(RELAY1, LOW);
exploded = false;
hasStarted = false;
running = false;
ledsRemaining = totalLeds;
timeValue = SEGMENT_HUNDREDTHS;
showTimeValueOnSeg(timeValue, true, true);
tftSetStatus("Bereit");
fillRing1(ledsRemaining, ledsRemaining - 1, true);
clearRing2();
tftNeedsFullRedraw = true;
}
if (tftPresent && now - lastTftUpdate >= TFT_UPDATE_MS) {
tftDraw();
lastTftUpdate = now;
}
return;
}
// Tasten abfragen
int startRaw = digitalRead(BTN_START);
if (!hasStarted) digitalWrite(LED_BTNS, HIGH);
else digitalWrite(LED_BTNS, LOW);
// START-Taste gedrückt halten
if (startRaw == LOW) {
if (holdStart == 0) {
holdStart = now;
actionExecutedWhileHeld = false;
wasRunningAtHoldStart = running;
prevRing2Lit = wasRunningAtHoldStart ? R2_LEDS : 0;
// Erster Start
if (!hasStarted) {
running = true;
hasStarted = true;
digitalWrite(RELAY2, HIGH);
previousMillis = now;
lastSecondBeepSec = timeValue / 100;
beepBlocking(1500,200); delay(70);
beepBlocking(1500,200); delay(70);
beepBlocking(1500,200); delay(260);
tftSetStatus("Timer gestartet");
holdStart = 1;
actionExecutedWhileHeld = true;
clearRing2();
return;
}
if (wasRunningAtHoldStart) {
pausedForHold = true;
running = false;
tftSetStatus("Pause (5s halten)");
} else {
pausedForHold = false;
tftSetStatus("Start (5s halten)");
}
ring2.clear();
if (wasRunningAtHoldStart) {
for (int i=0;i<R2_LEDS;i++)
ring2.setPixelColor(i, ring2.Color(150,0,0));
}
ring2.show();
}
// Halte-Fortschritt anzeigen
if (!actionExecutedWhileHeld) {
unsigned long held = now - holdStart;
float progress = (float)held / (float)HOLD_MS;
if (progress > 1.0f) progress = 1.0f;
updateRing2DuringHold(progress, wasRunningAtHoldStart);
// Aktion nach 5 Sekunden
if (progress >= 1.0f) {
if (!wasRunningAtHoldStart) {
running = true;
hasStarted = true;
digitalWrite(RELAY2, HIGH);
previousMillis = now;
lastSecondBeepSec = timeValue / 100;
tftSetStatus("Timer laeuft");
} else {
running = false;
digitalWrite(RELAY2, LOW);
tftSetStatus("Timer gestoppt");
}
beepBlocking(1000, CONFIRM_BEEP_MS);
actionExecutedWhileHeld = true;
clearRing2();
pausedForHold = false;
}
}
} else {
// START-Taste losgelassen
if (holdStart != 0) {
if (!actionExecutedWhileHeld && pausedForHold) {
running = true;
previousMillis = now;
lastSecondBeepSec = timeValue / 100;
pausedForHold = false;
tftSetStatus("Timer fortgesetzt");
}
holdStart = 0;
actionExecutedWhileHeld = false;
prevRing2Lit = -1;
clearRing2();
}
}
// LED-Anzahl einstellen (nur vor Start)
if (!hasStarted) {
if (digitalRead(BTN_UP) == LOW && now - lastUpPress >= BTN_DEBOUNCE_MS) {
if (totalLeds < 35) totalLeds++;
ledsRemaining = totalLeds;
shortBeepNonBlock(800, 80);
fillRing1(ledsRemaining, ledsRemaining - 1, true);
char statusBuf[40];
snprintf(statusBuf, sizeof(statusBuf), "LEDs: %d", totalLeds);
tftSetStatus(statusBuf);
lastUpPress = now;
}
if (digitalRead(BTN_DOWN) == LOW && now - lastDownPress >= BTN_DEBOUNCE_MS) {
if (totalLeds > 1) totalLeds--;
ledsRemaining = totalLeds;
shortBeepNonBlock(600, 80);
fillRing1(ledsRemaining, ledsRemaining - 1, true);
char statusBuf[40];
snprintf(statusBuf, sizeof(statusBuf), "LEDs: %d", totalLeds);
tftSetStatus(statusBuf);
lastDownPress = now;
}
}
// Timer-Logik
if (running) {
unsigned long now2 = millis();
if (previousMillis == 0) {
previousMillis = now2;
lastSecondBeepSec = timeValue / 100;
}
unsigned long elapsed = now2 - previousMillis;
if (elapsed >= 10) {
unsigned long steps = elapsed / 10;
int dec = (int)steps;
timeValue -= dec;
previousMillis += steps * 10UL;
// Relais 1 nach 40 Sekunden aktivieren
if (ledsRemaining == 1 && timeValue <= 4000 && timeValue > 3900 && !relay1Triggered) {
digitalWrite(RELAY1, HIGH);
relay1OnTime = now2;
relay1Triggered = true;
tftSetStatus("Relais1 aktiviert!");
Serial.println("Relais1 aktiviert (40s vor Ende)");
}
// Relais 1 nach 1 Sekunde deaktivieren
if (relay1Triggered && relay1OnTime != 0 && now2 - relay1OnTime >= 1000) {
digitalWrite(RELAY1, LOW);
relay1OnTime = 0;
Serial.println("Relais1 deaktiviert");
}
// Segment beendet
while (timeValue <= 0) {
timeValue += SEGMENT_HUNDREDTHS;
ledsRemaining--;
if (ledsRemaining < 0) ledsRemaining = 0;
fillRing1(ledsRemaining, ledsRemaining - 1, true);
relay1Triggered = false;
// Bombe explodiert
if (ledsRemaining <= 0) {
running = false;
digitalWrite(RELAY2, LOW);
tftSetStatus("BOOM! Bombe explodiert!");
exploded = true;
explosionStart = millis();
explosionToneOn = false;
relay1OnTime = 0;
timeValue = SEGMENT_HUNDREDTHS;
showTimeValueOnSeg(timeValue, true, true);
tftNeedsFullRedraw = true;
break;
}
}
showTimeValueOnSeg(timeValue, true, colonOn);
} else {
showTimeValueOnSeg(timeValue, true, colonOn);
}
// Sekunden-Ton und Doppelpunkt blinken
int currentSec = timeValue / 100;
if (currentSec != lastSecondBeepSec) {
lastSecondBeepSec = currentSec;
shortBeepNonBlock(2000, 80);
colonOn = !colonOn;
}
} else {
// Blinken im pausierten Zustand
if (now - displayBlinkMillis >= 500) {
displayBlinkMillis = now;
dispBlinkState = !dispBlinkState;
}
if (dispBlinkState) {
if (!hasStarted) tmDisplay.showNumberDecEx(SEGMENT_HUNDREDTHS, 0b01000000, true);
else showTimeValueOnSeg(timeValue, true, true);
} else {
tmDisplay.clear();
}
}
// LED-Ring 1 blinken lassen
if (running) {
if (now - ringBlinkMillis >= 500) {
ringBlinkMillis = now;
static bool ringBlinkState = true;
ringBlinkState = !ringBlinkState;
fillRing1(ledsRemaining, ledsRemaining - 1, ringBlinkState);
}
} else {
fillRing1(ledsRemaining, ledsRemaining - 1, true);
}
// TFT Display aktualisieren (nur bei Änderungen)
bool needRedraw = false;
if (now - lastTftUpdate >= TFT_UPDATE_MS) needRedraw = true;
if (ledsRemaining != lastLedsRemaining || totalLeds != lastTotalLeds) needRedraw = true;
if ((timeValue / 100) != lastDisplayedSeconds) needRedraw = true;
if (needRedraw && tftPresent) {
tftDraw();
lastTftUpdate = now;
}
}
1000 µF Kondensator
1000 µF Kondensator
Beleuchtung up/down
Hauptschalter
3S Lipo 11,1V
dc/dc Wandler 5V 3A (1)
dc/dc Wandler 5V 3A (2)
dc/dc Wandler 5V 3A (3)