Преглед на файлове

support dlc32 for different hardware

Tuan Nguyen преди 11 месеца
родител
ревизия
9666a8d877

+ 3 - 3
dune_weaver_flask/app.py

@@ -172,7 +172,7 @@ def move_to_center():
 
         logger.info("Moving device to center position")
         pattern_manager.reset_theta()
-        pattern_manager.interpolate_path(0, 0)
+        pattern_manager.move_polar(0, 0)
         return jsonify({"success": True})
     except Exception as e:
         logger.error(f"Failed to move to center: {str(e)}")
@@ -186,7 +186,7 @@ def move_to_perimeter():
             logger.warning("Attempted to move to perimeter without serial connection")
             return jsonify({"success": False, "error": "Serial connection not established"}), 400
         pattern_manager.reset_theta()
-        pattern_manager.interpolate_path(0,1)
+        pattern_manager.move_polar(0,1)
         return jsonify({"success": True})
     except Exception as e:
         logger.error(f"Failed to move to perimeter: {str(e)}")
@@ -227,7 +227,7 @@ def send_coordinate():
             return jsonify({"success": False, "error": "Theta and Rho are required"}), 400
 
         logger.debug(f"Sending coordinate: theta={theta}, rho={rho}")
-        pattern_manager.interpolate_path(theta, rho)
+        pattern_manager.move_polar(theta, rho)
         return jsonify({"success": True})
     except Exception as e:
         logger.error(f"Failed to send coordinate: {str(e)}")

+ 33 - 11
dune_weaver_flask/modules/core/pattern_manager.py

@@ -116,21 +116,43 @@ def wait_for_start_time(schedule_hours):
             time.sleep(30)
             
             
-def interpolate_path(theta, rho):
+def move_polar(theta, rho):
+    """
+    This functions take in a pair of theta rho coordinate, compute the distance to travel based on current theta, rho,
+    and translate the motion to gcode jog command and sent to grbl. 
+    
+    Since having similar steps_per_mm will make x and y axis moves at around the same speed, we have to scale the 
+    x_steps_per_mm and y_steps_per_mm so that they are roughly the same. Here's the range of motion:
+    
+    X axis (angular): 50mm = 1 revolution
+    Y axis (radial): 0 => 20mm = theta 0 (center) => 1 (perimeter)
+    
+    Args:
+        theta (_type_): _description_
+        rho (_type_): _description_
+    """
     # Adding soft limit to reduce hardware sound
-    soft_limit_threshold = 0.01
-    if rho < soft_limit_threshold:
-        rho = soft_limit_threshold
-    elif rho > (1-soft_limit_threshold):
-        rho = (1-soft_limit_threshold)
+    soft_limit_inner = 0.01
+    if rho < soft_limit_inner:
+        rho = soft_limit_inner
+    
+    soft_limit_outter = 0.015
+    if rho > (1-soft_limit_outter):
+        rho = (1-soft_limit_outter)
+    
+    x_scaling_factor = 2
+    y_scaling_factor = 5
     
     delta_theta = theta - state.current_theta
     delta_rho = rho - state.current_rho
-    # x_increment = delta_theta / (2 * pi) * 100
-    x_increment = delta_theta / (4 * pi) * 100
-    y_increment = delta_rho * 100/5
+    x_increment = delta_theta * 100 / (2 * pi * x_scaling_factor) # Scale down x from 100mm to 50mm per revolution
+    y_increment = delta_rho * 100 / y_scaling_factor # Scale down y from 100mm to 20mm from center to perimeter
     
-    offset = x_increment * (3200/5750/5) # Total angular steps = 16000 / gear ratio = 10 / angular steps = 5750
+    x_total_steps = state.x_steps_per_mm * (100/x_scaling_factor)
+    y_total_steps = state.y_steps_per_mm * (100/y_scaling_factor)
+        
+    x_increment / 50 * (x_total_steps)
+    offset = x_increment * (x_total_steps * x_scaling_factor / (state.gear_ratio * y_total_steps * y_scaling_factor))
     y_increment += offset
     
     new_x_abs = state.machine_x + x_increment
@@ -190,7 +212,7 @@ def run_theta_rho_file(file_path, schedule_hours=None):
                         state.pause_condition.wait()
 
                 schedule_checker(schedule_hours)
-                interpolate_path(theta, rho)
+                move_polar(theta, rho)
                 
                 if i != 0:
                     pbar.update(1)

+ 26 - 4
dune_weaver_flask/modules/core/state.py

@@ -19,9 +19,11 @@ class AppState:
         # Machine position variables
         self.machine_x = 0.0
         self.machine_y = 0.0
-        self.STATE_FILE = "state.json"
+        self.x_steps_per_mm = 0.0
+        self.y_steps_per_mm = 0.0
+        self.gear_ratio = 10
 
-        
+        self.STATE_FILE = "state.json"
         self.load()
 
     def to_dict(self):
@@ -37,6 +39,9 @@ class AppState:
             "speed": self.speed,
             "machine_x": self.machine_x,
             "machine_y": self.machine_y,
+            "x_steps_per_mm": self.x_steps_per_mm,
+            "y_steps_per_mm": self.y_steps_per_mm,
+            "gear_ratio": self.gear_ratio
         }
 
     def from_dict(self, data):
@@ -51,11 +56,17 @@ class AppState:
         self.speed = data.get("speed", 250)
         self.machine_x = data.get("machine_x", 0.0)
         self.machine_y = data.get("machine_y", 0.0)
+        self.x_steps_per_mm = data.get("x_steps_per_mm", 0.0)
+        self.y_steps_per_mm = data.get("y_steps_per_mm", 0.0)
+        self.gear_ratio = data.get('gear_ratio', 10)
 
     def save(self):
         """Save the current state to a JSON file."""
-        with open(self.STATE_FILE, "w") as f:
-            json.dump(self.to_dict(), f)
+        try:
+            with open(self.STATE_FILE, "w") as f:
+                json.dump(self.to_dict(), f)
+        except Exception as e:
+            print(f"Error saving state to {self.STATE_FILE}: {e}")
 
     def load(self):
         """Load state from a JSON file. If the file doesn't exist, create it with default values."""
@@ -70,5 +81,16 @@ class AppState:
         except Exception as e:
             print(f"Error loading state from {self.STATE_FILE}: {e}")
 
+    def update_steps_per_mm(self, x_steps, y_steps):
+        """Update and save steps per mm values."""
+        self.x_steps_per_mm = x_steps
+        self.y_steps_per_mm = y_steps
+        self.save()
+
+    def reset_state(self):
+        """Reset all state variables to their default values."""
+        self.__init__()  # Reinitialize the state
+        self.save()
+
 # Create a singleton instance that you can import elsewhere:
 state = AppState()

+ 56 - 16
dune_weaver_flask/modules/serial/serial_manager.py

@@ -26,6 +26,42 @@ def list_serial_ports():
     logger.debug(f"Available serial ports: {available_ports}")
     return available_ports
 
+def get_machine_steps():
+    """
+    Send "$$" to the serial port to retrieve machine settings and return values for $100 and $101.
+    Returns two floats: x_steps_per_mm and y_steps_per_mm, or (None, None) if not found.
+    """
+    if not is_connected():
+        logger.error("Serial connection is not established.")
+        return None, None
+
+    while True:
+        with serial_lock:
+            ser.write("$$\n".encode())
+            ser.flush()
+            time.sleep(1)  # Allow time for response
+
+            x_steps_per_mm = None
+            y_steps_per_mm = None
+            gear_ratio = None
+
+            while ser.in_waiting > 0:
+                line = ser.readline().decode().strip()
+                logger.debug(f"Config response: {line}")
+
+                if line.startswith("$100="):
+                    x_steps_per_mm = float(line.split("=")[1])
+                    state.x_steps_per_mm = x_steps_per_mm
+                elif line.startswith("$101="):
+                    y_steps_per_mm = float(line.split("=")[1])
+                    state.y_steps_per_mm = y_steps_per_mm
+                elif line.startswith("$131="):
+                    gear_ratio = float(line.split("=")[1])
+                    state.gear_ratio = gear_ratio
+
+                if x_steps_per_mm is not None and y_steps_per_mm is not None and gear_ratio is not None:
+                    return True
+        
 
 def connect_to_serial(port=None, baudrate=115200):
     """Automatically connect to the first available serial port or a specified port."""
@@ -43,6 +79,14 @@ def connect_to_serial(port=None, baudrate=115200):
                 ser.close()
             ser = serial.Serial(port, baudrate, timeout=2)
             ser_port = port
+
+        try:
+            if get_machine_steps():
+                logger.info(f"x_steps_per_mm: {state.x_steps_per_mm}, y_steps_per_mm: {state.y_steps_per_mm}, gear_ratio: {state.gear_ratio}")
+        except:
+            logger.fatal("Not GRBL firmware")
+            pass 
+            
         machine_x, machine_y = get_machine_position()
         if not machine_x or not machine_y or machine_x != state.machine_x or machine_y != state.machine_y:
             logger.info(f'x, y; {machine_x}, {machine_y}')
@@ -50,24 +94,21 @@ def connect_to_serial(port=None, baudrate=115200):
             home()
         else:
             logger.info('Machine position known, skipping home')
+            logger.info(f'Theta: {state.current_theta}, rho: {state.current_rho}')
+            logger.info(f'State x, y; {state.machine_x}, {state.machine_y}')
+            
         
         logger.info(f"Connected to serial port: {port}")
         time.sleep(2)  # Allow time for the connection to establish
 
-        # Read initial startup messages from Arduino
-        while ser.in_waiting > 0:
-            line = ser.readline().decode().strip()
-            logger.debug(f"Arduino: {line}")
-            if "Table:" in line:
-                arduino_table_name = line.replace("Table: ", "").strip()
-            elif "Drivers:" in line:
-                arduino_driver_type = line.replace("Drivers: ", "").strip()
-            elif "Version:" in line:
-                firmware_version = line.replace("Version: ", "").strip()
-
-        logger.info(f"Detected Table: {arduino_table_name or 'Unknown'}")
-        logger.info(f"Detected Drivers: {arduino_driver_type or 'Unknown'}")
-        return True
+        try:
+            if get_machine_steps(): 
+                logger.info(f"x_steps_per_mm: {state.x_steps_per_mm}, x_steps_per_mm: {state.y_steps_per_mm}, gear_ratio: {state.gear_ratio}")
+                return True
+        except:
+            logger.info("Not GRBL firmware")
+            return False
+        
     except serial.SerialException as e:
         logger.error(f"Failed to connect to serial port {port}: {e}")
         ser_port = None
@@ -115,7 +156,7 @@ def get_status_response():
             while ser.in_waiting > 0:
                 response = ser.readline().decode().strip()
                 if "MPos" in response:
-                    logger.info(f"Status response: {response}")
+                    logger.debug(f"Status response: {response}")
                     return response
         time.sleep(1)
 
@@ -229,7 +270,6 @@ def home(retry = 0):
     state.current_theta = state.current_rho = 0
     update_machine_position()
 
