feat: Add restart button functionality and motion calibration

- Implemented a restart button with debounce handling and long-press detection in App.cpp.
- Added motion calibration settings to Settings.cpp and Settings.h, allowing for roll and pitch zero offsets.
- Enhanced WebUI to include motion calibration controls and a restart option.
- Updated FaceRenderer to adjust eye and mouth positions based on screen dimensions.
- Introduced deadzone handling for motion sensor readings to improve stability.
This commit is contained in:
Joshua King
2026-02-22 13:58:59 -05:00
parent a8e8268b65
commit df9bd461d1
10 changed files with 392 additions and 84 deletions

View File

@@ -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();