Parcourir la source

remove speed adjustment, fix rho drift,fix threading issue

Tuan Nguyen il y a 1 an
Parent
commit
d3e6587b8b

+ 2 - 1
.gitignore

@@ -3,4 +3,5 @@ __pycache__/
 *.pyc
 *.pyc
 *.pyo
 *.pyo
 .env
 .env
-.idea
+.idea
+playlists.json

+ 104 - 40
app.py

@@ -2,6 +2,7 @@ from flask import Flask, request, jsonify, render_template
 import os
 import os
 import serial
 import serial
 import time
 import time
+import random
 import threading
 import threading
 import serial.tools.list_ports
 import serial.tools.list_ports
 import math
 import math
@@ -11,6 +12,11 @@ app = Flask(__name__)
 # Configuration
 # Configuration
 THETA_RHO_DIR = './patterns'
 THETA_RHO_DIR = './patterns'
 IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
 IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
+CLEAR_PATTERNS = {
+    "clear_from_in":  "./patterns/clear_from_in.thr",
+    "clear_from_out": "./patterns/clear_from_out.thr",
+    "clear_sideway":  "./patterns/clear_sideway.thr"
+}
 os.makedirs(THETA_RHO_DIR, exist_ok=True)
 os.makedirs(THETA_RHO_DIR, exist_ok=True)
 
 
 # Serial connection (First available will be selected by default)
 # Serial connection (First available will be selected by default)
@@ -67,7 +73,7 @@ def restart_serial(port, baudrate=115200):
 def parse_theta_rho_file(file_path):
 def parse_theta_rho_file(file_path):
     """
     """
     Parse a theta-rho file and return a list of (theta, rho) pairs.
     Parse a theta-rho file and return a list of (theta, rho) pairs.
-    Optionally apply transformations (rotation and mirroring).
+    Normalizes the list so the first theta is always 0.
     """
     """
     coordinates = []
     coordinates = []
     try:
     try:
@@ -84,8 +90,28 @@ def parse_theta_rho_file(file_path):
                     coordinates.append((theta, rho))
                     coordinates.append((theta, rho))
                 except ValueError:
                 except ValueError:
                     print(f"Skipping invalid line: {line}")
                     print(f"Skipping invalid line: {line}")
+                    continue
     except Exception as e:
     except Exception as e:
         print(f"Error reading file: {e}")
         print(f"Error reading file: {e}")
+        return coordinates
+
+    # ---- Normalization Step ----
+    if coordinates:
+        # Take the first coordinate's theta
+        first_theta = coordinates[0][0]
+
+        # Shift all thetas so the first coordinate has theta=0
+        normalized = []
+        for (theta, rho) in coordinates:
+            if rho > 1:
+                rho = 1
+            elif rho < 0:
+                rho = 0
+            normalized.append((theta - first_theta, rho))
+
+        # Replace original list with normalized data
+        coordinates = normalized
+
     return coordinates
     return coordinates
 
 
 def send_coordinate_batch(ser, coordinates):
 def send_coordinate_batch(ser, coordinates):
@@ -144,6 +170,60 @@ def run_theta_rho_file(file_path):
     # Reset theta after execution or stopping
     # Reset theta after execution or stopping
     reset_theta()
     reset_theta()
     ser.write("FINISHED\n".encode())
     ser.write("FINISHED\n".encode())
+    
+def get_clear_pattern_file(pattern_name):
+    """Return a .thr file path based on pattern_name."""
+    if pattern_name == "random":
+        # Randomly pick one of the three known patterns
+        return random.choice(list(CLEAR_PATTERNS.values()))
+    # If pattern_name is invalid or absent, default to 'clear_from_in'
+    return CLEAR_PATTERNS.get(pattern_name, CLEAR_PATTERNS["clear_from_in"])
+
+def run_theta_rho_files(
+    file_paths,
+    pause_time=0,
+    clear_between_files=False,
+    clear_pattern=None
+):
+    """
+    Runs multiple .thr files in sequence, optionally pausing between each pattern,
+    and optionally running a clear pattern between files. 
+    Now supports:
+      - no_clear: skips running a clear pattern
+      - random: chooses any of the 3 clear patterns randomly
+    """
+    global stop_requested
+    stop_requested = False  # reset stop flag at the start
+
+    for idx, path in enumerate(file_paths):
+        if stop_requested:
+            print("Execution stopped before starting next pattern.")
+            break
+
+        # Run the main pattern
+        print(f"Running pattern {idx + 1} of {len(file_paths)}: {path}")
+        run_theta_rho_file(path)
+
+        # If there are more files to run
+        if idx < len(file_paths) - 1:
+            # Pause after each pattern if requested
+            time.sleep(pause_time)
+
+            if stop_requested:
+                print("Execution stopped before running the next pattern or clear.")
+                break
+
+            # Conditionally run a "clear" pattern
+            if clear_between_files:
+                # If user explicitly wants no clear pattern, skip
+                if not clear_pattern:
+                    continue
+                # Otherwise, get the file (could be random or a known pattern)
+                clear_file_path = get_clear_pattern_file(clear_pattern)
+                print(f"Running clear pattern: {clear_file_path}")
+                run_theta_rho_file(clear_file_path)
+
+    print("All requested patterns completed (or stopped).")
 
 
 def reset_theta():
 def reset_theta():
     """Reset theta on the Arduino."""
     """Reset theta on the Arduino."""
