Arduino Code
// Pin Definitions
#define DIR_LINEAR 3
#define STEP_LINEAR 2
#define LIMIT_RIGHT 7
#define LIMIT_LEFT 6
#define DIR_ROTATION 12
#define STEP_ROTATION 13
#define LIMIT_ROTATION 5
#define MOTOR_IN1 8
#define MOTOR_IN2 9
#define BUTTON_PIN 10
// Constants
const long REVERSE_STEPS = 32000;
const long REVERSE_STEPS_ROTATION = 29500;
const int STEP_DELAY = 15;
// Part Configuration
const int NUM_PARTS = 4;
const int NUM_CUTS = 4;
long partSteps[4][5][2] = {
{ // PART1032
{48605, 0}, {-29651, -48000}, {-35438, 0}, {16484, 48000}, {0, 0}
},
{ // PART111503
{58164, 0}, {-32902, -48000}, {-37226, 0}, {11964, 48000}, {0, 0}
},
{ // PART16
{52019, 0}, {-28902, -48000}, {-35601, 0}, {12485, 48000}, {0, 0}
},
{ // PART11
{-11477, -48000}, {29586, 0}, {-18109, 48000}, {0, 0}, {0, 0}
}
};
// Global State
int currentPart = 0;
int currentCut = 0;
bool sequenceStarted = false;
bool buttonEnabled = true; // Start enabled, GUI will disable if needed
bool inCutSequence = false;
bool sequenceStartedByButton = false;
void setup() {
Serial.begin(9600);
pinMode(DIR_LINEAR, OUTPUT);
pinMode(STEP_LINEAR, OUTPUT);
pinMode(DIR_ROTATION, OUTPUT);
pinMode(STEP_ROTATION, OUTPUT);
pinMode(LIMIT_RIGHT, INPUT_PULLUP);
pinMode(LIMIT_LEFT, INPUT_PULLUP);
pinMode(LIMIT_ROTATION, INPUT_PULLUP);
pinMode(MOTOR_IN1, OUTPUT);
pinMode(MOTOR_IN2, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println("System ready. Awaiting commands...");
}
void loop() {
handleSerial();
handleButton();
checkLimitSwitches();
}
void handleSerial() {
if (!Serial.available()) return;
String command = Serial.readStringUntil('\n');
command.trim();
if (command.startsWith("PART")) handlePartCommand(command);
else if (command.startsWith("CUT")) {
sequenceStartedByButton = false;
handleCutCommand(command);
}
else if (command == "HOMING") handleHomingCommand();
else if (command == "HOMING_MAIN") handleHomingCommandMain(); // change later for no enable button
else if (command == "CLAMPING") clamping();
else if (command == "UNCLAMPING") unclamping();
else if (command == "ENABLE_BUTTON") enableButton();
else if (command == "DISABLE_BUTTON") disableButton();
else Serial.println("Unknown command: " + command);
}
void handleButton() {
if (!buttonEnabled || digitalRead(BUTTON_PIN) != HIGH) return;
delay(50); // Debounce
if (!inCutSequence) {
sequenceStartedByButton = true;
Serial.println("SEQ_STARTED_BY_BUTTON");
startCutSequence();
} else {
advanceCutSequence();
}
delay(500); // Prevent rapid triggering
}
void startCutSequence() {
inCutSequence = true;
sequenceStarted = true;
currentCut = 0;
clamping();
}
void advanceCutSequence() {
if (currentCut < NUM_CUTS) {
handleCutCommand("CUT" + String(currentCut + 1));
currentCut++;
}
if (currentCut >= NUM_CUTS) {
unclamping();
inCutSequence = false;
sequenceStarted = false;
currentCut = 0;
Serial.println("SEQ_END");
sequenceStartedByButton = false;
}
}
const char* partNames[] = {
"1032", // partID = 1032 (index 0)
"111503", // partID = 1033 (index 1)
"16", // partID = 1034 (index 2)
"11" // partID = 1035 (index 3)
};
// hande part commands
void handlePartCommand(String cmd) {
int partID = cmd.substring(4).toInt();
int partIndex = partID - 1032;
if (partIndex >= 0 && partIndex < NUM_PARTS) {
currentPart = partIndex;
Serial.print("PART_SELECTED:");
Serial.println(partNames[partIndex]); // Print the actual name
inCutSequence = false;
sequenceStarted = false;
currentCut = 0;
} else {
Serial.println("ERR:INVALID_PART");
}
}
// handle cut commands
void handleCutCommand(String cmd) {
int cutNumber = cmd.substring(3).toInt();
if (cutNumber < 1 || cutNumber > NUM_CUTS) {
Serial.println("ERR:INVALID_CUT");
return;
}
long linear = partSteps[currentPart][cutNumber-1][0];
long rotation = partSteps[currentPart][cutNumber-1][1];
executeMotion(linear, rotation);
Serial.print("CUT_DONE:");
Serial.println(cutNumber);
}
// executing motion for the cutting part
void executeMotion(long linearSteps, long rotationSteps) {
if (linearSteps == 0 && rotationSteps == 0) {
Serial.println("No motion required");
return;
}
digitalWrite(DIR_LINEAR, linearSteps >= 0 ? HIGH : LOW);
digitalWrite(DIR_ROTATION, rotationSteps >= 0 ? HIGH : LOW);
long absLinear = abs(linearSteps);
long absRotation = abs(rotationSteps);
long maxSteps = max(absLinear, absRotation);
for (long i = 0; i < maxSteps; i++) {
if (i < absLinear) stepPulse(STEP_LINEAR);
if (i < absRotation) stepPulse(STEP_ROTATION);
}
}
// homing for cut sequences
void handleHomingCommand() {
digitalWrite(DIR_LINEAR, LOW);
while (digitalRead(LIMIT_RIGHT) == HIGH) {
stepPulse(STEP_LINEAR);
}
digitalWrite(DIR_LINEAR, HIGH);
for (long i = 0; i < REVERSE_STEPS; i++) {
stepPulse(STEP_LINEAR);
}
digitalWrite(DIR_ROTATION, HIGH);
while (digitalRead(LIMIT_ROTATION) == HIGH) {
stepPulse(STEP_ROTATION);
}
digitalWrite(DIR_ROTATION, LOW);
for (long i = 0; i < REVERSE_STEPS_ROTATION; i++) {
stepPulse(STEP_ROTATION);
}
enableButton();
Serial.println("HOMING_COMPLETE");
inCutSequence = false;
sequenceStarted = false;
currentCut = 0;
}
// homing for main screen
void handleHomingCommandMain() {
digitalWrite(DIR_LINEAR, LOW);
while (digitalRead(LIMIT_RIGHT) == HIGH) {
stepPulse(STEP_LINEAR);
}
digitalWrite(DIR_LINEAR, HIGH);
for (long i = 0; i < REVERSE_STEPS; i++) {
stepPulse(STEP_LINEAR);
}
digitalWrite(DIR_ROTATION, HIGH);
while (digitalRead(LIMIT_ROTATION) == HIGH) {
stepPulse(STEP_ROTATION);
}
digitalWrite(DIR_ROTATION, LOW);
for (long i = 0; i < REVERSE_STEPS_ROTATION; i++) {
stepPulse(STEP_ROTATION);
}
Serial.println("HOMING_COMPLETE");
inCutSequence = false;
sequenceStarted = false;
currentCut = 0;
}
// clamping and unclamping
void clamping() {
digitalWrite(MOTOR_IN1, HIGH);
digitalWrite(MOTOR_IN2, LOW);
}
void unclamping() {
digitalWrite(MOTOR_IN1, LOW);
digitalWrite(MOTOR_IN2, HIGH);
}
// pulses for stepper motors
void stepPulse(int pin) {
digitalWrite(pin, HIGH);
delayMicroseconds(STEP_DELAY);
digitalWrite(pin, LOW);
delayMicroseconds(STEP_DELAY);
}
// enable and disable START button
void enableButton() {
buttonEnabled = true;
Serial.println("BTN_ENABLED");
}
void disableButton() {
buttonEnabled = false;
Serial.println("BTN_DISABLED");
}
// LEFT limit switch
void checkLimitSwitches() {
if (digitalRead(LIMIT_LEFT) == LOW && !inCutSequence) {
Serial.println("LEFT_LIMIT_TRIGGERED");
}
}
Python (GUI) Code
import tkinter as tk
import serial
import time
# =======================
# SERIAL COMMUNICATION
# =======================
SERIAL_PORT = 'COM4' # Update to match your system
BAUD_RATE = 9600
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(2)
print("Serial connection established.")
except serial.SerialException as e:
print(f"Serial connection error: {e}")
ser = None
# =======================
# GUI SETUP
# =======================
root = tk.Tk()
root.title("Automated Cutting Controller")
root.geometry("900x650")
root.configure(bg="#f0f0f5")
main_frame = tk.Frame(root, bg="#f0f0f5")
running_frame = tk.Frame(root, bg="#f0f0f5")
# =======================
# GLOBAL STATE
# =======================
parts_dict = {
"1032": "1032",
"1033": "111503",
"1034": "16",
"1035": "11"
}
current_part = "1032"
current_part_name = parts_dict.get(current_part, current_part)
cut_buttons = []
status_text = None
gui_initiated_sequence = False # Track if sequence is started by GUI
sequence_running = False # NEW: Track if any sequence is running
current_screen = "main" # Track which screen is currently active
# =======================
# STATUS LOGGING
# =======================
def log_status(message):
print(message)
if status_text:
status_text.insert(tk.END, message + "\n")
status_text.see(tk.END)
# =======================
# MAIN SCREEN
# =======================
def create_main_screen():
label = tk.Label(main_frame, text="Automated Cutting Machine", font=("Helvetica", 20, "bold"), bg="#f0f0f5")
label.pack(pady=30)
for part_id, part_name in parts_dict.items():
btn = tk.Button(
main_frame,
text=f"Part {part_name}",
width=20,
height=2,
font=("Helvetica", 14),
bg="#3498db", fg="white", activebackground="#5dade2",
command=lambda p=part_id: send_part_command(p)
)
btn.pack(pady=10)
main_home_button = tk.Button(main_frame, text="Homing", width=20, height=2,
font=("Helvetica", 14), bg="#7b8a8b", fg="white",
command=send_homing_command_main)
main_home_button.pack(pady=30)
# =======================
# RUNNING SCREEN
# =======================
def create_running_screen():
global cut_buttons, status_text
for widget in running_frame.winfo_children():
widget.destroy()
tk.Label(running_frame, text=f"Running Part {current_part_name}", font=("Helvetica", 18, "bold"),
bg="#f0f0f5").pack(pady=20)
# Clamping Controls
clamp_frame = tk.LabelFrame(running_frame, text="Clamp Controls", font=("Helvetica", 12, "bold"),
bg="#f0f0f5", padx=10, pady=10)
clamp_frame.pack(pady=10)
tk.Button(clamp_frame, text="Clamp", width=15, height=2, font=("Helvetica", 12),
bg="#4682B4", fg="white", activebackground="#5A9BD5", command=send_clamping_command).grid(row=0, column=0, padx=20, pady=10)
tk.Button(clamp_frame, text="Unclamp", width=15, height=2, font=("Helvetica", 12),
bg="#4682B4", fg="white", activebackground="#5A9BD5", command=send_unclamping_command).grid(row=0, column=1, padx=20, pady=10)
# Cutting Controls
cut_frame = tk.LabelFrame(running_frame, text="Cut Controls", font=("Helvetica", 12, "bold"),
bg="#f0f0f5", padx=10, pady=10)
cut_frame.pack(pady=10)
cut_buttons.clear()
for i in range(1, 5):
btn = tk.Button(cut_frame, text=f"Cut {i}", width=12, height=2, font=("Helvetica", 12),
bg="#2ECC71", fg="white", activebackground="#45D97F",
state="disabled", command=lambda n=i: send_cut_command(n))
btn.grid(row=0, column=i-1, padx=15, pady=10)
cut_buttons.append(btn)
# Only enable Cut 1 if no sequence is running
if not sequence_running:
cut_buttons[0].config(state="normal")
# Navigation and homing
nav_frame = tk.Frame(running_frame, bg="#f0f0f5")
nav_frame.pack(pady=20)
tk.Button(nav_frame, text="Homing", width=18, height=2, font=("Helvetica", 14),
bg="#7b8a8b", fg="white", command=send_homing_command).grid(row=0, column=0, padx=20)
tk.Button(nav_frame, text="Back to Main Menu", width=18, height=2, font=("Helvetica", 14),
bg="#f39c12", fg="white", command=on_back_button_click).grid(row=0, column=1, padx=20)
# Status Log
status_frame = tk.LabelFrame(running_frame, text="System Status", font=("Helvetica", 12, "bold"),
bg="#f0f0f5", padx=10, pady=10)
status_frame.pack(fill="both", expand=True, padx=10, pady=10)
status_scrollbar = tk.Scrollbar(status_frame)
status_scrollbar.pack(side="right", fill="y")
text_widget = tk.Text(status_frame, height=8, wrap="word", font=("Helvetica", 11),
yscrollcommand=status_scrollbar.set)
text_widget.pack(fill="both", expand=True)
status_scrollbar.config(command=text_widget.yview)
status_text = text_widget
# =======================
# NAVIGATION FUNCTIONS
# =======================
def show_main_screen():
global current_screen
current_screen = "main"
running_frame.pack_forget()
main_frame.pack(fill="both", expand=True)
if ser and ser.is_open:
ser.write(b"DISABLE_BUTTON\n")
log_status("Physical button disabled (DISABLE_BUTTON sent).")
def show_running_screen():
global current_screen
current_screen = "running"
main_frame.pack_forget()
running_frame.pack(fill="both", expand=True)
if ser and ser.is_open:
ser.write(b"ENABLE_BUTTON\n")
log_status("Physical button enabled (ENABLE_BUTTON sent).")
def on_back_button_click():
show_main_screen()
send_homing_command_main()
log_status("Returned to Main Menu and sent HOMING command.")
# =======================
# COMMAND SENDERS
# =======================
def send_part_command(part_id):
global current_part, current_part_name, gui_initiated_sequence, sequence_running
current_part = part_id
current_part_name = parts_dict.get(part_id, part_id)
gui_initiated_sequence = False # Reset sequence flag
sequence_running = False # Reset sequence running flag
if ser and ser.is_open:
create_running_screen()
show_running_screen()
ser.write(f"PART{part_id}\n".encode())
log_status(f"Sent 'PART{part_id}' command.")
else:
log_status("Serial port not open.")
def send_cut_command(cut_number):
global gui_initiated_sequence, sequence_running
gui_initiated_sequence = True # Mark as GUI-initiated sequence
sequence_running = True # Mark as running
if ser and ser.is_open:
ser.write(b"DISABLE_BUTTON\n") # Disable physical button
log_status("Physical button disabled (DISABLE_BUTTON sent).")
command = f"CUT{cut_number}\n".encode()
ser.write(command)
log_status(f"Sent 'CUT{cut_number}' command.")
for btn in cut_buttons:
btn.config(state="disabled")
else:
log_status("Serial port not open.")
def send_homing_command():
if ser and ser.is_open:
ser.write(b"HOMING\n")
log_status("Sent 'HOMING' command.")
global sequence_running
sequence_running = False
if cut_buttons:
cut_buttons[0].config(state="normal")
for btn in cut_buttons[1:]:
btn.config(state="disabled")
def send_homing_command_main():
if ser and ser.is_open:
ser.write(b"HOMING_MAIN\n")
log_status("Sent 'HOMING' command.")
else:
log_status("Serial port not open.")
def send_clamping_command():
if ser and ser.is_open:
ser.write(b"CLAMPING\n")
log_status("Sent 'CLAMPING' command.")
else:
log_status("Serial port not open.")
def send_unclamping_command():
if ser and ser.is_open:
ser.write(b"UNCLAMPING\n")
log_status("Sent 'UNCLAMPING' command.")
else:
log_status("Serial port not open.")
def disable_button():
if ser and ser.is_open:
ser.write(b"DISABLE_BUTTON\n")
log_status("Sent 'DISABLE BUTTON' command.")
else:
log_status("Serial port not open.")
# =======================
# global status
# =======================
last_left_limit_trigger_time = 0
LEFT_LIMIT_DEBOUNCE_MS = 1000 # 1 second debounce
def main_status():
global gui_initiated_sequence, last_left_limit_trigger_time, sequence_running
if ser and ser.is_open:
try:
line = ser.readline().decode(errors="ignore").strip()
if line:
print("Received from Arduino:", line) # DEBUG: See raw message
if line == "SEQ_STARTED_BY_BUTTON":
log_status("Sequence started by physical button.")
sequence_running = True
gui_initiated_sequence = False
for btn in cut_buttons:
btn.config(state="disabled")
elif line.startswith("CUT_DONE:"):
cut_number = int(line.split(":")[1])
log_status(f"Cut {cut_number} complete.")
if gui_initiated_sequence and cut_number < 4:
for i, btn in enumerate(cut_buttons):
btn.config(state="normal" if i == cut_number else "disabled")
elif cut_number >= 4 or line == "SEQ_END":
for i, btn in enumerate(cut_buttons):
btn.config(state="normal" if i == 0 else "disabled")
if ser and ser.is_open:
ser.write(b"ENABLE_BUTTON\n")
log_status("Physical button enabled (ENABLE_BUTTON sent).")
gui_initiated_sequence = False
sequence_running = False
elif line == "LEFT_LIMIT_TRIGGERED":
current_time = int(time.time() * 1000) # ms
if current_time - last_left_limit_trigger_time > LEFT_LIMIT_DEBOUNCE_MS:
last_left_limit_trigger_time = current_time
log_status("LEFT LIMIT switch triggered.")
if current_screen == "main":
log_status("In main screen — performing homing and disabling button.")
send_homing_command_main()
root.after(0, disable_button)
elif current_screen == "running":
log_status("In running screen — performing homing, resetting cuts, and enabling button.")
send_homing_command()
if not sequence_running:
if cut_buttons:
cut_buttons[0].config(state="normal")
for btn in cut_buttons[1:]:
btn.config(state="disabled")
else:
for btn in cut_buttons:
btn.config(state="disabled")
if ser and ser.is_open:
ser.write(b"ENABLE_BUTTON\n")
log_status("Physical button enabled (ENABLE_BUTTON sent).")
gui_initiated_sequence = False
sequence_running = False
else:
# Ignore repeated trigger within debounce time
pass
else:
log_status(f"Arduino: {line}")
except Exception as e:
log_status(f"Serial read error: {e}")
root.after(100, main_status)
# =======================
# INITIALIZATION
# =======================
create_main_screen()
create_running_screen()
show_main_screen()
if ser and ser.is_open:
send_homing_command_main()
main_status()
root.mainloop()
if ser and ser.is_open:
ser.close()
print("Serial connection closed.")