diff --git a/src/app/App.cpp b/src/app/App.cpp index 41f02ca..b8187a7 100644 --- a/src/app/App.cpp +++ b/src/app/App.cpp @@ -42,6 +42,12 @@ static unsigned long nextTimeCheckMs = 0; static bool lastScheduleSleep = false; static String lastConfiguredTz; static unsigned long motionWakeUntilMs = 0; +static bool restartButtonEnabled = true; +static bool restartButtonLastRaw = true; +static bool restartButtonStable = true; +static unsigned long restartButtonLastEdgeMs = 0; +static unsigned long restartButtonPressedSinceMs = 0; +static bool restartButtonArmed = true; static constexpr float DIM_LUX_MIN = 0.0f; static constexpr float DIM_LUX_MAX = 300.0f; @@ -49,6 +55,9 @@ static constexpr uint8_t DIM_CONTRAST_MIN = 0x10; static constexpr uint8_t DIM_CONTRAST_MAX = 0xFF; static constexpr int ROUTINE_WINDOW_MIN = 5; static constexpr unsigned long MOTION_WAKE_MS = 30000UL; +static constexpr int PIN_RESTART_BUTTON = 2; // Active-low button to GND +static constexpr unsigned long BUTTON_DEBOUNCE_MS = 30UL; +static constexpr unsigned long BUTTON_HOLD_RESTART_MS = 1200UL; static PlantEventType moodToEvent(FaceRenderer::Mood m) { if (m == FaceRenderer::DRY) return EVT_DRY; @@ -72,6 +81,56 @@ static uint8_t luxToContrast(float lux) { return (uint8_t)value; } +static void initRestartButton() { + pinMode(PIN_RESTART_BUTTON, INPUT_PULLUP); + bool raw = digitalRead(PIN_RESTART_BUTTON); + restartButtonLastRaw = raw; + restartButtonStable = raw; + restartButtonLastEdgeMs = millis(); + restartButtonPressedSinceMs = 0; + restartButtonArmed = true; + + Serial.print("[Button] Restart button on GPIO "); + Serial.print(PIN_RESTART_BUTTON); + Serial.print(" (active-low), initial="); + Serial.println(raw ? "released" : "pressed"); +} + +static void handleRestartButton() { + if (!restartButtonEnabled) return; + + unsigned long now = millis(); + bool raw = digitalRead(PIN_RESTART_BUTTON); + + if (raw != restartButtonLastRaw) { + restartButtonLastRaw = raw; + restartButtonLastEdgeMs = now; + } + + if ((now - restartButtonLastEdgeMs) >= BUTTON_DEBOUNCE_MS && restartButtonStable != raw) { + restartButtonStable = raw; + bool pressed = !restartButtonStable; // active-low + + if (pressed) { + restartButtonPressedSinceMs = now; + Serial.println("[Button] Restart button pressed"); + } else { + Serial.println("[Button] Restart button released"); + restartButtonPressedSinceMs = 0; + restartButtonArmed = true; + } + } + + bool pressed = !restartButtonStable; + if (pressed && restartButtonArmed && restartButtonPressedSinceMs != 0 && + (now - restartButtonPressedSinceMs) >= BUTTON_HOLD_RESTART_MS) { + restartButtonArmed = false; + Serial.println("[Button] Restart hold detected - rebooting"); + delay(50); + ESP.restart(); + } +} + struct ScheduleState { bool hasTime = false; bool sleeping = false; @@ -234,6 +293,7 @@ void App::setup() { display.begin(); display.showStatus("FacePlant", "Starting..."); + initRestartButton(); wifi.begin(settings, forceSetup); @@ -245,7 +305,12 @@ void App::setup() { webhook.begin(settings); - web.begin(settings, wifi, moisture, face, webhook, bootMs); + { + MotionCalibration mc = settings.motionCalibration(); + motion.setZeroOffsets(mc.rollZeroDeg, mc.pitchZeroDeg); + } + + web.begin(settings, wifi, moisture, motion, face, webhook, bootMs); ota.begin(web.server(), &display); lastMood = face.mood(); @@ -259,6 +324,7 @@ void App::loop() { static unsigned long lastDisplayUpdate = 0; BootTrigger::clearAfterStableUptime(); + handleRestartButton(); wifi.loop(); web.loop(); diff --git a/src/net/WebUI.cpp b/src/net/WebUI.cpp index 8005f60..9f15a3a 100644 --- a/src/net/WebUI.cpp +++ b/src/net/WebUI.cpp @@ -5,6 +5,7 @@ void WebUI::begin(Settings& settings, WiFiManager& wifi, MoistureSensor& moisture, + MotionSensor& motion, FaceRenderer& face, WebhookService& webhook, unsigned long bootMs) { @@ -23,6 +24,7 @@ void WebUI::begin(Settings& settings, String timezone = settings.timezone(); String bedtime = settings.bedtime(); String wakeTime = settings.wakeTime(); + MotionCalibration mc = settings.motionCalibration(); String page = "
" @@ -157,6 +159,26 @@ void WebUI::begin(Settings& settings, "" "" "Irreversible actions that will reset your device
" + "" "