@@ -220,10 +300,11 @@ def upload_theta_rho():
         return jsonify({'success': True})
         return jsonify({'success': True})
     return jsonify({'success': False})
     return jsonify({'success': False})
 
 
+
 @app.route('/run_theta_rho', methods=['POST'])
 @app.route('/run_theta_rho', methods=['POST'])
 def run_theta_rho():
 def run_theta_rho():
     file_name = request.json.get('file_name')
     file_name = request.json.get('file_name')
-    pre_execution = request.json.get('pre_execution')  # New parameter for pre-execution action
+    pre_execution = request.json.get('pre_execution')  # 'clear_in', 'clear_out', or 'none'
 
 
     if not file_name:
     if not file_name:
         return jsonify({'error': 'No file name provided'}), 400
         return jsonify({'error': 'No file name provided'}), 400
@@ -233,21 +314,33 @@ def run_theta_rho():
         return jsonify({'error': 'File not found'}), 404
         return jsonify({'error': 'File not found'}), 404
 
 
     try:
     try:
-        # Handle pre-execution actions
+        # Build a list of files to run in sequence
+        files_to_run = []
+        
         if pre_execution == 'clear_in':
         if pre_execution == 'clear_in':
-            clear_in_thread = threading.Thread(target=run_theta_rho_file, args=('./patterns/clear_from_in.thr',))
-            clear_in_thread.start()
-            clear_in_thread.join()  # Wait for completion before proceeding
+            files_to_run.append('./patterns/clear_from_in.thr')
         elif pre_execution == 'clear_out':
         elif pre_execution == 'clear_out':
-            clear_out_thread = threading.Thread(target=run_theta_rho_file, args=('./patterns/clear_from_out.thr',))
-            clear_out_thread.start()
-            clear_out_thread.join()  # Wait for completion before proceeding
+            files_to_run.append('./patterns/clear_from_out.thr')
+        elif pre_execution == 'clear_sideway':
+            files_to_run.append('./patterns/clear_sideway.thr')
         elif pre_execution == 'none':
         elif pre_execution == 'none':
             pass  # No pre-execution action required
             pass  # No pre-execution action required
 
 
-        # Start the main pattern execution
-        threading.Thread(target=run_theta_rho_file, args=(file_path,)).start()
+        # Finally, add the main file
+        files_to_run.append(file_path)
+
+        # Run them in one shot using run_theta_rho_files (blocking call)
+        threading.Thread(
+            target=run_theta_rho_files,
+            args=(files_to_run,),
+            kwargs={
+                'pause_time': 0,
+                'clear_between_files': False,
+                'clear_pattern': None
+            }
+        ).start()
         return jsonify({'success': True})
         return jsonify({'success': True})
+
     except Exception as e:
     except Exception as e:
         return jsonify({'error': str(e)}), 500
         return jsonify({'error': str(e)}), 500
 
 
@@ -377,35 +470,6 @@ def serial_status():
         'port': ser_port  # Include the port name
         'port': ser_port  # Include the port name
     })
     })
 
 
-@app.route('/set_speed', methods=['POST'])
-def set_speed():
-    """Set the speed for the Arduino."""
-    global ser
-    if ser is None or not ser.is_open:
-        return jsonify({"success": False, "error": "Serial connection not established"}), 400
-
-    try:
-        # Parse the speed value from the request
-        data = request.json
-        speed = data.get('speed')
-
-        if speed is None:
-            return jsonify({"success": False, "error": "Speed is required"}), 400
-
-        if not isinstance(speed, (int, float)) or speed <= 0:
-            return jsonify({"success": False, "error": "Invalid speed value"}), 400
-
-        # Send the SET_SPEED command to the Arduino
-        command = f"SET_SPEED {speed}"
-        send_command(command)
-        return jsonify({"success": True, "speed": speed})
-    except Exception as e:
-        return jsonify({"success": False, "error": str(e)}), 500
-
 if __name__ == '__main__':
 if __name__ == '__main__':
