#include #include #include #include #include "PulseTimer.h" #include "config.h" namespace { constexpr uint8_t kOledAddress = 0x3C; constexpr uint8_t kPinEncoderA = 14; constexpr uint8_t kPinEncoderB = 13; constexpr uint8_t kPinButton = 12; constexpr uint8_t kPinSda = 4; constexpr uint8_t kPinScl = 5; constexpr unsigned long kDisplayRefreshMs = 150; constexpr unsigned long kWifiRetryMs = 10000; constexpr unsigned long kButtonDebounceMs = 35; constexpr unsigned long kStatusHoldMs = 2500; uint16_t timerMs = trbc::clampTimerMs(INITIAL_TIMER_MS); bool needsDisplayUpdate = true; unsigned long lastDisplayRefresh = 0; unsigned long lastWifiAttempt = 0; unsigned long statusUntil = 0; String statusLine = "Bereit"; uint8_t lastEncoderState = 0; int8_t encoderAccumulator = 0; bool lastButtonReading = HIGH; bool stableButtonState = HIGH; unsigned long lastButtonChange = 0; U8G2_SSD1309_128X64_NONAME0_F_HW_I2C display(U8G2_R0, U8X8_PIN_NONE, kPinScl, kPinSda); void setStatus(const String &text) { statusLine = text; statusUntil = millis() + kStatusHoldMs; needsDisplayUpdate = true; } String formatTimer() { const uint16_t seconds = timerMs / 1000; const uint16_t tenths = (timerMs % 1000) / 100; return String(seconds) + "." + String(tenths) + " S"; } String wifiStatus() { if (WiFi.status() == WL_CONNECTED) { return String("IP ") + WiFi.localIP().toString(); } return "WLAN verbindet"; } void refreshDisplay() { display.clearBuffer(); display.setFont(u8g2_font_6x10_tf); display.drawStr(0, 10, "TASMOTA PULSE"); display.drawStr(0, 26, ("ZEIT " + formatTimer()).c_str()); display.drawStr(0, 42, wifiStatus().c_str()); display.drawStr(0, 58, (millis() < statusUntil ? statusLine : "DRUECKEN START").c_str()); display.sendBuffer(); } void connectWifi() { if (WiFi.status() == WL_CONNECTED) { return; } if (millis() - lastWifiAttempt < kWifiRetryMs && lastWifiAttempt != 0) { return; } lastWifiAttempt = millis(); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); needsDisplayUpdate = true; } bool sendTasmotaCommand(const String &command) { if (WiFi.status() != WL_CONNECTED) { return false; } WiFiClient client; HTTPClient http; const String url = String("http://") + TASMOTA_HOST + "/cm?cmnd=" + command; if (!http.begin(client, url)) { return false; } const int code = http.GET(); http.end(); return code >= 200 && code < 300; } void triggerPulse() { setStatus("SENDE..."); const uint16_t pulseValue = trbc::tasmotaPulseTimeValue(timerMs); const bool pulseOk = sendTasmotaCommand("PulseTime1%20" + String(pulseValue)); const bool powerOk = pulseOk && sendTasmotaCommand("Power1%20ON"); setStatus(powerOk ? "GETRIGGERT" : "FEHLER"); } void readEncoder() { const uint8_t state = (digitalRead(kPinEncoderA) << 1) | digitalRead(kPinEncoderB); if (state == lastEncoderState) { return; } const uint8_t transition = (lastEncoderState << 2) | state; if (transition == 0b0001 || transition == 0b0111 || transition == 0b1110 || transition == 0b1000) { encoderAccumulator++; } else if (transition == 0b0010 || transition == 0b1011 || transition == 0b1101 || transition == 0b0100) { encoderAccumulator--; } lastEncoderState = state; if (encoderAccumulator >= 4 || encoderAccumulator <= -4) { const int ticks = encoderAccumulator / 4; encoderAccumulator = 0; timerMs = trbc::adjustTimerMs(timerMs, ticks); needsDisplayUpdate = true; } } void readButton() { const bool reading = digitalRead(kPinButton); if (reading != lastButtonReading) { lastButtonChange = millis(); lastButtonReading = reading; } if (millis() - lastButtonChange <= kButtonDebounceMs || reading == stableButtonState) { return; } stableButtonState = reading; if (stableButtonState == LOW) { triggerPulse(); } } } // namespace void setup() { Serial.begin(115200); pinMode(kPinEncoderA, INPUT_PULLUP); pinMode(kPinEncoderB, INPUT_PULLUP); pinMode(kPinButton, INPUT_PULLUP); lastEncoderState = (digitalRead(kPinEncoderA) << 1) | digitalRead(kPinEncoderB); display.setI2CAddress(kOledAddress << 1); display.begin(); connectWifi(); refreshDisplay(); } void loop() { connectWifi(); readEncoder(); readButton(); if (needsDisplayUpdate || millis() - lastDisplayRefresh >= kDisplayRefreshMs) { refreshDisplay(); lastDisplayRefresh = millis(); needsDisplayUpdate = false; } delay(2); }