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:
@@ -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 =
|
||||
"<!DOCTYPE html><html><head>"
|
||||
@@ -157,6 +159,26 @@ void WebUI::begin(Settings& settings,
|
||||
"</form>"
|
||||
"</div>"
|
||||
"<div class='card'>"
|
||||
"<h2><i class='fas fa-compass' style='margin-right: 10px;'></i>Accelerometer Calibration</h2>"
|
||||
"<div style='background: #f5f5f7; padding: 15px; border-radius: 12px; margin-bottom: 20px;'>"
|
||||
"<div style='font-size: 0.85em; color: #86868b; margin-bottom: 5px;'>Current Tilt</div>"
|
||||
"<div style='font-size: 1em; color: #1d1d1f;'>Roll: " + String(motion.rollDeg(), 1) + "°</div>"
|
||||
"<div style='font-size: 1em; color: #1d1d1f;'>Pitch: " + String(motion.pitchDeg(), 1) + "°</div>"
|
||||
"<div style='font-size: 0.85em; color: #86868b; margin-top: 8px;'>"
|
||||
"Neutral offsets: roll " + String(mc.rollZeroDeg, 1) + "°, pitch " + String(mc.pitchZeroDeg, 1) + "°</div>"
|
||||
"</div>"
|
||||
"<form method='POST' action='/motion-calibrate' style='margin-bottom: 12px;'>"
|
||||
"<div class='btn-group'>"
|
||||
"<button type='submit' name='action' value='zero_now' class='btn btn-primary'>Set Current Position as Neutral</button>"
|
||||
"</div>"
|
||||
"</form>"
|
||||
"<form method='POST' action='/motion-calibrate'>"
|
||||
"<div class='btn-group'>"
|
||||
"<button type='submit' name='action' value='reset' class='btn btn-secondary'>Reset Accelerometer Calibration</button>"
|
||||
"</div>"
|
||||
"</form>"
|
||||
"</div>"
|
||||
"<div class='card'>"
|
||||
"<h2><i class='fas fa-sliders-h' style='margin-right: 10px;'></i>Sensor Calibration</h2>"
|
||||
"<div style='background: #f5f5f7; padding: 15px; border-radius: 12px; margin-bottom: 20px;'>"
|
||||
"<div style='font-size: 0.85em; color: #86868b; margin-bottom: 5px;'>Current Reading</div>"
|
||||
@@ -184,6 +206,12 @@ void WebUI::begin(Settings& settings,
|
||||
"<div class='card' style='border: 2px solid #ff3b30;'>"
|
||||
"<h2 style='color: #ff3b30;'><i class='fas fa-exclamation-triangle' style='margin-right: 10px;'></i>Danger Zone</h2>"
|
||||
"<p style='color: #86868b; margin-bottom: 15px;'>Irreversible actions that will reset your device</p>"
|
||||
"<form method='POST' action='/restart' id='restartForm' style='margin-bottom: 20px;'>"
|
||||
"<p style='margin-bottom: 15px;'><strong>Restart Device</strong></p>"
|
||||
"<p style='color: #86868b; font-size: 0.9em; margin-bottom: 15px;'>"
|
||||
"Reboots FacePlant without erasing settings.</p>"
|
||||
"<button type='submit' class='btn btn-secondary' style='width: 100%;'>Restart</button>"
|
||||
"</form>"
|
||||
"<form method='POST' action='/factory-reset' id='resetForm'>"
|
||||
"<p style='margin-bottom: 15px;'><strong>Factory Reset</strong></p>"
|
||||
"<p style='color: #86868b; font-size: 0.9em; margin-bottom: 15px;'>"
|
||||
@@ -210,6 +238,12 @@ void WebUI::begin(Settings& settings,
|
||||
" }"
|
||||
" }"
|
||||
"});"
|
||||
"document.getElementById('restartForm').addEventListener('submit', function(e) {"
|
||||
" e.preventDefault();"
|
||||
" if (confirm('Restart FacePlant now?')) {"
|
||||
" this.submit();"
|
||||
" }"
|
||||
"});"
|
||||
"document.getElementById('resetForm').addEventListener('submit', function(e) {"
|
||||
" e.preventDefault();"
|
||||
" if (confirm('Are you sure you want to reset to factory defaults? This cannot be undone.')) {"
|
||||
@@ -283,6 +317,28 @@ void WebUI::begin(Settings& settings,
|
||||
_server.send(303);
|
||||
});
|
||||
|
||||
_server.on("/motion-calibrate", HTTP_POST, [&]() {
|
||||
String action = _server.hasArg("action") ? _server.arg("action") : "zero_now";
|
||||
if (action == "reset") {
|
||||
settings.clearMotionCalibration();
|
||||
motion.setZeroOffsets(0.0f, 0.0f);
|
||||
Serial.println("[WebUI] Accelerometer calibration reset");
|
||||
} else {
|
||||
if (motion.available()) {
|
||||
settings.setMotionCalibration(motion.rawRollDeg(), motion.rawPitchDeg());
|
||||
motion.setZeroOffsets(motion.rawRollDeg(), motion.rawPitchDeg());
|
||||
Serial.print("[WebUI] Accelerometer neutral set from current position: roll=");
|
||||
Serial.print(motion.rawRollDeg(), 2);
|
||||
Serial.print(" pitch=");
|
||||
Serial.println(motion.rawPitchDeg(), 2);
|
||||
} else {
|
||||
Serial.println("[WebUI] Accelerometer calibration requested but MPU6050 unavailable");
|
||||
}
|
||||
}
|
||||
_server.sendHeader("Location", "/");
|
||||
_server.send(303);
|
||||
});
|
||||
|
||||
_server.on("/factory-reset", HTTP_POST, [&]() {
|
||||
Serial.println("[WebUI] Factory reset requested");
|
||||
_server.send(200, "text/html",
|
||||
@@ -311,6 +367,30 @@ void WebUI::begin(Settings& settings,
|
||||
ESP.restart();
|
||||
});
|
||||
|
||||
_server.on("/restart", HTTP_POST, [&]() {
|
||||
Serial.println("[WebUI] Restart requested");
|
||||
_server.send(200, "text/html",
|
||||
"<!DOCTYPE html><html><head><meta http-equiv='refresh' content='8;url=/'>"
|
||||
"<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css'>"
|
||||
"<style>body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;"
|
||||
"background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);min-height:100vh;display:flex;"
|
||||
"align-items:center;justify-content:center;margin:0;padding:20px;}"
|
||||
".card{background:rgba(255,255,255,0.95);border-radius:20px;padding:40px;text-align:center;"
|
||||
"box-shadow:0 20px 60px rgba(0,0,0,0.3);max-width:400px;}"
|
||||
"h1{font-size:2em;margin-bottom:10px;color:#1d1d1f;}"
|
||||
"p{color:#86868b;margin:10px 0;}"
|
||||
".spinner{font-size:3em;color:#667eea;margin:20px 0;animation:spin 2s linear infinite;}"
|
||||
"@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}</style></head><body>"
|
||||
"<div class='card'>"
|
||||
"<div class='spinner'><i class='fas fa-sync-alt'></i></div>"
|
||||
"<h1>Restarting</h1>"
|
||||
"<p>FacePlant is rebooting...</p>"
|
||||
"<p>Settings are preserved.</p>"
|
||||
"</div></body></html>");
|
||||
delay(500);
|
||||
ESP.restart();
|
||||
});
|
||||
|
||||
_server.on("/status", HTTP_GET, [&]() {
|
||||
JsonDocument doc;
|
||||
doc["device"] = "FacePlant";
|
||||
@@ -327,6 +407,11 @@ void WebUI::begin(Settings& settings,
|
||||
doc["timezone"] = settings.timezone();
|
||||
doc["bedtime"] = settings.bedtime();
|
||||
doc["wake_time"] = settings.wakeTime();
|
||||
doc["motion_ok"] = motion.available();
|
||||
doc["motion_roll_deg"] = motion.rollDeg();
|
||||
doc["motion_pitch_deg"] = motion.pitchDeg();
|
||||
doc["motion_roll_zero_deg"] = motion.rollZeroDeg();
|
||||
doc["motion_pitch_zero_deg"] = motion.pitchZeroDeg();
|
||||
doc["dead_mode"] = face.isDeadMode();
|
||||
doc["uptime_ms"] = millis() - bootMs;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user