-    # Auto-connect to serial
-    connect_to_serial()
-    
-    # Start the Flask app
     app.run(debug=True, host='0.0.0.0', port=8080)
     app.run(debug=True, host='0.0.0.0', port=8080)
 
 

+ 4 - 2
arduino_code/arduino_code.ino

@@ -180,7 +180,7 @@ void appMode()
         String input = Serial.readStringUntil('\n');
         String input = Serial.readStringUntil('\n');
 
 
         // Ignore invalid messages
         // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
+        if (input != "HOME" && input != "RESET_THETA" && !input.endsWith(";"))
         {
         {
             Serial.println("IGNORED");
             Serial.println("IGNORED");
             return;
             return;
@@ -336,7 +336,9 @@ void movePolar(float theta, float rho)
     totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
     totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
 
 
     // Apply the offset to the inout axis
     // Apply the offset to the inout axis
-    inOutSteps += offsetSteps;
+    if (!isFirstCoordinates) {
+        inOutSteps -= offsetSteps;
+    }
 
 
     // Define target positions for both motors
     // Define target positions for both motors
     long targetPositions[2];
     long targetPositions[2];

+ 5 - 3
arduino_code_TMC2209/arduino_code_TMC2209.ino

@@ -181,7 +181,7 @@ void appMode()
         String input = Serial.readStringUntil('\n');
         String input = Serial.readStringUntil('\n');
 
 
         // Ignore invalid messages
         // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
+        if (input != "HOME" && input != "RESET_THETA" && !input.endsWith(";"))
         {
         {
             Serial.println("IGNORED");
             Serial.println("IGNORED");
             return;
             return;
@@ -281,8 +281,8 @@ void appMode()
 
 
                 currentTheta = buffer[0][0];
                 currentTheta = buffer[0][0];
                 totalRevolutions = 0;
                 totalRevolutions = 0;
-                isFirstCoordinates = false; // Reset the flag after the first movement
                 movePolar(buffer[0][0], buffer[0][1]);
                 movePolar(buffer[0][0], buffer[0][1]);
+                isFirstCoordinates = false; // Reset the flag after the first movement
             }
             }
               else
               else
               {
               {
@@ -338,7 +338,9 @@ void movePolar(float theta, float rho)
     totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
     totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
 
 
     // Apply the offset to the inout axis
     // Apply the offset to the inout axis
-    inOutSteps -= offsetSteps;
+    if (!isFirstCoordinates) {
+        inOutSteps -= offsetSteps;
+    }
 
 
     // Define target positions for both motors
     // Define target positions for both motors
     long targetPositions[2];
     long targetPositions[2];

+ 1 - 35
esp32/esp32.ino

@@ -75,41 +75,12 @@ void loop()
         String input = Serial.readStringUntil('\n');
         String input = Serial.readStringUntil('\n');
 
 
         // Ignore invalid messages
         // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
+        if (input != "HOME" && input != "RESET_THETA" && !input.endsWith(";"))
         {
         {
             Serial.println("IGNORED");
             Serial.println("IGNORED");
             return;
             return;
         }
         }
 
 
-
-        if (input.startsWith("SET_SPEED"))
-        {
-            // Parse and set the speed
-            int spaceIndex = input.indexOf(' ');
-            if (spaceIndex != -1)
-            {
-                String speedStr = input.substring(spaceIndex + 1);
-                double speed = speedStr.toDouble();
-
-                if (speed > 0) // Ensure valid speed
-                {
-                    rotStepper.setMaxSpeed(speed);
-                    inOutStepper.setMaxSpeed(speed);
-                    Serial.println("SPEED_SET");
-                    Serial.println("R");
-                }
-                else
-                {
-                    Serial.println("INVALID_SPEED");
-                }
-            }
-            else
-            {
-                Serial.println("INVALID_COMMAND");
-            }
-            return;
-        }
-
         if (input == "HOME")
         if (input == "HOME")
         {
         {
             homing();
             homing();
@@ -219,11 +190,6 @@ void homing()
 
 
 void movePolar(double theta, double rho)
 void movePolar(double theta, double rho)
 {
 {
-    if (rho < 0.0) 
-        rho = 0.0;
-    else if (rho > 1.0) 
-        rho = 1.0;
-
     long rotSteps = lround(theta * (rot_total_steps / (2.0f * M_PI)));
     long rotSteps = lround(theta * (rot_total_steps / (2.0f * M_PI)));
     double revolutions = theta / (2.0 * M_PI);
     double revolutions = theta / (2.0 * M_PI);
     long offsetSteps = lround(revolutions * (rot_total_steps / gearRatio));
     long offsetSteps = lround(revolutions * (rot_total_steps / gearRatio));

+ 0 - 0
patterns/side_wiper.thr → patterns/clear_sideway.thr


+ 20 - 41
templates/index.html

@@ -34,6 +34,7 @@
                     <button onclick="moveToPerimeter()">Move to Perimeter</button>
                     <button onclick="moveToPerimeter()">Move to Perimeter</button>
                     <button onclick="runClearIn()">Clear from In</button>
                     <button onclick="runClearIn()">Clear from In</button>
                     <button onclick="runClearOut()">Clear from Out</button>
                     <button onclick="runClearOut()">Clear from Out</button>
+                    <button onclick="runSideway()">Clear Sideway</button>
                     <button onclick="stopExecution()" class="delete-button">Stop</button>
                     <button onclick="stopExecution()" class="delete-button">Stop</button>
                 </div>
                 </div>
                 <div class="coordinate-input button-group">
                 <div class="coordinate-input button-group">
@@ -43,11 +44,6 @@
                     <input type="number" id="rho_input" placeholder="Rho">
                     <input type="number" id="rho_input" placeholder="Rho">
                     <button class="small-button" onclick="sendCoordinate()">Send to coordinate</button>
                     <button class="small-button" onclick="sendCoordinate()">Send to coordinate</button>
                 </div>
                 </div>
-                <div class="button-group">
-                    <label for="speed_input">Speed:</label>
-                    <input type="number" id="speed_input" placeholder="1-5000" min="1" step="1" max="5000">
-                    <button class="small-button"  onclick="changeSpeed()">Set Speed</button>
-                </div>
             </div>
             </div>
             <div class="section">
             <div class="section">
                 <h2>Preview</h2>
                 <h2>Preview</h2>
@@ -71,6 +67,9 @@
                     <label>
                     <label>
                         <input type="radio" name="pre_execution" value="clear_out" id="clear_out"> Clear from Out
                         <input type="radio" name="pre_execution" value="clear_out" id="clear_out"> Clear from Out
                     </label>
                     </label>
+                    <label>
+                        <input type="radio" name="pre_execution" value="clear_sideway" id="clear_out"> Clear sideway
+                    </label>
                     <label>
                     <label>
                         <input type="radio" name="pre_execution" value="none" id="no_action" checked> None
                         <input type="radio" name="pre_execution" value="none" id="no_action" checked> None
                     </label>
                     </label>
@@ -300,23 +299,6 @@
             }
             }
         }
         }
 
 
-        async function runClearIn() {
-            await runFile('clear_from_in.thr');
-        }
-
-        async function runClearOut() {
-            await runFile('clear_from_out.thr');
-        }
-
-        async function runFile(fileName) {
-            const response = await fetch(`/run_theta_rho_file/${fileName}`, { method: 'POST' });
-            const result = await response.json();
-            if (result.success) {
-                logMessage(`Running file: ${fileName}`);
-            } else {
-                logMessage(`Failed to run file: ${fileName}`);
-            }
-        }
 
 
         let allFiles = []; // Store all files for filtering
         let allFiles = []; // Store all files for filtering
 
 
@@ -528,31 +510,28 @@
             }
             }
         }
         }
 
 
