From 389182a9dbf09458886f98f3301b8330a830ab76 Mon Sep 17 00:00:00 2001 From: devdesk Date: Fri, 6 Feb 2026 15:17:41 +0200 Subject: [PATCH] Add coordinated homing for both motors with toggle button - Add state machine for coordinated movement (REV/FWD toggle) - Each motor stops independently on stall detection - Add HOME BOTH button in web UI with state-based styling - Add /homing endpoint for programmatic control - Update /status endpoint with coordinated state info - Create homing.h header for function declarations --- include/homing.h | 10 +++++ src/main.cpp | 100 ++++++++++++++++++++++++++++++++++++++++++++++ src/webserver.cpp | 86 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 include/homing.h diff --git a/include/homing.h b/include/homing.h new file mode 100644 index 0000000..420ba79 --- /dev/null +++ b/include/homing.h @@ -0,0 +1,10 @@ +#ifndef HOMING_H +#define HOMING_H + +// Coordinated movement functions (defined in main.cpp) +int toggleCoordinatedMovement(int speed); +void stopCoordinatedMovement(); +int getCoordinatedState(); +bool isCoordinatedMovementDone(); + +#endif diff --git a/src/main.cpp b/src/main.cpp index bd6ad8d..f7dd571 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,16 +4,116 @@ #include "motor.h" #include "webserver.h" +// Coordinated movement state +enum CoordinatedState { + COORD_IDLE = 0, + COORD_MOVING_REV = 1, // Homing - moving to reverse end + COORD_MOVING_FWD = 2 // Moving to forward end +}; + +// Global coordinated movement state +static CoordinatedState coordState = COORD_IDLE; +static bool motor1Done = false; +static bool motor2Done = false; +static int coordSpeed = 50; // Default coordinated movement speed + +// Get current coordinated movement state +int getCoordinatedState() { + return (int)coordState; +} + +// Check if coordinated movement is complete +bool isCoordinatedMovementDone() { + return motor1Done && motor2Done; +} + // Called when motor 1 stall is detected void onMotor1Stall() { Serial.println("Motor1 stall callback triggered - stopping motor!"); motor1.stop(); + + // Mark motor1 as done if in coordinated movement + if (coordState != COORD_IDLE) { + motor1Done = true; + Serial.println("Motor1 reached end position (stall)"); + + if (isCoordinatedMovementDone()) { + Serial.println("Coordinated movement complete - both motors at end position"); + } + } } // Called when motor 2 stall is detected void onMotor2Stall() { Serial.println("Motor2 stall callback triggered - stopping motor!"); motor2.stop(); + + // Mark motor2 as done if in coordinated movement + if (coordState != COORD_IDLE) { + motor2Done = true; + Serial.println("Motor2 reached end position (stall)"); + + if (isCoordinatedMovementDone()) { + Serial.println("Coordinated movement complete - both motors at end position"); + } + } +} + +// Start coordinated movement in specified direction +// dir: -1 = reverse (homing), 1 = forward +void startCoordinatedMovement(int dir, int speed) { + coordSpeed = speed; + motor1Done = false; + motor2Done = false; + + if (dir < 0) { + coordState = COORD_MOVING_REV; + Serial.printf("Starting coordinated REVERSE movement at %d%% speed\n", speed); + } else { + coordState = COORD_MOVING_FWD; + Serial.printf("Starting coordinated FORWARD movement at %d%% speed\n", speed); + } + + // Start both motors + motor1.setSpeed(speed); + motor1.setDirection(dir); + + motor2.setSpeed(speed); + motor2.setDirection(dir); +} + +// Toggle coordinated movement (for button press) +// Returns: new state (0=idle, 1=moving_rev, 2=moving_fwd) +int toggleCoordinatedMovement(int speed) { + if (coordState == COORD_IDLE) { + // First press: start moving reverse (homing) + startCoordinatedMovement(-1, speed); + return COORD_MOVING_REV; + } else if (coordState == COORD_MOVING_REV) { + // If currently moving rev or completed rev, switch to forward + // Stop any still-moving motors first + if (!motor1Done) motor1.stop(); + if (!motor2Done) motor2.stop(); + startCoordinatedMovement(1, speed); + return COORD_MOVING_FWD; + } else { + // Currently moving fwd or completed fwd, switch to reverse + // Stop any still-moving motors first + if (!motor1Done) motor1.stop(); + if (!motor2Done) motor2.stop(); + startCoordinatedMovement(-1, speed); + return COORD_MOVING_REV; + } +} + +// Stop coordinated movement +void stopCoordinatedMovement() { + coordState = COORD_IDLE; + motor1Done = false; + motor2Done = false; + motor1.stop(); + motor2.stop(); + Serial.println("Coordinated movement stopped"); } void setupWiFi() { diff --git a/src/webserver.cpp b/src/webserver.cpp index 30e54b6..c4c674d 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -2,6 +2,7 @@ #include #include "motor.h" #include "config.h" +#include "homing.h" WebServer server(HTTP_PORT); @@ -102,6 +103,9 @@ const char index_html[] PROGMEM = R"rawliteral( .btn-forward { background: #00c853; color: white; } .btn-reverse { background: #ff5252; color: white; } .btn-stop { background: #ff9100; color: white; flex-basis: 100%; } + .btn-homing { background: #7c4dff; color: white; font-size: 18px; padding: 20px 40px; } + .btn-homing.moving-rev { background: #ff5252; } + .btn-homing.moving-fwd { background: #00c853; } .btn:hover { filter: brightness(1.1); } .speed-value { font-size: 36px; @@ -209,7 +213,11 @@ const char index_html[] PROGMEM = R"rawliteral(

