Started on the webserver sketch, connected it to the motor code with tcp communication, implemented a simple webserver interface to control the device
This commit is contained in:
+2
-1
@@ -1,2 +1,3 @@
|
|||||||
*.FCBak
|
*.FCBak
|
||||||
*.3mf
|
*.3mf
|
||||||
|
Code/.vscode
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-215
@@ -1,215 +0,0 @@
|
|||||||
#include <AccelStepper.h>
|
|
||||||
|
|
||||||
// === Pin Definitions ===
|
|
||||||
#define M1_STEP_PIN 0 // Reel motor step
|
|
||||||
#define M1_DIR_PIN 1 // Reel motor direction
|
|
||||||
|
|
||||||
#define M2_STEP_PIN 2 // Carriage motor step
|
|
||||||
#define M2_DIR_PIN 3 // Carriage motor direction
|
|
||||||
|
|
||||||
#define HOME_SW_PIN 6 // Home limit switch
|
|
||||||
#define END_SW_PIN 5 // End limit switch
|
|
||||||
|
|
||||||
AccelStepper reelMotor(AccelStepper::DRIVER, M1_STEP_PIN, M1_DIR_PIN);
|
|
||||||
AccelStepper carriageMotor(AccelStepper::DRIVER, M2_STEP_PIN, M2_DIR_PIN);
|
|
||||||
|
|
||||||
float RAIL_LENGTH = 50; // 100mm linear rail
|
|
||||||
float CENTER_POSITION = RAIL_LENGTH / 2.0f; // Center position
|
|
||||||
|
|
||||||
// === Motor Configuration ===
|
|
||||||
const int MOTOR_STEPS = 200; // 200 steps/rev for NEMA 17
|
|
||||||
const int MICROSTEPPING = 8; // Set on driver board (MS1/MS2 jumpers)
|
|
||||||
const float SCREW_LEAD = 4.0; // mm per revolution (T8 screw)
|
|
||||||
const float CALIBRATION_FACTOR = 2.0;
|
|
||||||
const float STEPS_PER_MM = ((MOTOR_STEPS * MICROSTEPPING) / (SCREW_LEAD / CALIBRATION_FACTOR));
|
|
||||||
|
|
||||||
// === Speed Settings ===
|
|
||||||
const float REEL_RPM = 150; // Reel motor speed
|
|
||||||
const float CARRIAGE_RPM = 100; // Carriage motor speed
|
|
||||||
|
|
||||||
// State management
|
|
||||||
enum State {
|
|
||||||
IDLE,
|
|
||||||
HOMING,
|
|
||||||
FIND_END,
|
|
||||||
RUNNING
|
|
||||||
};
|
|
||||||
State currentState = HOMING;
|
|
||||||
|
|
||||||
// Manual distance tracking
|
|
||||||
long targetSteps = 0;
|
|
||||||
long stepsMoved = 0;
|
|
||||||
// Position tracking
|
|
||||||
float currentPositionMM = 0; // Tracks actual position in mm
|
|
||||||
|
|
||||||
float desiredPosition = 25;
|
|
||||||
bool bounceBool = false;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
|
|
||||||
// Configure limit switches
|
|
||||||
pinMode(HOME_SW_PIN, INPUT_PULLUP);
|
|
||||||
pinMode(END_SW_PIN, INPUT_PULLUP);
|
|
||||||
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.println("Starting motors...");
|
|
||||||
|
|
||||||
// Configure reel motor (constant speed)
|
|
||||||
reelMotor.setMaxSpeed((REEL_RPM * MOTOR_STEPS * MICROSTEPPING) / 60.0);
|
|
||||||
reelMotor.setSpeed(-reelMotor.maxSpeed()); // Negative for reverse
|
|
||||||
reelMotor.setMinPulseWidth(50); // 50µs pulse width
|
|
||||||
|
|
||||||
// Configure carriage motor (constant speed)
|
|
||||||
carriageMotor.setMaxSpeed((CARRIAGE_RPM * MOTOR_STEPS * MICROSTEPPING) / 60.0);
|
|
||||||
carriageMotor.setSpeed(carriageMotor.maxSpeed()); // Positive direction
|
|
||||||
carriageMotor.setMinPulseWidth(50); // 50µs pulse width
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// State machine for carriage
|
|
||||||
switch(currentState) {
|
|
||||||
case HOMING:
|
|
||||||
runHoming();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FIND_END:
|
|
||||||
runFindEnd();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RUNNING:
|
|
||||||
//reelMotor.runSpeed();
|
|
||||||
if(bounceBool == false){
|
|
||||||
if(runMoveToPosition(desiredPosition - 0 )){
|
|
||||||
bounceBool = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(runMoveToPosition(desiredPosition - 20.0)){
|
|
||||||
bounceBool = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void runHoming(){
|
|
||||||
// Move carriage in positive direction
|
|
||||||
if (carriageMotor.runSpeed()) {
|
|
||||||
stepsMoved++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (digitalRead(HOME_SW_PIN) == LOW) { // Switch pressed (LOW with pullup)
|
|
||||||
carriageMotor.stop(); // Stop motor
|
|
||||||
carriageMotor.setSpeed(0); // Set speed to zero
|
|
||||||
|
|
||||||
stepsMoved = 0;
|
|
||||||
// Reset position tracking
|
|
||||||
currentPositionMM = 0;
|
|
||||||
carriageMotor.setCurrentPosition(0);
|
|
||||||
|
|
||||||
// Calculate steps needed to reach center
|
|
||||||
targetSteps = CENTER_POSITION * STEPS_PER_MM;
|
|
||||||
|
|
||||||
carriageMotor.setSpeed(-carriageMotor.maxSpeed()); // Positive direction
|
|
||||||
|
|
||||||
currentState = FIND_END;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void runFindEnd() {
|
|
||||||
// Run carriage at constant speed
|
|
||||||
if (carriageMotor.runSpeed()) {
|
|
||||||
stepsMoved++;
|
|
||||||
// Update position based on direction
|
|
||||||
currentPositionMM += (carriageMotor.speed() > 0) ?
|
|
||||||
(1.0 / STEPS_PER_MM) :
|
|
||||||
(-1.0 / STEPS_PER_MM);
|
|
||||||
|
|
||||||
// Print position every 100ms
|
|
||||||
// static unsigned long lastPrint = 0;
|
|
||||||
// if (millis() - lastPrint > 100) {
|
|
||||||
// lastPrint = millis();
|
|
||||||
// Serial.print("Position: ");
|
|
||||||
// Serial.print(currentPositionMM);
|
|
||||||
// Serial.println(" mm");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (digitalRead(END_SW_PIN) == LOW) { // Switch pressed (LOW with pullup)
|
|
||||||
currentState = RUNNING;
|
|
||||||
|
|
||||||
// Stop carriage motor completely
|
|
||||||
carriageMotor.stop();
|
|
||||||
carriageMotor.setSpeed(0);
|
|
||||||
|
|
||||||
// Print final position
|
|
||||||
Serial.print("Final position: ");
|
|
||||||
Serial.print(currentPositionMM);
|
|
||||||
|
|
||||||
RAIL_LENGTH = abs(currentPositionMM);
|
|
||||||
CENTER_POSITION = RAIL_LENGTH / 2.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool runMoveToPosition(float position){
|
|
||||||
|
|
||||||
float distance = abs(abs(currentPositionMM) - position);
|
|
||||||
float margin = 0.1f;
|
|
||||||
|
|
||||||
// Print position every 100ms
|
|
||||||
// static unsigned long lastPrint = 0;
|
|
||||||
// if (millis() - lastPrint > 100) {
|
|
||||||
// lastPrint = millis();
|
|
||||||
// Serial.print("Position: ");
|
|
||||||
// Serial.print(abs(currentPositionMM));
|
|
||||||
// Serial.println(" mm");
|
|
||||||
// Serial.print("Desired Position: ");
|
|
||||||
// Serial.print(position);
|
|
||||||
// Serial.println(" mm");
|
|
||||||
// Serial.println(distance);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Calculate direction
|
|
||||||
if(distance > margin){
|
|
||||||
int direction = sign(position - abs(currentPositionMM));
|
|
||||||
|
|
||||||
int previousDirection = (carriageMotor.speed() / -1.0f) / carriageMotor.maxSpeed();
|
|
||||||
if(previousDirection != direction){
|
|
||||||
carriageMotor.setSpeed(carriageMotor.maxSpeed() * direction * -1.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run carriage at constant speed
|
|
||||||
if (carriageMotor.runSpeed()) {
|
|
||||||
stepsMoved++;
|
|
||||||
// Update position based on direction
|
|
||||||
currentPositionMM += (carriageMotor.speed() > 0) ?
|
|
||||||
(1.0 / STEPS_PER_MM) :
|
|
||||||
(-1.0 / STEPS_PER_MM);
|
|
||||||
|
|
||||||
if (distance <= margin) {
|
|
||||||
// Stop carriage motor completely
|
|
||||||
carriageMotor.setSpeed(0);
|
|
||||||
carriageMotor.stop();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distance <= margin) {
|
|
||||||
// Stop carriage motor completely
|
|
||||||
carriageMotor.setSpeed(0);
|
|
||||||
carriageMotor.stop();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool withinRange(float value, float target, float margin) {
|
|
||||||
return (value >= (target - margin)) && (value <= (target + margin));
|
|
||||||
}
|
|
||||||
|
|
||||||
float sign(float value) {
|
|
||||||
return float((value>0)-(value<0));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
#include <AccelStepper.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
|
// === Pin Definitions ===
|
||||||
|
#define M1_STEP_PIN 0 // Reel motor step
|
||||||
|
#define M1_DIR_PIN 1 // Reel motor direction
|
||||||
|
|
||||||
|
#define M2_STEP_PIN 2 // Carriage motor step
|
||||||
|
#define M2_DIR_PIN 3 // Carriage motor direction
|
||||||
|
|
||||||
|
#define START_SW_PIN 6 // Home limit switch
|
||||||
|
#define END_SW_PIN 5 // End limit switch
|
||||||
|
|
||||||
|
// Wi-Fi credentials
|
||||||
|
const char* ssid = "";
|
||||||
|
const char* password = "";
|
||||||
|
|
||||||
|
WiFiServer motorServer(3333); // Open port 3333
|
||||||
|
WiFiClient activeClient; // Store the current client
|
||||||
|
|
||||||
|
String recvBuffer = "";
|
||||||
|
|
||||||
|
AccelStepper reelMotor(AccelStepper::DRIVER, M1_STEP_PIN, M1_DIR_PIN);
|
||||||
|
AccelStepper carriageMotor(AccelStepper::DRIVER, M2_STEP_PIN, M2_DIR_PIN);
|
||||||
|
|
||||||
|
// === Motor Configuration ===
|
||||||
|
const int MOTOR_STEPS = 200; // 200 steps/rev for NEMA 17
|
||||||
|
const int MICROSTEPPING = 8; // Set on driver board (MS1/MS2 jumpers)
|
||||||
|
const float DISTANCE_PER_REVOLUTION = 2.0; // mm per revolution (best to do a revolution by hand and measure distance and input it here)
|
||||||
|
const float STEPS_PER_MM = (MOTOR_STEPS * MICROSTEPPING) / DISTANCE_PER_REVOLUTION;
|
||||||
|
|
||||||
|
// === Speed Settings ===
|
||||||
|
const float REEL_RPM = 40; // Reel motor speed
|
||||||
|
float CARRIAGE_RPM = 100; // Carriage motor speed (will be dynamically set)
|
||||||
|
#define FORWARD -1 // In my setup the carriage is moving forward is in negative speed, meaning away from the motor, that why we need to invert the movement in tracking
|
||||||
|
|
||||||
|
// System state management
|
||||||
|
enum SystemState {
|
||||||
|
IDLE,
|
||||||
|
SYSTEM_CALIBRATION,
|
||||||
|
REEL_CALIBRATION,
|
||||||
|
REELING
|
||||||
|
};
|
||||||
|
SystemState currentSystemState = IDLE;
|
||||||
|
|
||||||
|
enum SystemCalibrationState {
|
||||||
|
FIND_START,
|
||||||
|
FIND_END
|
||||||
|
};
|
||||||
|
SystemCalibrationState systemCalibrationState = FIND_START;
|
||||||
|
|
||||||
|
// Position tracking
|
||||||
|
float railLength = 0;
|
||||||
|
float currentPositionMM = 0;
|
||||||
|
float targetPosition = 0;
|
||||||
|
bool isMovingToEnd = false;
|
||||||
|
|
||||||
|
// Gap values
|
||||||
|
float reelGapStartPosition = -1;
|
||||||
|
float reelGapEndPosition = -1;
|
||||||
|
|
||||||
|
bool starterSet = false; // Is start gap position set?
|
||||||
|
bool endSet = false; // Is start gap position set?
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
Serial.println("Motor ESP Connecting");
|
||||||
|
// Connect to Wi-Fi
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
Serial.println("\nMotor ESP connected. IP: " + WiFi.localIP().toString());
|
||||||
|
|
||||||
|
// FIXME : Perhaps use UDP communication instead maybe it will not block the ESP32C3 as much
|
||||||
|
motorServer.begin(); // Start TCP server
|
||||||
|
|
||||||
|
pinMode(START_SW_PIN, INPUT_PULLUP);
|
||||||
|
pinMode(END_SW_PIN, INPUT_PULLUP);
|
||||||
|
|
||||||
|
// Configure reel motor
|
||||||
|
reelMotor.setMaxSpeed((REEL_RPM * MOTOR_STEPS * MICROSTEPPING) / 60.0);
|
||||||
|
reelMotor.setSpeed(-reelMotor.maxSpeed()); // To reel the line in toward the fishing reel, i need the fishing reel to move anti-clockwise
|
||||||
|
reelMotor.setMinPulseWidth(50);
|
||||||
|
|
||||||
|
// Configure carriage motor
|
||||||
|
carriageMotor.setMaxSpeed((CARRIAGE_RPM * MOTOR_STEPS * MICROSTEPPING) / 60.0);
|
||||||
|
carriageMotor.setSpeed(carriageMotor.maxSpeed());
|
||||||
|
carriageMotor.setMinPulseWidth(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Accept new clients
|
||||||
|
if (!activeClient || !activeClient.connected()) {
|
||||||
|
activeClient = motorServer.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeClient && activeClient.connected() && activeClient.available()) {
|
||||||
|
while (activeClient.available()) {
|
||||||
|
char c = activeClient.read();
|
||||||
|
|
||||||
|
if (c == '\n') {
|
||||||
|
recvBuffer.trim(); // Remove trailing \r or whitespace
|
||||||
|
if (recvBuffer.length() > 0) {
|
||||||
|
handleCommand(recvBuffer, activeClient);
|
||||||
|
}
|
||||||
|
recvBuffer = ""; // Reset for next command
|
||||||
|
} else {
|
||||||
|
recvBuffer += c;
|
||||||
|
// Optional: protect against overflow
|
||||||
|
if (recvBuffer.length() > 200) recvBuffer = ""; // Reset if too long
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main state machine
|
||||||
|
switch (currentSystemState) {
|
||||||
|
case IDLE: break;
|
||||||
|
case REELING: runReeling(); break;
|
||||||
|
case REEL_CALIBRATION: runCalibration(); break;
|
||||||
|
case SYSTEM_CALIBRATION:
|
||||||
|
if (systemCalibrationState == FIND_START) runFindStart();
|
||||||
|
else if (systemCalibrationState == FIND_END) runFindEnd();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void runFindStart() {
|
||||||
|
// Force the carriage motor to find the start switch by moving it positive direction, meaning toward the motor
|
||||||
|
if(carriageMotor.speed() <= 0){
|
||||||
|
carriageMotor.setSpeed(carriageMotor.maxSpeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
carriageMotor.runSpeed();
|
||||||
|
|
||||||
|
//
|
||||||
|
if (digitalRead(START_SW_PIN) == LOW) {
|
||||||
|
carriageMotor.stop();
|
||||||
|
carriageMotor.setSpeed(0);
|
||||||
|
currentPositionMM = 0; // Start from zero
|
||||||
|
carriageMotor.setCurrentPosition(0);
|
||||||
|
carriageMotor.setSpeed(carriageMotor.maxSpeed()); // Always move forward from here
|
||||||
|
systemCalibrationState = FIND_END;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void runFindEnd() {
|
||||||
|
|
||||||
|
// Force the carriage motor to find the end switch by moving it negative direction, meaning away from the motor
|
||||||
|
if(carriageMotor.speed() >= 0){
|
||||||
|
carriageMotor.setSpeed(carriageMotor.maxSpeed() * FORWARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (carriageMotor.runSpeed()) {
|
||||||
|
currentPositionMM += (1.0 / STEPS_PER_MM); // Calculate the new position when the motor actually does steps
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digitalRead(END_SW_PIN) == LOW) {
|
||||||
|
carriageMotor.stop();
|
||||||
|
carriageMotor.setSpeed(0);
|
||||||
|
|
||||||
|
railLength = currentPositionMM; // Assign the maximum length the motor can travel
|
||||||
|
targetPosition = railLength;
|
||||||
|
|
||||||
|
currentSystemState = IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void runReeling() {
|
||||||
|
|
||||||
|
if(reelGapStartPosition < 0 || reelGapEndPosition < 0){
|
||||||
|
// TODO : Maybe throw an error for the user
|
||||||
|
stopMotors();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWithinRange(currentPositionMM, reelGapStartPosition, reelGapEndPosition)) {
|
||||||
|
|
||||||
|
if(reelMotor.speed() == 0){
|
||||||
|
reelMotor.setSpeed(-reelMotor.maxSpeed());
|
||||||
|
}
|
||||||
|
reelMotor.runSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Oscillate effect ===
|
||||||
|
if (!isMovingToEnd) {
|
||||||
|
if (runMoveToPosition(reelGapStartPosition)) {
|
||||||
|
isMovingToEnd = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (runMoveToPosition(reelGapEndPosition)) {
|
||||||
|
isMovingToEnd = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void runCalibration() {
|
||||||
|
runMoveToPosition(targetPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool runMoveToPosition(float position) {
|
||||||
|
|
||||||
|
// If it want's to move out of bounds, stop the motors, maybe it will damage the setup
|
||||||
|
if(position < 0.0f || position > railLength){
|
||||||
|
stopMotors();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float distance = abs(currentPositionMM - position);
|
||||||
|
float margin = 0.01f;
|
||||||
|
|
||||||
|
if (distance <= margin) {
|
||||||
|
carriageMotor.setSpeed(0);
|
||||||
|
carriageMotor.stop();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int direction = sign(position - currentPositionMM);
|
||||||
|
int previousDirection = (carriageMotor.speed() / FORWARD) / carriageMotor.maxSpeed();
|
||||||
|
|
||||||
|
if (previousDirection != direction) {
|
||||||
|
carriageMotor.setSpeed(carriageMotor.maxSpeed() * direction * FORWARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (carriageMotor.runSpeed()) {
|
||||||
|
currentPositionMM += (carriageMotor.speed() > 0) ? (-1.0 / STEPS_PER_MM) : (1.0 / STEPS_PER_MM);
|
||||||
|
|
||||||
|
if (distance <= margin) {
|
||||||
|
carriageMotor.setSpeed(0);
|
||||||
|
carriageMotor.stop();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopMotors(){
|
||||||
|
currentSystemState = IDLE;
|
||||||
|
carriageMotor.stop();
|
||||||
|
carriageMotor.setSpeed(0);
|
||||||
|
reelMotor.stop();
|
||||||
|
reelMotor.setSpeed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float calculateCarriageRPM(float reelRPM, float gapLengthMM) {
|
||||||
|
float timeForReelRevs = 4.0f * 60.0f / reelRPM;
|
||||||
|
float mmPerSec = gapLengthMM / timeForReelRevs;
|
||||||
|
float carriageRevsPerSec = mmPerSec / DISTANCE_PER_REVOLUTION;
|
||||||
|
return carriageRevsPerSec * 60.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWithinRange(float currentPosition, float start, float end) {
|
||||||
|
return currentPosition >= start && currentPosition <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sign(float value) {
|
||||||
|
return float((value > 0) - (value < 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
String stateToString(SystemState systemState) {
|
||||||
|
switch(systemState) {
|
||||||
|
case IDLE: return "IDLE";
|
||||||
|
case REELING: return "REELING";
|
||||||
|
case REEL_CALIBRATION: return "REEL_CALIBRATION";
|
||||||
|
case SYSTEM_CALIBRATION: return "SYSTEM_CALIBRATION";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleCommand(const String& cmd, WiFiClient& client) {
|
||||||
|
if (cmd.startsWith("starter-position-input")) {
|
||||||
|
float value;
|
||||||
|
int scanned = sscanf(cmd.c_str(), "starter-position-input %f", &value);
|
||||||
|
if (scanned == 1) {
|
||||||
|
reelGapStartPosition = value;
|
||||||
|
starterSet = true;
|
||||||
|
|
||||||
|
if (starterSet && endSet) {
|
||||||
|
float gap = reelGapEndPosition - reelGapStartPosition;
|
||||||
|
float newCarriageRPM = calculateCarriageRPM(REEL_RPM, gap);
|
||||||
|
CARRIAGE_RPM = newCarriageRPM;
|
||||||
|
float maxSpeed = (newCarriageRPM * MOTOR_STEPS * MICROSTEPPING) / 60.0;
|
||||||
|
carriageMotor.setMaxSpeed(maxSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.println("OK");
|
||||||
|
} else {
|
||||||
|
client.println("Invalid starter-position-input command");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (cmd.startsWith("end-position-input")) {
|
||||||
|
float value;
|
||||||
|
int scanned = sscanf(cmd.c_str(), "end-position-input %f", &value);
|
||||||
|
if (scanned == 1) {
|
||||||
|
reelGapEndPosition = value;
|
||||||
|
endSet = true;
|
||||||
|
|
||||||
|
if (starterSet && endSet) {
|
||||||
|
float gap = reelGapEndPosition - reelGapStartPosition;
|
||||||
|
float newCarriageRPM = calculateCarriageRPM(REEL_RPM, gap);
|
||||||
|
CARRIAGE_RPM = newCarriageRPM;
|
||||||
|
float maxSpeed = (newCarriageRPM * MOTOR_STEPS * MICROSTEPPING) / 60.0;
|
||||||
|
carriageMotor.setMaxSpeed(maxSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.println("OK");
|
||||||
|
} else {
|
||||||
|
client.println("Invalid end-position-input command");
|
||||||
|
}
|
||||||
|
} else if (cmd == "status") {
|
||||||
|
StaticJsonDocument<200> doc;
|
||||||
|
doc["position"] = currentPositionMM;
|
||||||
|
doc["state"] = stateToString(currentSystemState);
|
||||||
|
doc["railLength"] = railLength;
|
||||||
|
doc["starterSet"] = starterSet;
|
||||||
|
doc["endSet"] = endSet;
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
client.println(json);
|
||||||
|
|
||||||
|
} else if (cmd == "calibrate") {
|
||||||
|
currentSystemState = SYSTEM_CALIBRATION;
|
||||||
|
systemCalibrationState = FIND_START;
|
||||||
|
client.println("System calibration started");
|
||||||
|
|
||||||
|
} else if (cmd == "reel_cal") {
|
||||||
|
if(currentSystemState != REEL_CALIBRATION){
|
||||||
|
currentSystemState = REEL_CALIBRATION;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
currentSystemState = IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(starterSet && endSet){
|
||||||
|
targetPosition = (reelGapEndPosition - reelGapStartPosition) / 2.0f;
|
||||||
|
}else {
|
||||||
|
targetPosition = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.println("Reel calibration mode");
|
||||||
|
|
||||||
|
} else if (cmd == "start") {
|
||||||
|
if (starterSet && endSet) {
|
||||||
|
currentSystemState = REELING;
|
||||||
|
client.println("Started reeling");
|
||||||
|
} else {
|
||||||
|
client.println("Error: Gap not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (cmd == "stop") {
|
||||||
|
stopMotors();
|
||||||
|
client.println("Stopped");
|
||||||
|
} else if (cmd.startsWith("move")) {
|
||||||
|
float value;
|
||||||
|
int scanned = sscanf(cmd.c_str(), "move %f", &value);
|
||||||
|
if (scanned == 1) {
|
||||||
|
targetPosition = value;
|
||||||
|
currentSystemState = REEL_CALIBRATION; // Use existing logic to move to target
|
||||||
|
client.println("OK");
|
||||||
|
} else {
|
||||||
|
client.println("Invalid end-position-input command");
|
||||||
|
}
|
||||||
|
|
||||||
|
}else {
|
||||||
|
client.println("Unknown command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
// Wi-Fi credentials
|
||||||
|
const char* ssid = "";
|
||||||
|
const char* password = "";
|
||||||
|
|
||||||
|
// Web server on port 80
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
// TCP client for talking to motor ESP
|
||||||
|
WiFiClient motorClient;
|
||||||
|
IPAddress motorIP(192, 168, 1, 18); // Motor ESP IP (must be static or fixed by DHCP)
|
||||||
|
|
||||||
|
bool starterSet = false;
|
||||||
|
bool endSet = false;
|
||||||
|
|
||||||
|
// =================== COMMUNICATION ===================
|
||||||
|
|
||||||
|
bool ensureConnected() {
|
||||||
|
if (!motorClient.connected()) {
|
||||||
|
return motorClient.connect(motorIP, 3333); // Port must match motor ESP
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String sendMotorCommand(const String& cmd) {
|
||||||
|
if (!ensureConnected()) return "Motor Not Connected";
|
||||||
|
|
||||||
|
motorClient.println(cmd);
|
||||||
|
motorClient.flush();
|
||||||
|
|
||||||
|
String response = "";
|
||||||
|
unsigned long start = millis();
|
||||||
|
while (millis() - start < 300) {
|
||||||
|
while (motorClient.available()) {
|
||||||
|
char c = motorClient.read();
|
||||||
|
if (c == '\n') return response;
|
||||||
|
response += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================== SETUP ===================
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Connect to Wi-Fi
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
Serial.println("\nConnected to Wi-Fi. IP: " + WiFi.localIP().toString());
|
||||||
|
|
||||||
|
if (!LittleFS.begin(true)) {
|
||||||
|
Serial.println("Failed to mount LittleFS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
server.on("/cmd", handleCommand);
|
||||||
|
server.on("/move", handleMove);
|
||||||
|
server.on("/set", handleSet);
|
||||||
|
server.on("/status", handleStatus);
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================== WEB HANDLERS ===================
|
||||||
|
|
||||||
|
void handleRoot() {
|
||||||
|
File file = LittleFS.open("/index.html", "r");
|
||||||
|
if (!file) {
|
||||||
|
server.send(500, "text/plain", "Failed to load HTML file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.streamFile(file, "text/html");
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleCommand() {
|
||||||
|
String command = server.arg("code");
|
||||||
|
|
||||||
|
String reply = sendMotorCommand(command);
|
||||||
|
server.send(200, "text/plain", reply.length() > 0 ? reply : "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSet() {
|
||||||
|
if (!server.hasArg("type") || !server.hasArg("val")) {
|
||||||
|
server.send(400, "text/plain", "Missing arguments");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String type = server.arg("type");
|
||||||
|
float value = server.arg("val").toFloat();
|
||||||
|
|
||||||
|
if (type == "starter-position-input") {
|
||||||
|
starterSet = true;
|
||||||
|
String cmd = "starter-position-input " + String(value, 2);
|
||||||
|
sendMotorCommand(cmd);
|
||||||
|
} else if (type == "end-position-input") {
|
||||||
|
endSet = true;
|
||||||
|
String cmd = "end-position-input " + String(value, 2);
|
||||||
|
sendMotorCommand(cmd);
|
||||||
|
} else {
|
||||||
|
server.send(400, "text/plain", "Invalid type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.send(200, "text/plain", "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleStatus() {
|
||||||
|
String json = sendMotorCommand("status");
|
||||||
|
if (json.startsWith("{")) {
|
||||||
|
server.send(200, "application/json", json);
|
||||||
|
} else {
|
||||||
|
server.send(500, "text/plain", "Invalid status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleMove() {
|
||||||
|
float position = server.arg("pos").toFloat();
|
||||||
|
String cmd = "move " + String(position, 2);
|
||||||
|
sendMotorCommand(cmd);
|
||||||
|
|
||||||
|
// MUST send a response to complete the request
|
||||||
|
server.send(200, "text/plain", "OK");
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AFRS 1.3</title>
|
||||||
|
<style>
|
||||||
|
body{font-family:sans-serif;margin:20px}
|
||||||
|
button,input{padding:8px 12px;margin:4px}
|
||||||
|
.group
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="group">
|
||||||
|
<div id="system-state"> State : IDLE </div>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<div id="carriage-position-label">Carriage Position : 0.0 mm </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<button onclick="sendCommand('calibrate')" id="system-calibrate-button">System Calibration Mode</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<button onclick="sendCommand('reel_cal')" id="reel-calibratation-button">Reel Calibration Mode</button>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
Target Position : <input type="number" id="target-position-input" value="0">
|
||||||
|
<button onclick="moveToPosition()" id="move-button">Move</button>
|
||||||
|
<button onclick="adjustSetPosition('adjust-set-start-button')" id="adjust-set-start-button">Set Start</button>
|
||||||
|
<button onclick="adjustSetPosition('adjust-set-end-button')" id="adjust-set-end-button">Set End</button>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<button onclick="adjustPosition(1)">+1mm</button>
|
||||||
|
<button onclick="adjustPosition(-1)">-1mm</button>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
Start: <input type="number" id="starter-position-input" value="0" onchange="setChangedPosition('starter-position-input')">
|
||||||
|
<button onclick="setPosition('starter-position-input')" id="start-set-button">Set</button>
|
||||||
|
<input type="checkbox" id="starter-position-input-set" disabled>
|
||||||
|
End: <input type="number" id="end-position-input" value="0" onchange="setChangedPosition('end-position-input')">
|
||||||
|
<button onclick="setPosition('end-position-input')" id="end-set-button">Set</button>
|
||||||
|
<input type="checkbox"id="end-position-input-set" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<button onclick="sendCommand('start')" id="start-reeling-button">Start Reeling</button>
|
||||||
|
<button onclick="sendCommand('stop')" id="stop-reeling-button">Stop Reeling</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let currentState = 'IDLE';
|
||||||
|
let currentPosition = 0;
|
||||||
|
let railLength = 50;
|
||||||
|
|
||||||
|
let systemCalibrated = false;
|
||||||
|
let reelCalibrated = false;
|
||||||
|
|
||||||
|
let starterSet = false;
|
||||||
|
let endSet = false;
|
||||||
|
|
||||||
|
// Update UI based on system state
|
||||||
|
function updateUI() {
|
||||||
|
// Update state indicator
|
||||||
|
const stateElement = document.getElementById('system-state');
|
||||||
|
stateElement.textContent = `State : ${currentState}`;
|
||||||
|
|
||||||
|
document.getElementById("starter-position-input-set").checked = starterSet;
|
||||||
|
document.getElementById("end-position-input-set").checked = endSet;
|
||||||
|
|
||||||
|
// Update position
|
||||||
|
document.getElementById('carriage-position-label').textContent = `Carriage Position : ${currentPosition.toFixed(2)} mm`;
|
||||||
|
|
||||||
|
if(!systemCalibrated){
|
||||||
|
document.getElementById('reel-calibratation-button').disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('target-position-input').disabled = true;
|
||||||
|
document.getElementById('move-button').disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('starter-position-input').disabled = true;
|
||||||
|
document.getElementById('start-set-button').disabled = true;
|
||||||
|
document.getElementById('end-position-input').disabled = true;
|
||||||
|
document.getElementById('end-set-button').disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('start-reeling-button').disabled = true;
|
||||||
|
document.getElementById('stop-reeling-button').disabled = true;
|
||||||
|
}
|
||||||
|
else if(systemCalibrated && !reelCalibrated && currentState == "IDLE"){
|
||||||
|
document.getElementById('system-calibrate-button').disabled = false;
|
||||||
|
document.getElementById('reel-calibratation-button').disabled = false;
|
||||||
|
|
||||||
|
document.getElementById('target-position-input').disabled = true;
|
||||||
|
document.getElementById('move-button').disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('starter-position-input').disabled = true;
|
||||||
|
document.getElementById('start-set-button').disabled = true;
|
||||||
|
document.getElementById('end-position-input').disabled = true;
|
||||||
|
document.getElementById('end-set-button').disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('start-reeling-button').disabled = true;
|
||||||
|
document.getElementById('stop-reeling-button').disabled = true;
|
||||||
|
}
|
||||||
|
else if(systemCalibrated && currentState == "CALIBRATE_REEL"){
|
||||||
|
document.getElementById('system-calibrate-button').disabled = true;
|
||||||
|
document.getElementById('reel-calibratation-button').disabled = false;
|
||||||
|
|
||||||
|
document.getElementById('target-position-input').disabled = false;
|
||||||
|
document.getElementById('move-button').disabled = false;
|
||||||
|
|
||||||
|
document.getElementById('starter-position-input').disabled = false;
|
||||||
|
document.getElementById('start-set-button').disabled = false;
|
||||||
|
document.getElementById('end-position-input').disabled = false;
|
||||||
|
document.getElementById('end-set-button').disabled = false;
|
||||||
|
|
||||||
|
document.getElementById('start-reeling-button').disabled = true;
|
||||||
|
document.getElementById('stop-reeling-button').disabled = true;
|
||||||
|
}
|
||||||
|
else if(systemCalibrated && reelCalibrated && currentState == "IDLE"){
|
||||||
|
document.getElementById('system-calibrate-button').disabled = false;
|
||||||
|
document.getElementById('reel-calibratation-button').disabled = false;
|
||||||
|
|
||||||
|
document.getElementById('target-position-input').disabled = true;
|
||||||
|
document.getElementById('move-button').disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('starter-position-input').disabled = true;
|
||||||
|
document.getElementById('start-set-button').disabled = true;
|
||||||
|
document.getElementById('end-position-input').disabled = true;
|
||||||
|
document.getElementById('end-set-button').disabled = true;
|
||||||
|
|
||||||
|
document.getElementById('start-reeling-button').disabled = false;
|
||||||
|
document.getElementById('stop-reeling-button').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendCommand(cmd) {
|
||||||
|
fetch(`/cmd?code=${cmd}`)
|
||||||
|
.catch(e => console.error('Error:', e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveToPosition() {
|
||||||
|
const pos = document.getElementById('target-position-input').value;
|
||||||
|
fetch(`/move?pos=${pos}`)
|
||||||
|
.catch(e => console.error('Error:', e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustPosition(delta) {
|
||||||
|
const currentTargetPosition = document.getElementById('target-position-input').value;
|
||||||
|
let newTargetPosition = parseFloat(currentTargetPosition) + delta;
|
||||||
|
|
||||||
|
if(newTargetPosition < 0){
|
||||||
|
newTargetPosition = 0;
|
||||||
|
} else if (newTargetPosition > railLength)
|
||||||
|
{
|
||||||
|
newTargetPosition = railLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('target-position-input').value = newTargetPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustSetPosition(type){
|
||||||
|
let newVal = document.getElementById("target-position-input").value;
|
||||||
|
|
||||||
|
if(newVal < 0){
|
||||||
|
newVal = 0;
|
||||||
|
} else if (newVal > railLength)
|
||||||
|
{
|
||||||
|
newVal = railLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type === "adjust-set-start-button"){
|
||||||
|
document.getElementById("starter-position-input").value = newVal;
|
||||||
|
} else if(type === "adjust-set-end-button"){
|
||||||
|
document.getElementById("end-position-input").value = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPosition(type) {
|
||||||
|
const val = document.getElementById(type).value;
|
||||||
|
fetch(`/set?type=${type}&val=${val}`)
|
||||||
|
.catch(e => console.error('Error:', e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChangedPosition(type){
|
||||||
|
let inputValue = parseFloat(document.getElementById(type).value);
|
||||||
|
|
||||||
|
if(inputValue < 0){
|
||||||
|
inputValue = 0;
|
||||||
|
}
|
||||||
|
else if(inputValue > railLength){
|
||||||
|
inputValue = railLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById(type).value = inputValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get system status from ESP32
|
||||||
|
function getStatus() {
|
||||||
|
fetch('/status')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
currentState = data.state;
|
||||||
|
currentPosition = data.position;
|
||||||
|
railLength = data.railLength;
|
||||||
|
|
||||||
|
// Update workflow states based on device
|
||||||
|
systemCalibrated = (railLength > 0);
|
||||||
|
starterSet = data.starterSet;
|
||||||
|
endSet = data.endSet;
|
||||||
|
reelCalibrated = (data.starterSet && data.endSet);
|
||||||
|
|
||||||
|
updateUI();
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching status:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the controller
|
||||||
|
function initController() {
|
||||||
|
// Get initial status
|
||||||
|
getStatus();
|
||||||
|
|
||||||
|
// Set up periodic status updates
|
||||||
|
setInterval(getStatus, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when page loads
|
||||||
|
window.onload = initController;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user