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:
10
include/homing.h
Normal file
10
include/homing.h
Normal 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
|
||||
100
src/main.cpp
100
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() {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user