Global Controls

- +
+ + +
+
@@ -253,6 +261,40 @@ const char index_html[] PROGMEM = R"rawliteral( }); } + // Coordinated movement / homing + function toggleHoming() { + // Use speed from motor1 slider as the coordinated speed + const speed = motors[1].slider.value; + fetch('/homing?speed=' + speed) + .then(r => r.json()) + .then(data => { + updateHomingButton(data.state, data.motor1Done, data.motor2Done); + }); + } + + function updateHomingButton(state, m1Done, m2Done) { + const btn = document.getElementById('homingBtn'); + const statusEl = document.getElementById('coordStatus'); + btn.classList.remove('moving-rev', 'moving-fwd'); + + if (state === 0) { + btn.textContent = '🏠 HOME BOTH'; + statusEl.textContent = ''; + } else if (state === 1) { + btn.classList.add('moving-rev'); + btn.textContent = '⏩ MOVE FWD'; + const m1Status = m1Done ? '✓' : '⟳'; + const m2Status = m2Done ? '✓' : '⟳'; + statusEl.textContent = 'Homing REV: M1 ' + m1Status + ' M2 ' + m2Status; + } else if (state === 2) { + btn.classList.add('moving-fwd'); + btn.textContent = '⏪ MOVE REV'; + const m1Status = m1Done ? '✓' : '⟳'; + const m2Status = m2Done ? '✓' : '⟳'; + statusEl.textContent = 'Moving FWD: M1 ' + m1Status + ' M2 ' + m2Status; + } + } + function updateStatus(motor) { const m = motors[motor]; if (m.dir > 0) m.dirStatus.textContent = 'FORWARD'; @@ -285,6 +327,11 @@ const char index_html[] PROGMEM = R"rawliteral( document.getElementById('m2_currentR').textContent = data.motor2.currentR.toFixed(2); document.getElementById('m2_currentL').textContent = data.motor2.currentL.toFixed(2); document.getElementById('stallWarning2').classList.toggle('active', data.motor2.stalled); + + // Update coordinated movement status + if (data.coordState !== undefined) { + updateHomingButton(data.coordState, data.motor1Done, data.motor2Done); + } }); } @@ -354,11 +401,30 @@ void handleMotor2Stop() { // Global stop (emergency stop both motors) void handleStop() { - motor1.stop(); - motor2.stop(); + stopCoordinatedMovement(); // Also stops coordinated movement state server.send(200, "text/plain", "OK"); } +// Homing / coordinated movement handler +void handleHoming() { + int speed = 50; // Default speed + if (server.hasArg("speed")) { + speed = server.arg("speed").toInt(); + if (speed < 20) speed = 20; + if (speed > 100) speed = 100; + } + + int newState = toggleCoordinatedMovement(speed); + + // Return JSON with state info + String json = "{"; + json += "\"state\":" + String(newState) + ","; + json += "\"motor1Done\":" + String(isCoordinatedMovementDone() || (motor1.getDirection() == 0 && newState != 0) ? "true" : "false") + ","; + json += "\"motor2Done\":" + String(isCoordinatedMovementDone() || (motor2.getDirection() == 0 && newState != 0) ? "true" : "false"); + json += "}"; + server.send(200, "application/json", json); +} + // Legacy handlers (map to motor1 for backwards compatibility) void handleSpeed() { handleMotor1Speed(); @@ -368,7 +434,7 @@ void handleDirection() { handleMotor1Direction(); } -// Status handler - returns both motors' data +// Status handler - returns both motors' data and coordinated state void handleStatus() { String json = "{"; @@ -388,7 +454,16 @@ void handleStatus() { json += "\"currentR\":" + String(motor2.getCurrentRight(), 2) + ","; json += "\"currentL\":" + String(motor2.getCurrentLeft(), 2) + ","; json += "\"stalled\":" + String(motor2.isStalled() ? "true" : "false"); - json += "}"; + json += "},"; + + // Coordinated movement status + int coordState = getCoordinatedState(); + json += "\"coordState\":" + String(coordState) + ","; + // Motor is "done" if stalled/stopped during coordinated movement + bool m1Done = (coordState != 0) && (motor1.getDirection() == 0); + bool m2Done = (coordState != 0) && (motor2.getDirection() == 0); + json += "\"motor1Done\":" + String(m1Done ? "true" : "false") + ","; + json += "\"motor2Done\":" + String(m2Done ? "true" : "false"); json += "}"; server.send(200, "application/json", json); @@ -414,6 +489,7 @@ void setupWebServer() { // Global endpoints server.on("/stop", handleStop); // Emergency stop both motors server.on("/status", handleStatus); + server.on("/homing", handleHoming); // Coordinated movement / homing server.begin(); Serial.println("Web server started on port 80");