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
|
||||
*.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