#include #include // --- Pin-Definitionen --- #define PIN_HOOK 18 // GRÜN: HIGH = Hörer abgehoben, LOW = aufgelegt #define PIN_DIAL 19 // WEISS: HIGH bei Drehung, fallende Flanken = Impulse // --- Hardware & Timing-Konstanten --- #define DEBOUNCE_MS 30 // Entprellzeit für Impulse (ms) #define DIGIT_TIMEOUT_MS 350 // Pause nach Drehung, bis Ziffer erkannt ist (ms) #define COMPLETE_DIAL_TIMEOUT 2500 // Pause nach letzter Ziffer, bis Rufnummer komplett ist (ms) // --- BLE Keyboard Instanz --- BleKeyboard bleKeyboard("FeTAp 791 Telefon", "Deutsche Bundespost", 100); // --- Interrupt-Variablen (müssen volatile sein!) --- volatile int pulseCount = 0; volatile unsigned long lastPulseTime = 0; volatile bool isDialing = false; // --- Hauptprogramm-Variablen --- bool bleConnectedLastState = false; bool hookLastState = false; String dialedNumber = ""; unsigned long lastDigitCompletedTime = 0; bool waitingForCompleteNumber = false; // ------------------------------------------------------------------- // Interrupt Service Routine (ISR) für die fallende Flanke // ------------------------------------------------------------------- // 'IRAM_ATTR' sorgt dafür, dass dieser Code im schnellen RAM des ESP32 liegt void IRAM_ATTR handleDialPulse() { unsigned long currentMillis = millis(); // Entprellung direkt im Interrupt: // Nur zählen, wenn der letzte Impuls länger als DEBOUNCE_MS her ist if ((currentMillis - lastPulseTime) > DEBOUNCE_MS) { pulseCount++; isDialing = true; lastPulseTime = currentMillis; } } void setup() { Serial.begin(115200); delay(1000); Serial.println("\n========================================="); Serial.println("FeTAp 791 - ESP32 mit Hardware-Interrupt"); Serial.println("========================================="); pinMode(PIN_HOOK, INPUT_PULLDOWN); pinMode(PIN_DIAL, INPUT_PULLDOWN); // --- Hardware-Interrupt konfigurieren --- // Löst 'handleDialPulse()' EXAKT beim Übergang von HIGH nach LOW aus! attachInterrupt(digitalPinToInterrupt(PIN_DIAL), handleDialPulse, FALLING); bleKeyboard.begin(); Serial.println("[BLE] Warte auf Verbindung zum iPhone..."); } void triggerIphoneShortcut(String number) { if (!bleKeyboard.isConnected()) { Serial.println("[BLE] Nicht verbunden – Kurzbefehl kann nicht gesendet werden!"); return; } Serial.printf("[Kurzbefehl] Starte Aktion für Rufnummer: %s\n", number.c_str()); // Nummer eintippen und mit 'Enter' bestätigen bleKeyboard.print(number); delay(100); bleKeyboard.write(KEY_RETURN); } void processDialedDigit(int pulses) { char digitChar; if (pulses == 10) { digitChar = '0'; } else if (pulses >= 1 && pulses <= 9) { digitChar = '0' + pulses; } else { Serial.printf("[Wahl] Ungültige Impulsanzahl (%d) ignoriert.\n", pulses); return; } dialedNumber += digitChar; lastDigitCompletedTime = millis(); waitingForCompleteNumber = true; Serial.printf("[Wahl] Ziffer: %c | Aktuelle Nummer: %s\n", digitChar, dialedNumber.c_str()); } void loop() { unsigned long currentMillis = millis(); // 1. Bluetooth-Verbindungsstatus bool isConnectedNow = bleKeyboard.isConnected(); if (isConnectedNow && !bleConnectedLastState) { Serial.println("\n[BLE] >>> iPhone erfolgreich VERBUNDEN! <<<"); bleConnectedLastState = true; } else if (!isConnectedNow && bleConnectedLastState) { Serial.println("\n[BLE] >>> Verbindung zum iPhone VERLOREN! <<<"); bleConnectedLastState = false; } // 2. Gabelumschalter (Hook) bool currentHookState = digitalRead(PIN_HOOK); if (currentHookState != hookLastState) { delay(20); // Entprellen der Gabel currentHookState = digitalRead(PIN_HOOK); if (currentHookState != hookLastState) { hookLastState = currentHookState; if (currentHookState == HIGH) { Serial.println("\n[Hörer] ABGEHOBEN"); dialedNumber = ""; waitingForCompleteNumber = false; // Interrupt aktivieren / Zähler nullen pulseCount = 0; isDialing = false; } else { Serial.println("\n[Hörer] AUFGELEGT"); dialedNumber = ""; waitingForCompleteNumber = false; pulseCount = 0; isDialing = false; } } } // 3. Auswertung der Ziffern & Rufnummer (nur bei abgehobenem Hörer) if (hookLastState == HIGH) { // A) Ist die Ziffern-Drehung beendet? (Timeout seit letztem Interrupt-Impuls) if (isDialing && (currentMillis - lastPulseTime > DIGIT_TIMEOUT_MS)) { // Für die sichere Übergabe aus der ISR kurz Interrupts pausieren oder Wert kopieren noInterrupts(); int currentPulses = pulseCount; pulseCount = 0; isDialing = false; interrupts(); if (currentPulses > 0) { processDialedDigit(currentPulses); } } // B) Ist die komplette Rufnummer fertig gewählt? (Pause nach letzter Ziffer) if (waitingForCompleteNumber && (currentMillis - lastDigitCompletedTime > COMPLETE_DIAL_TIMEOUT)) { waitingForCompleteNumber = false; Serial.printf("\n[Wahl abgeschlossen] Vollständige Nummer: %s\n", dialedNumber.c_str()); // Kurzbefehl am iPhone ausführen triggerIphoneShortcut(dialedNumber); } } }