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
This commit is contained in:
devdesk
2026-02-06 15:17:41 +02:00
parent f157d3abc5
commit 389182a9db
3 changed files with 191 additions and 5 deletions

10
include/homing.h Normal file
View File

@@ -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

View File

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

View File

@@ -2,6 +2,7 @@
#include <WebServer.h>
#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(
<div class="global-controls">
<h2>Global Controls</h2>
<button class="btn btn-emergency" onclick="emergencyStop()">⛔ EMERGENCY STOP</button>
<div style="display: flex; gap: 20px; justify-content: center; flex-wrap: wrap;">
<button class="btn btn-homing" id="homingBtn" onclick="toggleHoming()">🏠 HOME BOTH</button>
<button class="btn btn-emergency" onclick="emergencyStop()">⛔ EMERGENCY STOP</button>
</div>
<div id="coordStatus" style="margin-top: 15px; font-size: 14px; color: #888;"></div>
</div>
</div>
@@ -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");