-        async function changeSpeed() {
-            const speedInput = document.getElementById('speed_input');
-            const speed = parseFloat(speedInput.value);
-
-            if (isNaN(speed) || speed <= 0) {
-                logMessage('Invalid speed. Please enter a positive number.');
-                return;
-            }
-
-            logMessage(`Setting speed to: ${speed}...`);
-            const response = await fetch('/set_speed', {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                body: JSON.stringify({ speed })
-            });
-
+        async function runFile(fileName) {
+            const response = await fetch(`/run_theta_rho_file/${fileName}`, { method: 'POST' });
             const result = await response.json();
             const result = await response.json();
             if (result.success) {
             if (result.success) {
-                document.getElementById('speed_status').textContent = `Current Speed: ${speed}`;
-                logMessage(`Speed set to: ${speed}`);
+                logMessage(`Running file: ${fileName}`);
             } else {
             } else {
-                logMessage(`Failed to set speed: ${result.error}`);
+                logMessage(`Failed to run file: ${fileName}`);
             }
             }
         }
         }
 
 
+        async function runClearIn() {
+            await runFile('clear_from_in.thr');
+        }
+
+        async function runClearOut() {
+            await runFile('clear_from_out.thr');
+        }
+
+        async function runSideway() {
+            await runFile('clear_sideway.thr');
+        }
+        
         // Call this function on page load
         // Call this function on page load
         checkSerialStatus();
         checkSerialStatus();