-
 def check_idle():
     """
     Continuously check if the machine is in the 'Idle' state.

+ 0 - 416
firmware/arduino_code/arduino_code.ino

@@ -1,416 +0,0 @@
-#include <AccelStepper.h>
-#include <MultiStepper.h>
-#include <math.h> // For M_PI and mathematical operations
-
-#define rotInterfaceType AccelStepper::DRIVER
-#define inOutInterfaceType AccelStepper::DRIVER
-
-#define stepPin_rot 2
-#define dirPin_rot 5
-#define stepPin_InOut 3
-#define dirPin_InOut 6
-
-#define rot_total_steps 16000.0
-#define inOut_total_steps 5760.0
-#define gearRatio 10
-
-#define BUFFER_SIZE 10 // Maximum number of theta-rho pairs in a batch
-
-#define buttonPin 11 // Z- signal pin on the CNC shield
-#define pot1 A1      // Potentiometer 1, Abort pin on the CNC shield
-#define pot2 A0      // Potentiometer 2, Hold pint on the CNC shield
-
-#define MODE_APP 0
-#define MODE_SPIROGRAPH 1
-
-// Create stepper motor objects
-AccelStepper rotStepper(rotInterfaceType, stepPin_rot, dirPin_rot);
-AccelStepper inOutStepper(inOutInterfaceType, stepPin_InOut, dirPin_InOut);
-
-// Create a MultiStepper object
-MultiStepper multiStepper;
-
-// Buffer for storing theta-rho pairs
-float buffer[BUFFER_SIZE][2]; // Store theta, rho pairs
-int bufferCount = 0;          // Number of pairs in the buffer
-bool batchComplete = false;
-
-// Track the current position in polar coordinates
-float currentTheta = 0.0; // Current theta in radians
-float currentRho = 0.0;   // Current rho (0 to 1)
-bool isFirstCoordinates = true;
-float totalRevolutions = 0.0; // Tracks cumulative revolutions
-float maxSpeed = 5000;
-float maxAcceleration = 5000;
-long interpolationResolution = 0.001;
-float userDefinedSpeed = maxSpeed; // Store user-defined speed
-
-// Running Mode
-int currentMode = MODE_APP; // Default mode is app mode.
-
-// FIRMWARE VERSION
-const char* firmwareVersion = "1.4.0";
-const char* motorType = "DRV8825";
-
-void setup()
-{
-    // Set maximum speed and acceleration
-    rotStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    rotStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    inOutStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    inOutStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    // Add steppers to MultiStepper
-    multiStepper.addStepper(rotStepper);
-    multiStepper.addStepper(inOutStepper);
-
-    // Configure the buttons and potentiometers for Spirograph mode
-    pinMode(buttonPin, INPUT_PULLUP); // Configure button pin with internal pull-up
-    pinMode(A0, INPUT); // Potentiometer 1 input
-    pinMode(A1, INPUT); // Potentiometer 2 input
-
-    // Initialize serial communication
-    Serial.begin(115200);
-    Serial.println("R");
-    homing();
-}
-
-void getVersion()
-{
-    Serial.println("Table: Dune Weaver");
-    Serial.println("Drivers: DRV8825");
-    Serial.println("Version: 1.4.0");
-}
-
-void resetTheta()
-{
-    isFirstCoordinates = true; // Set flag to skip interpolation for the next movement
-    Serial.println("THETA_RESET"); // Notify Python
-}
-
-void loop() {
-    updateModeSwitch(); // Check and handle mode switching
-
-    // Call the appropriate mode function based on the current mode
-    if (currentMode == MODE_SPIROGRAPH) {
-        spirographMode();
-    } else if (currentMode == MODE_APP) {
-        appMode();
-    }
-}
-
-void updateModeSwitch() {
-    // Read the current state of the latching switch
-    bool currentSwitchState = digitalRead(buttonPin);
-    int newMode = currentSwitchState == LOW ? MODE_SPIROGRAPH : MODE_APP;
-
-    if (newMode != currentMode) {
-        handleModeChange(newMode); // Handle mode-specific transitions
-        currentMode = newMode; // Update the current mode
-    }
-}
-
-void handleModeChange(int newMode) {
-    // Print mode switch information
-    if (newMode == MODE_SPIROGRAPH) {
-        Serial.println("Spirograph Mode Active");
-        rotStepper.setMaxSpeed(userDefinedSpeed * 0.5); // Use 50% of user-defined speed
-        inOutStepper.setMaxSpeed(userDefinedSpeed * 0.5);
-        isFirstCoordinates = false;
-    } else if (newMode == MODE_APP) {
-        Serial.println("App Mode Active");
-        rotStepper.setMaxSpeed(userDefinedSpeed); // Restore user-defined speed
-        inOutStepper.setMaxSpeed(userDefinedSpeed);
-        resetTheta();
-    }
-
-    movePolar(currentTheta, 0); // Move to the center
-}
-
-void spirographMode() {
-    static float currentFrequency = 2.95; // Track the current frequency (default value)
-    static float phaseShift = 0.0;       // Track the phase shift for smooth transitions
-
-    // Read potentiometer for frequency adjustment
-    int pot1Value = analogRead(pot1);
-    float newFrequency = mapFloat(pot1Value, 0, 1023, 0.5, 6); // Map to range
-    newFrequency = round(newFrequency * 10) / 10.0;            // Round to one decimal place
-
-    // Force the value to x.95 or x.10 to have a slight variation each revolution
-    if (fmod(newFrequency, 1.0) >= 0.5) {
-        newFrequency = floor(newFrequency) + 0.95; // Round up to x.95
-    } else {
-        newFrequency = floor(newFrequency) + 0.10; // Round down to x.10
-    }
-
-    // Adjust phase shift if frequency changes
-    if (newFrequency != currentFrequency) {
-        phaseShift += currentTheta * (currentFrequency - newFrequency);
-        currentFrequency = newFrequency; // Update the current frequency
-    }
-
-    // Read variation knob to adjust the minimum rho
-    int pot2Value = analogRead(pot2);
-    float minRho = round(mapFloat(pot2Value, 0, 1023, 0, 0.5) * 20) / 20.0; // Minimum rho in steps of 0.05
-
-    // Calculate amplitude and offset for the sine wave
-    float amplitude = (1.0 - minRho) / 2.0; // Half of the oscillation range
-    float offset = minRho + amplitude;      // Center the wave within the range [minRho, 1]
-
-    // Calculate the next target theta
-    float stepSize = maxSpeed * (2 * M_PI / rot_total_steps) / 10; // Smaller steps for finer control
-    float nextTheta = currentTheta + stepSize;
-
-    // Count total revolutions
-    totalRevolutions = (nextTheta / (2 * M_PI));
-
-    // Calculate rho using the adjusted sine wave with phase shift
-    currentRho = offset + amplitude * cos((currentTheta * currentFrequency) + phaseShift);
-    float nextRho = offset + amplitude * cos((nextTheta * currentFrequency) + phaseShift);
-
-    // Move the steppers to the calculated position
-    movePolar(nextTheta, constrain(nextRho, 0, 1));
-
-    // Update the current theta to the new position
-    currentTheta = nextTheta;
-}
-
-float mapFloat(long x, long inMin, long inMax, float outMin, float outMax) {
-    if (inMax == inMin) {
-        Serial.println("Error: mapFloat division by zero");
-        return outMin; // Return the minimum output value as a fallback
-    }
-    return (float)(x - inMin) * (outMax - outMin) / (float)(inMax - inMin) + outMin;
-}
-
-void appMode()
-{
-    // Check for incoming serial commands or theta-rho pairs
-    if (Serial.available() > 0)
-    {
-        String input = Serial.readStringUntil('\n');
-
-        // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA" && input != "GET_VERSION" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
-        {
-            Serial.print("IGNORED: ");
-            Serial.println(input);
-            return;
-        }
-
-        if (input == "GET_VERSION")
-        {
-            getVersion();
-        }
-
-        if (input == "RESET_THETA")
-        {
-            resetTheta(); // Reset currentTheta
-            Serial.println("THETA_RESET"); // Notify Python
-            Serial.println("READY");
-            return;
-        }
-        if (input == "HOME")
-        {
-            homing();
-            return;
-        }
-
-        // Example: The user calls "SET_SPEED 60" => 60% of maxSpeed
-        if (input.startsWith("SET_SPEED"))
-        {
-            // Parse out the speed value from the command string
-            int spaceIndex = input.indexOf(' ');
-            if (spaceIndex != -1)
-            {
-                String speedStr = input.substring(spaceIndex + 1);
-                float speedPercentage = speedStr.toFloat();
-
-                // Make sure the percentage is valid
-                if (speedPercentage >= 1.0 && speedPercentage <= 100.0)
-                {
-                    // Convert percentage to actual speed
-                    long newSpeed = (speedPercentage / 100.0) * maxSpeed;
-                    userDefinedSpeed = newSpeed;
-
-                    // Set the stepper speeds
-                    rotStepper.setMaxSpeed(newSpeed);
-                    inOutStepper.setMaxSpeed(newSpeed);
-
-                    Serial.println("SPEED_SET");  
-                }
-                else
-                {
-                    Serial.println("INVALID_SPEED");
-                }
-            }
-            else
-            {
-                Serial.println("INVALID_COMMAND");
-            }
-            return;
-        }
-
-        // If not a command, assume it's a batch of theta-rho pairs
-        if (!batchComplete)
-        {
-            int pairIndex = 0;
-            int startIdx = 0;
-
-            // Split the batch line into individual theta-rho pairs
-            while (pairIndex < BUFFER_SIZE)
-            {
-                int endIdx = input.indexOf(";", startIdx);
-                if (endIdx == -1)
-                    break; // No more pairs in the line
-
-                String pair = input.substring(startIdx, endIdx);
-                int commaIndex = pair.indexOf(',');
-
-                // Parse theta and rho values
-                float theta = pair.substring(0, commaIndex).toFloat(); // Theta in radians
-                float rho = pair.substring(commaIndex + 1).toFloat();  // Rho (0 to 1)
-
-                buffer[pairIndex][0] = theta;
-                buffer[pairIndex][1] = rho;
-                pairIndex++;
-
-                startIdx = endIdx + 1; // Move to next pair
-            }
-            bufferCount = pairIndex;
-            batchComplete = true;
-        }
-    }
-
-    // Process the buffer if a batch is ready
-    if (batchComplete && bufferCount > 0)
-    {
-        // Start interpolation from the current position
-        float startTheta = currentTheta;
-        float startRho = currentRho;
-
-        for (int i = 0; i < bufferCount; i++)
-        {
-            if (isFirstCoordinates)
-            {
-                // Directly move to the first coordinate of the new pattern
-                long initialRotSteps = buffer[0][0] * (rot_total_steps / (2.0 * M_PI));
-                rotStepper.setCurrentPosition(initialRotSteps);
-                inOutStepper.setCurrentPosition(inOutStepper.currentPosition() - totalRevolutions * rot_total_steps / gearRatio);
-                currentTheta = buffer[0][0];
-                totalRevolutions = 0;
-                isFirstCoordinates = false; // Reset the flag after the first movement
-                movePolar(buffer[0][0], buffer[0][1]);
-            }
-              else
-              {
-                // Use interpolation for subsequent movements
-                interpolatePath(
-                    startTheta, startRho,
-                    buffer[i][0], buffer[i][1],
-                    interpolationResolution
-                );
-              }
-            // Update the starting point for the next segment
-            startTheta = buffer[i][0];
-            startRho = buffer[i][1];
-        }
-
-        batchComplete = false; // Reset batch flag
-        bufferCount = 0;       // Clear buffer
-        Serial.println("R");
-    }
-}
-
-void homing()
-{
-    Serial.println("HOMING");
-
-    // Move inOutStepper inward for homing
-    inOutStepper.setSpeed(-maxSpeed); // Adjust speed for homing
-    while (true)
-    {
-        inOutStepper.runSpeed();
-        if (inOutStepper.currentPosition() <= -inOut_total_steps * 1.1)
-        { // Adjust distance for homing
-            break;
-        }
-    }
-    inOutStepper.setCurrentPosition(0); // Set home position
-    currentTheta = 0.0;                 // Reset polar coordinates
-    currentRho = 0.0;
-    Serial.println("HOMED");
-}
-
-void movePolar(float theta, float rho)
-{
-    // Define different scaling factors for rho <= 0.5 and rho > 0.5
-    float speedScalingFactorLow = 0.7;  // Scaling factor for rho <= 0.5
-    float speedScalingFactorHigh = 0.4; // Scaling factor for rho > 0.5
-
-    float speedScalingFactor;
-
-    // Determine which factor to use
-    if (rho <= 0.5) {
-        speedScalingFactor = speedScalingFactorLow;
-    } else {
-        speedScalingFactor = speedScalingFactorHigh;
-    }
-
-    // Scale speed dynamically based on rho
-    long dynamicSpeed = maxSpeed * (1.0 + speedScalingFactor * (1.0 - 2.0 * rho));
-
-    // Ensure the speed is within a valid range
-    dynamicSpeed = constrain(dynamicSpeed, 1, maxSpeed);
-
-    // Set stepper speeds dynamically
-    rotStepper.setMaxSpeed(dynamicSpeed);
-    inOutStepper.setMaxSpeed(dynamicSpeed);
-
-    // Convert polar coordinates to motor steps
-    long rotSteps = theta * (rot_total_steps / (2.0 * M_PI)); // Steps for rot axis
-    long inOutSteps = rho * inOut_total_steps;                // Steps for in-out axis
-
-    // Calculate offset for inOut axis
-    float revolutions = theta / (2.0 * M_PI); // Fractional revolutions (can be positive or negative)
-    long offsetSteps = revolutions * rot_total_steps / gearRatio;    // 1600 steps inward or outward per revolution
-
-    // Update the total revolutions to keep track of the offset history
-    totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
-
-    // Apply the offset to the inout axis
-    if (!isFirstCoordinates) {
-        inOutSteps -= offsetSteps;
-    }
-
-    // Define target positions for both motors
-    long targetPositions[2];
-    targetPositions[0] = rotSteps;
-    targetPositions[1] = inOutSteps;
-
-    // Move both motors synchronously
-    multiStepper.moveTo(targetPositions);
-    multiStepper.runSpeedToPosition(); // Blocking call
-
-    // Update the current coordinates
-    currentTheta = theta;
-    currentRho = rho;
-}
-
-void interpolatePath(float startTheta, float startRho, float endTheta, float endRho, float stepSize)
-{
-    // Calculate the total distance in the polar coordinate system
-    float distance = sqrt(pow(endTheta - startTheta, 2) + pow(endRho - startRho, 2));
-    int numSteps = max(1, (int)(distance / stepSize)); // Ensure at least one step
-
-    for (int step = 0; step <= numSteps; step++)
-    {
-        float t = (float)step / numSteps; // Interpolation factor (0 to 1)
-        float interpolatedTheta = startTheta + t * (endTheta - startTheta);
-        float interpolatedRho = startRho + t * (endRho - startRho);
-
-        // Move to the interpolated theta-rho
-        movePolar(interpolatedTheta, interpolatedRho);
-    }
-}

+ 0 - 894
firmware/arduino_code/arduino_code.ino.hex

@@ -1,894 +0,0 @@
-:100000000C948F000C94B7000C94B7000C94B700BC
-:100010000C94B7000C94B7000C94B7000C94B70084
-:100020000C94B7000C94B7000C94B7000C94B70074
-:100030000C94B7000C94B7000C94B7000C94B70064
-:100040000C94160B0C94B7000C94860B0C94600B5C
-:100050000C94B7000C94B7000C94B7000C94B70044
-:100060000C94B7000C94B70005A84CCDB2D44EB98F
-:100070003836A9020C50B9918688083CA6AAAA2A4B
-:10008000BE000000803F4E414E494E495459494EF2
-:1000900046CDCCCC3D0AD7233C17B7D13877CC2BF3
-:1000A000329595E6241FB14F0A000020410000C898
-:1000B0004200401C4620BCBE4CCA1B0E5AAEC59D19
-:1000C0007400000000230026002900000000002426
-:1000D0000027002A0000000000250028002B000453
-:1000E00004040404040404020202020202030303DF
-:1000F00003030301020408102040800102040810D9
-:100100002001020408102000000008000201000085
-:10011000030407000000000000000000B80B1124D9
-:100120001FBECFEFD8E0DEBFCDBF23E0A6E1B2E037
-:1001300001C01D92A83CB207E1F712E0A0E0B1E0D7
-:10014000E2EAF6E302C005900D92A631B107D9F7B5
-:1001500010E0CFE8D0E004C02197FE010E94F9181A
-:10016000CE38D107C9F70E94CA0C0C944F1B0C94CF
-:100170000000833081F028F4813099F08230A9F0BA
-:1001800008958730A9F08830C9F08430B1F48091A7
-:1001900080008F7D03C0809180008F7780938000E6
-:1001A000089584B58F7784BD089584B58F7DFBCF86
-:1001B0008091B0008F778093B00008958091B00057
-:1001C0008F7DF9CF1F93CF93DF93282F30E0F90174
-:1001D000E95FFE4F8491F901ED50FF4FD491F90191
-:1001E000E152FF4FC491CC23A9F0162F81110E9438
-:1001F000B900EC2FF0E0EE0FFF1FEB52FF4FA5917F
-:10020000B4918FB7F894EC91111108C0D095DE230A
-:10021000DC938FBFDF91CF911F910895DE2BF8CF34
-:10022000CF93DF9390E0FC01ED50FF4F249181527A
-:100230009F4FFC0184918823C9F090E0880F991F9B
-:10024000FC01E553FF4FA591B491FC01EB52FF4F28
-:10025000C591D49161110DC09FB7F8948C912095F0
-:1002600082238C938881282328839FBFDF91CF919D
-:100270000895623051F49FB7F8943C91822F809595
-:1002800083238C93E8812E2BEFCF8FB7F894EC91DA
-:100290002E2B2C938FBFEACF8E50806480937C00EE
-:1002A00080917A00806480937A0080917A0086FD44
-:1002B000FCCF80917800909179000895AF92BF9221
-:1002C000CF92DF92EF92FF920F931F93CF93DF9322
-:1002D0006C017B018B01040F151FEB015E01AE1851
-:1002E000BF08C017D10759F06991D601ED91FC9173
-:1002F0000190F081E02DC6010995892B79F7C501A0
-:10030000DF91CF911F910F91FF90EF90DF90CF90F1
-:10031000BF90AF900895FC01538D448D252F30E0A0
-:10032000842F90E0821B930B541710F0CF96089502
-:1003300001970895FC01918D828D981761F0A28D2F
-:10034000AE0FBF2FB11D5D968C91928D9F5F9F73F5
-:10035000928F90E008958FEF9FEF08952FB7F89454
-:100360008091800290918102A0918202B0918302DB
-:100370002FBF80938C0290938D02A0938E02B09336
-:100380008F0284E892E00E949A0197FF26C02FB75F
-:10039000F8948091800290918102A0918202B091A4
-:1003A00083022FBF40918C0250918D0260918E028A
-:1003B00070918F02841B950BA60BB70B409188029E
-:1003C0005091890260918A0270918B02841795077F
-:1003D000A607B707B0F28FEF9FEF0895FC01918D4C
-:1003E000828D981731F0828DE80FF11D858D90E098
-:1003F00008958FEF9FEF0895FC01918D228D892F35
-:1004000090E0805C9F4F821B91098F73992708951C
-:1004100084E892E00E94FC0121E0892B09F420E0AD
-:10042000822F089580E090E0892B29F00E94080235
-:1004300081110C9400000895FC01A48DA80FB92F20
-:10044000B11DA35ABF4F2C91848D90E001968F73FC
-:100450009927848FA689B7892C93A089B1898C911B
-:10046000837080648C93938D848D981306C002886A
-:10047000F389E02D80818F7D80830895EF92FF9234
-:100480000F931F93CF93DF93EC0181E0888F9B8DB7
-:100490008C8D98131AC0E889F989808185FF15C071
-:1004A0009FB7F894EE89FF896083E889F989808194
-:1004B0008370806480839FBF81E090E0DF91CF9163
-:1004C0001F910F91FF90EF900895F62E0B8D10E085
-:1004D0000F5F1F4F0F731127E02E8C8D8E110CC0F4
-:1004E0000FB607FCFACFE889F989808185FFF5CF3F
-:1004F000CE010E941C02F1CFEB8DEC0FFD2FF11D00
-:10050000E35AFF4FF0829FB7F8940B8FEA89FB897B
-:1005100080818062CFCFCF93DF93EC01888D8823D9
-:10052000B9F0AA89BB89E889F9898C9185FD03C056
-:10053000808186FD0DC00FB607FCF7CF8C9185FF3B
-:10054000F2CF808185FFEDCFCE010E941C02E9CF62
-:10055000DF91CF9108954F925F926F927F92AF9209
-:10056000BF92CF92DF92EF92FF920F931F93CF93A0
-:10057000DF93EC015A018B014C8C5D8C6E8C7F8C6F
-:1005800073016201F7FAF094F7F8F0949A01AB0165
-:10059000C701B6010E942918181664F09501A80138
-:1005A000C301B2010E94AC147301620187FD02C055
-:1005B0006501780120E030E0A901C701B6010E9481
-:1005C000AC14811117C01B821C821D821E82C88E32
-:1005D000D98EEA8EFB8EDF91CF911F910F91FF9004
-:1005E000EF90DF90CF90BF90AF907F906F905F9033
-:1005F0004F900895A701960160E074E284E799E4C2
-:100600000E9496169F770E940F176B837C838D83C1
-:100610009E8311E020E030E0A901C701B6010E94ED
-:10062000291818160CF010E01A83D1CFCF92DF9260
-:10063000EF92FF92CF93DF93EC013FB7F894809154
-:100640007B0290917C02A0917D02B0917E0226B542
-:10065000A89B05C02F3F19F00196A11DB11D3FBFFA
-:10066000BA2FA92F982F88276C017D01C20ED11CAB
-:10067000E11CF11C42E0CC0CDD1CEE1CFF1C4A9579
-:10068000D1F788A599A5AAA5BBA5B701A601481BC6
-:10069000590B6A0B7B0B8B819C81AD81BE81481706
-:1006A00059076A077B0748F188899989AA89BB8914
-:1006B0002A812223F1F00196A11DB11D888B998B0F
-:1006C000AA8BBB8B488959896A897B89E881F98122
-:1006D0000484F585E02DCE010995C8A6D9A6EAA621
-:1006E000FBA681E0DF91CF91FF90EF90DF90CF905C
-:1006F00008950197A109B109E1CF80E0F3CFCF932D
-:10070000DF93FC012781222359F1EC0161E0808510
-:100710000E94100161E089850E9410018F81843060
-:1007200011F08830B1F461E08A850E94100161E027
-:100730008B850E9410018FA58F3F91F061E00E9490
-:1007400010016EA581E068278FA5DF91CF910C94F1
-:10075000E200833011F0863071F761E08A85E9CFDD
-:10076000DF91CF910895CF93DF93FC01278122235E
-:10077000A9F0EC010190F081E02D0284F385E02DD9
-:1007800060E009958FA58F3F49F061E00E9410015C
-:100790006EA58FA5DF91CF910C94E200DF91CF91F0
-:1007A0000895DC01ED91FC91228533854770552732
-:1007B0006627772741505109610971094730510572
-:1007C0006105710560F4FA01E851FC4F0C94F918C9
-:1007D000F203F403F603F803FA03FC03FE0361E0FB
-:1007E000F901099465E0FCCF64E0FACF66E0F8CF48
-:1007F00062E0F6CF6AE0F4CF68E0F2CF69E0F0CFD4
-:10080000CF93DF93EC01CB01BA0126E030E040E06A
-:1008100050E00E94DA18623071058105910589F176
-:100820006CF46115710581059105D1F06130710598
-:1008300081059105F9F0DF91CF910895643071053C
-:100840008105910561F124F16530710581059105FE
-:1008500091F7E881F9810284F385E02D66E006C016
-:10086000E881F9810284F385E02D64E0CE01DF9117
-:10087000CF910994E881F9810284F385E02D65E048
-:10088000F5CFE881F9810284F385E02D61E0EECFB8
-:10089000E881F9810284F385E02D63E0E7CFE88108
-:1008A000F9810284F385E02D62E0E0CFDC01ED9177
-:1008B000FC910284F385E02D437055276627772746
-:1008C000423051056105710571F0433051056105F4
-:1008D000710559F0413051056105710511F065E070
-:1008E000099466E0FDCF6AE0FBCF69E0F9CFCF93D2
-:1008F000DF93EC01CB01BA0123E030E040E050E0AF
-:100900000E94DA18613071058105910599F0623015
-:10091000710581059105A9F0672B682B692BC1F43E
-:10092000E881F9810284F385E02D64E0CE01DF9156
-:10093000CF910994E881F9810284F385E02D61E08B
-:10094000F5CFE881F9810284F385E02D62E0EECFF6
-:10095000DF91CF910895DC01ED91FC910284F38544
-:10096000E02D4370552766277727423051056105F2
-:10097000710571F0433051056105710559F0413041
-:1009800051056105710511F062E0099463E0FDCF46
-:1009900061E0FBCF60E0F9CFCF93DF93EC01E8811A
-:1009A000F9810284F385E02D8A8160E0811162E0A3
-:1009B000CE010995E881F9810284F385E02D8A81D1
-:1009C00061E0811163E0CE0109958CA59DA582307F
-:1009D000910538F0880F991F880F991F0597019787
-:1009E000F1F7E881F9810284F385E02D8A8160E0E6
-:1009F000811162E0CE01DF91CF910994CF93DF9313
-:100A0000EC0120E030E0A901688D798D8A8D9B8D05
-:100A10000E94291818162CF4E8A9F9A9DF91CF91A2
-:100A20000994EAA9FBA9FACFCF92DF92EF92FF9245
-:100A30000F931F93CF93DF93DC011796CC91C430B3
-:100A400039F0C83059F1C33019F0C63049F1C2E06D
-:100A50008C01085F1F4FF12CE12CC62ED12CD1E068
-:100A6000F8016481C6010E2C02C0959587950A9401
-:100A7000E2F780FD6D270F5F1F4F80810E94E2002B
-:100A8000BFEFEB1AFB0AEC1658F3DF91CF911F91E1
-:100A90000F91FF90EF90DF90CF900895C4E0D8CFF2
-:100AA000C3E0D6CFDC011796EC911797E93008F038
-:100AB00038C0F0E0E25AFA4F0C94F91867056D055A
-:100AC000730579057F059105850591058B05ED91E8
-:100AD000FC910684F785E02D0994ED91FC91008846
-:100AE000F189E02DF9CFED91FC910288F389E02D99
-:100AF000F3CFED91FC910488F589E02DEDCFED91D8
-:100B0000FC910688F789E02DE7CFED91FC91008CF0
-:100B1000F18DE02DE1CFED91FC91028CF38DE02D74
-:100B2000DBCF08954F925F926F927F928F929F9248
-:100B3000AF92BF92CF92DF92EF92FF920F931F93EB
-:100B4000CF93DF93EC01CC88DD88EE88FF8888891D
-:100B50009989AA89BB89C81AD90AEA0AFB0A688D49
-:100B6000798D8A8D9B8D9B01AC010E9412154B01E2
-:100B70005C0168A179A18AA19BA19B01AC010E94A3
-:100B800025169B01AC01C501B4010E9496160E9476
-:100B90000817C114D104E104F10409F0B6C06230B1
-:100BA0007105810591050CF0D0C01B821C821D824D
-:100BB0001E82188E198E1A8E1B8E1CAA1DAA1EAAA2
-:100BC0001FAAC12CD12C7601C701B601DF91CF91AC
-:100BD0001F910F91FF90EF90DF90CF90BF90AF905B
-:100BE0009F908F907F906F905F904F900895101618
-:100BF000110612061306B4F46C157D058E059F05CB
-:100C00001CF42A812111A1C09B01AC0188279927DE
-:100C1000DC01821B930BA40BB50B8CAB9DABAEAB75
-:100C2000BFAB93C0011511052105310509F48DC035
-:100C30006C157D058E059F050CF087C08A81882381
-:100C400009F483C030952095109501951F4F2F4FC3
-:100C50003F4F0CAB1DAB2EAB3FAB77C00115110561
-:100C60002105310509F471C08824992454018C1898
-:100C70009D08AE08BF08681579058A059B050CF02C
-:100C800064C08A81811161C0DDCFCCACDDACEEAC3B
-:100C9000FFACA7019601C701B6010E9425162B01E2
-:100CA0003C01C501B4010E94B31420E030E040E8EB
-:100CB00050E40E94121520E030E040E85FE30E941B
-:100CC00025169B01AC01C301B2010E9496169B013F
-:100CD000AC01C701B6010E9424163B016C01FE0164
-:100CE000E05CFF4FE080F180028113819701A80151
-:100CF0000E942918181614F473018601C701D8013F
-:100D00008CAF9DAFAEAFBFAF3AC00CA91DA92EA945
-:100D10003FA91C141D041E041F040CF468CF1016F8
-:100D20001106120613060CF099CF0027112798011F
-:100D30000C191D092E093F096017710782079307D7
-:100D40000CF062CF2A8121115FCF8CA89DA8AEA89C
-:100D5000BFA881149104A104B10409F096CF88AD15
-:100D600099ADAAADBBAD8CAF9DAFAEAFBFAF81E0CB
-:100D70001C141D041E041F040CF080E08A833FEF46
-:100D8000831A930AA30AB30A8CAA9DAAAEAABFAA81
-:100D90008CAC9DACAEACBFACC501B4010E940F17CA
-:100DA0006B017C01CB82DC82ED82FE82A501940185
-:100DB00060E074E284E799E40E949616688F798F68
-:100DC0008A8F9B8F2A812111FFCE9058688F798F4F
-:100DD0008A8F9B8FF9CECF92DF92EF92FF920F9383
-:100DE0001F93CF93DF93EC016A017B0120E030E099
-:100DF000A901CB01B6010E94AC1487FF04C0F7FA29
-:100E0000F094F7F8F094A70196016C8D7D8D8E8D8E
-:100E10009F8D0E94AC14882309F446C0CC8EDD8ED1
-:100E2000EE8EFF8E8E01005C1F4FA701960160E0E1
-:100E300074E284E799E40E949616F8016083718356
-:100E4000828393838CA99DA9AEA9BFA91816190600
-:100E50001A061B064CF5688D798D8A8D9B8D9B013A
-:100E6000AC010E9412156B017C0168A179A18AA1D5
-:100E70009BA19B01AC010E9425169B01AC01C701FF
-:100E8000B6010E9496160E9408176CAB7DAB8EAB24
-:100E90009FABCE01DF91CF911F910F91FF90EF900B
-:100EA000DF90CF900C949205DF91CF911F910F911D
-:100EB000FF90EF90DF90CF90089508952F923F928A
-:100EC0004F925F926F927F928F929F92AF92BF925A
-:100ED000CF92DF92EF92FF920F931F93CF93DF9306
-:100EE000CDB7DEB762970FB6F894DEBF0FBECDBFA9
-:100EF0006B017C012D873E874F87588B2AEA37E2AA
-:100F00004FE155E40E9412150E9408172B013C0185
-:100F100020E030E044EB55E46D857E858F8598892F
-:100F20000E9412150E9408174B015C0120912202B9
-:100F3000309123024091240250912502C701B6014D
-:100F40000E9424162BED3FE049EC50E40E949616D7
-:100F50009B01AC0160911A0270911B0280911C02EE
-:100F600090911D020E94251660931A0270931B0235
-:100F700080931C0290931D028091040181111AC07C
-:100F80002BED3FE049EC50E4C701B6010E949616F4
-:100F900020E030E04AE756E40E94121520E030E0FD
-:100FA00040E251E40E9496160E940817861A970A9A
-:100FB000A80AB90A49825A826B827C828D829E82FB
-:100FC000AF82B886209039039E012F5F3F4F2901E1
-:100FD00045E253E05A874987912C312C00E010E01C
-:100FE000812C2914C9F1D2016D917D918D919D9132
-:100FF0002D01E985FA85A190B190FA87E987D5019D
-:1010000050962D913D914D915C915397621B730BBE
-:10101000840B950B97FF07C090958095709561950F
-:101020007F4F8F4F9F4F0E94B314F501248D358D54
-:10103000468D578D0E9496163B015C01232D302F63
-:10104000412F582D0E942918181624F4362C072DEC
-:101050001A2D8B2C9394C5CF20E030E0A901632D8D
-:10106000702F812F982D0E94291818160CF06CC033
-:10107000912C80913903981608F066C0892D90E074
-:10108000FC01EE0FFF1FEE0FFF1F21E030E02C0FE1
-:101090003D1FE20FF31F4080518062807380AC01DE
-:1010A000440F551F5A8B498BFA01EB5DFC4FA08012
-:1010B000B180F50180899189A289B389A301920148
-:1010C000281B390B4A0B5B0BCA01B9010E94B314F0
-:1010D000232D302F412F582D0E94961669877A872D
-:1010E0008B879C87F50184899589A689B7894816DD
-:1010F00059066A067B0661F0448A558A668A778AB1
-:101100000190F081E02D0084F185E02DC501099565
-:10111000E989FA89EB5DFC4FA080B180F501208D53
-:10112000318D428D538D69857A858B859C850E9492
-:10113000AC14882339F049855A856B857C85C501B7
-:101140000E94AB02939495CF80E010E09091390318
-:10115000191720F5E12FF0E0EE0FFF1FEB5DFC4FBC
-:101160000190F081E02D84889588A688B788408911
-:1011700051896289738984169506A606B70661F0BF
-:1011800083819481A581B681892B8A2B8B2B19F0C1
-:10119000CF010E94160381E01F5FD8CF8111D4CF09
-:1011A000C0922202D0922302E0922402F092250201
-:1011B0002D853E854F85588920931E0230931F024E
-:1011C000409320025093210262960FB6F894DEBF3E
-:1011D0000FBECDBFDF91CF911F910F91FF90EF9088
-:1011E000DF90CF90BF90AF909F908F907F906F9047
-:1011F0005F904F903F902F900895FC010190002048
-:10120000E9F73197AF01481B590BBC0184E892E024
-:101210000C945E01CF93DF930E94FD08EC018DE3F7
-:1012200091E00E94FD088C0F9D1FDF91CF910895E2
-:1012300080E491E00E940A0920E030E44CE955EC9A
-:101240006091520370915303809154039091550320
-:101250000E94AC14882341F040E050E46CE975EC46
-:101260008AE393E00E94AB0280913D0390913E039C
-:10127000A0913F03B0914003892B8A2B8B2B21F047
-:101280008AE393E00E94160360914A0370914B0336
-:1012900080914C0390914D030E94B31420E030E004
-:1012A00046EC55EC0E94AC141816F4F210924A0366
-:1012B00010924B0310924C0310924D0310924E0368
-:1012C00010924F03109250031092510310926E032C
-:1012D00010926F03109270031092710310923D03ED
-:1012E00010923E0310923F0310924003109252035B
-:1012F000109253031092540310925503109222023D
-:1013000010922302109224021092250210921E02C3
-:1013100010921F02109220021092210287E491E0A5
-:101320000C940A094F925F926F927F928F929F92D4
-:10133000AF92BF92CF92DF92EF92FF92CF93DF9363
-:10134000EC016A017B0120E030E0A901CB01B6018C
-:101350000E94AC1487FF04C0F7FAF094F7F8F094F9
-:1013600088A099A0AAA0BBA0A7019601C501B401BD
-:101370000E94AC14882309F44DC06CA97DA98EA9E4
-:101380009FA90E94B3142B013C01A7019601C5013E
-:10139000B4010E9496169B01AC01C301B2010E94E8
-:1013A00012150E9408176CAB7DAB8EAB9FABA701EB
-:1013B000960160E070E080E090E40E9496160E9442
-:1013C000581826E53EE04DE25FE30E94121520E04A
-:1013D00034E244E759E40E94121568AF79AF8AAF4E
-:1013E0009BAFC8A2D9A2EAA2FBA2E881F98100843E
-:1013F000F185E02DCE01DF91CF91FF90EF90DF904E
-:10140000CF90BF90AF909F908F907F906F905F90A4
-:101410004F900994DF91CF91FF90EF90DF90CF90A4
-:10142000BF90AF909F908F907F906F905F904F9004
-:101430000895FC0124813581232B39F421E0FB013F
-:101440008081882349F020E007C0808191810E943B
-:10145000171B21E0892BB9F7822F0895FC018081A9
-:101460009181009711F00C94BD1908950C94BD1949
-:10147000FB0144815581FC01248135812417350706
-:1014800078F080819181009759F0FB016081718132
-:101490006115710529F00E94271B21E0892B09F0B5
-:1014A00020E0822F08950F931F93CF93DF93EC01D9
-:1014B00088819981009759F02A813B812617370747
-:1014C00030F081E0DF91CF911F910F9108958B0152
-:1014D0006F5F7F4F0E94461A009759F09983888367
-:1014E0001B830A832C813D81232B59F7FC01108239
-:1014F000E8CF80E0E7CFEF92FF920F931F93CF9357
-:10150000DF93EC017B018A01BA010E94530A288112
-:101510003981811114C02115310519F0C9010E94CA
-:10152000BD19198218821D821C821B821A82CE016B
-:10153000DF91CF911F910F91FF90EF9008951D8340
-:101540000C83B701C9010E94201BF1CFFC0111825D
-:1015500010821382128215821482FB0101900020F6
-:10156000E9F73197AF01461B570B0C947B0AAF92FA
-:10157000BF92CF92DF92EF92FF920F931F93CF9380
-:10158000DF93EC016B015A0179012417350720F430
-:101590008B2D5901E42EF82E6FE371E0CE010E94ED
-:1015A000A60AD60114960D911C91A016B10628F535
-:1015B000E016F10608F48701D601ED91FC91119730
-:1015C000E00FF11FF08010826D917C916A0D7B1D00
-:1015D00061157105F1F0FB0101900020E9F73197E9
-:1015E000AF01461B570BCE010E947B0AF60180819A
-:1015F0009181080F191FD801FC92CE01DF91CF9184
-:101600001F910F91FF90EF90DF90CF90BF90AF9020
-:10161000089588819981009711F00E94BD1919825F
-:1016200018821D821C821B821A82E0CF1F920F92A9
-:101630000FB60F9211242F933F938F939F93AF93E5
-:10164000BF938091800290918102A0918202B0911B
-:10165000830230917F0223E0230F2D3758F5019646
-:10166000A11DB11D20937F0280938002909381027F
-:10167000A0938202B093830280917B0290917C02BE
-:10168000A0917D02B0917E020196A11DB11D8093B3
-:101690007B0290937C02A0937D02B0937E02BF9167
-:1016A000AF919F918F913F912F910F900FBE0F900F
-:1016B0001F90189526E8230F0296A11DB11DD2CFC9
-:1016C0001F920F920FB60F9211242F933F934F93B7
-:1016D0005F936F937F938F939F93AF93BF93EF939A
-:1016E000FF9384E892E00E941C02FF91EF91BF916A
-:1016F000AF919F918F917F916F915F914F913F91AA
-:101700002F910F900FBE0F901F9018951F920F9260
-:101710000FB60F9211242F938F939F93EF93FF9304
-:10172000E0919402F09195028081E0919A02F0910B
-:101730009B0282FD1BC0908180919D028F5F8F7301
-:1017400020919E02821741F0E0919D02F0E0EC575B
-:10175000FD4F958F80939D02FF91EF919F918F9107
-:101760002F910F900FBE0F901F9018958081F4CF8E
-:101770008F929F92AF92BF92CF92DF92EF92FF92A1
-:101780000F931F93CF93DF93E4E8F2E0138212826A
-:1017900088EE93E0A0E0B0E084839583A683B783CE
-:1017A0008FE091E09183808385EC90E0958784873A
-:1017B00084EC90E09787868780EC90E0918B808B1B
-:1017C00081EC90E0938B828B82EC90E0958B848B04
-:1017D00086EC90E0978B868B118E128E138E148E72
-:1017E000EEE7F3E001E211E01183008388248394A3
-:1017F0008782108A118A128A138A148A158A168A95
-:10180000178A108E118E128E138E148E158E168ED0
-:10181000178E10A211A212A213A2C12CD12C80E803
-:10182000E82E8FE3F82EC4A2D5A2E6A2F7A2138277
-:10183000148215821682C1E0D0E0D5A7C4A79924EE
-:101840009A9497A610A611A612A613A682E08087E6
-:1018500095E0B92EB18624E0A22EA286B38616A604
-:1018600014AA15AA16AA17AA10AE11AE12AE13AE7C
-:1018700014AE15AE16AE17AEC092BE03D092BF0323
-:10188000E092C003F092C103128214861586168678
-:101890001786CF010E947F03B701A6018EE793E070
-:1018A0000E949209B701A6018EE793E00E94EB0621
-:1018B000EAE3F3E0118300838782108A118A128A97
-:1018C000138A148A158A168A178A108E118E128E20
-:1018D000138E148E158E168E178E10A211A212A2C0
-:1018E00013A2C4A2D5A2E6A2F7A213821482158283
-:1018F0001682D5A7C4A797A610A611A612A613A64E
-:1019000083E0808786E08187A286B38616A614AA24
-:1019100015AA16AA17AA10AE11AE12AE13AE14AEC7
-:1019200015AE16AE17AEC0927A03D0927B03E0924A
-:101930007C03F0927D031282148615861686178624
-:10194000CF010E947F03B701A6018AE393E00E94C2
-:101950009209B701A6018AE393E00E94EB06109278
-:10196000390380E090E4ACE9B5E4809321039093DF
-:101970002203A0932303B0932403DF91CF911F91FF
-:101980000F91FF90EF90DF90CF90BF90AF909F901E
-:101990008F900895CF93DF93CDB7DEB7A6970FB69C
-:1019A000F894DEBF0FBECDBF789484B5826084BD4D
-:1019B00084B5816084BD85B5826085BD85B5816053
-:1019C00085BD80916E00816080936E0010928100D1
-:1019D000809181008260809381008091810081608C
-:1019E000809381008091800081608093800080914D
-:1019F000B10084608093B1008091B00081608093D9
-:101A0000B00080917A00846080937A0080917A009F
-:101A1000826080937A0080917A00816080937A005E
-:101A200080917A00806880937A001092C10040E033
-:101A300050E46CE975E48EE793E00E94EB0640E029
-:101A400050E46CE975E48EE793E00E94920940E06F
-:101A500050E46CE975E48AE393E00E94EB0640E011
-:101A600050E46CE975E48AE393E00E949209E09106
-:101A70003903EA3068F481E08E0F80933903F0E097
-:101A8000EE0FFF1FEB5DFC4F8EE793E091838083A9
-:101A9000E0913903EA3068F481E08E0F80933903D6
-:101AA000F0E0EE0FFF1FEB5DFC4F8AE393E09183C4
-:101AB000808362E08BE00E94100160E08EE00E9473
-:101AC000100160E08FE00E941001E0919402F0911B
-:101AD000950282E08083E0919002F0919102108261
-:101AE000E0919202F091930280E1808310929C0237
-:101AF000E0919802F091990286E08083E09196024D
-:101B0000F0919702808180618083E0919602F0914C
-:101B10009702808188608083E0919602F09197021D
-:101B2000808180688083E0919602F09197028081A5
-:101B30008F7D80838DE491E00E940A090E9418093C
-:101B4000E2E1F1E08491EEEFF0E00491EAEEF0E002
-:101B50001491112309F4C4C181110E94B900E12F2D
-:101B6000F0E0EE0FFF1FEF53FF4FA591B4918C9162
-:101B7000082391E080E009F090E0092F182F809170
-:101B8000790290917A0280179107B9F1013011051D
-:101B900009F0A9C18FE491E00E940A0920E030E039
-:101BA00040E05FE360912103709122038091230361
-:101BB000909124030E9412156B017C01BC01A601C7
-:101BC0008EE793E00E94EB06B701A6018AE393E05B
-:101BD0000E94EB0610920401609122027091230290
-:101BE000809124029091250220E030E0A9010E941A
-:101BF0005E0710937A0200937902809179029091A6
-:101C00007A028130910509F09BC18FE00E944C015E
-:101C1000BC01990F880B990B0E94B31420E030E0AF
-:101C200040EB50E40E94121520E030EC4FE754E402
-:101C30000E94961620E030E040E05FE30E94251607
-:101C400020E030E040E251E40E9412150E942E187C
-:101C500020E030E040E251E40E9496166B017C01E6
-:101C600020E030E040E85FE30E94601720E030E0D1
-:101C700040E05FE30E94291887FD55C1C701B60106
-:101C80000E943E1723E333E343E75FE30E942516F8
-:101C90006B017C018090000190900101A0900201F5
-:101CA000B0900301409022025090230260902402E1
-:101CB00070902502AC019B01C501B4010E94AC14D7
-:101CC000882331F1A7019601C501B4010E942416B1
-:101CD000A30192010E9412159B01AC0160911602B2
-:101CE0007091170280911802909119020E94251696
-:101CF0006093160270931702809318029093190252
-:101D0000C0920001D0920101E0920201F092030121
-:101D10008EE00E944C01BC01990F880B990B0E9428
-:101D2000B31420E030E040E05FE30E94121520E0B1
-:101D300030EC4FE754E40E94961620E030E0A90111
-:101D40000E94251620E030E040EA51E40E9412157E
-:101D50000E942E1820E030E040EA51E40E949616DE
-:101D60006B017C01AC019B0160E070E080E89FE3C7
-:101D70000E94241620E030E040E05FE30E9412154C
-:101D80004B015C01AC019B01C701B6010E94251605
-:101D90006B8F7C8F8D8F9E8F2BED3FE049E45EE350
-:101DA000C301B2010E9425166B017C012BED3FE0BF
-:101DB00049EC50E40E94961660931A0270931B023D
-:101DC00080931C0290931D0280910001909101016B
-:101DD000A0910201B09103018B8B9C8BAD8BBE8BCC
-:101DE0008091160290911702A0911802B0911902E9
-:101DF0008F8B988FA98FBA8FA30192016B897C89F1
-:101E00008D899E890E9412152F89388D498D5A8D92
-:101E10000E9425160E9491169B01AC01C501B401D8
-:101E20000E9412152B8D3C8D4D8D5E8D0E942516C6
-:101E300060931E0270931F028093200290932102F0
-:101E40002B893C894D895E89C701B6010E94121514
-:101E50002F89388D498D5A8D0E9425160E94911622
-:101E60009B01AC01C501B4010E9412152B8D3C8D64
-:101E70004D8D5E8D0E9425164B015C0120E030E007
-:101E8000A9010E94AC1487FD57C020E030E040E873
-:101E90005FE3C501B4010E942918181634F4812C9F
-:101EA000912C30E8A32E3FE3B32EA5019401C70186
-:101EB000B6010E945E07C0922202D0922302E092F5
-:101EC0002402F092250280E090E0892B09F438CEBC
-:101ED0000E940802882309F433CE0E94000030CE0D
-:101EE00001E010E04CCE86E691E00E940A09C09025
-:101EF0002103D0902203E0902303F0902403B70144
-:101F0000A6018EE793E00E94EB06B701A6018AE3E3
-:101F100093E00E94EB0681E08093040186E791E064
-:101F20000E940A0959CEC701B6010E943E172DEC46
-:101F30003CEC4CEC5DE3AACE812C912C5401B5CF46
-:101F4000892B09F684E892E00E94FC011816190614
-:101F50000CF0F7C16FE371E0CE010D960E94A60A66
-:101F60000E94AE0197FD1EC08A309105D9F0898389
-:101F70001A8209891A890F5F1F4FB801CE010D9689
-:101F80000E94530A882361F32D853E8589899A89A9
-:101F9000BE016F5F7F4F820F931F0E94201B1A8B21
-:101FA000098BDECF62E871E0CE010D960E94190A1E
-:101FB000811162C067E871E0CE010D960E94190A96
-:101FC00081115AC063E971E0CE010D960E94190A91
-:101FD000811152C06FE971E0CE0101960E94A60AFC
-:101FE000BE016F5F7F4FCE010D960E94380A10E050
-:101FF000811127C069EA71E0CE0107960E94A60A06
-:1020000029893A894B855C852417350708F42DC347
-:102010008D859E85009709F428C36F8178856115A9
-:10202000710509F422C3241B350B820F931F0E94F4
-:10203000171B11E0892B09F410E0CE0107960E94CE
-:102040002E0ACE0101960E942E0A1123A9F08BEAD6
-:1020500091E00E94FD0849895A896D857E8584E852
-:1020600092E00E945E018DE391E00E94FD08CE01A6
-:102070000D960E942E0A27CF63E971E0CE010D96DE
-:102080000E94190A882361F085EB91E00E940A09F9
-:1020900088EC91E00E940A0989ED91E00E940A090A
-:1020A00067E871E0CE010D960E94190A882381F03D
-:1020B00081E08093040186E791E00E940A0986E7A7
-:1020C00091E00E940A0988EE91E00E940A09CFCFB0
-:1020D00062E871E0CE010D960E94190A882319F07A
-:1020E0000E941809C4CF6FE971E0CE0101960E94E9
-:1020F000A60ABE016F5F7F4FCE010D960E94380A7F
-:10210000182FCE0101960E942E0A112309F473C0E4
-:1021100009891A890115110509F46AC0ED84FE8444
-:1021200060E270E0C7010E940C1B009709F460C0D8
-:10213000AC014E195F094F3F540709F459C04F5F76
-:102140005F4F9801BE01635F7F4FCE0101960E94F1
-:10215000B70A89819A81009709F447C00E94371312
-:102160006B017C0120E030E040E85FE30E94291829
-:1021700087FD3BC020E030E048EC52E4C701B601E7
-:102180000E94AC1418168CF120E030E048EC52E4C8
-:10219000C701B6010E94961620E030E44CE955E4F0
-:1021A0000E9412150E9408170E94B3146B017C0153
-:1021B000C0922103D0922203E0922303F0922403E1
-:1021C000BC01A6018EE793E00E94EB06B701A601D1
-:1021D0008AE393E00E94EB068EEE91E00E940A09EA
-:1021E000CE0101960E942E0A42CF88EF91E0F6CFF1
-:1021F00086E092E06ACF8091780281119EC028E249
-:10220000222E22E0322E10E000E0D12CC12C69EA0F
-:1022100071E0CE0101960E94A60A89899A89081761
-:10222000190770F48D849E8469817A81C401800FBE
-:10223000911F0E94351B7C01E818F908892B19F4BD
-:10224000EE24EA94FE2CCE0101960E942E0AAFEFF6
-:10225000EA16FA0609F46AC09701A801BE01635F95
-:102260007F4FCE0107960E94B70A8B859C85892BEC
-:1022700061F08F8098846CE270E0C4010E940C1BB6
-:102280008C0108191909892B11F40FEF1FEF980120
-:1022900050E040E0BE01695F7F4FCE0101960E9491
-:1022A000B70A89819A81412C512C3201009721F083
-:1022B0000E9437132B013C01CE0101960E942E0A89
-:1022C0002B853C85A8014F5F5F4FBE01695F7F4F43
-:1022D000CE0101960E94B70A89819A81812C912CA6
-:1022E0005401009721F00E9437134B015C01CE018D
-:1022F00001960E942E0AF10140825182628273820D
-:1023000084829582A682B782BFEFCB1ADB0A87014F
-:102310000F5F1F4FCE0107960E942E0AE8E02E0E97
-:10232000311CFAE0CF16D10409F071CFD092270208
-:10233000C092260281E080937802CE010D960E9421
-:102340002E0A80917802882309F4BDCD809126025F
-:1023500090912702181619060CF0B5CD8091220233
-:1023600090912302A0912402B09125028B8B9C8B2B
-:10237000AD8BBE8B80911E0290911F02A091200216
-:10238000B09121028F8B988FA98FBA8F88E2682E27
-:1023900082E0782E512C412C8091260290912702C8
-:1023A000481659060CF056C180910401882309F49F
-:1023B000C4C0C0902802D0902902E0902A02F09078
-:1023C0002B022AEA37E24FE155E4C701B6010E9429
-:1023D00012150E94081760938E0370938F038093E9
-:1023E000900390939103609392037093930380936F
-:1023F0009403909395031092B2031092B30310923A
-:10240000B4031092B50310928103109282031092CC
-:1024100083031092840310929603109297031092F4
-:1024200098031092990360914A0370914B03809135
-:102430004C0390914D030E94B3144B015C0120E0CA
-:1024400030E04AE756E460911A0270911B028091D5
-:102450001C0290911D020E94121520E030E040E223
-:1024600051E40E9496169B01AC01C501B4010E9483
-:1024700024160E94081760934A0370934B038093BD
-:102480004C0390934D0360934E0370934F038093DE
-:1024900050039093510310926E0310926F031092A9
-:1024A00070031092710310923D0310923E0310923C
-:1024B0003F03109240031092520310925303109264
-:1024C000540310925503C0922202D0922302E0924C
-:1024D0002402F092250210921A0210921B0210920E
-:1024E0001C0210921D021092040120912C023091C6
-:1024F0002D0240912E0250912F02C701B6010E9479
-:102500005E07D3018D919D910D90BC91A02D8B8B79
-:102510009C8BAD8BBE8BD30114968D919D910D90AC
-:10252000BC91A02D8F8B988FA98FBA8FBFEF4B1ABC
-:102530005B0AE8E06E0E711C2FCF2B893C894D8918
-:102540005E89D3016D917D918D919C910E9424169D
-:102550006B8F7C8F8D8F9E8F2F89388D498D5A8DF3
-:10256000F30164817581868197810E9424166B0135
-:102570007C01AC019B010E9412154B015C012B8D6B
-:102580003C8D4D8D5E8DCA01B9010E9412159B01D3
-:10259000AC01C501B4010E9425160E94581820E024
-:1025A00030E0A9010E9496160E9408178B011616AA
-:1025B000170614F001E010E0312C212CC801012E87
-:1025C000000CAA0BBB0B8F8F98A3A9A3BAA3B101D0
-:1025D000032C000C880B990B0E94B3144B015C0177
-:1025E0006F8D78A189A19AA10E94B3149B01AC01BF
-:1025F000C501B4010E9496164B015C01AC019B0120
-:10260000C701B6010E9412152F89388D498D5A8D48
-:102610000E9425166BA37CA38DA39EA3A501940104
-:102620006B8D7C8D8D8D9E8D0E9412152B893C8922
-:102630004D895E890E9425162BA13CA14DA15EA16A
-:102640000E945E079FEF291A390A021513050CF044
-:10265000BECF57CF10927802109227021092260216
-:102660008DE491E00E940A092ECC11E0E6CC6627A9
-:1026700077270C943B13B0E0A0E0E1E4F3E10C9485
-:10268000E4155C017B016115710519F0DB018D9387
-:102690009C9385010F5F1F4FF501D0818D2F90E036
-:1026A0000E948B146C01892BB9F5DD32B9F50F5FEF
-:1026B0001F4FD5011196DC91C1E05801F1E0AF1A2E
-:1026C000B10843E050E06EE870E0C5010E94941448
-:1026D000892B69F5680182E0C80ED11C45E050E005
-:1026E00069E870E0C6010E949414892B21F4680106
-:1026F00097E0C90ED11CE114F10419F0D701CD9275
-:10270000DC9260E070E080E89FEFC111FFC060E004
-:1027100070E080E89FE7FAC05801BBCFDB3229F4B4
-:1027200085010E5F1F4FF501D181C0E0C6CF43E0A8
-:1027300050E066E870E0C5010E949414892BE9F02E
-:10274000F80110E000E020E030E0A9015F01B0ED09
-:102750008B2E8D0E89E08815C8F19C2E689491F817
-:102760008C2F8870C2FF16C0811102C00F5F1F4FEF
-:102770003196D501DC91C92DE9CFE114F10429F09E
-:102780000E5F1F4FF7011183008360E070E080EC63
-:102790009FE7BCC0882311F001501109A5E0B0E00B
-:1027A0000E94D3159B01AC01220F331F441F551FFC
-:1027B000280D311D411D511D283999E93907490757
-:1027C00099E15907A8F2C6609C2ED2CFAEEF8A12CB
-:1027D00006C0C3FD3CC09C2E689493F8C9CFDF7D32
-:1027E000D534A9F580818D3239F4C061DF011296AC
-:1027F000818162E070E006C0DF018B32C1F3119687
-:1028000061E070E080535D01A61AB70A8A30F8F4DF
-:10281000E0E8CE16ECE0DE065CF4B601660F771F4A
-:10282000660F771FC60ED71ECC0CDD1CC80ED11C40
-:102830005D01FFEFAF1ABF0A8C9180538A30A8F177
-:10284000C4FF03C0D194C194D1080C0D1D1DC1FF5C
-:1028500009C0E114F10431F081E0A81AB108D701F0
-:10286000AD92BC92CA01B9010E94B114C370C330C9
-:1028700009F490584B015C0120E030E0A9010E946E
-:10288000AC14882309F440C0CDEBD0E017FF05C09D
-:10289000119501951109C5EAD0E06E01B8E1CB1A96
-:1028A000D10880E2E82EF12C0FC0D501B1CFFE0196
-:1028B00025913591459154910E191F09C501B40117
-:1028C0000E9412154B015C01D501C4010E151F05B4
-:1028D00074F72497F594E794CC16DD06A9F78A2FB0
-:1028E000880F8B2F881F8F3F49F020E030E0A9012F
-:1028F000C501B4010E94AC14811106C082E290E0CF
-:102900009093C3038093C203C501B401CDB7DEB772
-:10291000ECE00C94001691110C947F15803219F0A4
-:1029200089508550C8F70895FB01DC0141505040A3
-:1029300088F08D9181341CF08B350CF4805E6591AC
-:1029400061341CF06B350CF4605E861B611171F311
-:10295000990B0895881BFCCF0E94EE1408F481E0C7
-:102960000895E89409C097FB3EF490958095709582
-:1029700061957F4F8F4F9F4F9923A9F0F92F96E9CB
-:10298000BB279395F695879577956795B795F11140
-:10299000F8CFFAF4BB0F11F460FF1BC06F5F7F4FDD
-:1029A0008F4F9F4F16C0882311F096E911C07723EF
-:1029B00021F09EE8872F762F05C0662371F096E8F8
-:1029C000862F70E060E02AF09A95660F771F881FC7
-:1029D000DAF7880F9695879597F90895990F00086B
-:1029E000550FAA0BE0E8FEEF16161706E807F907E1
-:1029F000C0F012161306E407F50798F0621B730B7C
-:102A0000840B950B39F40A2661F0232B242B252BFC
-:102A100021F408950A2609F4A140A6958FEF811D9F
-:102A2000811D08950E9425150C9499150E948B15FF
-:102A300038F00E94921520F0952311F00C94821525
-:102A40000C94881511240C94CD150E94AA1570F3CE
-:102A5000959FC1F3950F50E0551F629FF001729F43
-:102A6000BB27F00DB11D639FAA27F00DB11DAA1F52
-:102A7000649F6627B00DA11D661F829F2227B00D9F
-:102A8000A11D621F739FB00DA11D621F839FA00D2A
-:102A9000611D221F749F3327A00D611D231F849F7A
-:102AA000600D211D822F762F6A2F11249F575040D1
-:102AB0009AF0F1F088234AF0EE0FFF1FBB1F661F4C
-:102AC000771F881F91505040A9F79E3F510580F015
-:102AD0000C9482150C94CD155F3FE4F3983ED4F32B
-:102AE000869577956795B795F795E7959F5FC1F7B9
-:102AF000FE2B880F911D9695879597F90895992734
-:102B00008827089597F99F6780E870E060E008954E
-:102B10009FEF80EC089500240A94161617061806F5
-:102B20000906089500240A941216130614060506D1
-:102B30000895092E0394000C11F4882352F0BB0F62
-:102B400040F4BF2B11F460FF04C06F5F7F4F8F4FC5
-:102B50009F4F089557FD9058440F551F59F05F3F00
-:102B600071F04795880F97FB991F61F09F3F79F0AF
-:102B700087950895121613061406551FF2CF469531
-:102B8000F1DF08C0161617061806991FF1CF8695B3
-:102B90007105610508940895E894BB276627772797
-:102BA000CB0197F908950E941516A59F900DB49F2B
-:102BB000900DA49F800D911D112408952F923F9296
-:102BC0004F925F926F927F928F929F92AF92BF923D
-:102BD000CF92DF92EF92FF920F931F93CF93DF93E9
-:102BE000CDB7DEB7CA1BDB0B0FB6F894DEBF0FBE46
-:102BF000CDBF09942A88398848885F846E847D8493
-:102C00008C849B84AA84B984C884DF80EE80FD8094
-:102C10000C811B81AA81B981CE0FD11D0FB6F8940A
-:102C2000DEBF0FBECDBFED010895A29FB001B39FDF
-:102C3000C001A39F700D811D1124911DB29F700DC5
-:102C4000811D1124911D08955058BB27AA270E9469
-:102C50003C160C9499150E948B1538F00E94921521
-:102C600020F039F49F3F19F426F40C9488150EF4E3
-:102C7000E095E7FB0C948215E92F0E94AA1558F302
-:102C8000BA17620773078407950720F079F4A6F551
-:102C90000C94CC150EF4E0950B2EBA2FA02D0B0141
-:102CA000B90190010C01CA01A0011124FF27591B91
-:102CB00099F0593F50F4503E68F11A16F040A22F97
-:102CC000232F342F4427585FF3CF46953795279508
-:102CD000A795F0405395C9F77EF41F16BA0B620B07
-:102CE000730B840BBAF09150A1F0FF0FBB1F661F4E
-:102CF000771F881FC2F70EC0BA0F621F731F841F91
-:102D000048F4879577956795B795F7959E3F08F0B6
-:102D1000B0CF9395880F08F09927EE0F9795879578
-:102D200008950E94D417E3950C94FD170E94AA16EB
-:102D30000C9499150E94921558F00E948B1540F042
-:102D400029F45F3F29F00C94821551110C94CD1594
-:102D50000C9488150E94AA1568F39923B1F35523A2
-:102D600091F3951B550BBB27AA276217730784079E
-:102D700038F09F5F5F4F220F331F441FAA1FA9F334
-:102D800035D00E2E3AF0E0E832D091505040E69522
-:102D9000001CCAF72BD0FE2F29D0660F771F881F83
-:102DA000BB1F261737074807AB07B0E809F0BB0B76
-:102DB000802DBF01FF2793585F4F3AF09E3F51058A
-:102DC00078F00C9482150C94CD155F3FE4F3983E97
-:102DD000D4F3869577956795B795F7959F5FC9F773
-:102DE000880F911D9695879597F90895E1E0660FF4
-:102DF000771F881FBB1F621773078407BA0720F06D
-:102E0000621B730B840BBA0BEE1F88F7E0950895D5
-:102E10000E940F176894B1110C94CD1508950E946B
-:102E2000B21588F09F5798F0B92F9927B751B0F095
-:102E3000E1F0660F771F881F991F1AF0BA95C9F73E
-:102E400014C0B13091F00E94CC15B1E008950C94FB
-:102E5000CC15672F782F8827B85F39F0B93FCCF3AE
-:102E6000869577956795B395D9F73EF490958095BB
-:102E7000709561957F4F8F4F9F4F08950E94111855
-:102E800090F09F3748F4911116F00C94CD1560E046
-:102E900070E080E89FEB089526F41B16611D711DFC
-:102EA000811D0C94A9170C94C4170E948B1520F057
-:102EB00019F00E94921550F40C9488150C94CD15BD
-:102EC000E92F0E94AA1588F35523B1F3E7FB621797
-:102ED000730784079507A8F189F3E92FFF27882353
-:102EE0002AF03197660F771F881FDAF7952F55273D
-:102EF000442332F091505040220F331F441FD2F729
-:102F0000BB27E91BF50B621B730B840BB109B1F2F4
-:102F100022F4620F731F841FB11D31972AF0660FD0
-:102F2000771F881FBB1FEFCF9150504062F041F0D8
-:102F3000882332F0660F771F881F91505040C1F7E9
-:102F400093950C94C4178695779567959F5FD9F7ED
-:102F5000F7CF882371F4772321F09850872B762FB1
-:102F600007C0662311F499270DC09051862B70E09D
-:102F700060E02AF09A95660F771F881FDAF7880FAE
-:102F80009695879597F908959F3F31F0915020F4D9
-:102F9000879577956795B795880F911D9695879535
-:102FA00097F908950C9488150E94B215D8F3E89407
-:102FB000E0E0BB279F57F0F02AED3FE049EC06C068
-:102FC000EE0FBB0F661F771F881F28F0B23A62070B
-:102FD0007307840728F0B25A620B730B840BE395D6
-:102FE0009A9572F7803830F49A95BB0F661F771F59
-:102FF000881FD2F790480C94C617EF93E0FF07C0E4
-:10300000A2EA2AED3FE049EC5FEB0E943C160E94E9
-:1030100099150F90039401FC9058E8E6F0E00C94A9
-:103020009F180E94B215A0F0BEE7B91788F4BB271D
-:103030009F3860F41616B11D672F782F8827985F88
-:10304000F7CF869577956795B11D93959639C8F317
-:1030500008950E94EE1408F48FEF08950E94B215AF
-:10306000E8F09E37E8F09639B8F49E3848F4672FB8
-:10307000782F8827985FF9CF8695779567959395F0
-:103080009539D0F3B62FB1706B0F711D811D20F4EF
-:1030900087957795679593950C94A9170C94C41709
-:1030A0000C94CD1519F416F40C9488150C94C417CF
-:1030B0000E94B215B8F39923C9F3B6F39F57550B85
-:1030C00087FF0E9498180024A0E640EA90018058EB
-:1030D0005695979528F4805C660F771F881F20F01F
-:1030E00026173707480730F4621B730B840B20291F
-:1030F00031294A2BA69517940794202531254A2774
-:1031000058F7660F771F881F20F0261737074807E4
-:1031100030F4620B730B840B200D311D411DA09503
-:1031200081F7B901842F9158880F96958795089556
-:1031300091505040660F771F881FD2F708959F93D4
-:103140008F937F936F93FF93EF939B01AC010E944A
-:103150001215EF91FF910E94B3182F913F914F915B
-:103160005F910C941215DF93CF931F930F93FF92EF
-:10317000EF92DF927B018C01689406C0DA2EEF019A
-:103180000E942515FE01E894A59125913591459160
-:103190005591A6F3EF010E943C16FE019701A8018C
-:1031A000DA9469F7DF90EF90FF900F911F91CF9124
-:1031B000DF910895052E97FB1EF400940E94F118EC
-:1031C00057FD07D00E94FF1807FC03D04EF40C9463
-:1031D000F11850954095309521953F4F4F4F5F4FD7
-:1031E000089590958095709561957F4F8F4F9F4F73
-:1031F0000895EE0FFF1F0590F491E02D0994A1E2D0
-:103200001A2EAA1BBB1BFD010DC0AA1FBB1FEE1F60
-:10321000FF1FA217B307E407F50720F0A21BB30BAB
-:10322000E40BF50B661F771F881F991F1A9469F727
-:1032300060957095809590959B01AC01BD01CF0183
-:1032400008950F931F93CF93DF938230910510F46D
-:1032500082E090E0E091C603F091C70330E020E007
-:10326000B0E0A0E0309799F42115310509F44AC087
-:10327000281B390B24303105D8F58A819B816115D3
-:10328000710589F1FB0193838283FE0111C04081A6
-:1032900051810281138148175907E0F048175907F7
-:1032A00099F4109761F012960C93129713961C9351
-:1032B0003296CF01DF91CF911F910F910895009326
-:1032C000C6031093C703F4CF2115310551F04217FF
-:1032D000530738F0A901DB019A01BD01DF01F801B4
-:1032E000C1CFEF01F9CF9093C7038093C603CDCF31
-:1032F000FE01E20FF31F819391932250310939832C
-:103300002883D7CF2091C4033091C503232B41F4E8
-:1033100020910701309108013093C5032093C40325
-:1033200020910501309106012115310541F42DB799
-:103330003EB74091090150910A01241B350BE091E1
-:10334000C403F091C503E217F307A0F42E1B3F0B53
-:103350002817390778F0AC014E5F5F4F2417350707
-:1033600048F04E0F5F1F5093C5034093C4038193F1
-:1033700091939FCFF0E0E0E09CCFCF93DF93009755
-:10338000E9F0FC01329713821282A091C603B0913A
-:10339000C703ED0130E020E01097A1F420813181D6
-:1033A000820F931F2091C4033091C503281739075A
-:1033B00009F061C0F093C503E093C403DF91CF919E
-:1033C0000895EA01CE17DF07E8F54A815B819E0187
-:1033D00041155105B1F7E901FB83EA834991599100
-:1033E000C40FD51FEC17FD0761F48081918102960F
-:1033F000840F951FE90199838883828193819B8340
-:103400008A83F0E0E0E012968D919C9113970097EB
-:10341000B9F52D913C911197CD010296820F931F22
-:103420002091C4033091C5032817390739F6309726
-:1034300051F51092C7031092C603B093C503A09331
-:10344000C403BCCFD383C28340815181840F951FB5
-:10345000C817D90761F44E5F5F4F88819981480F83
-:10346000591F518340838A819B81938382832115D5
-:10347000310509F0B0CFF093C703E093C6039ECFA8
-:10348000FD01DC01C0CF13821282D7CFB0E0A0E0F3
-:10349000ECE4FAE10C94E0158C01009751F4CB01B7
-:1034A0000E9421198C01C801CDB7DEB7E0E10C9470
-:1034B000FC15FC01E60FF71F9C0122503109E217B1
-:1034C000F30708F49DC0D901CD91DC911197C6177F
-:1034D000D70798F0C530D10530F3CE010497861791
-:1034E000970708F3C61BD70B2297C193D1936D930F
-:1034F0007C93CF010E94BD19D6CF5B01AC1ABD0AE7
-:103500004C018C0E9D1EA091C603B091C703512C97
-:10351000412CF12CE12C109731F58091C40390914E
-:10352000C5038815990509F05CC04616570608F0D2
-:1035300058C08091050190910601009741F48DB724
-:103540009EB74091090150910A01841B950BE81721
-:10355000F90708F055C0F093C503E093C403F901DF
-:1035600071836083A0CF8D919C91119712966C907E
-:10357000129713967C901397A815B90559F56C010D
-:1035800042E0C40ED11CCA14DB0420F1AC014A197C
-:103590005B09DA011296159780F06282738251837B
-:1035A0004083D9016D937C93E114F10471F0D7014C
-:1035B0001396FC93EE93129776CF22968C0F9D1F55
-:1035C000F90191838083F301EFCFF093C703E09378
-:1035D000C60369CF4816590608F42C017D01D301B2
-:1035E0009ACFCB010E9421197C01009749F0AE01CE
-:1035F000B8010E94031BC8010E94BD19870153CF67
-:1036000010E000E050CFFB01DC0102C001900D9200
-:1036100041505040D8F70895FC018191861721F060
-:103620008823D9F7992708953197CF010895FB0191
-:10363000DC018D91019080190110D9F3990B089547
-:10364000FB01DC0101900D920020E1F70895FB01E0
-:10365000DC014150504030F08D910190801919F4F7
-:103660000020B9F7881B990B0895FB015191552350
-:10367000A9F0BF01DC014D9145174111E1F759F463
-:10368000CD010190002049F04D9140154111C9F341
-:10369000FB014111EFCF81E090E001970895F8948C
-:0236A000FFCF5A
-:1036A200CDCC3C40010000C8038000000000003E79
-:1036B200025E018B018B02FC019A01EE0100000007
-:1036C20000B3037F035D07360A920514055205FE17
-:1036D20004CC04AB04770456040004D1030D0A00A1
-:1036E200484F4D494E4700484F4D45440052005304
-:1036F2007069726F6772617068204D6F64652041F6
-:10370200637469766500417070204D6F6465204175
-:1037120063746976650054484554415F5245534588
-:103722005400484F4D450052455345545F54484557
-:103732005441004745545F56455253494F4E00533A
-:1037420045545F5350454544003B0049474E4F5254
-:1037520045443A20005461626C653A2044756E65B6
-:103762002057656176657200447269766572733AB4
-:1037720020445256383832350056657273696F6E7E
-:103782003A20312E342E30005245414459005350D4
-:103792004545445F53455400494E56414C49445FA8
-:1037A200535045454400494E56414C49445F434FAE
-:0637B2004D4D414E4400A4
-:00000001FF

+ 0 - 417
firmware/arduino_code_TMC2209/arduino_code_TMC2209.ino

@@ -1,417 +0,0 @@
-#include <AccelStepper.h>
-#include <MultiStepper.h>
-#include <math.h> // For M_PI and mathematical operations
-
-#define rotInterfaceType AccelStepper::DRIVER
-#define inOutInterfaceType AccelStepper::DRIVER
-
-#define stepPin_rot 2
-#define dirPin_rot 5
-#define stepPin_InOut 3
-#define dirPin_InOut 6
-
-#define rot_total_steps 16000.0
-#define inOut_total_steps 5760.0
-#define gearRatio 10
-
-#define BUFFER_SIZE 10 // Maximum number of theta-rho pairs in a batch
-
-#define buttonPin 11 // Z- signal pin on the CNC shield
-#define pot1 A1      // Potentiometer 1, Abort pin on the CNC shield
-#define pot2 A0      // Potentiometer 2, Hold pint on the CNC shield
-
-#define MODE_APP 0
-#define MODE_SPIROGRAPH 1
-
-// Create stepper motor objects
-AccelStepper rotStepper(rotInterfaceType, stepPin_rot, dirPin_rot);
-AccelStepper inOutStepper(inOutInterfaceType, stepPin_InOut, dirPin_InOut);
-
-// Create a MultiStepper object
-MultiStepper multiStepper;
-
-// Buffer for storing theta-rho pairs
-float buffer[BUFFER_SIZE][2]; // Store theta, rho pairs
-int bufferCount = 0;          // Number of pairs in the buffer
-bool batchComplete = false;
-
-// Track the current position in polar coordinates
-float currentTheta = 0.0; // Current theta in radians
-float currentRho = 0.0;   // Current rho (0 to 1)
-bool isFirstCoordinates = true;
-float totalRevolutions = 0.0; // Tracks cumulative revolutions
-long maxSpeed = 1000;
-float maxAcceleration = 50;
-long interpolationResolution = 0.001;
-float userDefinedSpeed = maxSpeed; // Store user-defined speed
-
-// Running Mode
-int currentMode = MODE_APP; // Default mode is app mode.
-
-// FIRMWARE VERSION
-const char* firmwareVersion = "1.4.0";
-const char* motorType = "TMC2209";
-
-void setup()
-{
-    // Set maximum speed and acceleration
-    rotStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    rotStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    inOutStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    inOutStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    // Add steppers to MultiStepper
-    multiStepper.addStepper(rotStepper);
-    multiStepper.addStepper(inOutStepper);
-
-    // Configure the buttons and potentiometers for Spirograph mode
-    pinMode(buttonPin, INPUT_PULLUP); // Configure button pin with internal pull-up
-    pinMode(A0, INPUT); // Potentiometer 1 input
-    pinMode(A1, INPUT); // Potentiometer 2 input
-
-    // Initialize serial communication
-    Serial.begin(115200);
-    Serial.println("R");
-    homing();
-}
-
-void getVersion()
-{
-    Serial.println("Table: Dune Weaver");
-    Serial.println("Drivers: TMC2209");
-    Serial.println("Version: 1.4.0");
-}
-
-void resetTheta()
-{
-    isFirstCoordinates = true; // Set flag to skip interpolation for the next movement
-    Serial.println("THETA_RESET"); // Notify Python
-}
-
-void loop() {
-    updateModeSwitch(); // Check and handle mode switching
-
-    // Call the appropriate mode function based on the current mode
-    if (currentMode == MODE_SPIROGRAPH) {
-        spirographMode();
-    } else if (currentMode == MODE_APP) {
-        appMode();
-    }
-}
-
-void updateModeSwitch() {
-    // Read the current state of the latching switch
-    bool currentSwitchState = digitalRead(buttonPin);
-    int newMode = currentSwitchState == LOW ? MODE_SPIROGRAPH : MODE_APP;
-
-    if (newMode != currentMode) {
-        handleModeChange(newMode); // Handle mode-specific transitions
-        currentMode = newMode; // Update the current mode
-    }
-}
-
-void handleModeChange(int newMode) {
-    // Print mode switch information
-    if (newMode == MODE_SPIROGRAPH) {
-        Serial.println("Spirograph Mode Active");
-        rotStepper.setMaxSpeed(userDefinedSpeed * 0.5); // Use 50% of user-defined speed
-        inOutStepper.setMaxSpeed(userDefinedSpeed * 0.5);
-        isFirstCoordinates = false;
-    } else if (newMode == MODE_APP) {
-        Serial.println("App Mode Active");
-        rotStepper.setMaxSpeed(userDefinedSpeed); // Restore user-defined speed
-        inOutStepper.setMaxSpeed(userDefinedSpeed);
-        resetTheta();
-    }
-
-    movePolar(currentTheta, 0); // Move to the center
-}
-
-void spirographMode() {
-    static float currentFrequency = 2.95; // Track the current frequency (default value)
-    static float phaseShift = 0.0;       // Track the phase shift for smooth transitions
-
-    // Read potentiometer for frequency adjustment
-    int pot1Value = analogRead(pot1);
-    float newFrequency = mapFloat(pot1Value, 0, 1023, 0.5, 6); // Map to range
-    newFrequency = round(newFrequency * 10) / 10.0;            // Round to one decimal place
-
-    // Force the value to x.95 or x.10 to have a slight variation each revolution
-    if (fmod(newFrequency, 1.0) >= 0.5) {
-        newFrequency = floor(newFrequency) + 0.95; // Round up to x.95
-    } else {
-        newFrequency = floor(newFrequency) + 0.10; // Round down to x.10
-    }
-
-    // Adjust phase shift if frequency changes
-    if (newFrequency != currentFrequency) {
-        phaseShift += currentTheta * (currentFrequency - newFrequency);
-        currentFrequency = newFrequency; // Update the current frequency
-    }
-
-    // Read variation knob to adjust the minimum rho
-    int pot2Value = analogRead(pot2);
-    float minRho = round(mapFloat(pot2Value, 0, 1023, 0, 0.5) * 20) / 20.0; // Minimum rho in steps of 0.05
-
-    // Calculate amplitude and offset for the sine wave
-    float amplitude = (1.0 - minRho) / 2.0; // Half of the oscillation range
-    float offset = minRho + amplitude;      // Center the wave within the range [minRho, 1]
-
-    // Calculate the next target theta
-    float stepSize = maxSpeed * (2 * M_PI / rot_total_steps) / 10; // Smaller steps for finer control
-    float nextTheta = currentTheta + stepSize;
-
-    // Count total revolutions
-    totalRevolutions = (nextTheta / (2 * M_PI));
-
-    // Calculate rho using the adjusted sine wave with phase shift
-    currentRho = offset + amplitude * cos((currentTheta * currentFrequency) + phaseShift);
-    float nextRho = offset + amplitude * cos((nextTheta * currentFrequency) + phaseShift);
-
-    // Move the steppers to the calculated position
-    movePolar(nextTheta, constrain(nextRho, 0, 1));
-
-    // Update the current theta to the new position
-    currentTheta = nextTheta;
-}
-
-float mapFloat(long x, long inMin, long inMax, float outMin, float outMax) {
-    if (inMax == inMin) {
-        Serial.println("Error: mapFloat division by zero");
-        return outMin; // Return the minimum output value as a fallback
-    }
-    return (float)(x - inMin) * (outMax - outMin) / (float)(inMax - inMin) + outMin;
-}
-
-void appMode()
-{
-    // Check for incoming serial commands or theta-rho pairs
-    if (Serial.available() > 0)
-    {
-        String input = Serial.readStringUntil('\n');
-
-        // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA" && input != "GET_VERSION" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
-        {
-            Serial.print("IGNORED: ");
-            Serial.println(input);
-            return;
-        }
-
-        if (input == "GET_VERSION") {
-            getVersion();
-        }
-
-        if (input == "RESET_THETA")
-        {
-            resetTheta(); // Reset currentTheta
-            Serial.println("THETA_RESET"); // Notify Python
-            Serial.println("READY");
-            return;
-        }
-
-        if (input == "HOME")
-        {
-            homing();
-            return;
-        }
-
-
-        if (input.startsWith("SET_SPEED"))
-        {
-            // Parse out the speed value from the command string
-            int spaceIndex = input.indexOf(' ');
-            if (spaceIndex != -1)
-            {
-                String speedStr = input.substring(spaceIndex + 1);
-                float speedPercentage = speedStr.toFloat();
-
-                // Make sure the percentage is valid
-                if (speedPercentage >= 1.0 && speedPercentage <= 100.0)
-                {
-                    // Convert percentage to actual speed
-                    long newSpeed = (speedPercentage / 100.0) * maxSpeed;
-                    userDefinedSpeed = newSpeed;
-
-                    // Set the stepper speeds
-                    rotStepper.setMaxSpeed(newSpeed);
-                    inOutStepper.setMaxSpeed(newSpeed);
-
-                    Serial.println("SPEED_SET");  
-                }
-                else
-                {
-                    Serial.println("INVALID_SPEED");
-                }
-            }
-            else
-            {
-                Serial.println("INVALID_COMMAND");
-            }
-            return;
-        }
-
-        // If not a command, assume it's a batch of theta-rho pairs
-        if (!batchComplete)
-        {
-            int pairIndex = 0;
-            int startIdx = 0;
-
-            // Split the batch line into individual theta-rho pairs
-            while (pairIndex < BUFFER_SIZE)
-            {
-                int endIdx = input.indexOf(";", startIdx);
-                if (endIdx == -1)
-                    break; // No more pairs in the line
-
-                String pair = input.substring(startIdx, endIdx);
-                int commaIndex = pair.indexOf(',');
-
-                // Parse theta and rho values
-                float theta = pair.substring(0, commaIndex).toFloat(); // Theta in radians
-                float rho = pair.substring(commaIndex + 1).toFloat();  // Rho (0 to 1)
-
-                buffer[pairIndex][0] = theta;
-                buffer[pairIndex][1] = rho;
-                pairIndex++;
-
-                startIdx = endIdx + 1; // Move to next pair
-            }
-            bufferCount = pairIndex;
-            batchComplete = true;
-        }
-    }
-
-    // Process the buffer if a batch is ready
-    if (batchComplete && bufferCount > 0)
-    {
-        // Start interpolation from the current position
-        float startTheta = currentTheta;
-        float startRho = currentRho;
-
-        for (int i = 0; i < bufferCount; i++)
-        {
-            if (isFirstCoordinates)
-            {
-                // Directly move to the first coordinate of the new pattern
-                long initialRotSteps = buffer[0][0] * (rot_total_steps / (2.0 * M_PI));
-                rotStepper.setCurrentPosition(initialRotSteps);
-                inOutStepper.setCurrentPosition(inOutStepper.currentPosition() + (totalRevolutions * rot_total_steps / gearRatio));
-
-                currentTheta = buffer[0][0];
-                totalRevolutions = 0;
-                movePolar(buffer[0][0], buffer[0][1]);
-                isFirstCoordinates = false; // Reset the flag after the first movement
-            }
-              else
-              {
-                // Use interpolation for subsequent movements
-                interpolatePath(
-                    startTheta, startRho,
-                    buffer[i][0], buffer[i][1],
-                    interpolationResolution
-                );
-              }
-            // Update the starting point for the next segment
-            startTheta = buffer[i][0];
-            startRho = buffer[i][1];
-        }
-
-        batchComplete = false; // Reset batch flag
-        bufferCount = 0;       // Clear buffer
-        Serial.println("R");
-    }
-}
-
-void homing()
-{
-    Serial.println("HOMING");
-
-    // Move inOutStepper inward for homing
-    inOutStepper.setSpeed(-maxSpeed); // Adjust speed for homing
-    while (true)
-    {
-        inOutStepper.runSpeed();
-        if (inOutStepper.currentPosition() <= -inOut_total_steps * 1.1)
-        { // Adjust distance for homing
-            break;
-        }
-    }
-    inOutStepper.setCurrentPosition(0); // Set home position
-    currentTheta = 0.0;                 // Reset polar coordinates
-    currentRho = 0.0;
-    Serial.println("HOMED");
-}
-
-void movePolar(float theta, float rho)
-{
-    // Define different scaling factors for rho <= 0.5 and rho > 0.5
-    float speedScalingFactorLow = 0.7;  // Scaling factor for rho <= 0.5
-    float speedScalingFactorHigh = 0.4; // Scaling factor for rho > 0.5
-
-    float speedScalingFactor;
-
-    // Determine which factor to use
-    if (rho <= 0.5) {
-        speedScalingFactor = speedScalingFactorLow;
-    } else {
-        speedScalingFactor = speedScalingFactorHigh;
-    }
-
-    // Scale speed dynamically based on rho
-    long dynamicSpeed = maxSpeed * (1.0 + speedScalingFactor * (1.0 - 2.0 * rho));
-
-    // Ensure the speed is within a valid range
-    dynamicSpeed = constrain(dynamicSpeed, 1, maxSpeed);
-
-    // Set stepper speeds dynamically
-    rotStepper.setMaxSpeed(dynamicSpeed);
-    inOutStepper.setMaxSpeed(dynamicSpeed);
-
-    // Convert polar coordinates to motor steps
-    long rotSteps = theta * (rot_total_steps / (2.0 * M_PI)); // Steps for rot axis
-    long inOutSteps = rho * inOut_total_steps;                // Steps for in-out axis
-
-    // Calculate offset for inOut axis
-    float revolutions = theta / (2.0 * M_PI); // Fractional revolutions (can be positive or negative)
-    long offsetSteps = revolutions * rot_total_steps / gearRatio;    // 1600 steps inward or outward per revolution
-
-    // Update the total revolutions to keep track of the offset history
-    totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
-
-    // Apply the offset to the inout axis
-    if (!isFirstCoordinates) {
-        inOutSteps -= offsetSteps;
-    }
-
-    // Define target positions for both motors
-    long targetPositions[2];
-    targetPositions[0] = rotSteps;
-    targetPositions[1] = inOutSteps;
-
-    // Move both motors synchronously
-    multiStepper.moveTo(targetPositions);
-    multiStepper.runSpeedToPosition(); // Blocking call
-
-    // Update the current coordinates
-    currentTheta = theta;
-    currentRho = rho;
-}
-
-void interpolatePath(float startTheta, float startRho, float endTheta, float endRho, float stepSize)
-{
-    // Calculate the total distance in the polar coordinate system
-    float distance = sqrt(pow(endTheta - startTheta, 2) + pow(endRho - startRho, 2));
-    int numSteps = max(1, (int)(distance / stepSize)); // Ensure at least one step
-
-    for (int step = 0; step <= numSteps; step++)
-    {
-        float t = (float)step / numSteps; // Interpolation factor (0 to 1)
-        float interpolatedTheta = startTheta + t * (endTheta - startTheta);
-        float interpolatedRho = startRho + t * (endRho - startRho);
-
-        // Move to the interpolated theta-rho
-        movePolar(interpolatedTheta, interpolatedRho);
-    }
-}

+ 0 - 894
firmware/arduino_code_TMC2209/arduino_code_TMC2209.ino.hex

@@ -1,894 +0,0 @@
-:100000000C948F000C94B7000C94B7000C94B700BC
-:100010000C94B7000C94B7000C94B7000C94B70084
-:100020000C94B7000C94B7000C94B7000C94B70074
-:100030000C94B7000C94B7000C94B7000C94B70064
-:100040000C94160B0C94B7000C94860B0C94600B5C
-:100050000C94B7000C94B7000C94B7000C94B70044
-:100060000C94B7000C94B70005A84CCDB2D44EB98F
-:100070003836A9020C50B9918688083CA6AAAA2A4B
-:10008000BE000000803F4E414E494E495459494EF2
-:1000900046CDCCCC3D0AD7233C17B7D13877CC2BF3
-:1000A000329595E6241FB14F0A000020410000C898
-:1000B0004200401C4620BCBE4CCA1B0E5AAEC59D19
-:1000C0007400000000230026002900000000002426
-:1000D0000027002A0000000000250028002B000453
-:1000E00004040404040404020202020202030303DF
-:1000F00003030301020408102040800102040810D9
-:100100002001020408102000000008000201000085
-:10011000030407000000000000000000B80B1124D9
-:100120001FBECFEFD8E0DEBFCDBF23E0A6E1B2E037
-:1001300001C01D92A83CB207E1F712E0A0E0B1E0D7
-:10014000E6EAF6E302C005900D92A631B107D9F7B1
-:1001500010E0CFE8D0E004C02197FE010E94FB1818
-:10016000CE38D107C9F70E94CA0C0C94511B0C94CD
-:100170000000833081F028F4813099F08230A9F0BA
-:1001800008958730A9F08830C9F08430B1F48091A7
-:1001900080008F7D03C0809180008F7780938000E6
-:1001A000089584B58F7784BD089584B58F7DFBCF86
-:1001B0008091B0008F778093B00008958091B00057
-:1001C0008F7DF9CF1F93CF93DF93282F30E0F90174
-:1001D000E95FFE4F8491F901ED50FF4FD491F90191
-:1001E000E152FF4FC491CC23A9F0162F81110E9438
-:1001F000B900EC2FF0E0EE0FFF1FEB52FF4FA5917F
-:10020000B4918FB7F894EC91111108C0D095DE230A
-:10021000DC938FBFDF91CF911F910895DE2BF8CF34
-:10022000CF93DF9390E0FC01ED50FF4F249181527A
-:100230009F4FFC0184918823C9F090E0880F991F9B
-:10024000FC01E553FF4FA591B491FC01EB52FF4F28
-:10025000C591D49161110DC09FB7F8948C912095F0
-:1002600082238C938881282328839FBFDF91CF919D
-:100270000895623051F49FB7F8943C91822F809595
-:1002800083238C93E8812E2BEFCF8FB7F894EC91DA
-:100290002E2B2C938FBFEACF8E50806480937C00EE
-:1002A00080917A00806480937A0080917A0086FD44
-:1002B000FCCF80917800909179000895AF92BF9221
-:1002C000CF92DF92EF92FF920F931F93CF93DF9322
-:1002D0006C017B018B01040F151FEB015E01AE1851
-:1002E000BF08C017D10759F06991D601ED91FC9173
-:1002F0000190F081E02DC6010995892B79F7C501A0
-:10030000DF91CF911F910F91FF90EF90DF90CF90F1
-:10031000BF90AF900895FC01538D448D252F30E0A0
-:10032000842F90E0821B930B541710F0CF96089502
-:1003300001970895FC01918D828D981761F0A28D2F
-:10034000AE0FBF2FB11D5D968C91928D9F5F9F73F5
-:10035000928F90E008958FEF9FEF08952FB7F89454
-:100360008091800290918102A0918202B0918302DB
-:100370002FBF80938C0290938D02A0938E02B09336
-:100380008F0284E892E00E949A0197FF26C02FB75F
-:10039000F8948091800290918102A0918202B091A4
-:1003A00083022FBF40918C0250918D0260918E028A
-:1003B00070918F02841B950BA60BB70B409188029E
-:1003C0005091890260918A0270918B02841795077F
-:1003D000A607B707B0F28FEF9FEF0895FC01918D4C
-:1003E000828D981731F0828DE80FF11D858D90E098
-:1003F00008958FEF9FEF0895FC01918D228D892F35
-:1004000090E0805C9F4F821B91098F73992708951C
-:1004100084E892E00E94FC0121E0892B09F420E0AD
-:10042000822F089580E090E0892B29F00E94080235
-:1004300081110C9400000895FC01A48DA80FB92F20
-:10044000B11DA35ABF4F2C91848D90E001968F73FC
-:100450009927848FA689B7892C93A089B1898C911B
-:10046000837080648C93938D848D981306C002886A
-:10047000F389E02D80818F7D80830895EF92FF9234
-:100480000F931F93CF93DF93EC0181E0888F9B8DB7
-:100490008C8D98131AC0E889F989808185FF15C071
-:1004A0009FB7F894EE89FF896083E889F989808194
-:1004B0008370806480839FBF81E090E0DF91CF9163
-:1004C0001F910F91FF90EF900895F62E0B8D10E085
-:1004D0000F5F1F4F0F731127E02E8C8D8E110CC0F4
-:1004E0000FB607FCFACFE889F989808185FFF5CF3F
-:1004F000CE010E941C02F1CFEB8DEC0FFD2FF11D00
-:10050000E35AFF4FF0829FB7F8940B8FEA89FB897B
-:1005100080818062CFCFCF93DF93EC01888D8823D9
-:10052000B9F0AA89BB89E889F9898C9185FD03C056
-:10053000808186FD0DC00FB607FCF7CF8C9185FF3B
-:10054000F2CF808185FFEDCFCE010E941C02E9CF62
-:10055000DF91CF9108954F925F926F927F92AF9209
-:10056000BF92CF92DF92EF92FF920F931F93CF93A0
-:10057000DF93EC015A018B014C8C5D8C6E8C7F8C6F
-:1005800073016201F7FAF094F7F8F0949A01AB0165
-:10059000C701B6010E942B18181664F09501A80136
-:1005A000C301B2010E94AE147301620187FD02C053
-:1005B0006501780120E030E0A901C701B6010E9481
-:1005C000AE14811117C01B821C821D821E82C88E30
-:1005D000D98EEA8EFB8EDF91CF911F910F91FF9004
-:1005E000EF90DF90CF90BF90AF907F906F905F9033
-:1005F0004F900895A701960160E074E284E799E4C2
-:100600000E9498169F770E9411176B837C838D83BD
-:100610009E8311E020E030E0A901C701B6010E94ED
-:100620002B1818160CF010E01A83D1CFCF92DF925E
-:10063000EF92FF92CF93DF93EC013FB7F894809154
-:100640007B0290917C02A0917D02B0917E0226B542
-:10065000A89B05C02F3F19F00196A11DB11D3FBFFA
-:10066000BA2FA92F982F88276C017D01C20ED11CAB
-:10067000E11CF11C42E0CC0CDD1CEE1CFF1C4A9579
-:10068000D1F788A599A5AAA5BBA5B701A601481BC6
-:10069000590B6A0B7B0B8B819C81AD81BE81481706
-:1006A00059076A077B0748F188899989AA89BB8914
-:1006B0002A812223F1F00196A11DB11D888B998B0F
-:1006C000AA8BBB8B488959896A897B89E881F98122
-:1006D0000484F585E02DCE010995C8A6D9A6EAA621
-:1006E000FBA681E0DF91CF91FF90EF90DF90CF905C
-:1006F00008950197A109B109E1CF80E0F3CFCF932D
-:10070000DF93FC012781222359F1EC0161E0808510
-:100710000E94100161E089850E9410018F81843060
-:1007200011F08830B1F461E08A850E94100161E027
-:100730008B850E9410018FA58F3F91F061E00E9490
-:1007400010016EA581E068278FA5DF91CF910C94F1
-:10075000E200833011F0863071F761E08A85E9CFDD
-:10076000DF91CF910895CF93DF93FC01278122235E
-:10077000A9F0EC010190F081E02D0284F385E02DD9
-:1007800060E009958FA58F3F49F061E00E9410015C
-:100790006EA58FA5DF91CF910C94E200DF91CF91F0
-:1007A0000895DC01ED91FC91228533854770552732
-:1007B0006627772741505109610971094730510572
-:1007C0006105710560F4FA01E851FC4F0C94FB18C7
-:1007D000F203F403F603F803FA03FC03FE0361E0FB
-:1007E000F901099465E0FCCF64E0FACF66E0F8CF48
-:1007F00062E0F6CF6AE0F4CF68E0F2CF69E0F0CFD4
-:10080000CF93DF93EC01CB01BA0126E030E040E06A
-:1008100050E00E94DC18623071058105910589F174
-:100820006CF46115710581059105D1F06130710598
-:1008300081059105F9F0DF91CF910895643071053C
-:100840008105910561F124F16530710581059105FE
-:1008500091F7E881F9810284F385E02D66E006C016
-:10086000E881F9810284F385E02D64E0CE01DF9117
-:10087000CF910994E881F9810284F385E02D65E048
-:10088000F5CFE881F9810284F385E02D61E0EECFB8
-:10089000E881F9810284F385E02D63E0E7CFE88108
-:1008A000F9810284F385E02D62E0E0CFDC01ED9177
-:1008B000FC910284F385E02D437055276627772746
-:1008C000423051056105710571F0433051056105F4
-:1008D000710559F0413051056105710511F065E070
-:1008E000099466E0FDCF6AE0FBCF69E0F9CFCF93D2
-:1008F000DF93EC01CB01BA0123E030E040E050E0AF
-:100900000E94DC18613071058105910599F0623013
-:10091000710581059105A9F0672B682B692BC1F43E
-:10092000E881F9810284F385E02D64E0CE01DF9156
-:10093000CF910994E881F9810284F385E02D61E08B
-:10094000F5CFE881F9810284F385E02D62E0EECFF6
-:10095000DF91CF910895DC01ED91FC910284F38544
-:10096000E02D4370552766277727423051056105F2
-:10097000710571F0433051056105710559F0413041
-:1009800051056105710511F062E0099463E0FDCF46
-:1009900061E0FBCF60E0F9CFCF93DF93EC01E8811A
-:1009A000F9810284F385E02D8A8160E0811162E0A3
-:1009B000CE010995E881F9810284F385E02D8A81D1
-:1009C00061E0811163E0CE0109958CA59DA582307F
-:1009D000910538F0880F991F880F991F0597019787
-:1009E000F1F7E881F9810284F385E02D8A8160E0E6
-:1009F000811162E0CE01DF91CF910994CF93DF9313
-:100A0000EC0120E030E0A901688D798D8A8D9B8D05
-:100A10000E942B1818162CF4E8A9F9A9DF91CF91A0
-:100A20000994EAA9FBA9FACFCF92DF92EF92FF9245
-:100A30000F931F93CF93DF93DC011796CC91C430B3
-:100A400039F0C83059F1C33019F0C63049F1C2E06D
-:100A50008C01085F1F4FF12CE12CC62ED12CD1E068
-:100A6000F8016481C6010E2C02C0959587950A9401
-:100A7000E2F780FD6D270F5F1F4F80810E94E2002B
-:100A8000BFEFEB1AFB0AEC1658F3DF91CF911F91E1
-:100A90000F91FF90EF90DF90CF900895C4E0D8CFF2
-:100AA000C3E0D6CFDC011796EC911797E93008F038
-:100AB00038C0F0E0E25AFA4F0C94FB1867056D0558
-:100AC000730579057F059105850591058B05ED91E8
-:100AD000FC910684F785E02D0994ED91FC91008846
-:100AE000F189E02DF9CFED91FC910288F389E02D99
-:100AF000F3CFED91FC910488F589E02DEDCFED91D8
-:100B0000FC910688F789E02DE7CFED91FC91008CF0
-:100B1000F18DE02DE1CFED91FC91028CF38DE02D74
-:100B2000DBCF08954F925F926F927F928F929F9248
-:100B3000AF92BF92CF92DF92EF92FF920F931F93EB
-:100B4000CF93DF93EC01CC88DD88EE88FF8888891D
-:100B50009989AA89BB89C81AD90AEA0AFB0A688D49
-:100B6000798D8A8D9B8D9B01AC010E9414154B01E0
-:100B70005C0168A179A18AA19BA19B01AC010E94A3
-:100B800027169B01AC01C501B4010E9498160E9472
-:100B90000A17C114D104E104F10409F0B6C06230AF
-:100BA0007105810591050CF0D0C01B821C821D824D
-:100BB0001E82188E198E1A8E1B8E1CAA1DAA1EAAA2
-:100BC0001FAAC12CD12C7601C701B601DF91CF91AC
-:100BD0001F910F91FF90EF90DF90CF90BF90AF905B
-:100BE0009F908F907F906F905F904F900895101618
-:100BF000110612061306B4F46C157D058E059F05CB
-:100C00001CF42A812111A1C09B01AC0188279927DE
-:100C1000DC01821B930BA40BB50B8CAB9DABAEAB75
-:100C2000BFAB93C0011511052105310509F48DC035
-:100C30006C157D058E059F050CF087C08A81882381
-:100C400009F483C030952095109501951F4F2F4FC3
-:100C50003F4F0CAB1DAB2EAB3FAB77C00115110561
-:100C60002105310509F471C08824992454018C1898
-:100C70009D08AE08BF08681579058A059B050CF02C
-:100C800064C08A81811161C0DDCFCCACDDACEEAC3B
-:100C9000FFACA7019601C701B6010E9427162B01E0
-:100CA0003C01C501B4010E94B51420E030E040E8E9
-:100CB00050E40E94141520E030E040E85FE30E9419
-:100CC00027169B01AC01C301B2010E9498169B013B
-:100CD000AC01C701B6010E9426163B016C01FE0162
-:100CE000E05CFF4FE080F180028113819701A80151
-:100CF0000E942B18181614F473018601C701D8013D
-:100D00008CAF9DAFAEAFBFAF3AC00CA91DA92EA945
-:100D10003FA91C141D041E041F040CF468CF1016F8
-:100D20001106120613060CF099CF0027112798011F
-:100D30000C191D092E093F096017710782079307D7
-:100D40000CF062CF2A8121115FCF8CA89DA8AEA89C
-:100D5000BFA881149104A104B10409F096CF88AD15
-:100D600099ADAAADBBAD8CAF9DAFAEAFBFAF81E0CB
-:100D70001C141D041E041F040CF080E08A833FEF46
-:100D8000831A930AA30AB30A8CAA9DAAAEAABFAA81
-:100D90008CAC9DACAEACBFACC501B4010E941117C8
-:100DA0006B017C01CB82DC82ED82FE82A501940185
-:100DB00060E074E284E799E40E949816688F798F66
-:100DC0008A8F9B8F2A812111FFCE9058688F798F4F
-:100DD0008A8F9B8FF9CECF92DF92EF92FF920F9383
-:100DE0001F93CF93DF93EC016A017B0120E030E099
-:100DF000A901CB01B6010E94AE1487FF04C0F7FA27
-:100E0000F094F7F8F094A70196016C8D7D8D8E8D8E
-:100E10009F8D0E94AE14882309F446C0CC8EDD8ECF
-:100E2000EE8EFF8E8E01005C1F4FA701960160E0E1
-:100E300074E284E799E40E949816F8016083718354
-:100E4000828393838CA99DA9AEA9BFA91816190600
-:100E50001A061B064CF5688D798D8A8D9B8D9B013A
-:100E6000AC010E9414156B017C0168A179A18AA1D3
-:100E70009BA19B01AC010E9427169B01AC01C701FD
-:100E8000B6010E9498160E940A176CAB7DAB8EAB20
-:100E90009FABCE01DF91CF911F910F91FF90EF900B
-:100EA000DF90CF900C949205DF91CF911F910F911D
-:100EB000FF90EF90DF90CF90089508952F923F928A
-:100EC0004F925F926F927F928F929F92AF92BF925A
-:100ED000CF92DF92EF92FF920F931F93CF93DF9306
-:100EE000CDB7DEB762970FB6F894DEBF0FBECDBFA9
-:100EF0006B017C012D873E874F87588B2AEA37E2AA
-:100F00004FE155E40E9414150E940A172B013C0181
-:100F100020E030E044EB55E46D857E858F8598892F
-:100F20000E9414150E940A174B015C0120912202B5
-:100F3000309123024091240250912502C701B6014D
-:100F40000E9426162BED3FE049EC50E40E949816D3
-:100F50009B01AC0160911A0270911B0280911C02EE
-:100F600090911D020E94271660931A0270931B0233
-:100F700080931C0290931D028091040181111AC07C
-:100F80002BED3FE049EC50E4C701B6010E949816F2
-:100F900020E030E04AE756E40E94141520E030E0FB
-:100FA00040E251E40E9498160E940A17861A970A96
-:100FB000A80AB90A49825A826B827C828D829E82FB
-:100FC000AF82B886209039039E012F5F3F4F2901E1
-:100FD00045E253E05A874987912C312C00E010E01C
-:100FE000812C2914C9F1D2016D917D918D919D9132
-:100FF0002D01E985FA85A190B190FA87E987D5019D
-:1010000050962D913D914D915C915397621B730BBE
-:10101000840B950B97FF07C090958095709561950F
-:101020007F4F8F4F9F4F0E94B514F501248D358D52
-:10103000468D578D0E9498163B015C01232D302F61
-:10104000412F582D0E942B18181624F4362C072DEA
-:101050001A2D8B2C9394C5CF20E030E0A901632D8D
-:10106000702F812F982D0E942B1818160CF06CC031
-:10107000912C80913903981608F066C0892D90E074
-:10108000FC01EE0FFF1FEE0FFF1F21E030E02C0FE1
-:101090003D1FE20FF31F4080518062807380AC01DE
-:1010A000440F551F5A8B498BFA01EB5DFC4FA08012
-:1010B000B180F50180899189A289B389A301920148
-:1010C000281B390B4A0B5B0BCA01B9010E94B514EE
-:1010D000232D302F412F582D0E94981669877A872B
-:1010E0008B879C87F50184899589A689B7894816DD
-:1010F00059066A067B0661F0448A558A668A778AB1
-:101100000190F081E02D0084F185E02DC501099565
-:10111000E989FA89EB5DFC4FA080B180F501208D53
-:10112000318D428D538D69857A858B859C850E9492
-:10113000AE14882339F049855A856B857C85C501B5
-:101140000E94AB02939495CF80E010E09091390318
-:10115000191720F5E12FF0E0EE0FFF1FEB5DFC4FBC
-:101160000190F081E02D84889588A688B788408911
-:1011700051896289738984169506A606B70661F0BF
-:1011800083819481A581B681892B8A2B8B2B19F0C1
-:10119000CF010E94160381E01F5FD8CF8111D4CF09
-:1011A000C0922202D0922302E0922402F092250201
-:1011B0002D853E854F85588920931E0230931F024E
-:1011C000409320025093210262960FB6F894DEBF3E
-:1011D0000FBECDBFDF91CF911F910F91FF90EF9088
-:1011E000DF90CF90BF90AF909F908F907F906F9047
-:1011F0005F904F903F902F900895FC010190002048
-:10120000E9F73197AF01481B590BBC0184E892E024
-:101210000C945E01CF93DF930E94FD08EC018DE3F7
-:1012200091E00E94FD088C0F9D1FDF91CF910895E2
-:1012300080E491E00E940A0920E030E04AE754ECA3
-:101240006091520370915303809154039091550320
-:101250000E94AE14882341F040E050E06AE774EC4D
-:101260008AE393E00E94AB0280913D0390913E039C
-:10127000A0913F03B0914003892B8A2B8B2B21F047
-:101280008AE393E00E94160360914A0370914B0336
-:1012900080914C0390914D030E94B51420E030E002
-:1012A00046EC55EC0E94AE141816F4F210924A0364
-:1012B00010924B0310924C0310924D0310924E0368
-:1012C00010924F03109250031092510310926E032C
-:1012D00010926F03109270031092710310923D03ED
-:1012E00010923E0310923F0310924003109252035B
-:1012F000109253031092540310925503109222023D
-:1013000010922302109224021092250210921E02C3
-:1013100010921F02109220021092210287E491E0A5
-:101320000C940A094F925F926F927F928F929F92D4
-:10133000AF92BF92CF92DF92EF92FF92CF93DF9363
-:10134000EC016A017B0120E030E0A901CB01B6018C
-:101350000E94AE1487FF04C0F7FAF094F7F8F094F7
-:1013600088A099A0AAA0BBA0A7019601C501B401BD
-:101370000E94AE14882309F44DC06CA97DA98EA9E2
-:101380009FA90E94B5142B013C01A7019601C5013C
-:10139000B4010E9498169B01AC01C301B2010E94E6
-:1013A00014150E940A176CAB7DAB8EAB9FABA701E7
-:1013B000960160E070E080E090E40E9498160E9440
-:1013C0005A1826E53EE04DE25FE30E94141520E046
-:1013D00034E244E759E40E94141568AF79AF8AAF4C
-:1013E0009BAFC8A2D9A2EAA2FBA2E881F98100843E
-:1013F000F185E02DCE01DF91CF91FF90EF90DF904E
-:10140000CF90BF90AF909F908F907F906F905F90A4
-:101410004F900994DF91CF91FF90EF90DF90CF90A4
-:10142000BF90AF909F908F907F906F905F904F9004
-:101430000895FC0124813581232B39F421E0FB013F
-:101440008081882349F020E007C0808191810E943B
-:10145000191B21E0892BB9F7822F0895FC018081A7
-:101460009181009711F00C94BF1908950C94BF1945
-:10147000FB0144815581FC01248135812417350706
-:1014800078F080819181009759F0FB016081718132
-:101490006115710529F00E94291B21E0892B09F0B3
-:1014A00020E0822F08950F931F93CF93DF93EC01D9
-:1014B00088819981009759F02A813B812617370747
-:1014C00030F081E0DF91CF911F910F9108958B0152
-:1014D0006F5F7F4F0E94481A009759F09983888365
-:1014E0001B830A832C813D81232B59F7FC01108239
-:1014F000E8CF80E0E7CFEF92FF920F931F93CF9357
-:10150000DF93EC017B018A01BA010E94530A288112
-:101510003981811114C02115310519F0C9010E94CA
-:10152000BF19198218821D821C821B821A82CE0169
-:10153000DF91CF911F910F91FF90EF9008951D8340
-:101540000C83B701C9010E94221BF1CFFC0111825B
-:1015500010821382128215821482FB0101900020F6
-:10156000E9F73197AF01461B570B0C947B0AAF92FA
-:10157000BF92CF92DF92EF92FF920F931F93CF9380
-:10158000DF93EC016B015A0179012417350720F430
-:101590008B2D5901E42EF82E6FE371E0CE010E94ED
-:1015A000A60AD60114960D911C91A016B10628F535
-:1015B000E016F10608F48701D601ED91FC91119730
-:1015C000E00FF11FF08010826D917C916A0D7B1D00
-:1015D00061157105F1F0FB0101900020E9F73197E9
-:1015E000AF01461B570BCE010E947B0AF60180819A
-:1015F0009181080F191FD801FC92CE01DF91CF9184
-:101600001F910F91FF90EF90DF90CF90BF90AF9020
-:10161000089588819981009711F00E94BF1919825D
-:1016200018821D821C821B821A82E0CF1F920F92A9
-:101630000FB60F9211242F933F938F939F93AF93E5
-:10164000BF938091800290918102A0918202B0911B
-:10165000830230917F0223E0230F2D3758F5019646
-:10166000A11DB11D20937F0280938002909381027F
-:10167000A0938202B093830280917B0290917C02BE
-:10168000A0917D02B0917E020196A11DB11D8093B3
-:101690007B0290937C02A0937D02B0937E02BF9167
-:1016A000AF919F918F913F912F910F900FBE0F900F
-:1016B0001F90189526E8230F0296A11DB11DD2CFC9
-:1016C0001F920F920FB60F9211242F933F934F93B7
-:1016D0005F936F937F938F939F93AF93BF93EF939A
-:1016E000FF9384E892E00E941C02FF91EF91BF916A
-:1016F000AF919F918F917F916F915F914F913F91AA
-:101700002F910F900FBE0F901F9018951F920F9260
-:101710000FB60F9211242F938F939F93EF93FF9304
-:10172000E0919402F09195028081E0919A02F0910B
-:101730009B0282FD1BC0908180919D028F5F8F7301
-:1017400020919E02821741F0E0919D02F0E0EC575B
-:10175000FD4F958F80939D02FF91EF919F918F9107
-:101760002F910F900FBE0F901F9018958081F4CF8E
-:101770008F929F92AF92BF92CF92DF92EF92FF92A1
-:101780000F931F93CF93DF93E4E8F2E0138212826A
-:1017900088EE93E0A0E0B0E084839583A683B783CE
-:1017A0008FE091E09183808385EC90E0958784873A
-:1017B00084EC90E09787868780EC90E0918B808B1B
-:1017C00081EC90E0938B828B82EC90E0958B848B04
-:1017D00086EC90E0978B868B118E128E138E148E72
-:1017E000EEE7F3E001E211E01183008388248394A3
-:1017F0008782108A118A128A138A148A158A168A95
-:10180000178A108E118E128E138E148E158E168ED0
-:10181000178E10A211A212A213A2C12CD12C80E803
-:10182000E82E8FE3F82EC4A2D5A2E6A2F7A2138277
-:10183000148215821682C1E0D0E0D5A7C4A79924EE
-:101840009A9497A610A611A612A613A682E08087E6
-:1018500095E0B92EB18624E0A22EA286B38616A604
-:1018600014AA15AA16AA17AA10AE11AE12AE13AE7C
-:1018700014AE15AE16AE17AEC092BE03D092BF0323
-:10188000E092C003F092C103128214861586168678
-:101890001786CF010E947F03B701A6018EE793E070
-:1018A0000E949209B701A6018EE793E00E94EB0621
-:1018B000EAE3F3E0118300838782108A118A128A97
-:1018C000138A148A158A168A178A108E118E128E20
-:1018D000138E148E158E168E178E10A211A212A2C0
-:1018E00013A2C4A2D5A2E6A2F7A213821482158283
-:1018F0001682D5A7C4A797A610A611A612A613A64E
-:1019000083E0808786E08187A286B38616A614AA24
-:1019100015AA16AA17AA10AE11AE12AE13AE14AEC7
-:1019200015AE16AE17AEC0927A03D0927B03E0924A
-:101930007C03F0927D031282148615861686178624
-:10194000CF010E947F03B701A6018AE393E00E94C2
-:101950009209B701A6018AE393E00E94EB06109278
-:10196000390380E090E0AAE7B4E4809321039093E8
-:101970002203A0932303B0932403DF91CF911F91FF
-:101980000F91FF90EF90DF90CF90BF90AF909F901E
-:101990008F900895CF93DF93CDB7DEB7A6970FB69C
-:1019A000F894DEBF0FBECDBF789484B5826084BD4D
-:1019B00084B5816084BD85B5826085BD85B5816053
-:1019C00085BD80916E00816080936E0010928100D1
-:1019D000809181008260809381008091810081608C
-:1019E000809381008091800081608093800080914D
-:1019F000B10084608093B1008091B00081608093D9
-:101A0000B00080917A00846080937A0080917A009F
-:101A1000826080937A0080917A00816080937A005E
-:101A200080917A00806880937A001092C10040E033
-:101A300050E06AE774E48EE793E00E94EB0640E032
-:101A400050E068E472E48EE793E00E94920940E07F
-:101A500050E06AE774E48AE393E00E94EB0640E01A
-:101A600050E068E472E48AE393E00E949209E09116
-:101A70003903EA3068F481E08E0F80933903F0E097
-:101A8000EE0FFF1FEB5DFC4F8EE793E091838083A9
-:101A9000E0913903EA3068F481E08E0F80933903D6
-:101AA000F0E0EE0FFF1FEB5DFC4F8AE393E09183C4
-:101AB000808362E08BE00E94100160E08EE00E9473
-:101AC000100160E08FE00E941001E0919402F0911B
-:101AD000950282E08083E0919002F0919102108261
-:101AE000E0919202F091930280E1808310929C0237
-:101AF000E0919802F091990286E08083E09196024D
-:101B0000F0919702808180618083E0919602F0914C
-:101B10009702808188608083E0919602F09197021D
-:101B2000808180688083E0919602F09197028081A5
-:101B30008F7D80838DE491E00E940A090E9418093C
-:101B4000E2E1F1E08491EEEFF0E00491EAEEF0E002
-:101B50001491112309F4C4C181110E94B900E12F2D
-:101B6000F0E0EE0FFF1FEF53FF4FA591B4918C9162
-:101B7000082391E080E009F090E0092F182F809170
-:101B8000790290917A0280179107B9F1013011051D
-:101B900009F0A9C18FE491E00E940A0920E030E039
-:101BA00040E05FE360912103709122038091230361
-:101BB000909124030E9414156B017C01BC01A601C5
-:101BC0008EE793E00E94EB06B701A6018AE393E05B
-:101BD0000E94EB0610920401609122027091230290
-:101BE000809124029091250220E030E0A9010E941A
-:101BF0005E0710937A0200937902809179029091A6
-:101C00007A028130910509F09BC18FE00E944C015E
-:101C1000BC01990F880B990B0E94B51420E030E0AD
-:101C200040EB50E40E94141520E030EC4FE754E400
-:101C30000E94981620E030E040E05FE30E94271603
-:101C400020E030E040E251E40E9414150E94301878
-:101C500020E030E040E251E40E9498166B017C01E4
-:101C600020E030E040E85FE30E94621720E030E0CF
-:101C700040E05FE30E942B1887FD55C1C701B60104
-:101C80000E94401723E333E343E75FE30E942716F4
-:101C90006B017C018090000190900101A0900201F5
-:101CA000B0900301409022025090230260902402E1
-:101CB00070902502AC019B01C501B4010E94AE14D5
-:101CC000882331F1A7019601C501B4010E942616AF
-:101CD000A30192010E9414159B01AC0160911602B0
-:101CE0007091170280911802909119020E94271694
-:101CF0006093160270931702809318029093190252
-:101D0000C0920001D0920101E0920201F092030121
-:101D10008EE00E944C01BC01990F880B990B0E9428
-:101D2000B51420E030E040E05FE30E94141520E0AD
-:101D300030EC4FE754E40E94981620E030E0A9010F
-:101D40000E94271620E030E040EA51E40E9414157A
-:101D50000E94301820E030E040EA51E40E949816DA
-:101D60006B017C01AC019B0160E070E080E89FE3C7
-:101D70000E94261620E030E040E05FE30E94141548
-:101D80004B015C01AC019B01C701B6010E94271603
-:101D90006B8F7C8F8D8F9E8F2CE739ED40E25DE35A
-:101DA000C301B2010E9427166B017C012BED3FE0BD
-:101DB00049EC50E40E94981660931A0270931B023B
-:101DC00080931C0290931D0280910001909101016B
-:101DD000A0910201B09103018B8B9C8BAD8BBE8BCC
-:101DE0008091160290911702A0911802B0911902E9
-:101DF0008F8B988FA98FBA8FA30192016B897C89F1
-:101E00008D899E890E9414152F89388D498D5A8D90
-:101E10000E9427160E9493169B01AC01C501B401D4
-:101E20000E9414152B8D3C8D4D8D5E8D0E942716C2
-:101E300060931E0270931F028093200290932102F0
-:101E40002B893C894D895E89C701B6010E94141512
-:101E50002F89388D498D5A8D0E9427160E9493161E
-:101E60009B01AC01C501B4010E9414152B8D3C8D62
-:101E70004D8D5E8D0E9427164B015C0120E030E005
-:101E8000A9010E94AE1487FD57C020E030E040E871
-:101E90005FE3C501B4010E942B18181634F4812C9D
-:101EA000912C30E8A32E3FE3B32EA5019401C70186
-:101EB000B6010E945E07C0922202D0922302E092F5
-:101EC0002402F092250280E090E0892B09F438CEBC
-:101ED0000E940802882309F433CE0E94000030CE0D
-:101EE00001E010E04CCE86E691E00E940A09C09025
-:101EF0002103D0902203E0902303F0902403B70144
-:101F0000A6018EE793E00E94EB06B701A6018AE3E3
-:101F100093E00E94EB0681E08093040186E791E064
-:101F20000E940A0959CEC701B6010E9440172DEC44
-:101F30003CEC4CEC5DE3AACE812C912C5401B5CF46
-:101F4000892B09F684E892E00E94FC011816190614
-:101F50000CF0F7C16FE371E0CE010D960E94A60A66
-:101F60000E94AE0197FD1EC08A309105D9F0898389
-:101F70001A8209891A890F5F1F4FB801CE010D9689
-:101F80000E94530A882361F32D853E8589899A89A9
-:101F9000BE016F5F7F4F820F931F0E94221B1A8B1F
-:101FA000098BDECF62E871E0CE010D960E94190A1E
-:101FB000811162C067E871E0CE010D960E94190A96
-:101FC00081115AC063E971E0CE010D960E94190A91
-:101FD000811152C06FE971E0CE0101960E94A60AFC
-:101FE000BE016F5F7F4FCE010D960E94380A10E050
-:101FF000811127C069EA71E0CE0107960E94A60A06
-:1020000029893A894B855C852417350708F42FC345
-:102010008D859E85009709F42AC36F8178856115A7
-:10202000710509F424C3241B350B820F931F0E94F2
-:10203000191B11E0892B09F410E0CE0107960E94CC
-:102040002E0ACE0101960E942E0A1123A9F08BEAD6
-:1020500091E00E94FD0849895A896D857E8584E852
-:1020600092E00E945E018DE391E00E94FD08CE01A6
-:102070000D960E942E0A27CF63E971E0CE010D96DE
-:102080000E94190A882361F085EB91E00E940A09F9
-:1020900088EC91E00E940A0989ED91E00E940A090A
-:1020A00067E871E0CE010D960E94190A882381F03D
-:1020B00081E08093040186E791E00E940A0986E7A7
-:1020C00091E00E940A0988EE91E00E940A09CFCFB0
-:1020D00062E871E0CE010D960E94190A882319F07A
-:1020E0000E941809C4CF6FE971E0CE0101960E94E9
-:1020F000A60ABE016F5F7F4FCE010D960E94380A7F
-:10210000182FCE0101960E942E0A112309F473C0E4
-:1021100009891A890115110509F46AC0ED84FE8444
-:1021200060E270E0C7010E940E1B009709F460C0D6
-:10213000AC014E195F094F3F540709F459C04F5F76
-:102140005F4F9801BE01635F7F4FCE0101960E94F1
-:10215000B70A89819A81009709F447C00E94391310
-:102160006B017C0120E030E040E85FE30E942B1827
-:1021700087FD3BC020E030E048EC52E4C701B601E7
-:102180000E94AE1418168CF120E030E048EC52E4C6
-:10219000C701B6010E94981620E030E04AE754E4F7
-:1021A0000E9414150E940A170E94B5146B017C014D
-:1021B000C0922103D0922203E0922303F0922403E1
-:1021C000BC01A6018EE793E00E94EB06B701A601D1
-:1021D0008AE393E00E94EB068EEE91E00E940A09EA
-:1021E000CE0101960E942E0A42CF88EF91E0F6CFF1
-:1021F00086E092E06ACF8091780281119EC028E249
-:10220000222E22E0322E10E000E0D12CC12C69EA0F
-:1022100071E0CE0101960E94A60A89899A89081761
-:10222000190770F48D849E8469817A81C401800FBE
-:10223000911F0E94371B7C01E818F908892B19F4BB
-:10224000EE24EA94FE2CCE0101960E942E0AAFEFF6
-:10225000EA16FA0609F46AC09701A801BE01635F95
-:102260007F4FCE0107960E94B70A8B859C85892BEC
-:1022700061F08F8098846CE270E0C4010E940E1BB4
-:102280008C0108191909892B11F40FEF1FEF980120
-:1022900050E040E0BE01695F7F4FCE0101960E9491
-:1022A000B70A89819A81412C512C3201009721F083
-:1022B0000E9439132B013C01CE0101960E942E0A87
-:1022C0002B853C85A8014F5F5F4FBE01695F7F4F43
-:1022D000CE0101960E94B70A89819A81812C912CA6
-:1022E0005401009721F00E9439134B015C01CE018B
-:1022F00001960E942E0AF10140825182628273820D
-:1023000084829582A682B782BFEFCB1ADB0A87014F
-:102310000F5F1F4FCE0107960E942E0AE8E02E0E97
-:10232000311CFAE0CF16D10409F071CFD092270208
-:10233000C092260281E080937802CE010D960E9421
-:102340002E0A80917802882309F4BDCD809126025F
-:1023500090912702181619060CF0B5CD8091220233
-:1023600090912302A0912402B09125028B8B9C8B2B
-:10237000AD8BBE8B80911E0290911F02A091200216
-:10238000B09121028F8B988FA98FBA8F88E2682E27
-:1023900082E0782E512C412C8091260290912702C8
-:1023A000481659060CF058C180910401882309F49D
-:1023B000C4C0C0902802D0902902E0902A02F09078
-:1023C0002B022AEA37E24FE155E4C701B6010E9429
-:1023D00014150E940A1760938E0370938F038093E5
-:1023E000900390939103609392037093930380936F
-:1023F0009403909395031092B2031092B30310923A
-:10240000B4031092B50310928103109282031092CC
-:1024100083031092840310929603109297031092F4
-:1024200098031092990320E030E04AE756E4609167
-:102430001A0270911B0280911C0290911D020E9451
-:10244000141520E030E040E251E40E9498164B0160
-:102450005C0160914A0370914B0380914C03909111
-:102460004D030E94B5149B01AC01C501B4010E944B
-:1024700027160E940A1760934A0370934B038093B8
-:102480004C0390934D0360934E0370934F038093DE
-:1024900050039093510310926E0310926F031092A9
-:1024A00070031092710310923D0310923E0310923C
-:1024B0003F03109240031092520310925303109264
-:1024C000540310925503C0922202D0922302E0924C
-:1024D0002402F092250210921A0210921B0210920E
-:1024E0001C0210921D0220912C0230912D0240916D
-:1024F0002E0250912F02C701B6010E945E07109272
-:102500000401D3018D919D910D90BC91A02D8B8BD9
-:102510009C8BAD8BBE8BD30114968D919D910D90AC
-:10252000BC91A02D8F8B988FA98FBA8FBFEF4B1ABC
-:102530005B0AE8E06E0E711C2FCF2B893C894D8918
-:102540005E89D3016D917D918D919C910E9426169B
-:102550006B8F7C8F8D8F9E8F2F89388D498D5A8DF3
-:10256000F30164817581868197810E9426166B0133
-:102570007C012B8D3C8D4D8D5E8DCA01B9010E9471
-:1025800014154B015C01A7019601C701B6010E9419
-:1025900014159B01AC01C501B4010E9427160E94CD
-:1025A0005A1820E030E0A9010E9498160E940A17EC
-:1025B0008B011616170614F001E010E0312C212CC7
-:1025C000C801012E000CAA0BBB0B8F8F98A3A9A3E7
-:1025D000BAA3B101032C000C880B990B0E94B5140F
-:1025E0004B015C016F8D78A189A19AA10E94B5145D
-:1025F0009B01AC01C501B4010E9498164B015C011E
-:10260000AC019B01C701B6010E9414152F89388DBA
-:10261000498D5A8D0E9427166BA37CA38DA39EA380
-:10262000A50194016B8D7C8D8D8D9E8D0E9414155E
-:102630002B893C894D895E890E9427162BA13CA1DC
-:102640004DA15EA10E945E079FEF291A390A02156B
-:1026500013050CF0BECF55CF1092780210922702CE
-:10266000109226028DE491E00E940A092CCC11E020
-:10267000E4CC662777270C943D13B0E0A0E0E3E4B8
-:10268000F3E10C94E6155C017B016115710519F00D
-:10269000DB018D939C9385010F5F1F4FF501D08166
-:1026A0008D2F90E00E948D146C01892BB9F5DD32DD
-:1026B000B9F50F5F1F4FD5011196DC91C1E05801AC
-:1026C000F1E0AF1AB10843E050E06EE870E0C501F8
-:1026D0000E949614892B69F5680182E0C80ED11C0E
-:1026E00045E050E069E870E0C6010E949614892B2D
-:1026F00021F4680197E0C90ED11CE114F10419F02E
-:10270000D701CD92DC9260E070E080E89FEFC111CC
-:10271000FFC060E070E080E89FE7FAC05801BBCFDF
-:10272000DB3229F485010E5F1F4FF501D181C0E036
-:10273000C6CF43E050E066E870E0C5010E94961401
-:10274000892BE9F0F80110E000E020E030E0A90179
-:102750005F01B0ED8B2E8D0E89E08815C8F19C2E9F
-:10276000689491F88C2F8870C2FF16C0811102C046
-:102770000F5F1F4F3196D501DC91C92DE9CFE114D0
-:10278000F10429F00E5F1F4FF7011183008360E011
-:1027900070E080EC9FE7BCC0882311F00150110964
-:1027A000A5E0B0E00E94D5159B01AC01220F331FBC
-:1027B000441F551F280D311D411D511D283999E910
-:1027C0003907490799E15907A8F2C6609C2ED2CF74
-:1027D000AEEF8A1206C0C3FD3CC09C2E689493F8ED
-:1027E000C9CFDF7DD534A9F580818D3239F4C06140
-:1027F000DF011296818162E070E006C0DF018B325A
-:10280000C1F3119661E070E080535D01A61AB70A2A
-:102810008A30F8F4E0E8CE16ECE0DE065CF4B601AF
-:10282000660F771F660F771FC60ED71ECC0CDD1CF8
-:10283000C80ED11C5D01FFEFAF1ABF0A8C91805307
-:102840008A30A8F1C4FF03C0D194C194D1080C0D03
-:102850001D1DC1FF09C0E114F10431F081E0A81A87
-:10286000B108D701AD92BC92CA01B9010E94B3145C
-:10287000C370C33009F490584B015C0120E030E094
-:10288000A9010E94AE14882309F440C0CDEBD0E02A
-:1028900017FF05C0119501951109C5EAD0E06E0139
-:1028A000B8E1CB1AD10880E2E82EF12C0FC0D50197
-:1028B000B1CFFE0125913591459154910E191F0913
-:1028C000C501B4010E9414154B015C01D501C4017E
-:1028D0000E151F0574F72497F594E794CC16DD06C2
-:1028E000A9F78A2F880F8B2F881F8F3F49F020E090
-:1028F00030E0A901C501B4010E94AE14811106C0E7
-:1029000082E290E09093C3038093C203C501B401B7
-:10291000CDB7DEB7ECE00C94021691110C94811542
-:10292000803219F089508550C8F70895FB01DC0109
-:102930004150504088F08D9181341CF08B350CF45F
-:10294000805E659161341CF06B350CF4605E861B13
-:10295000611171F3990B0895881BFCCF0E94F0144C
-:1029600008F481E00895E89409C097FB3EF490953F
-:102970008095709561957F4F8F4F9F4F9923A9F058
-:10298000F92F96E9BB279395F695879577956795E7
-:10299000B795F111F8CFFAF4BB0F11F460FF1BC02B
-:1029A0006F5F7F4F8F4F9F4F16C0882311F096E9BE
-:1029B00011C0772321F09EE8872F762F05C066236C
-:1029C00071F096E8862F70E060E02AF09A95660F25
-:1029D000771F881FDAF7880F9695879597F90895DE
-:1029E000990F0008550FAA0BE0E8FEEF1616170620
-:1029F000E807F907C0F012161306E407F50798F088
-:102A0000621B730B840B950B39F40A2661F0232BA0
-:102A1000242B252B21F408950A2609F4A140A6951C
-:102A20008FEF811D811D08950E9427150C949B1521
-:102A30000E948D1538F00E94941520F0952311F016
-:102A40000C9484150C948A1511240C94CF150E94B3
-:102A5000AC1570F3959FC1F3950F50E0551F629F21
-:102A6000F001729FBB27F00DB11D639FAA27F00DE7
-:102A7000B11DAA1F649F6627B00DA11D661F829F0E
-:102A80002227B00DA11D621F739FB00DA11D621FF3
-:102A9000839FA00D611D221F749F3327A00D611D10
-:102AA000231F849F600D211D822F762F6A2F1124F2
-:102AB0009F5750409AF0F1F088234AF0EE0FFF1F25
-:102AC000BB1F661F771F881F91505040A9F79E3F7C
-:102AD000510580F00C9484150C94CF155F3FE4F3FE
-:102AE000983ED4F3869577956795B795F795E795D2
-:102AF0009F5FC1F7FE2B880F911D9695879597F9DB
-:102B0000089599278827089597F99F6780E870E0CE
-:102B100060E008959FEF80EC089500240A94161653
-:102B2000170618060906089500240A9412161306BB
-:102B3000140605060895092E0394000C11F4882349
-:102B400052F0BB0F40F4BF2B11F460FF04C06F5F65
-:102B50007F4F8F4F9F4F089557FD9058440F551F3B
-:102B600059F05F3F71F04795880F97FB991F61F00F
-:102B70009F3F79F087950895121613061406551F86
-:102B8000F2CF4695F1DF08C0161617061806991FF2
-:102B9000F1CF86957105610508940895E894BB27E7
-:102BA00066277727CB0197F908950E941716A59FEE
-:102BB000900DB49F900DA49F800D911D1124089538
-:102BC0002F923F924F925F926F927F928F929F923D
-:102BD000AF92BF92CF92DF92EF92FF920F931F932B
-:102BE000CF93DF93CDB7DEB7CA1BDB0B0FB6F894DC
-:102BF000DEBF0FBECDBF09942A88398848885F841C
-:102C00006E847D848C849B84AA84B984C884DF808C
-:102C1000EE80FD800C811B81AA81B981CE0FD11D70
-:102C20000FB6F894DEBF0FBECDBFED010895A29F91
-:102C3000B001B39FC001A39F700D811D1124911D90
-:102C4000B29F700D811D1124911D08955058BB270E
-:102C5000AA270E943E160C949B150E948D1538F0F1
-:102C60000E94941520F039F49F3F19F426F40C9437
-:102C70008A150EF4E095E7FB0C948415E92F0E9469
-:102C8000AC1558F3BA17620773078407950720F04D
-:102C900079F4A6F50C94CE150EF4E0950B2EBA2F10
-:102CA000A02D0B01B90190010C01CA01A001112452
-:102CB000FF27591B99F0593F50F4503E68F11A16FE
-:102CC000F040A22F232F342F4427585FF3CF46958F
-:102CD00037952795A795F0405395C9F77EF41F16B1
-:102CE000BA0B620B730B840BBAF09150A1F0FF0F7B
-:102CF000BB1F661F771F881FC2F70EC0BA0F621F67
-:102D0000731F841F48F4879577956795B795F79556
-:102D10009E3F08F0B0CF9395880F08F09927EE0FEB
-:102D20009795879508950E94D617E3950C94FF1701
-:102D30000E94AC160C949B150E94941558F00E94AA
-:102D40008D1540F029F45F3F29F00C948415511142
-:102D50000C94CF150C948A150E94AC1568F3992336
-:102D6000B1F3552391F3951B550BBB27AA27621787
-:102D70007307840738F09F5F5F4F220F331F441F94
-:102D8000AA1FA9F335D00E2E3AF0E0E832D09150C8
-:102D90005040E695001CCAF72BD0FE2F29D0660FB5
-:102DA000771F881FBB1F261737074807AB07B0E8F8
-:102DB00009F0BB0B802DBF01FF2793585F4F3AF0FE
-:102DC0009E3F510578F00C9484150C94CF155F3F0D
-:102DD000E4F3983ED4F3869577956795B795F79584
-:102DE0009F5FC9F7880F911D9695879597F908956C
-:102DF000E1E0660F771F881FBB1F62177307840708
-:102E0000BA0720F0621B730B840BBA0BEE1F88F716
-:102E1000E09508950E9411176894B1110C94CF1594
-:102E200008950E94B41588F09F5798F0B92F9927FC
-:102E3000B751B0F0E1F0660F771F881F991F1AF0A5
-:102E4000BA95C9F714C0B13091F00E94CE15B1E027
-:102E500008950C94CE15672F782F8827B85F39F026
-:102E6000B93FCCF3869577956795B395D9F73EF43E
-:102E700090958095709561957F4F8F4F9F4F0895E6
-:102E80000E94131890F09F3748F4911116F00C949B
-:102E9000CF1560E070E080E89FEB089526F41B16E4
-:102EA000611D711D811D0C94AB170C94C6170E94F7
-:102EB0008D1520F019F00E94941550F40C948A1589
-:102EC0000C94CF15E92F0E94AC1588F35523B1F36C
-:102ED000E7FB6217730784079507A8F189F3E92FC9
-:102EE000FF2788232AF03197660F771F881FDAF7AC
-:102EF000952F5527442332F091505040220F331F15
-:102F0000441FD2F7BB27E91BF50B621B730B840B25
-:102F1000B109B1F222F4620F731F841FB11D319702
-:102F20002AF0660F771F881FBB1FEFCF91505040CC
-:102F300062F041F0882332F0660F771F881F9150AE
-:102F40005040C1F793950C94C61786957795679571
-:102F50009F5FD9F7F7CF882371F4772321F098503A
-:102F6000872B762F07C0662311F499270DC0905147
-:102F7000862B70E060E02AF09A95660F771F881F15
-:102F8000DAF7880F9695879597F908959F3F31F066
-:102F9000915020F4879577956795B795880F911D87
-:102FA0009695879597F908950C948A150E94B41503
-:102FB000D8F3E894E0E0BB279F57F0F02AED3FE01C
-:102FC00049EC06C0EE0FBB0F661F771F881F28F065
-:102FD000B23A62077307840728F0B25A620B730B88
-:102FE000840BE3959A9572F7803830F49A95BB0F6D
-:102FF000661F771F881FD2F790480C94C817EF936D
-:10300000E0FF07C0A2EA2AED3FE049EC5FEB0E9437
-:103010003E160E949B150F90039401FC9058E8E621
-:10302000F0E00C94A1180E94B415A0F0BEE7B91707
-:1030300088F4BB279F3860F41616B11D672F782FD0
-:103040008827985FF7CF869577956795B11D9395FB
-:103050009639C8F308950E94F01408F48FEF08958C
-:103060000E94B415E8F09E37E8F09639B8F49E381F
-:1030700048F4672F782F8827985FF9CF8695779542
-:10308000679593959539D0F3B62FB1706B0F711D7D
-:10309000811D20F487957795679593950C94AB17D0
-:1030A0000C94C6170C94CF1519F416F40C948A15C9
-:1030B0000C94C6170E94B415B8F39923C9F3B6F35C
-:1030C0009F57550B87FF0E949A180024A0E640EAFC
-:1030D000900180585695979528F4805C660F771F6D
-:1030E000881F20F026173707480730F4621B730B40
-:1030F000840B202931294A2BA69517940794202563
-:1031000031254A2758F7660F771F881F20F02617AA
-:103110003707480730F4620B730B840B200D311D09
-:10312000411DA09581F7B901842F9158880F96957C
-:103130008795089591505040660F771F881FD2F7EA
-:1031400008959F938F937F936F93FF93EF939B01CA
-:10315000AC010E941415EF91FF910E94B5182F91B8
-:103160003F914F915F910C941415DF93CF931F9370
-:103170000F93FF92EF92DF927B018C01689406C05F
-:10318000DA2EEF010E942715FE01E894A591259102
-:10319000359145915591A6F3EF010E943E16FE012F
-:1031A0009701A801DA9469F7DF90EF90FF900F91F3
-:1031B0001F91CF91DF910895052E97FB1EF4009487
-:1031C0000E94F31857FD07D00E94011907FC03D095
-:1031D0004EF40C94F31850954095309521953F4F3F
-:1031E0004F4F5F4F089590958095709561957F4FF3
-:1031F0008F4F9F4F0895EE0FFF1F0590F491E02D24
-:103200000994A1E21A2EAA1BBB1BFD010DC0AA1F27
-:10321000BB1FEE1FFF1FA217B307E407F50720F03F
-:10322000A21BB30BE40BF50B661F771F881F991FBA
-:103230001A9469F760957095809590959B01AC0103
-:10324000BD01CF0108950F931F93CF93DF93823079
-:10325000910510F482E090E0E091C603F091C7037D
-:1032600030E020E0B0E0A0E0309799F4211531057E
-:1032700009F44AC0281B390B24303105D8F58A815E
-:103280009B816115710589F1FB0193838283FE01A6
-:1032900011C0408151810281138148175907E0F024
-:1032A0004817590799F4109761F012960C931297EA
-:1032B00013961C933296CF01DF91CF911F910F91FE
-:1032C00008950093C6031093C703F4CF2115310569
-:1032D00051F04217530738F0A901DB019A01BD01F3
-:1032E000DF01F801C1CFEF01F9CF9093C7038093BD
-:1032F000C603CDCFFE01E20FF31F819391932250BD
-:10330000310939832883D7CF2091C4033091C50375
-:10331000232B41F420910701309108013093C5031C
-:103320002093C40320910501309106012115310538
-:1033300041F42DB73EB74091090150910A01241B79
-:10334000350BE091C403F091C503E217F307A0F435
-:103350002E1B3F0B2817390778F0AC014E5F5F4FEB
-:103360002417350748F04E0F5F1F5093C503409355
-:10337000C403819391939FCFF0E0E0E09CCFCF9383
-:10338000DF930097E9F0FC01329713821282A0913B
-:10339000C603B091C703ED0130E020E01097A1F41F
-:1033A00020813181820F931F2091C4033091C50386
-:1033B0002817390709F061C0F093C503E093C403EF
-:1033C000DF91CF910895EA01CE17DF07E8F54A8132
-:1033D0005B819E0141155105B1F7E901FB83EA8349
-:1033E00049915991C40FD51FEC17FD0761F48081F5
-:1033F00091810296840F951FE901998388838281C8
-:1034000093819B838A83F0E0E0E012968D919C91FA
-:1034100013970097B9F52D913C911197CD01029624
-:10342000820F931F2091C4033091C50328173907D9
-:1034300039F6309751F51092C7031092C603B09336
-:10344000C503A093C403BCCFD383C2834081518101
-:10345000840F951FC817D90761F44E5F5F4F8881AD
-:103460009981480F591F518340838A819B8193839F
-:1034700082832115310509F0B0CFF093C703E093A3
-:10348000C6039ECFFD01DC01C0CF13821282D7CFCD
-:10349000B0E0A0E0EEE4FAE10C94E2158C010097B4
-:1034A00051F4CB010E9423198C01C801CDB7DEB7BE
-:1034B000E0E10C94FE15FC01E60FF71F9C01225081
-:1034C0003109E217F30708F49DC0D901CD91DC91D1
-:1034D0001197C617D70798F0C530D10530F3CE0144
-:1034E00004978617970708F3C61BD70B2297C1933B
-:1034F000D1936D937C93CF010E94BF19D6CF5B010E
-:10350000AC1ABD0A4C018C0E9D1EA091C603B09151
-:10351000C703512C412CF12CE12C109731F58091EF
-:10352000C4039091C5038815990509F05CC046163F
-:10353000570608F058C08091050190910601009748
-:1035400041F48DB79EB74091090150910A01841B47
-:10355000950BE817F90708F055C0F093C503E09301
-:10356000C403F90171836083A0CF8D919C91119761
-:1035700012966C90129713967C901397A815B90524
-:1035800059F56C0142E0C40ED11CCA14DB0420F1D1
-:10359000AC014A195B09DA011296159780F0628234
-:1035A000738251834083D9016D937C93E114F104BC
-:1035B00071F0D7011396FC93EE93129776CF229673
-:1035C0008C0F9D1FF90191838083F301EFCFF0935E
-:1035D000C703E093C60369CF4816590608F42C01C7
-:1035E0007D01D3019ACFCB010E9423197C01009762
-:1035F00049F0AE01B8010E94051BC8010E94BF1925
-:10360000870153CF10E000E050CFFB01DC0102C086
-:1036100001900D9241505040D8F70895FC018191DE
-:10362000861721F08823D9F7992708953197CF017C
-:103630000895FB01DC018D91019080190110D9F3EF
-:10364000990B0895FB01DC0101900D920020E1F738
-:103650000895FB01DC014150504030F08D91019004
-:10366000801919F40020B9F7881B990B0895FB0104
-:1036700051915523A9F0BF01DC014D91451741112E
-:10368000E1F759F4CD010190002049F04D9140152A
-:103690004111C9F3FB014111EFCF81E090E00197A7
-:0636A0000895F894FFCF2D
-:1036A600CDCC3C40010000C8038000000000003E75
-:1036B600025E018B018B02FC019A01EE0100000003
-:1036C60000B3037F035D07360A920514055205FE13
-:1036D60004CC04AB04770456040004D1030D0A009D
-:1036E600484F4D494E4700484F4D45440052005300
-:1036F6007069726F6772617068204D6F64652041F2
-:10370600637469766500417070204D6F6465204171
-:1037160063746976650054484554415F5245534584
-:103726005400484F4D450052455345545F54484553
-:103736005441004745545F56455253494F4E005336
-:1037460045545F5350454544003B0049474E4F5250
-:1037560045443A20005461626C653A2044756E65B2
-:103766002057656176657200447269766572733AB0
-:1037760020544D43323230390056657273696F6E8C
-:103786003A20312E342E30005245414459005350D0
-:103796004545445F53455400494E56414C49445FA4
-:1037A600535045454400494E56414C49445F434FAA
-:0637B6004D4D414E4400A0
-:00000001FF

+ 2 - 2
firmware/dlc32_config.yaml

@@ -1,6 +1,6 @@
 board: MKS-DLC32 V2.1
 name: Dune Weaver
-meta: By Tuan Nguyen (2025-01-31)
+meta: By Tuan Nguyen (2025-02-16)
 kinematics: {}
 stepping:
   engine: I2S_STATIC
@@ -33,7 +33,7 @@ axes:
     steps_per_mm: 287.5
     max_rate_mm_per_min: 3000
     acceleration_mm_per_sec2: 1000
-    max_travel_mm: 220
+    max_travel_mm: 10
     soft_limits: false
     motor0:
       limit_neg_pin: gpio.35

+ 92 - 0
firmware/dlc32_dwpro.yaml

@@ -0,0 +1,92 @@
+board: MKS-DLC32 V2.1
+name: Dune Weaver Pro
+meta: By Tuan Nguyen (2025-02-16)
+kinematics: {}
+stepping:
+  engine: I2S_STATIC
+  idle_ms: 0
+  pulse_us: 4
+  dir_delay_us: 1
+  disable_delay_us: 0
+axes:
+  shared_stepper_disable_pin: i2so.0
+  x:
+    steps_per_mm: 320
+    max_rate_mm_per_min: 1500
+    acceleration_mm_per_sec2: 300
+    max_travel_mm: 325
+    soft_limits: false
+    motor0:
+      limit_neg_pin: gpio.36
+      hard_limits: false
+      pulloff_mm: 2
+      stepstick:
+        step_pin: i2so.1
+        direction_pin: i2so.2
+        disable_pin: NO_PIN
+        ms1_pin: NO_PIN
+        ms2_pin: NO_PIN
+        ms3_pin: NO_PIN
+      limit_pos_pin: NO_PIN
+      limit_all_pin: NO_PIN
+  y:
+    steps_per_mm: 320
+    max_rate_mm_per_min: 2000
+    acceleration_mm_per_sec2: 300
+    max_travel_mm: 10
+    soft_limits: false
+    motor0:
+      limit_neg_pin: gpio.35
+      pulloff_mm: 2
+      stepstick:
+        step_pin: i2so.5
+        direction_pin: i2so.6:low
+        disable_pin: NO_PIN
+        ms1_pin: NO_PIN
+        ms2_pin: NO_PIN
+        ms3_pin: NO_PIN
+      limit_pos_pin: NO_PIN
+      limit_all_pin: NO_PIN
+i2so:
+  bck_pin: gpio.16
+  data_pin: gpio.21
+  ws_pin: gpio.17
+sdcard:
+  cs_pin: gpio.15
+  card_detect_pin: NO_PIN
+control:
+  safety_door_pin: NO_PIN
+  reset_pin: NO_PIN
+  feed_hold_pin: NO_PIN
+  cycle_start_pin: NO_PIN
+  macro0_pin: gpio.33:pu:low
+  macro1_pin: NO_PIN
+  macro2_pin: NO_PIN
+  macro3_pin: NO_PIN
+  fault_pin: NO_PIN
+  estop_pin: NO_PIN
+macros:
+  macro0: G90
+coolant:
+  flood_pin: NO_PIN
+  mist_pin: NO_PIN
+  delay_ms: 0
+user_outputs:
+  analog0_pin: NO_PIN
+  analog1_pin: NO_PIN
+  analog2_pin: NO_PIN
+  analog3_pin: NO_PIN
+  analog0_hz: 5000
+  analog1_hz: 5000
+  analog2_hz: 5000
+  analog3_hz: 5000
+  digital0_pin: NO_PIN
+  digital1_pin: NO_PIN
+  digital2_pin: NO_PIN
+  digital3_pin: NO_PIN
+start:
+  must_home: false
+spi:
+  miso_pin: gpio.12
+  mosi_pin: gpio.13
+  sck_pin: gpio.14

+ 122 - 0
firmware/dlc32_dwpro_sensorless_homing.yaml

@@ -0,0 +1,122 @@
+board: MKS-DLC32 V2.1
+name: Dune Weaver Pro
+meta: By Tuan Nguyen (2025-02-16)
+verbose_errors: true
+kinematics: {}
+stepping:
+  engine: I2S_STATIC
+  idle_ms: 0
+  pulse_us: 4
+  dir_delay_us: 1
+  disable_delay_us: 0
+axes:
+  shared_stepper_disable_pin: i2so.0
+  homing_runs: 2
+  x:
+    steps_per_mm: 320
+    max_rate_mm_per_min: 900
+    acceleration_mm_per_sec2: 1000
+    max_travel_mm: 325
+    soft_limits: false
+    motor0:
+      limit_neg_pin: gpio.36
+      hard_limits: false
+      pulloff_mm: 2
+      stepstick:
+        step_pin: i2so.1
+        direction_pin: i2so.2
+        disable_pin: NO_PIN
+        ms1_pin: NO_PIN
+        ms2_pin: NO_PIN
+        ms3_pin: NO_PIN
+      limit_pos_pin: NO_PIN
+      limit_all_pin: NO_PIN
+  y:
+    steps_per_mm: 312
+    max_rate_mm_per_min: 3000
+    acceleration_mm_per_sec2: 1000
+    max_travel_mm: 10
+    soft_limits: false
+    motor0:
+      limit_neg_pin: gpio.35
+      limit_pos_pin: NO_PIN
+      limit_all_pin: NO_PIN
+      hard_limits: false
+      pulloff_mm: 2
+      tmc_2209:
+        uart_num: 1
+        addr: 0
+        r_sense_ohms: 0.12
+        run_amps: 0.8
+        hold_amps: 0
+        homing_amps: 0.8
+        microsteps: 8
+        stallguard: 63
+        stallguard_debug: true
+        toff_disable: 0
+        toff_stealthchop: 5
+        toff_coolstep: 3
+        run_mode: StealthChop
+        homing_mode: StealthChop
+        use_enable: false
+        step_pin: i2so.5
+        direction_pin: i2so.6:low
+        disable_pin: NO_PIN
+    homing:
+      cycle: 2
+      positive_direction: false
+      allow_single_axis: true
+      mpos_mm: 0.4
+      feed_mm_per_min: 200
+      seek_mm_per_min: 200
+i2so:
+  bck_pin: gpio.16
+  data_pin: gpio.21
+  ws_pin: gpio.17
+sdcard:
+  cs_pin: gpio.15
+  card_detect_pin: NO_PIN
+control:
+  safety_door_pin: NO_PIN
+  reset_pin: NO_PIN
+  feed_hold_pin: NO_PIN
+  cycle_start_pin: NO_PIN
+  macro0_pin: NO_PIN
+  macro1_pin: NO_PIN
+  macro2_pin: NO_PIN
+  macro3_pin: NO_PIN
+  fault_pin: NO_PIN
+  estop_pin: NO_PIN
+macros:
+  macro0: $HY
+  macro1: G1 Y0 F200
+coolant:
+  flood_pin: NO_PIN
+  mist_pin: NO_PIN
+  delay_ms: 0
+user_outputs:
+  analog0_pin: NO_PIN
+  analog1_pin: NO_PIN
+  analog2_pin: NO_PIN
+  analog3_pin: NO_PIN
+  analog0_hz: 5000
+  analog1_hz: 5000
+  analog2_hz: 5000
+  analog3_hz: 5000
+  digital0_pin: NO_PIN
+  digital1_pin: NO_PIN
+  digital2_pin: NO_PIN
+  digital3_pin: NO_PIN
+start:
+  must_home: true
+  check_limits: false
+spi:
+  miso_pin: gpio.12
+  mosi_pin: gpio.13
+  sck_pin: gpio.14
+uart1:
+  txd_pin: gpio.0
+  rxd_pin: gpio.4
+  rts_pin: NO_PIN
+  baud: 115200
+  mode: 8N1

+ 3 - 3
firmware/dlc32_sensorless_homing.yml

@@ -1,5 +1,5 @@
 board: MKS-DLC32 V2.1
-name: Dune Weaver (UART TMC2209)
+name: Dune Weaver
 meta: By Tuan Nguyen (2025-02-05)
 verbose_errors: true
 kinematics: {}
@@ -35,7 +35,7 @@ axes:
     steps_per_mm: 287.5
     max_rate_mm_per_min: 3000
     acceleration_mm_per_sec2: 1000
-    max_travel_mm: 220
+    max_travel_mm: 10
     soft_limits: false
     motor0:
       limit_neg_pin: gpio.35
@@ -48,7 +48,7 @@ axes:
         addr: 0
         r_sense_ohms: 0.12
         run_amps: 0.8
-        hold_amps: 0.5
+        hold_amps: 0
         homing_amps: 0.8
         microsteps: 8
         stallguard: 30

+ 4 - 4
firmware/esp3.yaml → firmware/esp32.yaml

@@ -1,4 +1,4 @@
-name: Dune Weaver ESP32
+name: Dune Weaver Mini
 meta: By Tuan Nguyen (2025-02-11)
 board: ESP32
 kinematics: {}
@@ -19,9 +19,9 @@ axes:
       hard_limits: false
       unipolar:
         phase0_pin: gpio.14
-        phase1_pin: gpio.12
+        phase1_pin: gpio.27
         phase2_pin: gpio.26
-        phase3_pin: gpio.27
+        phase3_pin: gpio.12
         half_step: false
       limit_neg_pin: NO_PIN
       limit_pos_pin: NO_PIN
@@ -30,7 +30,7 @@ axes:
     steps_per_mm: 232
     max_rate_mm_per_min: 500
     acceleration_mm_per_sec2: 500
-    max_travel_mm: 4000
+    max_travel_mm: 6.25
     motor0:
       hard_limits: false
       unipolar:

+ 0 - 312
firmware/esp32/esp32.ino

@@ -1,312 +0,0 @@
-#include <AccelStepper.h>
-#include <MultiStepper.h>
-#include <math.h> // For M_PI and mathematical operations
-
-#define rotInterfaceType AccelStepper::DRIVER
-#define inOutInterfaceType AccelStepper::DRIVER
-
-#define ROT_PIN1 27
-#define ROT_PIN2 26
-#define ROT_PIN3 12
-#define ROT_PIN4 14
-
-#define INOUT_PIN1 19
-#define INOUT_PIN2 18
-#define INOUT_PIN3 17
-#define INOUT_PIN4 16
-
-
-#define rot_total_steps 12800
-#define inOut_total_steps 4642
-// #define inOut_total_steps 4660
-const double gearRatio = 100.0f / 16.0f;
-
-#define BUFFER_SIZE 10 // Maximum number of theta-rho pairs in a batch
-
-// Create stepper motor objects
-AccelStepper rotStepper(AccelStepper::FULL4WIRE, ROT_PIN1, ROT_PIN3, ROT_PIN2, ROT_PIN4); // Rot axis
-AccelStepper inOutStepper(AccelStepper::FULL4WIRE, INOUT_PIN1, INOUT_PIN3, INOUT_PIN2, INOUT_PIN4); // In-out axis
-
-// Create a MultiStepper object
-MultiStepper multiStepper;
-
-// Buffer for storing theta-rho pairs
-double buffer[BUFFER_SIZE][2]; // Store theta, rho pairs
-int bufferCount = 0;          // Number of pairs in the buffer
-bool batchComplete = false;
-
-// Track the current position in polar coordinates
-double currentTheta = 0.0; // Current theta in radians
-double currentRho = 0.0;   // Current rho (0 to 1)
-bool isFirstCoordinates = true;
-double totalRevolutions = 0.0; // Tracks cumulative revolutions
-double maxSpeed = 500;
-double maxAcceleration = 5000;
-double subSteps = 1;
-
-// FIRMWARE VERSION
-const char* firmwareVersion = "1.4.0";
-const char* motorType = "esp32";
-
-int modulus(int x, int y) {
-  return x < 0 ? ((x + 1) % y) + y - 1 : x % y;
-}
-
-void setup()
-{
-    // Set maximum speed and acceleration
-    rotStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    rotStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    inOutStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    inOutStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    // Add steppers to MultiStepper
-    multiStepper.addStepper(rotStepper);
-    multiStepper.addStepper(inOutStepper);
-
-    // Initialize serial communication
-    Serial.begin(115200);
-    Serial.println("R");
-    homing();
-}
-
-void getVersion()
-{
-    Serial.println("Table: Mini Dune Weaver");
-    Serial.println("Drivers: ULN2003");
-    Serial.println("Version: 1.4.0");
-}
-
-void loop()
-{
-    // Check for incoming serial commands or theta-rho pairs
-    if (Serial.available() > 0)
-    {
-        String input = Serial.readStringUntil('\n');
-
-        // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA"  && input != "GET_VERSION" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
-        {
-            Serial.println("IGNORED");
-            return;
-        }
-
-        if (input == "GET_VERSION") {
-            getVersion();
-        }
-
-        // Example: The user calls "SET_SPEED 60" => 60% of maxSpeed
-        if (input.startsWith("SET_SPEED"))
-        {
-            // Parse out the speed value from the command string
-            int spaceIndex = input.indexOf(' ');
-            if (spaceIndex != -1)
-            {
-                String speedStr = input.substring(spaceIndex + 1);
-                float speedPercentage = speedStr.toFloat();
-
-                // Make sure the percentage is valid
-                if (speedPercentage >= 1.0 && speedPercentage <= 100.0)
-                {
-                    // Convert percentage to actual speed
-                    long newSpeed = (speedPercentage / 100.0) * maxSpeed;
-
-                    // Set the stepper speeds
-                    rotStepper.setMaxSpeed(newSpeed);
-                    inOutStepper.setMaxSpeed(newSpeed);
-
-                    Serial.println("SPEED_SET");  
-                }
-                else
-                {
-                    Serial.println("INVALID_SPEED");
-                }
-            }
-            else
-            {
-                Serial.println("INVALID_COMMAND");
-            }
-            return;
-        }
-
-        if (input == "HOME")
-        {
-            homing();
-            return;
-        }
-
-        if (input == "RESET_THETA")
-        {
-            isFirstCoordinates = true;
-            currentTheta = 0;
-            currentRho = 0;
-            Serial.println("THETA_RESET"); // Notify Python
-            Serial.println("R");
-            return;
-        }
-
-
-        // If not a command, assume it's a batch of theta-rho pairs
-        if (!batchComplete)
-        {
-            int pairIndex = 0;
-            int startIdx = 0;
-
-            // Split the batch line into individual theta-rho pairs
-            while (pairIndex < BUFFER_SIZE)
-            {
-                int endIdx = input.indexOf(";", startIdx);
-                if (endIdx == -1)
-                    break; // No more pairs in the line
-
-                String pair = input.substring(startIdx, endIdx);
-                int commaIndex = pair.indexOf(',');
-
-                // Parse theta and rho values
-                double theta = pair.substring(0, commaIndex).toDouble(); // Theta in radians
-                double rho = pair.substring(commaIndex + 1).toDouble();  // Rho (0 to 1)
-
-                buffer[pairIndex][0] = theta;
-                buffer[pairIndex][1] = rho;
-                pairIndex++;
-
-                startIdx = endIdx + 1; // Move to next pair
-            }
-            bufferCount = pairIndex;
-            batchComplete = true;
-        }
-    }
-
-    // Process the buffer if a batch is ready
-    if (batchComplete && bufferCount > 0)
-    {
-        rotStepper.enableOutputs();
-        inOutStepper.enableOutputs();
-        // Start interpolation from the current position
-        double startTheta = currentTheta;
-        double startRho = currentRho;
-
-        for (int i = 0; i < bufferCount; i++)
-        {
- 
-            if (isFirstCoordinates)
-            {
-                // Directly move to the first coordinate of the new pattern
-                long initialRotSteps = buffer[0][0] * (rot_total_steps / (2.0 * M_PI));
-                rotStepper.setCurrentPosition(initialRotSteps);
-                inOutStepper.setCurrentPosition(inOutStepper.currentPosition() + (totalRevolutions * rot_total_steps / gearRatio));
-
-                currentTheta = buffer[0][0];
-                totalRevolutions = 0;
-                movePolar(buffer[0][0], buffer[0][1]);
-                isFirstCoordinates = false; // Reset the flag after the first movement
-            } else
-            {
-                interpolatePath(
-                    startTheta, startRho,
-                    buffer[i][0], buffer[i][1],
-                    subSteps
-                );
-            }
-            // Update the starting point for the next segment
-            startTheta = buffer[i][0];
-            startRho = buffer[i][1];
-        }
-
-        rotStepper.disableOutputs();
-        inOutStepper.disableOutputs();
-        batchComplete = false; // Reset batch flag
-        bufferCount = 0;       // Clear buffer
-        Serial.println("R");
-    }
-}
-
-void homing()
-{
-    Serial.println("HOMING");
-    inOutStepper.enableOutputs();
-    // Move inOutStepper inward for homing
-    inOutStepper.setSpeed(-maxSpeed); // Adjust speed for homing
-    long currentInOut = inOutStepper.currentPosition();
-    while (true)
-    {
-        inOutStepper.runSpeed();
-        if (inOutStepper.currentPosition() <= currentInOut - inOut_total_steps * 1.1)
-        { // Adjust distance for homing
-            break;
-        }
-    }
-    inOutStepper.setCurrentPosition(0); // Set home position
-    rotStepper.setCurrentPosition(0);
-    currentTheta = 0.0;                 // Reset polar coordinates
-    currentRho = 0.0;
-    inOutStepper.disableOutputs();
-    Serial.println("HOMED");
-}
-
-
-void movePolar(double theta, double rho)
-{
-    // Define different scaling factors for rho <= 0.5 and rho > 0.5
-    float speedScalingFactorLow = 0.7;  // Scaling factor for rho <= 0.5
-    float speedScalingFactorHigh = 0.4; // Scaling factor for rho > 0.5
-
-    float speedScalingFactor;
-
-    // Determine which factor to use
-    if (rho <= 0.5) {
-        speedScalingFactor = speedScalingFactorLow;
-    } else {
-        speedScalingFactor = speedScalingFactorHigh;
-    }
-
-    // Scale speed dynamically based on rho
-    long dynamicSpeed = maxSpeed * (1.0 + speedScalingFactor * (1.0 - 2.0 * rho));
-
-    // Ensure the speed is within a valid range
-    dynamicSpeed = constrain(dynamicSpeed, 1, maxSpeed);
-
-    // Set stepper speeds dynamically
-    rotStepper.setMaxSpeed(dynamicSpeed);
-    inOutStepper.setMaxSpeed(dynamicSpeed);
-
-    long rotSteps = lround(theta * (rot_total_steps / (2.0f * M_PI)));
-    double revolutions = theta / (2.0 * M_PI);
-    long offsetSteps = lround(revolutions * (rot_total_steps / gearRatio));
-
-    // Now inOutSteps is always derived from the absolute rho, not incrementally
-    long inOutSteps = lround(rho * inOut_total_steps);
-    
-    totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
-    
-    if (!isFirstCoordinates)
-    {
-        inOutSteps -= offsetSteps;
-    }
-
-    long targetPositions[2] = {rotSteps, inOutSteps};
-    multiStepper.moveTo(targetPositions);
-    multiStepper.runSpeedToPosition(); // Blocking call
-
-    // Update current coordinates
-    currentTheta = theta;
-    currentRho = rho;
-}
-
-void interpolatePath(double startTheta, double startRho, double endTheta, double endRho, double subSteps)
-{
-    // Calculate the total distance in the polar coordinate system
-    double distance = sqrt(pow(endTheta - startTheta, 2) + pow(endRho - startRho, 2));
-    long numSteps = max(1, (int)(distance / subSteps)); // Ensure at least one step
-    
-    for (long step = 0; step <= numSteps; step++)
-    {
-        double t = (double)step / numSteps; // Interpolation factor (0 to 1)
-        double interpolatedTheta = startTheta + t * (endTheta - startTheta);
-        double interpolatedRho = startRho + t * (endRho - startRho);
-
-        // Move to the interpolated theta-rho
-        movePolar(interpolatedTheta, interpolatedRho);
-    }
-}

BIN
firmware/esp32/esp32.ino.bin


+ 0 - 308
firmware/esp32_TMC2209/esp32_TMC2209.ino

@@ -1,308 +0,0 @@
-#include <AccelStepper.h>
-#include <MultiStepper.h>
-#include <math.h> // For M_PI and mathematical operations
-
-#define rotInterfaceType AccelStepper::DRIVER
-#define inOutInterfaceType AccelStepper::DRIVER
-
-#define stepPin_rot 22
-#define dirPin_rot 23
-#define stepPin_InOut 18
-#define dirPin_InOut 19
-
-
-#define rot_total_steps 16000.0
-#define inOut_total_steps 5760.0
-#define gearRatio 10
-
-#define BUFFER_SIZE 10 // Maximum number of theta-rho pairs in a batch
-
-// Create stepper motor objects
-AccelStepper rotStepper(rotInterfaceType, stepPin_rot, dirPin_rot);
-AccelStepper inOutStepper(inOutInterfaceType, stepPin_InOut, dirPin_InOut);
-
-// Create a MultiStepper object
-MultiStepper multiStepper;
-
-// Buffer for storing theta-rho pairs
-float buffer[BUFFER_SIZE][2]; // Store theta, rho pairs
-int bufferCount = 0;          // Number of pairs in the buffer
-bool batchComplete = false;
-
-// Track the current position in polar coordinates
-float currentTheta = 0.0; // Current theta in radians
-float currentRho = 0.0;   // Current rho (0 to 1)
-bool isFirstCoordinates = true;
-float totalRevolutions = 0.0; // Tracks cumulative revolutions
-long maxSpeed = 1000;
-float maxAcceleration = 1000;
-long interpolationResolution = 1;
-float userDefinedSpeed = maxSpeed; // Store user-defined speed
-
-void setup()
-{
-    // Set maximum speed and acceleration
-    rotStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    rotStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    inOutStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    inOutStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    // Add steppers to MultiStepper
-    multiStepper.addStepper(rotStepper);
-    multiStepper.addStepper(inOutStepper);
-
-    // Initialize serial communication
-    Serial.begin(115200);
-    Serial.println("R");
-    homing();
-}
-
-void getVersion() {
-    Serial.println("Table: Dune Weaver");
-    Serial.println("Drivers: ESP32-TMC2209");
-    Serial.println("Version: 1.4.0");
-}
-
-
-void resetTheta()
-{
-    isFirstCoordinates = true; // Set flag to skip interpolation for the next movement
-    Serial.println("THETA_RESET"); // Notify Python
-}
-
-void loop() {
-        appMode();
-}
-
-void appMode()
-{
-    // Check for incoming serial commands or theta-rho pairs
-    if (Serial.available() > 0)
-    {
-        String input = Serial.readStringUntil('\n');
-
-        // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA" && input != "GET_VERSION" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
-        {
-            Serial.print("IGNORED: ");
-            Serial.println(input);
-            return;
-        }
-
-        if (input == "GET_VERSION")
-        {
-            getVersion();
-        }
-
-        if (input == "RESET_THETA")
-        {
-            resetTheta(); // Reset currentTheta
-            Serial.println("THETA_RESET"); // Notify Python
-            Serial.println("READY");
-            return;
-        }
-        if (input == "HOME")
-        {
-            homing();
-            return;
-        }
-
-
-        if (input.startsWith("SET_SPEED"))
-        {
-            // Parse out the speed value from the command string
-            int spaceIndex = input.indexOf(' ');
-            if (spaceIndex != -1)
-            {
-                String speedStr = input.substring(spaceIndex + 1);
-                float speedPercentage = speedStr.toFloat();
-
-                // Make sure the percentage is valid
-                if (speedPercentage >= 1.0 && speedPercentage <= 100.0)
-                {
-                    // Convert percentage to actual speed
-                    long newSpeed = (speedPercentage / 100.0) * maxSpeed;
-                    userDefinedSpeed = newSpeed;
-
-                    // Set the stepper speeds
-                    rotStepper.setMaxSpeed(newSpeed);
-                    inOutStepper.setMaxSpeed(newSpeed);
-
-                    Serial.println("SPEED_SET");  
-                }
-                else
-                {
-                    Serial.println("INVALID_SPEED");
-                }
-            }
-            else
-            {
-                Serial.println("INVALID_COMMAND");
-            }
-            return;
-        }
-
-        // If not a command, assume it's a batch of theta-rho pairs
-        if (!batchComplete)
-        {
-            int pairIndex = 0;
-            int startIdx = 0;
-
-            // Split the batch line into individual theta-rho pairs
-            while (pairIndex < BUFFER_SIZE)
-            {
-                int endIdx = input.indexOf(";", startIdx);
-                if (endIdx == -1)
-                    break; // No more pairs in the line
-
-                String pair = input.substring(startIdx, endIdx);
-                int commaIndex = pair.indexOf(',');
-
-                // Parse theta and rho values
-                float theta = pair.substring(0, commaIndex).toFloat(); // Theta in radians
-                float rho = pair.substring(commaIndex + 1).toFloat();  // Rho (0 to 1)
-
-                buffer[pairIndex][0] = theta;
-                buffer[pairIndex][1] = rho;
-                pairIndex++;
-
-                startIdx = endIdx + 1; // Move to next pair
-            }
-            bufferCount = pairIndex;
-            batchComplete = true;
-        }
-    }
-
-    // Process the buffer if a batch is ready
-    if (batchComplete && bufferCount > 0)
-    {
-        // Start interpolation from the current position
-        float startTheta = currentTheta;
-        float startRho = currentRho;
-
-        for (int i = 0; i < bufferCount; i++)
-        {
-            if (isFirstCoordinates)
-            {
-                // Directly move to the first coordinate of the new pattern
-                long initialRotSteps = buffer[0][0] * (rot_total_steps / (2.0 * M_PI));
-                rotStepper.setCurrentPosition(initialRotSteps);
-                inOutStepper.setCurrentPosition(inOutStepper.currentPosition() + (totalRevolutions * rot_total_steps / gearRatio));
-
-                currentTheta = buffer[0][0];
-                totalRevolutions = 0;
-                movePolar(buffer[0][0], buffer[0][1]);
-                isFirstCoordinates = false; // Reset the flag after the first movement
-            }
-              else
-              {
-                // Use interpolation for subsequent movements
-                interpolatePath(
-                    startTheta, startRho,
-                    buffer[i][0], buffer[i][1],
-                    interpolationResolution
-                );
-              }
-            // Update the starting point for the next segment
-            startTheta = buffer[i][0];
-            startRho = buffer[i][1];
-        }
-
-        batchComplete = false; // Reset batch flag
-        bufferCount = 0;       // Clear buffer
-        Serial.println("R");
-    }
-}
-
-void homing()
-{
-    Serial.println("HOMING");
-
-    // Move inOutStepper inward for homing
-    inOutStepper.setSpeed(-maxSpeed); // Adjust speed for homing
-    while (true)
-    {
-        inOutStepper.runSpeed();
-        if (inOutStepper.currentPosition() <= -inOut_total_steps * 1.1)
-        { // Adjust distance for homing
-            break;
-        }
-    }
-    inOutStepper.setCurrentPosition(0); // Set home position
-    currentTheta = 0.0;                 // Reset polar coordinates
-    currentRho = 0.0;
-    Serial.println("HOMED");
-}
-
-void movePolar(float theta, float rho)
-{
-    // Define different scaling factors for rho <= 0.5 and rho > 0.5
-    float speedScalingFactorLow = 0.7;  // Scaling factor for rho <= 0.5
-    float speedScalingFactorHigh = 0.4; // Scaling factor for rho > 0.5
-
-    float speedScalingFactor;
-
-    // Determine which factor to use
-    if (rho <= 0.5) {
-        speedScalingFactor = speedScalingFactorLow;
-    } else {
-        speedScalingFactor = speedScalingFactorHigh;
-    }
-
-    // Scale speed dynamically based on rho
-    long dynamicSpeed = maxSpeed * (1.0 + speedScalingFactor * (1.0 - 2.0 * rho));
-
-    // Ensure the speed is within a valid range
-    dynamicSpeed = constrain(dynamicSpeed, 1, maxSpeed);
-
-    // Set stepper speeds dynamically
-    rotStepper.setMaxSpeed(dynamicSpeed);
-    inOutStepper.setMaxSpeed(dynamicSpeed);
-
-    // Convert polar coordinates to motor steps
-    long rotSteps = theta * (rot_total_steps / (2.0 * M_PI)); // Steps for rot axis
-    long inOutSteps = rho * inOut_total_steps;                // Steps for in-out axis
-
-    // Calculate offset for inOut axis
-    float revolutions = theta / (2.0 * M_PI); // Fractional revolutions (can be positive or negative)
-    long offsetSteps = revolutions * rot_total_steps / gearRatio;    // Steps inward or outward per revolution
-
-    // Update the total revolutions to keep track of the offset history
-    totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
-
-    // Apply the offset to the inout axis
-    if (!isFirstCoordinates) {
-        inOutSteps -= offsetSteps;
-    }
-
-    // Define target positions for both motors
-    long targetPositions[2];
-    targetPositions[0] = rotSteps;
-    targetPositions[1] = inOutSteps;
-
-    // Move both motors synchronously
-    multiStepper.moveTo(targetPositions);
-    multiStepper.runSpeedToPosition(); // Blocking call
-
-    // Update the current coordinates
-    currentTheta = theta;
-    currentRho = rho;
-}
-
-void interpolatePath(float startTheta, float startRho, float endTheta, float endRho, float stepSize)
-{
-    // Calculate the total distance in the polar coordinate system
-    float distance = sqrt(pow(endTheta - startTheta, 2) + pow(endRho - startRho, 2));
-    int numSteps = max(1, (int)(distance / stepSize)); // Ensure at least one step
-
-    for (int step = 0; step <= numSteps; step++)
-    {
-        float t = (float)step / numSteps; // Interpolation factor (0 to 1)
-        float interpolatedTheta = startTheta + t * (endTheta - startTheta);
-        float interpolatedRho = startRho + t * (endRho - startRho);
-
-        // Move to the interpolated theta-rho
-        movePolar(interpolatedTheta, interpolatedRho);
-    }
-}

BIN
firmware/esp32_TMC2209/esp32_TMC2209.ino.bin


+ 0 - 302
firmware/esp32_tmc_ringbuf_WIP/esp32.ino

@@ -1,302 +0,0 @@
-#include <AccelStepper.h>
-#include <MultiStepper.h>
-#include <math.h> // For M_PI and mathematical operations
-#include <RingBufCPP.h>
-
-#define rotInterfaceType AccelStepper::DRIVER
-#define inOutInterfaceType AccelStepper::DRIVER
-
-#define stepPin_rot 22
-#define dirPin_rot 23
-#define stepPin_InOut 18
-#define dirPin_InOut 19
-
-
-#define rot_total_steps 16000.0
-#define inOut_total_steps 5760.0
-#define gearRatio 10
-
-#define BUFFER_SIZE 10 // Maximum number of theta-rho pairs in a batch
-
-// struct to hold a batch of coordinates
-struct Batch {
-  float coords[BUFFER_SIZE][2];
-  int batchCount;
-};
-
-//ring buffer to hold batches
-RingBufCPP<struct Batch, 1> buf;
-
-// Create stepper motor objects
-AccelStepper rotStepper(rotInterfaceType, stepPin_rot, dirPin_rot);
-AccelStepper inOutStepper(inOutInterfaceType, stepPin_InOut, dirPin_InOut);
-
-// Create a MultiStepper object
-MultiStepper multiStepper;
-
-// Buffer for storing theta-rho pairs
-float buffer[BUFFER_SIZE][2]; // Store theta, rho pairs
-bool batchComplete = false;
-
-// Track the current position in polar coordinates
-float currentTheta = 0.0; // Current theta in radians
-float currentRho = 0.0;   // Current rho (0 to 1)
-bool isFirstCoordinates = true;
-float totalRevolutions = 0.0; // Tracks cumulative revolutions
-long maxSpeed = 1000;
-float maxAcceleration = 1000;
-long interpolationResolution = 1;
-float userDefinedSpeed = maxSpeed; // Store user-defined speed
-
-void setup()
-{
-    // Set maximum speed and acceleration
-    rotStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    rotStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    inOutStepper.setMaxSpeed(maxSpeed);     // Adjust as needed
-    inOutStepper.setAcceleration(maxAcceleration); // Adjust as needed
-
-    // Add steppers to MultiStepper
-    multiStepper.addStepper(rotStepper);
-    multiStepper.addStepper(inOutStepper);
-
-    // Initialize serial communication
-    Serial.begin(115200);
-    Serial.println("R");
-    homing();
-}
-
-void getVersion() {
-    Serial.println("Table: Dune Weaver");
-    Serial.println("Drivers: ESP32-TMC2209");
-    Serial.println("Version: 1.4.0");
-}
-
-
-void resetTheta()
-{
-    isFirstCoordinates = true; // Set flag to skip interpolation for the next movement
-    Serial.println("THETA_RESET"); // Notify Python
-}
-
-void loop() {
-        appMode();
-}
-
-void appMode()
-{
-    // Check for incoming serial commands or theta-rho pairs
-    if (Serial.available() > 0)
-    {
-        String input = Serial.readStringUntil('\n');
-
-        // Ignore invalid messages
-        if (input != "HOME" && input != "RESET_THETA" && input != "GET_VERSION" && !input.startsWith("SET_SPEED") && !input.endsWith(";"))
-        {
-            Serial.print("IGNORED: ");
-            Serial.println(input);
-            return;
-        }
-
-        if (input == "GET_VERSION")
-        {
-            getVersion();
-        }
-
-        if (input == "RESET_THETA")
-        {
-            resetTheta(); // Reset currentTheta
-            Serial.println("THETA_RESET"); // Notify Python
-            Serial.println("READY");
-            return;
-        }
-        if (input == "HOME")
-        {
-            homing();
-            return;
-        }
-
-
-        if (input.startsWith("SET_SPEED"))
-        {
-            // Parse out the speed value from the command string
-            int spaceIndex = input.indexOf(' ');
-            if (spaceIndex != -1)
-            {
-                String speedStr = input.substring(spaceIndex + 1);
-                float speedPercentage = speedStr.toFloat();
-
-                // Make sure the percentage is valid
-                if (speedPercentage >= 1.0 && speedPercentage <= 100.0)
-                {
-                    // Convert percentage to actual speed
-                    long newSpeed = (speedPercentage / 100.0) * maxSpeed;
-                    userDefinedSpeed = newSpeed;
-
-                    // Set the stepper speeds
-                    rotStepper.setMaxSpeed(newSpeed);
-                    inOutStepper.setMaxSpeed(newSpeed);
-
-                    Serial.println("SPEED_SET");  
-                }
-                else
-                {
-                    Serial.println("INVALID_SPEED");
-                }
-            }
-            else
-            {
-                Serial.println("INVALID_COMMAND");
-            }
-            return;
-        }
-
-        // If not a command, assume it's a batch of theta-rho pairs
-        if (!buf.isFull())
-        {
-            int pairIndex = 0;
-            int startIdx = 0;
-
-            // declare the batch struct to be filled
-            struct Batch bat;
-
-            // Split the batch line into individual theta-rho pairs
-            while (pairIndex < BUFFER_SIZE)
-            {
-                int endIdx = input.indexOf(";", startIdx);
-                if (endIdx == -1)
-                    break; // No more pairs in the line
-
-                String pair = input.substring(startIdx, endIdx);
-                int commaIndex = pair.indexOf(',');
-
-                // Parse theta and rho values
-                float theta = pair.substring(0, commaIndex).toFloat(); // Theta in radians
-                float rho = pair.substring(commaIndex + 1).toFloat();  // Rho (0 to 1)
-
-                bat.coords[pairIndex][0] = theta;
-                bat.coords[pairIndex][1] = rho;
-                pairIndex++;
-
-                startIdx = endIdx + 1; // Move to next pair
-            }
-
-            //add the batch to 
-            bat.batchCount = pairIndex;
-            buf.add(bat);
-
-        }
-    }
-
-    // Process the buffer if a batch is ready
-    if (!buf.isEmpty())
-    {
-        // Start interpolation from the current position
-        float startTheta = currentTheta;
-        float startRho = currentRho;
-
-        struct Batch bat;
-        
-        buf.pull(&bat);
-        
-        for (int i = 0; i < bat.batchCount; i++)
-        {
-            if (isFirstCoordinates)
-            {
-                // Directly move to the first coordinate of the new pattern
-                long initialRotSteps = bat.coords[0][0] * (rot_total_steps / (2.0 * M_PI));
-                rotStepper.setCurrentPosition(initialRotSteps);
-                inOutStepper.setCurrentPosition(inOutStepper.currentPosition() + (totalRevolutions * rot_total_steps / gearRatio));
-
-                currentTheta = bat.coords[0][0];
-                totalRevolutions = 0;
-                movePolar(bat.coords[0][0], bat.coords[0][1]);
-                isFirstCoordinates = false; // Reset the flag after the first movement
-            }
-              else
-              {
-                // Use interpolation for subsequent movements
-                interpolatePath(
-                    startTheta, startRho,
-                    bat.coords[i][0], bat.coords[i][1],
-                    interpolationResolution
-                );
-              }
-            // Update the starting point for the next segment
-            startTheta = bat.coords[i][0];
-            startRho = bat.coords[i][1];
-        }
-
-        Serial.println("R");
-    }
-}
-
-void homing()
-{
-    Serial.println("HOMING");
-
-    // Move inOutStepper inward for homing
-    inOutStepper.setSpeed(-maxSpeed); // Adjust speed for homing
-    while (true)
-    {
-        inOutStepper.runSpeed();
-        if (inOutStepper.currentPosition() <= -inOut_total_steps * 1.1)
-        { // Adjust distance for homing
-            break;
-        }
-    }
-    inOutStepper.setCurrentPosition(0); // Set home position
-    currentTheta = 0.0;                 // Reset polar coordinates
-    currentRho = 0.0;
-    Serial.println("HOMED");
-}
-
-void movePolar(float theta, float rho)
-{
-    // Convert polar coordinates to motor steps
-    long rotSteps = theta * (rot_total_steps / (2.0 * M_PI)); // Steps for rot axis
-    long inOutSteps = rho * inOut_total_steps;                // Steps for in-out axis
-
-    // Calculate offset for inOut axis
-    float revolutions = theta / (2.0 * M_PI); // Fractional revolutions (can be positive or negative)
-    long offsetSteps = revolutions * rot_total_steps / gearRatio;    // 1600 steps inward or outward per revolution
-
-    // Update the total revolutions to keep track of the offset history
-    totalRevolutions += (theta - currentTheta) / (2.0 * M_PI);
-
-    // Apply the offset to the inout axis
-    if (!isFirstCoordinates) {
-        inOutSteps -= offsetSteps;
-    }
-
-    // Define target positions for both motors
-    long targetPositions[2];
-    targetPositions[0] = rotSteps;
-    targetPositions[1] = inOutSteps;
-
-    // Move both motors synchronously
-    multiStepper.moveTo(targetPositions);
-    multiStepper.runSpeedToPosition(); // Blocking call
-
-    // Update the current coordinates
-    currentTheta = theta;
-    currentRho = rho;
-}
-
-void interpolatePath(float startTheta, float startRho, float endTheta, float endRho, float stepSize)
-{
-    // Calculate the total distance in the polar coordinate system
-    float distance = sqrt(pow(endTheta - startTheta, 2) + pow(endRho - startRho, 2));
-    int numSteps = max(1, (int)(distance / stepSize)); // Ensure at least one step
-
-    for (int step = 0; step <= numSteps; step++)
-    {
-        float t = (float)step / numSteps; // Interpolation factor (0 to 1)
-        float interpolatedTheta = startTheta + t * (endTheta - startTheta);
-        float interpolatedRho = startRho + t * (endRho - startRho);
-
-        // Move to the interpolated theta-rho
-        movePolar(interpolatedTheta, interpolatedRho);
-    }
-}

+ 0 - 0
steps_calibration/steps_calibration.ino → steps_calibration/steps_calibration_esp32.ino


+ 94 - 0
steps_calibration/steps_calibration_tmc2209.ino

@@ -0,0 +1,94 @@
+#include <AccelStepper.h>
+#include <MultiStepper.h>
+#include <math.h> // For M_PI and mathematical operations
+
+#define rotInterfaceType AccelStepper::DRIVER
+#define inOutInterfaceType AccelStepper::DRIVER
+
+#define stepPin_rot 22
+#define dirPin_rot 23
+#define stepPin_InOut 18
+#define dirPin_InOut 19
+
+// Create stepper motor objects
+AccelStepper rotStepper(rotInterfaceType, stepPin_rot, dirPin_rot);
+AccelStepper inOutStepper(inOutInterfaceType, stepPin_InOut, dirPin_InOut);
+
+
+// Calibration variables
+long rotSteps = 0;   // Steps for the ROT axis
+long inOutSteps = 0; // Steps for the INOUT axis
+
+void setup() {
+  Serial.begin(115200); // Start serial communication
+  Serial.println("Stepper Calibration Tool");
+
+  // Set max speeds and accelerations
+  rotStepper.setMaxSpeed(1000);
+  rotStepper.setAcceleration(500);
+
+  inOutStepper.setMaxSpeed(1000);
+  inOutStepper.setAcceleration(500);
+
+  // Initial message
+  Serial.println("Commands:");
+  Serial.println(" r <steps> - Move rotation axis by <steps>");
+  Serial.println(" i <steps> - Move in-out axis by <steps>");
+  Serial.println(" reset r - Reset ROT axis steps to 0");
+  Serial.println(" reset i - Reset IN-OUT axis steps to 0");
+  Serial.println(" Example: r 200 (moves ROT axis 200 steps)");
+}
+
+void loop() {
+  if (Serial.available() > 0) {
+    String input = Serial.readStringUntil('\n'); // Read user input
+    input.trim();
+
+    if (input.length() > 0) {
+      if (input.startsWith("reset")) {
+        // Reset commands
+        char axis = input.charAt(6); // Get the axis to reset ('r' or 'i')
+
+        if (axis == 'r') {
+          // Reset ROT axis steps
+          rotSteps = 0;
+          Serial.println("ROT axis steps reset to 0.");
+        } else if (axis == 'i') {
+          // Reset IN-OUT axis steps
+          inOutSteps = 0;
+          Serial.println("IN-OUT axis steps reset to 0.");
+        } else {
+          Serial.println("Invalid reset command. Use 'reset r' or 'reset i'.");
+        }
+      } else {
+        // Movement commands
+        char axis = input.charAt(0);        // First character is the axis
+        long steps = input.substring(2).toInt(); // Convert remaining input to steps
+
+        if (axis == 'r') {
+          // Move rotation axis
+          Serial.print("Moving ROT axis by ");
+          Serial.print(steps);
+          Serial.println(" steps.");
+          rotSteps += steps;
+          rotStepper.move(steps);
+          while (rotStepper.run()); // Wait for movement to finish
+          Serial.print("Current ROT steps: ");
+          Serial.println(rotSteps);
+        } else if (axis == 'i') {
+          // Move in-out axis
+          Serial.print("Moving IN-OUT axis by ");
+          Serial.print(steps);
+          Serial.println(" steps.");
+          inOutSteps += steps;
+          inOutStepper.move(steps);
+          while (inOutStepper.run()); // Wait for movement to finish
+          Serial.print("Current IN-OUT steps: ");
+          Serial.println(inOutSteps);
+        } else {
+          Serial.println("Invalid command. Use 'r <steps>' or 'i <steps>'.");
+        }
+      }
+    }
+  }
+}